Converted homepage to use SihnonFramework and updated content

This commit is contained in:
2011-12-30 02:41:53 +00:00
parent e1012e034f
commit 28117760a5
67 changed files with 1829 additions and 1468 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Eclipse project
.buildpath
.project
.settings

View File

@@ -1,38 +0,0 @@
<?php
/*
* MySQL Authentication Module
*
*/
class Authenticator_mysql implements IAuthenticator {
public function __construct() {
}
public function initialise() {
// Nothing required
}
public function uninitialise() {
// Nothing required
}
public function user_exists( $username ) {
return true;
}
public function authenticate( $username, $password ) {
return true;
}
public function username2fullname( $username ) {
throw new NotImplementedException();
}
public function fullname2username( $fullname ) {
throw new NotImplementedException();
}
};
?>

View File

@@ -1,103 +0,0 @@
<?php
/*
* Configuration
* This file contains all global configuration variables
*/
$_config = array();
// DEBUG MODE
// This config variable triggers verbose error output, which is useful for developers, but
// would be ugly or bad practice to show to users. Enable this variable on development systems
// and disable it for a production system.
$_config['DEBUG'] = true;
error_reporting( E_ALL | E_NOTICE | E_STRICT );
// Current timezone settings
date_default_timezone_set('Europe/London');
require_once('request_handler.php');
require_once("functions.php");
require_once("exceptions.php");
require_once("session_handler.php");
// Variable used for passing information around the script,
// Should not be used by the user
$_meta = array( 'base-dir' => get_web_base_dir(),
'script-dir' => get_fs_base_dir(),
'error-code' => 403,
'error-message' => 'Unknown Error');
// Placeholder for template fragments
$_template = array( 'messages' => array(),
'redirect-to' => false,
'head' => '',
'title' => 'Default',
'page' => '');
// Forward declarations
$_session = null;
// Templating
$_config['template-file'] = "{$_meta['script-dir']}/templates/default.php";
// Homepage
$_config['homepage'] = "{$_meta['script-dir']}/page-sources/home.php";
// Sessions
// How long a logged in session will last without activity from the user
$_config['session-login-timeout'] = 60*60*1; // One hour
// How long the username will be stoed in the users cookie
$_config['session-username-timeout'] = 60*60*24*7; // Seven days
// See session-handler.php for reasons behind the session-network-* config variables
// session-network-mask is used to determine how much of the users IP is hashed into the salt
$_config['session-network-mask'] = 24;
// session-network-mode defines whether the above parameter was passed as a CIDR form, or dotted decimal form
$_config['session-network-mode'] = MASK_CIDR;
// Maximum number of items to keep in the users session history
$_config['max-history'] = 5;
// Account Lockout
// How many incorrect authentication attempts before the user is locked out
$_config['lockout-attempts'] = 3;
// How long the account lockout period lasts. During this time, the user will not
// be able to authenticate, even witha valid passwords
$_config['lockout-duration'] = 60*10; // Ten minutes
// Authantication
require_once( "{$_meta['script-dir']}/code/iauthenticator.php" );
$_config['authentication-module'] = 'mysql';
// Database
// Mysql connections parameters
$_config['db'] = null;
$_config['mysql'] = array();
$_config['mysql']['host'] = 'localhost';
$_config['mysql']['port'] = 3306;
$_config['mysql']['username'] = '';
$_config['mysql']['password'] = '';
$_config['mysql']['database'] = '';
// Database tables
$_config['mysql']['prefix'] = '';
// Connecting to the database
$_db = null;
require_once( "{$_meta['script-dir']}/code/db_mysql.php" );
// Only show php error messages if the application is in debug mode
if( isset($_GET['nodebug']) ) $_config['DEBUG'] = false;
if( !$_config['DEBUG'] ) {
set_error_handler( "null_error_handler" );
set_exception_handler( "null_exception_handler" );
}
// Set the default template for all individual pages
$_template['template-file'] = $_config['template-file'];
?>

View File

@@ -1,21 +0,0 @@
<?php
/*
* MySQL Database wrappper
*
*/
if( $_config['db'] == 'mysql' ) {
// Egress checking, make sure everyting we need is available
if( !class_exists( 'mysqli' ) ) throw new ConfigException('Missing required PHP Extension "mysqli".');
if( !is_array( $_config['mysql'] ) ) throw new ConfigException('Missing configuration data: mysql');
// Connect to the database using the config variables provided in _config
$_db = new mysqli( $_config['mysql']['host'], $_config['mysql']['username'], $_config['mysql']['password'], $_config['mysql']['database'], $_config['mysql']['port'] );
if( mysqli_connect_errno() ){
throw new ConfigException('Cannot connect to the mysql database');
}
}
?>

View File

@@ -1,41 +0,0 @@
<?php
/*
* Exceptions
*
*/
class BaseException extends Exception {
// Overridden constuctor with message and code parameters, which will optionally display error output
public function __construct( $message = '', $code = 0) {
global $_config;
parent::__construct( $message, $code );
// If debug mode is on, print out the raw data
if( $_config['DEBUG'] ) {
echo get_class($this) . ": Code='{$code}', Message='{$message}'<br />\n";
echo '<pre>';print_r($this->getTrace());echo '</pre>';
}
}
};
class FatalException extends BaseException {
// Overridden constructor prints an error message, then terminates the application
public function __construct( $message = '', $code = 0 ) {
parent::__construct( $message, $code );
die( 'FATAL EXCEPTION: ' . $message );
}
};
class ConfigException extends BaseException {};
class SessionException extends BaseException {};
class AccountLockoutException extends BaseException {};
class AuthenticationException extends BaseException {};
class ParameterException extends BaseException {};
class NotImplementedException extends BaseException {};
?>

View File

@@ -1,78 +0,0 @@
<?php
/*
* Utility Functions
*
*/
define("MASK_DOTTED_DECIMAL", 1);
define("MASK_CIDR", 2);
// Override the default error handler to prevent error messages being shown to the user
function null_error_handler( $errno, $errstr, $errfile, $errline, $errcontext ) {
switch( $errono ) {
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
case E_RECOVERABLE_ERROR: {
// Fatal error, cause the page to fail here
header("HTTP/1.1 500 Internal Server Error");
exit(0);
}
default: {
// non fatal error, let it pass
return true;
}
}
}
// Override the default exception handler to prevent error messages being shown to the user
function null_exception_handler( $exception ) {
return true;
}
function get_web_base_dir() {
static $Dir;
if (!isset($Dir))
$Dir = dirname($_SERVER['SCRIPT_NAME']);
// Doesnt cope well with being at the site root
if ($Dir == "/") $Dir = "";
return $Dir;
}
function get_fs_base_dir() {
static $Dir;
if (!isset($Dir))
$Dir = dirname($_SERVER['SCRIPT_FILENAME']);
return $Dir;
}
function get_network_mask( $ip, $subnet, $mode = MASK_DOTTED_DECIMAL ) {
$ip_l = ip2long( $ip );
if( $mode == MASK_DOTTED_DECIMAL )
// 255.255.255.0 type subnet
$subnet_l = ip2long( $subnet );
else
// CIDR type subnet
$subnet_l = (~0) << (32 - $subnet);
// Mask the two together
$network_l = $ip_l & $subnet_l;
return long2ip($network_l);
}
function array_clone( &$source, &$dest ) {
foreach( $source as $key => $value ) {
$dest[$key] = $value;
}
}
function print_rating_graph( $star_rating ) {
global $_meta;
?>
<img class="foreground" src="<?php echo $_meta['base-dir']; ?>/resources/ratings/<?php echo $star_rating; ?>" alt="Rated: <?php echo $star_rating; ?>" />
<?php
}
?>

View File

@@ -1,60 +0,0 @@
<?php
/*
* IAuthenticator
* Interface for authentication classes
*/
interface IAuthenticator {
// Do any module-specific initialisation
public function initialise();
// Shutdown the module cleanly when finished
public function uninitialise();
// Identifies whether a user with a given name exists
public function user_exists( $username );
// Checks a given username and password
public function authenticate( $username, $password );
// Does translation between a username and full name
public function username2fullname( $username );
// And vice versa
public function fullname2username( $fullname );
};
/*
* IAuthenticatorFactory
* Creates an instance of an Authenticator module, given its name
*/
class IAuthenticatorFactory {
// Prevent any instances of this class being constructed
private function __construct() {
}
public static function get( $module ) {
global $_meta;
// Get the filename and classnames for this module
$filename = "{$_meta['script-dir']}/code/auth_{$module}.php";
$classname = "Authenticator_{$module}";
// Check to make sure this module exists
if( !file_exists($filename) ) throw new ConfigException("Authentication module could not be found: '{$filename}.'", 500);
// Import the auth modules code
require_once( $filename );
// Ensure the class has been defined
if( !class_exists( $classname ) ) throw new ConfigException("Authentication module is invalid: '{$classname}'.", 500);
// Create an instance of the module, and return it
return new $classname();
}
};
?>

View File

@@ -1,58 +0,0 @@
<?php
/*
* IAuthorisor
* Interfakce for authorisation classes
*/
interface IAuthorisor {
// Do any module-specific initialisation
public function initialise();
// Shutdown the module cleanly when finished
public function uninitialise();
// Find out which usergroup a given member belongs
public function is_student( $username, $module, $year = null );
public function is_lecturer( $username, $module, $year = null );
public function is_sysadmin( $username );
// A sysadmin should not be able to administrate a course for which they are a student
public function may_sysadmin( $username, $module, $year = null );
};
/*
* IAuthorisor
* Interface for authorisation classes
*/
class IAuthorisorFactory {
public function __construct() {
}
public static function get( $module ) {
global $_meta;
// Get the filename and classnames for this module
$filename = "{$_meta['script-dir']}/code/auth_{$module}.php";
$classname = "Authorisor_{$module}";
// Check to make sure this module exists
if( !file_exists($filename) ) throw new ConfigException("Authorisation module could not be found: '{$filename}.'", 500);
// Import the auth modules code
require_once( $filename );
// Ensure the class has been defined
if( !class_exists( $classname ) ) throw new ConfigException("Authorisation module is invalid: '{$classname}'.", 500);
// Create an instance of the module, and return it
return new $classname();
}
};
?>

View File

@@ -1,71 +0,0 @@
<?php
/*
* RequestHandler
* This class decodes the Request URI in order to store multiple variables in the request URI itself
*/
class RequestHandler {
// The request uri
private $request_string;
// Stores a list of all the variables we've already found to avoid needing to
// find them using regular expressions many times.
private $cache;
public function __construct( $request_string ) {
$this->request_string = $request_string;
$this->cache = array();
}
public function current_page() {
return $this->request_string;
}
public function get( $key, $value_pattern = '[^/]*' ) {
// Look in the cache to see if weve already decoded this variable
if( in_array( $key, $this->cache ) ) return $this->cache[ $key ];
// Construct the regex to search for /$key/$value/ pairs, and return the $value part
$key = str_replace('£', '\£', $key);
$value_pattern = str_replace('£', '\£', $value_pattern);
$pattern = "£/{$key}/({$value_pattern})(/|\$";
// Look to see if this variable is in the request string
$count = preg_match( $pattern, $this->request_string, $matches );
// See if the variable was set
if( $count == 0 ) return null;
// Store the result for next time
$this->cache[ $key ] = $matches[1];
// And return it to the user
return $matches[1];
}
public function construct() {
global $_meta;
// Varargsy
$args = func_get_args();
// Construct the proper request string for these arguments
$request_string = "{$_meta['base-dir']}/";
$count = count( $args );
for( $i = 0 ; $i < $count; $i++ ) {
$arg = $args[ $i ];
// If this item is null, try to find the value of the previous key from the current
// request object as a convenience to the user. It assumes the default value pattern.
if( $arg === null && $i > 0) {
$arg = $this->get( $args[ $i -1 ] );
}
$request_string .= urlencode($arg) . '/';
}
return $request_string;
}
};
?>

View File

@@ -1,229 +0,0 @@
<?php
/*
* SessionHandler
* This class handles using php sessions to maintain user state across
* multiple sessions
*/
class SessionHandler {
private $authentication;
public function __construct() {
global $_config;
// Initialise PHP's Session subsystem
session_start();
// Check to see if this is a new session
if( !isset( $_SESSION['initialised'] ) || !$_SESSION['initialised'] ) {
$this->start_new_session();
} else {
// The session already exists, check its validity
if( $this->is_session_valid() ) {
} else {
// BAD USER SESSION, start a new one
$this->start_new_session();
// And inform the user
$_template['messages'][] = "Bad session, starting a new one";
}
}
// Load the Authentication modules
$this->authentication = IAuthenticatorFactory::get( $_config['authentication-module'] );
$this->authentication->initialise();
}
public function __destruct() {
// Shut down the authentication modules
$this->authentication->uninitialise();
}
/*
* Accessors
*/
public function authenticator() { return $this->authentication; }
/*
* The following methods deal with the user session
*/
// Initialises all the variables we require in each user session
public function start_new_session() {
global $_config;
// Set up all the session variables
$_SESSION['initialised'] = true;
$_SESSION['logged_in'] = false;
$_SESSION['username'] = '';
$_SESSION['hash'] = $this->generate_hash();
$_SESSION['previous_page'] = $_config['homepage'];
$_SESSION['requested_page'] = '';
$_SESSION['history'] = array('home');
$_SESSION['lockout'] = false;
$_SESSION['lockout-attempts'] = 0;
// Be paranoid, change session id
$this->auth_state_changed();
}
public function is_session_valid() {
// Check that the identifying information given by the user matches that which was
// saved in the session when it was created
return ($_SESSION['hash'] == $this->generate_hash());
}
/*
* This function should be called whenever the authorisation level
* changes in order to keep the session secure.
* It prevents against Session Fixation (http://www.acros.si/papers/session_fixation.pdf)
*/
public function auth_state_changed() {
session_regenerate_id();
}
/*
* This function marks a user as having been logged into the system
*/
private function mark_logged_in_as( $username ) {
$_SESSION['username'] = $username;
$_SESSION['logged_in'] = true;
// The login state has changed
$this->auth_state_changed();
}
/*
* This function marks a user as having been logged out of the system
*/
private function mark_logged_out() {
$_SESSION['logged_in'] = false;
// The login state has changedd
$this->auth_state_changed();
}
/*
* This function returns whether or not the user is logged in
*/
public function is_logged_in() {
return $_SESSION['logged_in'];
}
/*
* This function returns the username of the currently logged in user
*/
public function username() {
return $_SESSION['username'];
}
/*
* This function requires a user to be logged in, else an exception is thrown
*/
public function require_logged_in() {
if( !$_SESSION['logged_in'] ) throw new AuthenticationException('You must be logged on to view this resource');
}
/*
* This function generates a hash from user identifiable information
* to try and prevent session theft.
* If the session id is stolen by an attacker, chances are some of the
* information used to generate the hash will change, and the session
* will be immediately marked as invalid. This hash is checked for
* consistency on every request.
*/
public function generate_hash() {
global $_config;
// Hash together the following pieces of identifying information which remain constant throughout the session:
// User Agent string - This will be constant for the user, but might not be for the attacker
// Netork Mask - ensures each request is coming from the same network. We may not be able to use the
// while ip, because some ISPs use load-balanced proxies, so subsequent requests may come from a
// different machine ip, but we can still use at least part of the address to filter out would be attackers.
$key = $_SERVER['HTTP_USER_AGENT'] . get_network_mask( $_SERVER['REMOTE_ADDR'], $_config['session-network-mask'], $_config['session-network-mode']);
return md5( $key );
}
/*
* The following methods deal with the users history
* These will be used to set up redirection after special pages, such as login
*/
// Add a new item to the user history
public function history_add_request( $page ) {
global $_config;
// Add this item to the beginning of the history array
array_unshift( $_SESSION['history'], $page );
// Keep the size below a fixed limit
if( count($_SESSION['history']) > $_config['max-history'] ) {
array_pop( $_SESSION['history'] );
}
}
// Remove the current item from the user history
public function history_drop_request() {
// remove the item from the beginning of the array
array_shift( $_SESSION['history'] );
}
// Return an item from the user history
public function history_get( $index ) {
if( is_numeric($index) ) {
if( $index < count($_SESSION['history']) ) {
return $_SESSION['history'][$index];
}
}
}
/*
* The following methods deal with user authentication and authorisation
*/
// Log the user in
public function login( $username, $password ) {
global $_config;
// Check the user hasnt been locked out before trying to login
if( $_SESSION['lockout'] == true ) {
// See if the lockout has expired
if($_SESSION['lockout-expiry'] > time() ) throw new AccountLockoutException('Your session is currently locked as a result of enterring too many incorrect passwords. You will not be able to attempt a login for another ' . date('i \m\i\n(\s), s \s\e\c(\s)', $_SESSION['lockout-expiry']-time()));
else {
// Unset the lockout
$_SESSION['lockout'] = false;
$_SESSION['lockout-expiry'] = 0;
$_SESSION['lockout-attempts'] = 0;
}
}
try {
// Attempt to authenticate with these credentials
$this->authentication->authenticate( $username, $password );
} catch( Exception $e ) {
// Increment the number of failed authentication attempts
$_SESSION['lockout-attempts']++;
// Check the lockout attempts
if( $_SESSION['lockout-attempts'] >= $_config['lockout-attempts'] ) {
$_SESSION['lockout'] = true;
$_SESSION['lockout-expiry'] = time() + $_config['lockout-duration'];
throw new AccountLockoutException('You have enterred an incorrect password too many times, and your session has been locked. You will not be able to attempt another login for the next 10 minutes.');
}
// The login failed, rethrow the original exception
throw $e;
}
// Successful login, update the session state
$this->mark_logged_in_as( $username );
}
// Log the current user out
public function logout() {
$this->mark_logged_out();
}
};
?>

View File

@@ -1,115 +0,0 @@
<?php
/*
* IValidator
*
*/
interface IValidator {
/* Overhead to be called internally by the validator */
// Called when the class is first loaded
public static function initialise();
// Called whent the validation engine shuts down
public static function shutdown();
// Called when a validator object is associated with a variable to be validated
public function associate();
// Called when a validator obhject is disassociated from a variable having been validated
public function disassociate();
// Validate some input against the constructor parameters
public function validate($input);
};
/*
* IValidatorFactory
* Creates an instance of an Validator module, given its name
*/
class IValidatorFactory {
// Prevent any instances of this class being constructed
private function __construct() {
}
public static function get( $module ) {
global $_meta;
// Get the filename and classnames for this module
$filename = "{$_meta['script-dir']}/code/validation/{$module}_validator.php";
$classname = "{$module}Validator";
// Check to make sure this module exists
if( !file_exists($filename) ) throw new ConfigException("Validation module could not be found: '{$filename}.'", 500);
// Import the auth modules code
require_once( $filename );
// Ensure the class has been defined
if( !class_exists( $classname ) ) throw new ConfigException("Validation module is invalid: '{$classname}'.", 500);
// Create an instance of the module, and return it
return new $classname();
}
};
/*
* Validator
* This class provides validation for a variable using a variety of algorithms
*/
abstract class Validator {
public static function check($name, &$value) {
// Retrieve the varargs for this function
$args = func_get_args();
// Remove the first two arguments, since we already have those by name
array_shift($args); array_shift($args);
try {
// Each of the remaining arguments should be implementations of IValidator
foreach ($args as $validator) {
// Ensure this object is a validator
if (!( $validator instanceof IValidator)) {
throw new ValidationException("Invalid Validator object");
}
// Attempt to validate the input through each validator in turn
$validator->validate( $value );
}
// All successful
} catch (ValidationException $e) {
// Add the friendly name of the variable that failed validation, and rethrow the exception
// for the calling code to catch
$e->append_name($name);
throw $e;
}
}
};
/*
* Validation Exceptions
*/
class ValidationException extends BaseException {
public function __construct($message) {
parent::__construct($message);
}
public function append_name($name) {
$this->e .= ", while validating '{$name}'.";
}
};
?>

View File

@@ -1,40 +0,0 @@
<?php
/*
*
*
*/
class RangeValidator implements IValidator {
private $min;
private $max;
public function __construct($min, $max) {
}
public static function initialise() {
// Nothing to do
}
public static function shutdown() {
// Nothing to do
}
public function associate() {
// Nothing to do
}
public function disassociate() {
// Nothing to do
}
public function validate($input) {
if( $input < $min || $input > $max )
throw new ValidationException("Input is no in the range {$this->min}-{$this->max}");
}
};
?>

View File

@@ -1,82 +0,0 @@
<?php
/*
* Index
* This page handles all requests, authentication, authorisation, etc.
* It loads the page requested by the user, embeds it in the template, and sends it to the browser
*/
// Begin output buffering on the entire document, so we can set cookies
// after some output has been printed.
ob_start();
// Include our global configuration.
// It will pull all other includes we need, as well as define all global configuration and state variables.
require_once('code/config.php');
// Process the request uri for this page
$_req = new RequestHandler( $_SERVER['REQUEST_URI'] );
try {
// We will use PHP sessions to track users of the system.
// On their own, php sessions are not secure by default, so we will
// do some additional work to ensure they are used correctly.
$_session = new SessionHandler();
// See if the requested page exists
$_chroot = realpath("{$_meta['script-dir']}/page-sources/");
$_pagename = (!is_null($_req->get('page')) ? strtolower($_req->get('page')) : 'home');
$_page = $_chroot . '/' . $_pagename . '.php';
// Capture this request so we know where the user wanted to go
$_session->history_add_request( $_SERVER['REQUEST_URI'] );
// Check that this path exists
$_realpath = realpath($_page);
if( $_realpath === false ) throw new Exception('Requested page doesnt exist', 404);
// Check that the real file exists under the pages directory
if( substr($_realpath, 0, strlen($_chroot)) != $_chroot ) throw new Exception('Forbidden', 403);
} catch( Exception $e ) {
$_meta['error-code'] = $e->getCode();
$_meta['error-message'] = $e->getMessage();
$_page = "{$_meta['script-dir']}/page-sources/error.php";
}
// Capture the output and store it in the template
ob_start();
try {
include( $_page );
} catch( AuthenticationException $e ) {
// Redirect to the login page
$_template['messages'][] = $e->getMessage();
$_page = "{$_meta['script-dir']}/page-sources/login.php";
// Get the new page
ob_clean();
include( $_page );
} catch( ParameterException $e ) {
// Redirect to the error page
$_meta['error-code'] = $e->getCode();
$_meta['error-message'] = "Required parameter is either missing, or contains an illegal value: '{$e->getMessage()}'";
$_page = "{$_meta['script-dir']}/page-sources/error.php";
// Get the new page
ob_clean();
include( $_page );
}
$_template['page'] = ob_get_contents();
// Since we've already caught them, prevent the contents from being
// passed to the browser
ob_end_clean();
// Get the template
include( $_template['template-file'] );
// Send any remaining output to the browser
ob_end_flush();
?>

View File

@@ -1,26 +0,0 @@
<?php
/*
* Third Year Project
*
*/
$_template['title'] = "Third Year Project";
?>
<p>
I am still working on my project, but in the mean time I have some binaries available for user testing.
Please report any bugs you might find to <a href="https://dev.sihnon.net/bugs" title="Sihnon Bug Tracker">my bug tracker</a>.
(Note: The <a href="http://www.startcom.org" title="StartCom Certification Authority">provider</a> of my SSL certificates does not yet have their root CA certificate distributed with Internet Explorer or Opera, so if you need to, please add them as a Trusted CA.)
</p>
<p>
These binaries are solely for testing purposes, and may not be redistributed from anywhere other than this site.
I am in the process of packaging a source distribution.
<ul>
<li><a href="<?php echo $_meta['base-dir']; ?>/files/rsdss_1.01_i386.deb" title="Ubuntu x86 deb package">rsdss-1.01-i386.deb</a></li>
<li><a href="<?php echo $_meta['base-dir']; ?>/files/rsdss-1.0.1-i386.tar.bz2" title="Linux x86 binary archive">rsdss-1.0.1-i386.tar.bz2</a></li>
<li><a href="<?php echo $_meta['base-dir']; ?>/files/rsdss-1.0.2-win32.zip" title="Windows 32-bit zip archive">rsdss-1.0.2-win32.zip</a></li>
</ul>
</p>

View File

@@ -1,15 +0,0 @@
<?php
/*
*
*
*/
// Hide this page from the users history in the session
$_session->history_drop_request();
// Display the error message, and redirect
$_template['title'] = "Errawr";
$_template['messages'][] = $_meta['error-message'];
$_template['redirect-to'] = $_session->history_get(0); // Top of the list now
?>

View File

@@ -1,31 +0,0 @@
<?php
/*
*
*
*/
$_template['title'] = 'Home';
?>
<div>
<img class="avatar" style="width: 10em;" src="<?php echo $_meta['base-dir']; ?>/files/portrait.jpg" alt="Photo of Ben Roberts" />
<p>
I have almost finished the final year of a Master's degree in Computer Science at ECS, University of Southampton, UK and am due to graduate in June 2010.
</p>
<p>
Starting this summer, I will be working for <a href="http://www.uk.atosorigin.com/en-uk/" title="Atos Origin UK homepage">Atos Origin</a> as a <em>Graduate Technical Specialist</em> in the <em>Managed Operations</em> division.
</p>
<p>
I previously worked for <a href="http://www.netcraft.com/" title="Netcraft homepage">Netcraft</a> in Bath, while taking a year out from my degree studies. My roles included developing and running the <a href="http://news.netcraft.com/SSL-survey" title="Netcraft's SSL Server Survey">SSL Server Survey</a>, reviewing <a href="http://audited.netcraft.com/audited" title="Netcraft's Automated Vulnerability Scanning">Automated Vulnerability Scan</a> results, and performing occasional <a href="http://audited.netcraft.com/web-application" title="Netcraft's Application Penetration & Security Testing">penetration tests</a> against web applications for financial institutions.
</p>
<p>
On this site you can find a full copy of my <a href="<?php echo $_meta['base-dir']; ?>/files/BenRobertsCv.pdf" title="Curriculum Vitae">CV</a>,
or see what <a href="<?php echo $_req->construct('page','projects'); ?>" title="Projects">projects</a> I have been working on in my spare time.
</p>
<p>
You can contact me via me<img class="at" src="<?php echo $_meta['base-dir']; ?>/resources/at.png" alt="@" />benroberts.net.
</p>
</div>

View File

@@ -1,59 +0,0 @@
<?php
/*
* Login
* This page allows and processes a user login
*
*/
$_template['title'] = 'Login';
// Has the login form been submitted?
if( count( $_POST ) > 0 ) {
// Hide this request from the user's history
$_session->history_drop_request();
// Now it wont matter how many times a user fails authentication, they will always be redirected to the
// page they requested in the first place
// Check for the presence of the required form fields
if( !isset($_POST['username']) ) throw new ParameterException(); $username = $_POST['username'];
$password = '';
if( isset($_POST['password']) ) $password = $_POST['password'];
// Attempt the login
try {
$_session->login( $username, $password );
// Present a message to the user
$_template['messages'][] = 'You have successfully logged in.';
// Set a redirection to the page the user was originally on (now the top of the list, because we dropped this page)
$_template['redirect-to'] = $_session->history_get(0);
} catch (AuthenticationException $e) {
// Authentication failed
$_template['messages'][] = 'Authentication failed';
_show_login_form();
}
} else {
_show_login_form();
}
function _show_login_form() {
global $_meta, $_req;
// Present the login form to the user
?>
<form method="post" action="<?php echo $_req->construct('page','login'); ?>">
<p class="loginform">
<input class="username" name="username" type="text" /><label for="username">Username</label><br />
<input name="submit" type="submit" value="Login" />
</p>
</form>
<?php
}
?>

View File

@@ -1,17 +0,0 @@
<?php
/*
*
*
*/
// Log the user our of their current session
$_session->logout();
// Leave the user a notice, and redirect them back to the home page
$_template['title'] = 'Logout';
$_template['messages'][] = 'You have successfully been logged out.';
$_template['redirect-to'] = $_req->construct('page','home');
?>

View File

@@ -1,25 +0,0 @@
<?php
$_template['title'] = "Gentoo Portage Overlay";
?>
<p>
The ebuilds in this overlay have mostly been taken from the Gentoo
bugzilla, where the packages haven't yet made it into the portage tree. There
are also a couple of version-bumped packages, and packages to install my own
software on my own machines. Feel free to use these packages, but do so at your
own risk.
</p>
<p>
You can install this overlay using layman. Add the the
<a href="https://dev.sihnon.net/projects/gentoo-overlay/layman.xml" title="Sihnon overlay url">Sihnon overlay url</a>
to your overlays variable in <code class="file">/etc/layman/layman.cfg</code>. Then update
the list of overlays with <code class="root">layman -L</code> and add the Sihnon overlay
with <code class="root">layman -a sihnon</code>.
</p>
<p>
The contents of the overlay can be browsed in the
<a href="https://dev.sihnon.net/websvn/listing.php?repname=gentoo-overlay" title="Sihnon overlay browser">Sihnon overlay browser</a>.
</p>

View File

@@ -1,33 +0,0 @@
<?php
/*
*
*
*/
$_template['title'] = "Projects";
?>
<h2>Development projects:</h2>
<dl>
<dt><a href="<?php echo $_req->construct("page", "overlay"); ?>" title="Gentoo Overlay">Gentoo Portage Overlay</a></dt>
<dd>Personally developed software, or miscellaneous ebuilds that can't be found in any other overlay.</dd>
<dt><a href="https://wiki.sihnon.net/" title="Sihnon Wiki">Sihnon Wiki</a></dt>
<dd>Documentation for various systems I've configured; mostly for personal reference, but may be useful to others.</dd>
<dt><a href="https://git.sihnon.net/cgit.cgi/handbrake-cluster.git" title="HandBrakeCluster">HandBrakeCluster</a></dt>
<dd>A collection of perl scripts to use HandBrakeCLI to batch rip DVD images using multiple machines.</dd>
<dt><a href="https://git.sihnon.net/cgit.cgi/handbrake-cluster-webui.git/" title="HandBrakeCluster WebUI">HandBrakeCluster WebUI</a></dt>
<dd>An alternative web-based UI for the HandBrakeCluster scripts, written in PHP.</dd>
</dl>
<h2>University projects:</h2>
<dl>
<dt><a href="<?php echo $_req->construct("page", "3yp"); ?>" title="Third Year Project">Third Year Project</a></dt>
<dd>A cross-platform, zero-config file sharing client using public keys and a web of trust for password-less authentication and access control.</dd>
</dl>

4
private/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# configuration files
config.php
dbconfig.conf
settings.txt

92
private/config.php.dist Normal file
View File

@@ -0,0 +1,92 @@
<?php
/**
* Sihnon Framework Library path
*
* Specifies the absolute or relative path to the Sihnon Framework library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('SihnonFramework_Lib', '/usr/lib/sihnon-php-lib/source/lib/');
/**
* Sihnon Library path
*
* Specifies the absolute or relative path to the Sihnon library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('Sihnon_Lib', '/usr/lib/homepage/source/lib/');
/**
* Homepage Library path
*
* Specifies the absolute or relative path to the Homepage library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('Homepage_Lib', '/usr/lib/homepage/source/lib/');
/**
* Sihnon Database Support
*
* Specifies whether or not to include database support in the Framework
*
* @var bool
*/
define('Sihnon_DatabaseSupport', true);
/**
* Sihnon Database Configuration
*
* Specifies the absolute or relative path to the Sihnon database configuration file, required when Database support is enabled.
* This is a standard ini type file, containing the config required to connect to the database.
*
* @var string
*/
define('Sihnon_DBConfig', '/etc/homepage/dbconfig.conf');
/**
* Sihnon Config Plugin
*
* Specifies the plugin to use for configuration value storage.
* Options include:
* - Database (Requires Database Support to be enabled, and the Sihnon_ConfigTable option set)
* - FlatFile (Requires the Sihnon_ConfigFile option set)
*
* @var string
*/
define('Sihnon_ConfigPlugin', 'FlatFile');
/**
* Sihnon Config Table
*
* Specifies the name of the database table thats used for storing configuration values
* when the Database Config Plugin is used.
*
* @var string
*/
define('Sihnon_ConfigTable', 'settings');
/**
* Sihnon Config File
*
* Specifies the name of the file used to store configuration values when the FlatFile Config Plugin is used
*
* @var string
*/
define('Sihnon_ConfigFile', '/etc/homepage/settings.txt');
/**
* Sihnon Development Mode
*
* Specifies whether or not the Framework should operate in debug mode
*
* @var bool
*/
define('Sihnon_Dev', false);
?>

View File

@@ -0,0 +1,5 @@
hostname = localhost
username = homepage
password = changeme
dbname = homepage

16
private/htaccess.dist Normal file
View File

@@ -0,0 +1,16 @@
SetEnv STATUSBOARD_CONFIG /etc/homepage/config.php
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(ajax/.*)$ a.php?l=$1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?l=$1
</IfModule>

35
private/settings.txt.dist Normal file
View File

@@ -0,0 +1,35 @@
[logging.plugins]
type="array(string)"
value=FlatFile
[logging.FlatFile]
type="array(string)"
value=logfile
[logging.FlatFile.logfile.filename]
type=string
value=/var/log/homepage/homepage.log
[logging.FlatFile.logfile.format]
type=string
value="%timestamp% %hostname%:%pid% %progname%:%shortfile%[%line%] %message%"
[logging.FlatFile.logfile.severity]
type="array(string)"
value="debug
info
warning
error"
[logging.FlatFile.logfile.category]
type="array(string)"
value="default"
[cache.base_dir]
type=string
value=/dev/shm/homepage
[debug.display_exceptions]
type=bool
value=1

16
public/.htaccess Normal file
View File

@@ -0,0 +1,16 @@
SetEnv HOMEPAGE_CONFIG /home/ben/projects/homepage/private/config.php
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /~ben/homepage/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(ajax/.*)$ a.php?l=$1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?l=$1
</IfModule>

16
public/_inc.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
if (isset($_SERVER['HOMEPAGE_CONFIG']) &&
file_exists($_SERVER['HOMEPAGE_CONFIG']) &&
is_readable($_SERVER['HOMEPAGE_CONFIG'])) {
require_once($_SERVER['HOMEPAGE_CONFIG']);
} else {
require_once '/etc/homepage/config.php';
}
require_once SihnonFramework_Lib . 'SihnonFramework/Main.class.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib,
'Homepage', Homepage_Lib);
?>

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

21
public/index.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
define('Homepage_File', 'index');
require '_inc.php';
try {
$main = Homepage_Main::instance();
Homepage_LogEntry::setLocalProgname('webui');
$smarty = $main->smarty();
$page = new Homepage_Page($smarty, $main->request());
if ($page->evaluate()) {
$smarty->display('index.tpl');
}
} catch (Homepage_Exception $e) {
die("Uncaught Exception: " . $e->getMessage());
}
?>

View File

@@ -0,0 +1,113 @@
/* ==========================================================
* bootstrap-alerts.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#alerts
* ==========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ){
"use strict"
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
* ======================================================= */
var transitionEnd
$(document).ready(function () {
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
return support
})()
// set CSS transition event type
if ( $.support.transition ) {
transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
}
})
/* ALERT CLASS DEFINITION
* ====================== */
var Alert = function ( content, options ) {
this.settings = $.extend({}, $.fn.alert.defaults, options)
this.$element = $(content)
.delegate(this.settings.selector, 'click', this.close)
}
Alert.prototype = {
close: function (e) {
var $element = $(this).parent('.alert-message')
e && e.preventDefault()
$element.removeClass('in')
function removeElement () {
$element.remove()
}
$.support.transition && $element.hasClass('fade') ?
$element.bind(transitionEnd, removeElement) :
removeElement()
}
}
/* ALERT PLUGIN DEFINITION
* ======================= */
$.fn.alert = function ( options ) {
if ( options === true ) {
return this.data('alert')
}
return this.each(function () {
var $this = $(this)
if ( typeof options == 'string' ) {
return $this.data('alert')[options]()
}
$(this).data('alert', new Alert( this, options ))
})
}
$.fn.alert.defaults = {
selector: '.close'
}
$(document).ready(function () {
new Alert($('body'), {
selector: '.alert-message[data-alert] .close'
})
})
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,55 @@
/* ============================================================
* bootstrap-dropdown.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#dropdown
* ============================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
/* DROPDOWN PLUGIN DEFINITION
* ========================== */
$.fn.dropdown = function ( selector ) {
return this.each(function () {
$(this).delegate(selector || d, 'click', function (e) {
var li = $(this).parent('li')
, isActive = li.hasClass('open')
clearMenus()
!isActive && li.toggleClass('open')
return false
})
})
}
/* APPLY TO STANDARD DROPDOWN ELEMENTS
* =================================== */
var d = 'a.menu, .dropdown-toggle'
function clearMenus() {
$(d).parent('li').removeClass('open')
}
$(function () {
$('html').bind("click", clearMenus)
$('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
})
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,260 @@
/* =========================================================
* bootstrap-modal.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#modal
* =========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ){
"use strict"
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
* ======================================================= */
var transitionEnd
$(document).ready(function () {
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
return support
})()
// set CSS transition event type
if ( $.support.transition ) {
transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
}
})
/* MODAL PUBLIC CLASS DEFINITION
* ============================= */
var Modal = function ( content, options ) {
this.settings = $.extend({}, $.fn.modal.defaults, options)
this.$element = $(content)
.delegate('.close', 'click.modal', $.proxy(this.hide, this))
if ( this.settings.show ) {
this.show()
}
return this
}
Modal.prototype = {
toggle: function () {
return this[!this.isShown ? 'show' : 'hide']()
}
, show: function () {
var that = this
this.isShown = true
this.$element.trigger('show')
escape.call(this)
backdrop.call(this, function () {
var transition = $.support.transition && that.$element.hasClass('fade')
that.$element
.appendTo(document.body)
.show()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
transition ?
that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) :
that.$element.trigger('shown')
})
return this
}
, hide: function (e) {
e && e.preventDefault()
if ( !this.isShown ) {
return this
}
var that = this
this.isShown = false
escape.call(this)
this.$element
.trigger('hide')
.removeClass('in')
$.support.transition && this.$element.hasClass('fade') ?
hideWithTransition.call(this) :
hideModal.call(this)
return this
}
}
/* MODAL PRIVATE METHODS
* ===================== */
function hideWithTransition() {
// firefox drops transitionEnd events :{o
var that = this
, timeout = setTimeout(function () {
that.$element.unbind(transitionEnd)
hideModal.call(that)
}, 500)
this.$element.one(transitionEnd, function () {
clearTimeout(timeout)
hideModal.call(that)
})
}
function hideModal (that) {
this.$element
.hide()
.trigger('hidden')
backdrop.call(this)
}
function backdrop ( callback ) {
var that = this
, animate = this.$element.hasClass('fade') ? 'fade' : ''
if ( this.isShown && this.settings.backdrop ) {
var doAnimate = $.support.transition && animate
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body)
if ( this.settings.backdrop != 'static' ) {
this.$backdrop.click($.proxy(this.hide, this))
}
if ( doAnimate ) {
this.$backdrop[0].offsetWidth // force reflow
}
this.$backdrop.addClass('in')
doAnimate ?
this.$backdrop.one(transitionEnd, callback) :
callback()
} else if ( !this.isShown && this.$backdrop ) {
this.$backdrop.removeClass('in')
$.support.transition && this.$element.hasClass('fade')?
this.$backdrop.one(transitionEnd, $.proxy(removeBackdrop, this)) :
removeBackdrop.call(this)
} else if ( callback ) {
callback()
}
}
function removeBackdrop() {
this.$backdrop.remove()
this.$backdrop = null
}
function escape() {
var that = this
if ( this.isShown && this.settings.keyboard ) {
$(document).bind('keyup.modal', function ( e ) {
if ( e.which == 27 ) {
that.hide()
}
})
} else if ( !this.isShown ) {
$(document).unbind('keyup.modal')
}
}
/* MODAL PLUGIN DEFINITION
* ======================= */
$.fn.modal = function ( options ) {
var modal = this.data('modal')
if (!modal) {
if (typeof options == 'string') {
options = {
show: /show|toggle/.test(options)
}
}
return this.each(function () {
$(this).data('modal', new Modal(this, options))
})
}
if ( options === true ) {
return modal
}
if ( typeof options == 'string' ) {
modal[options]()
} else if ( modal ) {
modal.toggle()
}
return this
}
$.fn.modal.Modal = Modal
$.fn.modal.defaults = {
backdrop: false
, keyboard: false
, show: false
}
/* MODAL DATA- IMPLEMENTATION
* ========================== */
$(document).ready(function () {
$('body').delegate('[data-controls-modal]', 'click', function (e) {
e.preventDefault()
var $this = $(this).data('show', true)
$('#' + $this.attr('data-controls-modal')).modal( $this.data() )
})
})
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,90 @@
/* ===========================================================
* bootstrap-popover.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#popover
* ===========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================================================== */
!function( $ ) {
"use strict"
var Popover = function ( element, options ) {
this.$element = $(element)
this.options = options
this.enabled = true
this.fixTitle()
}
/* NOTE: POPOVER EXTENDS BOOTSTRAP-TWIPSY.js
========================================= */
Popover.prototype = $.extend({}, $.fn.twipsy.Twipsy.prototype, {
setContent: function () {
var $tip = this.tip()
$tip.find('.title')[this.options.html ? 'html' : 'text'](this.getTitle())
$tip.find('.content p')[this.options.html ? 'html' : 'text'](this.getContent())
$tip[0].className = 'popover'
}
, hasContent: function () {
return this.getTitle() || this.getContent()
}
, getContent: function () {
var content
, $e = this.$element
, o = this.options
if (typeof this.options.content == 'string') {
content = $e.attr(this.options.content)
} else if (typeof this.options.content == 'function') {
content = this.options.content.call(this.$element[0])
}
return content
}
, tip: function() {
if (!this.$tip) {
this.$tip = $('<div class="popover" />')
.html(this.options.template)
}
return this.$tip
}
})
/* POPOVER PLUGIN DEFINITION
* ======================= */
$.fn.popover = function (options) {
if (typeof options == 'object') options = $.extend({}, $.fn.popover.defaults, options)
$.fn.twipsy.initWith.call(this, options, Popover, 'popover')
return this
}
$.fn.popover.defaults = $.extend({} , $.fn.twipsy.defaults, {
placement: 'right'
, content: 'data-content'
, template: '<div class="arrow"></div><div class="inner"><h3 class="title"></h3><div class="content"><p></p></div></div>'
})
$.fn.twipsy.rejectAttrOptions.push( 'content' )
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,80 @@
/* ========================================================
* bootstrap-tabs.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function( $ ){
"use strict"
function activate ( element, container ) {
container
.find('> .active')
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
}
function tab( e ) {
var $this = $(this)
, $ul = $this.closest('ul:not(.dropdown-menu)')
, href = $this.attr('href')
, previous
, $href
if ( /^#\w+/.test(href) ) {
e.preventDefault()
if ( $this.parent('li').hasClass('active') ) {
return
}
previous = $ul.find('.active a').last()[0]
$href = $(href)
activate($this.parent('li'), $ul)
activate($href, $href.parent())
$this.trigger({
type: 'change'
, relatedTarget: previous
})
}
}
/* TABS/PILLS PLUGIN DEFINITION
* ============================ */
$.fn.tabs = $.fn.pills = function ( selector ) {
return this.each(function () {
$(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab)
})
}
$(document).ready(function () {
$('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a')
})
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,321 @@
/* ==========================================================
* bootstrap-twipsy.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#twipsy
* Adapted from the original jQuery.tipsy by Jason Frame
* ==========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ) {
"use strict"
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
* ======================================================= */
var transitionEnd
$(document).ready(function () {
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
return support
})()
// set CSS transition event type
if ( $.support.transition ) {
transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
}
})
/* TWIPSY PUBLIC CLASS DEFINITION
* ============================== */
var Twipsy = function ( element, options ) {
this.$element = $(element)
this.options = options
this.enabled = true
this.fixTitle()
}
Twipsy.prototype = {
show: function() {
var pos
, actualWidth
, actualHeight
, placement
, $tip
, tp
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animate) {
$tip.addClass('fade')
}
$tip
.remove()
.css({ top: 0, left: 0, display: 'block' })
.prependTo(document.body)
pos = $.extend({}, this.$element.offset(), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
placement = maybeCall(this.options.placement, this, [ $tip[0], this.$element[0] ])
switch (placement) {
case 'below':
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'above':
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}
break
}
$tip
.css(tp)
.addClass(placement)
.addClass('in')
}
}
, setContent: function () {
var $tip = this.tip()
$tip.find('.twipsy-inner')[this.options.html ? 'html' : 'text'](this.getTitle())
$tip[0].className = 'twipsy'
}
, hide: function() {
var that = this
, $tip = this.tip()
$tip.removeClass('in')
function removeElement () {
$tip.remove()
}
$.support.transition && this.$tip.hasClass('fade') ?
$tip.bind(transitionEnd, removeElement) :
removeElement()
}
, fixTitle: function() {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
}
}
, hasContent: function () {
return this.getTitle()
}
, getTitle: function() {
var title
, $e = this.$element
, o = this.options
this.fixTitle()
if (typeof o.title == 'string') {
title = $e.attr(o.title == 'title' ? 'data-original-title' : o.title)
} else if (typeof o.title == 'function') {
title = o.title.call($e[0])
}
title = ('' + title).replace(/(^\s*|\s*$)/, "")
return title || o.fallback
}
, tip: function() {
return this.$tip = this.$tip || $('<div class="twipsy" />').html(this.options.template)
}
, validate: function() {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function() {
this.enabled = true
}
, disable: function() {
this.enabled = false
}
, toggleEnabled: function() {
this.enabled = !this.enabled
}
, toggle: function () {
this[this.tip().hasClass('in') ? 'hide' : 'show']()
}
}
/* TWIPSY PRIVATE METHODS
* ====================== */
function maybeCall ( thing, ctx, args ) {
return typeof thing == 'function' ? thing.apply(ctx, args) : thing
}
/* TWIPSY PLUGIN DEFINITION
* ======================== */
$.fn.twipsy = function (options) {
$.fn.twipsy.initWith.call(this, options, Twipsy, 'twipsy')
return this
}
$.fn.twipsy.initWith = function (options, Constructor, name) {
var twipsy
, binder
, eventIn
, eventOut
if (options === true) {
return this.data(name)
} else if (typeof options == 'string') {
twipsy = this.data(name)
if (twipsy) {
twipsy[options]()
}
return this
}
options = $.extend({}, $.fn[name].defaults, options)
function get(ele) {
var twipsy = $.data(ele, name)
if (!twipsy) {
twipsy = new Constructor(ele, $.fn.twipsy.elementOptions(ele, options))
$.data(ele, name, twipsy)
}
return twipsy
}
function enter() {
var twipsy = get(this)
twipsy.hoverState = 'in'
if (options.delayIn == 0) {
twipsy.show()
} else {
twipsy.fixTitle()
setTimeout(function() {
if (twipsy.hoverState == 'in') {
twipsy.show()
}
}, options.delayIn)
}
}
function leave() {
var twipsy = get(this)
twipsy.hoverState = 'out'
if (options.delayOut == 0) {
twipsy.hide()
} else {
setTimeout(function() {
if (twipsy.hoverState == 'out') {
twipsy.hide()
}
}, options.delayOut)
}
}
if (!options.live) {
this.each(function() {
get(this)
})
}
if (options.trigger != 'manual') {
binder = options.live ? 'live' : 'bind'
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'
this[binder](eventIn, enter)[binder](eventOut, leave)
}
return this
}
$.fn.twipsy.Twipsy = Twipsy
$.fn.twipsy.defaults = {
animate: true
, delayIn: 0
, delayOut: 0
, fallback: ''
, placement: 'above'
, html: false
, live: false
, offset: 0
, title: 'title'
, trigger: 'hover'
, template: '<div class="twipsy-arrow"></div><div class="twipsy-inner"></div>'
}
$.fn.twipsy.rejectAttrOptions = [ 'title' ]
$.fn.twipsy.elementOptions = function(ele, options) {
var data = $(ele).data()
, rejects = $.fn.twipsy.rejectAttrOptions
, i = rejects.length
while (i--) {
delete data[rejects[i]]
}
return $.extend({}, options, data)
}
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,73 @@
/*
* Chained - jQuery non AJAX(J) chained selects plugin
*
* Copyright (c) 2010-2011 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
*/
(function($) {
$.fn.chained = function(parent_selector, options) {
return this.each(function() {
/* Save this to self because this changes when scope changes. */
var self = this;
var backup = $(self).clone();
/* Handles maximum two parents now. */
$(parent_selector).each(function() {
$(this).bind("change", function() {
$(self).html(backup.html());
/* If multiple parents build classname like foo\bar. */
var selected = "";
$(parent_selector).each(function() {
selected += "\\" + $(":selected", this).val();
});
selected = selected.substr(1);
/* Also check for first parent without subclassing. */
/* TODO: This should be dynamic and check for each parent */
/* without subclassing. */
var first = $(parent_selector).first();
var selected_first = $(":selected", first).val();
$("option", self).each(function() {
/* Remove unneeded items but save the default value. */
if (!$(this).hasClass(selected) &&
!$(this).hasClass(selected_first) && $(this).val() !== "") {
$(this).remove();
}
});
/* If we have only the default value disable select. */
if (1 == $("option", self).size() && $(self).val() === "") {
$(self).attr("disabled", "disabled");
} else {
$(self).removeAttr("disabled");
}
$(self).trigger("change");
});
/* Force IE to see something selected on first page load, */
/* unless something is already selected */
if ( !$("option:selected", this).length ) {
$("option", this).first().attr("selected", "selected");
}
/* Force updating the children. */
$(this).trigger("change");
});
});
};
/* Alias for those who like to use more English like syntax. */
$.fn.chainedTo = $.fn.chained;
})(jQuery);

File diff suppressed because one or more lines are too long

88
public/scripts/main.js Normal file
View File

@@ -0,0 +1,88 @@
/**
* StatusBoard main script file
*
*
*/
var sb = {
init: function() {
// Properly format any alert boxes
$('.alert-data').alert();
// Properly format any tab widgets
$('.tabs').tabs();
// Display popovers on all configured items
$("a[rel=popover]").popover({
offset: 10,
html: true,
});
},
admin: {
init: function() {
$('#confirm_delete').modal({
backdrop: true,
keyboard: true
});
$('#confirm_delete_cancel').click(function() {
$('#confirm_delete').modal('hide');
});
},
deleteItem: function(url) {
$('#confirm_delete_do').click(function() {
sb.request.post(url);
});
$('#confirm_delete').modal('show');
},
},
usercp: {
init: function() {
$('#usercp_newpassword,#usercp_confirmpassword').bind('keyup', sb.usercp.checkPassword);
},
checkPassword: function() {
password = $('#usercp_newpassword');
confirm = $('#usercp_confirmpassword');
confirm_container = confirm.parent().parent();
if (password.val() == confirm.val()) {
console.log("passwords match");
confirm_container.removeClass('error').addClass('success');
$('#usercp_confirmpassword_help').hide();
} else {
console.log("passwords do not match");
confirm_container.addClass('error').removeClass('success');
$('#usercp_confirmpassword_help').show();
}
}
},
request: {
post: function(url, data) {
console.log('Posting');
var form = $('<form />').attr('method', 'post').attr('action', url);
for (var key in data) {
form.appendChild($('<input type="hidden">').attr('name', key).val(data[key]));
}
form.submit();
}
}
};
$('document').ready(sb.init);

47
public/styles/normal.css Normal file
View File

@@ -0,0 +1,47 @@
/**
* StatusBoard normal stylesheet
*
*/
@import url('http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css');
@CHARSET "UTF-8";
body {
margin: 0em;
margin-top: 60px;
padding-top: 40px;
padding: 0em;
font-family: verdana, helvetica, sans-serif;
background: #F7F7F7;
}
a {
color: gray;
}
label {
margin-left: 1em;
margin-right: 1em;
}
footer {
padding: 2em;
font-size: smaller;
font-style: italic;
color: #333333;
}
/*
Images
*/
img.avatar {
float: right;
vertical-align: top;
border: 1px solid grey;
padding: 0.2em;
margin: 0.75em;
}
img.at {
height: 1em;
width: 1em;
}

View File

@@ -1,9 +0,0 @@
window.onload = function() {
for (var imgs = document.getElementsByTagName('img'),i=0,l=imgs.length; i<l; i++) {
if (/at\.png$/.exec(imgs[i].src) ) {
imgs[i].parentNode.insertBefore(document.createTextNode('@'),imgs[i]);
imgs[i].parentNode.removeChild(imgs[i]);
}
}
}

View File

@@ -1,183 +0,0 @@
/*
Layout
*/
body {
margin: 0em;
padding: 0em;
font-family: verdana, helvetica, tahoma, sans-serif;
}
div.header {
/*border-bottom: 1px solid grey;*/
padding-bottom: 0.5em;
margin: 0.5em 1em 1em 1em;
}
div.sidebar {
border-right: 1px solid grey;
margin: 0em;
padding: 1em;
float: left;
line-height: 1.8em;
}
div.page {
margin: 1em;
margin-left: 15em;
max-width: 60em;
}
div.footer {
border-top: 1px solid grey;
margin: 1em;
margin-top: 2em;
padding-top: 0.5em;
height: 3em;
clear: both;
font-size: smaller;
color: grey;
text-align: center;
}
div.sidebar ul {
list-style: none;
}
/*
Page styles
*/
div.header h1 {
font-size: 3.0em;
margin: 0.2em;
margin-left: 0em;
color: dimgrey;
}
p.subtitle {
font-weight: bold;
font-style: italic;
color: dimgrey;
}
h2 {
color: steelblue;
}
a {
text-decoration: underline;
color: steelblue;
}
a:visited {
color: dimgrey;
text-decoration: underline;
}
/*
Utilities
*/
br.spacer {
clear: both;
}
div.quote {
margin-left: 1em;
padding-left: 2em;
border: 1px solid moccasin;
background-color: moccasin;
background-image: url('../resources/quote-32.png');
background-repeat: no-repeat;
background-position: top left;
}
div.quote blockquote {
margin: 0em;
padding: 0.3em 0.6em 0em 0.3em;
font-style: italic;
background-color: lightyellow;
}
img.at {
height: 1em;
width: 1em;
}
/*
Dialog boxes
*/
p.message {
border: 1px solid red;
background-color: lightsalmon;
color: darkred;
padding: 10px;
}
p.information {
border: 1px solid blue;
background-color: powderblue;
color: darkslateblue;
padding: 10px;
}
/*
Boxed item
*/
div.box {
margin-bottom: 2em;
padding: 1.5em;
border: 1px solid grey;
}
div.box ul {
width: 20em;
line-height: 1.5em;
}
div.box span.result {
float: right;
}
/*
Forms
*/
p.loginform {
width: 30em;
}
p.loginform label {
line-height: 200%;
}
p.loginform input {
float: right;
}
/*
Images
*/
img.avatar {
float: right;
vertical-align: top;
border: 1px solid grey;
padding: 0.1em;
margin: 0.77777775em;
}
/*
*
*/
code.root {
color: crimson;
font-weight: bold;
}
code.file {
color: steelblue;
font-weight: bold;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,36 +0,0 @@
<?php
/*
*
*
*/
if( !isset($_GET['r']) || (float)$_GET['r'] < 0.0 || 10.0 < (float)$_GET['r'] ) {
header("HTTP/1.0 500 Illegal Parameter", true, 500);
die();
}
$rating = (float)$_GET['r'] / 10;
// Load in the two images
$bg = imagecreatefrompng( "rating_bg.png" );
$fg = imagecreatefrompng( "rating_fg.png" );
// Find the size of the source images
$width = imagesx( $bg );
$height = imagesy( $bg );
// Modify the width to crop to the required percentage
$fgwidth = $rating * $width;
// Crop the foreground to the desired width, copying it over the background
imagecopyresampled( $bg, $fg, 0, 0, 0, 0, $fgwidth, $height, $fgwidth, $height );
// Send the correct image content type header
header("Content-Type: image/png");
// Send the new combined image
imagepng( $bg );
?>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,68 @@
<?php
require 'smarty/Smarty.class.php';
class Homepage_Main extends SihnonFramework_Main {
const TEMPLATE_DIR = '../source/webui/templates/';
const CODE_DIR = '../source/webui/pages/';
protected static $instance;
protected $smarty;
protected $request;
protected function __construct() {
parent::__construct();
}
protected function init() {
parent::init();
$request_string = isset($_GET['l']) ? $_GET['l'] : '';
$this->request = new Homepage_RequestParser($request_string, self::TEMPLATE_DIR, self::CODE_DIR);
switch (Homepage_File) {
case 'ajax':
case 'index': {
$smarty_tmp = $this->config->get('templates.tmp_path');
$this->smarty = new Smarty();
$this->smarty->template_dir = static::makeAbsolutePath(self::TEMPLATE_DIR);
$this->smarty->compile_dir = static::makeAbsolutePath($smarty_tmp . '/templates');
$this->smarty->cache_dir = static::makeAbsolutePath($smarty_tmp . '/cache');
$this->smarty->config_dir = static::makeAbsolutePath($smarty_tmp . '/config');
$this->smarty->plugins_dir[]= static::makeAbsolutePath('../source/smarty/plugins');
$this->smarty->registerPlugin('modifier', 'formatDuration', array('Homepage_Main', 'formatDuration'));
$this->smarty->registerPlugin('modifier', 'formatFilesize', array('Homepage_Main', 'formatFilesize'));
$this->smarty->registerPlugin('modifier', 'fuzzyTime', array('Homepage_DateTime', 'fuzzyTime'));
$this->smarty->assign('version', '0.1.0');
$this->smarty->assign('messages', array());
$this->smarty->assign('base_uri', $this->base_uri);
$this->smarty->assign('base_url', static::absoluteUrl(''));
$this->smarty->assign('title', 'Homepage');
} break;
}
}
public function smarty() {
return $this->smarty;
}
/**
*
* @return StatusBoard_RequestParser
*/
public function request() {
return $this->request;
}
}
?>

View File

@@ -0,0 +1,9 @@
<?php
$main = Homepage_Main::instance();
$req = $main->request();
$this->smarty->assign('requested_page', $req->request_string());
?>

View File

@@ -0,0 +1,9 @@
<?php
$main = Homepage_Main::instance();
$req = $main->request();
$this->smarty->assign('requested_page', $req->request_string());
?>

View File

@@ -0,0 +1,10 @@
<?php
$main = Homepage_Main::instance();
$config = $main->config();
$this->smarty->assign('display_exceptions', $config->get('debug.display_exceptions'));
$this->smarty->assign('exception', $exception);
$this->smarty->assign('exception_type', get_class($exception));
?>

View File

@@ -0,0 +1,6 @@
<h2>Curriculum Vitae</h2>
<p>
I will update this page with an overview of my CV, but in the meatime, please feel free to download a
<a href="{$base_uri}files/BenRobertsCv.pdf" title="Ben Roberts CV">PDF of my full CV</a>.
</p>

View File

@@ -0,0 +1,5 @@
<h2>This page is not accessible.</h2>
<p>
The page you requested ({$requested_page|escape:html}) could not be opened.
Please ensure you are logged in and have permission to access this page.
</p>

View File

@@ -0,0 +1,6 @@
<h2>The requested page could not be found</h2>
<p>
The file you requested ({$requested_page|escape:html}) could not be found.
If you typed in the address manually, check that you have spelled it correctly,
or if you followed a link, let us know and we'll look into it.
</p>

View File

@@ -0,0 +1,62 @@
<h2>An unhandled error has occurred</h2>
<p>
There was a problem trying to complete the requested action. Please try again and if the problem persists, let us know.
</p>
{if $display_exceptions}
<p>
An unhandled exception was caught during the page template processing. The full details are shown below:
</p>
<div class="container">
<div class="row">
<div class="span4 column">
<h2>Exception</h2>
</div>
<div class="span11 column">
{$exception_type|escape:html}
</div>
</div>
<div class="row">
<div class="span4 column">
<h2>File</h2>
</div>
<div class="span11 column">
{$exception->getFile()|escape:html}
</div>
</div>
<div class="row">
<div class="span4 column">
<h2>Line</h2>
</div>
<div class="span11 column">
{$exception->getLine()}
</div>
</div>
<div class="row">
<div class="span4 column">
<h2>Message</h2>
</div>
<div class="span11 column">
{$exception->getMessage()}
</div>
</div>
<div class="row">
<div class="span4 column">
<h2>Stack Trace</h2>
</div>
<div class="span11 column">
{$exception->getTrace()|var_dump}
</div>
</div>
</div>
<p>
<em>Note:</em> Exception details should not be displayed on production systems.
Disable the <a href="{$base_uri}admin/tab/settings/"><code>Display Exceptions</code></a>
setting to omit the exception details from this page.
</p>
{/if}

View File

@@ -0,0 +1,29 @@
<div>
<img class="avatar" style="width: 10em;" src="{$base_uri}images/portrait.jpg" alt="Ben Roberts" />
<p>
I currently work as a Network Projects engineer for <a href="http://uk.atos.net/en-uk/" title="Atos - United Kingdom">Atos</a>
in the Major Projects division. My role here includes design and implementation of LAN and WAN systems in Data Centre environments.
</p>
<p>
I previously worked for <a href="http://www.netcraft.com/" title="Netcraft homepage">Netcraft</a> in Bath,
while taking a year out from my degree studies.
My roles included developing and running the <a href="http://news.netcraft.com/SSL-survey" title="Netcraft's SSL Server Survey">SSL Server Survey</a>,
reviewing <a href="http://audited.netcraft.com/audited" title="Netcraft's Automated Vulnerability Scanning">Automated Vulnerability Scan</a> results,
and performing occasional <a href="http://audited.netcraft.com/web-application" title="Netcraft's Application Penetration & Security Testing">penetration tests</a> against web applications for financial institutions.
</p>
<p>
I am a former graudate of Computer Science from ECS (University of Southampton) which I studied to the Masters level.
</p>
<p>
On this site you can find a copy of my <a href="{$base_uri}cv/" title="Curriculum Vitae">CV</a>,
or see what <a href="{$base_uri}projects/" title="Projects">projects</a> I have been working on in my spare time.
</p>
<p>
You can contact me via {mailto address="me@benroberts.net" encode="hex"}.
</p>
</div>

View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
<!-- JQuery //-->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
<!-- JQuery Plugins //-->
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/jquery.chained.js"></script>
<!-- Less //-->
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/less-1.1.5.min.js"></script>
<!-- Bootstrap //-->
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-alerts.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-twipsy.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-popover.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-dropdown.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-tabs.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/bootstrap-modal.js"></script>
<!-- Local //-->
<script type="text/javascript" src="{$base_uri}scripts/main.js"></script>
<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/smoothness/jquery-ui.css" rel="Stylesheet" />
<link rel="stylesheet/less" href="{$base_uri}less/bootstrap.less" media="all" />
<link rel="stylesheet" type="text/css" href="{$base_uri}styles/normal.css" />
</head>
<body>
<div class="topbar">
<div class="topbar-inner">
<div class="container-fluid">
{$page->include_template('navigation')}
</div><!-- /tobar-inner -->
</div><!-- /container-fliud -->
</div><!-- /topbar -->
<div class="container">
<div class="row">
<div class="span16">
<h1>
Ben Roberts
<small>MEng Computer Science @ ecs.soton.ac.uk</small>
</h1>
</div>
</div>
<div class="row">
{if ! $messages}
{$session = Homepage_Main::instance()->session()}
{$messages = $session->get('messages')}
{$session->delete('messages')}
{/if}
{if $messages}
<div id="messages">
{foreach from=$messages item=message}
{if is_array($message)}
{$severity=$message['severity']}
<div class="alert-message {$severity}">
{$message['content']|escape:html}
</div>
{else}
<div class="alert-message info">
{$message|escape:html}
</div>
{/if}
{/foreach}
</div><!-- /messages -->
{/if}
{$page_content}
</div>
<footer>
<p>
Copyright &copy; 2012 by Ben Roberts. All rights reserved unless otherwise specified.
</p>
</footer>
</div>
<!-- Piwik -->
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://miranda.sihnon.net/logs/" : "http://miranda.sihnon.net/logs/");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
</script><script type="text/javascript">
try {
var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 3);
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
</script><noscript><p><img src="http://miranda.sihnon.net/logs/piwik.php?idsite=3" style="border:0" alt=""/></p></noscript>
<!-- End Piwik Tag -->
</body>
</html>

View File

@@ -0,0 +1,24 @@
<a class="brand" href="{$base_uri}home/">Ben Roberts</a>
<ul class="nav">
<li {if $requested_page == "home"}class="active"{/if}>
<a href="{$base_uri}home/" title="Home">Home</a>
</li>
<li {if $requested_page == "cv"}class="active"{/if}>
<a href="{$base_uri}cv/" title="CV">CV</a>
</li>
<li class="dropdown {if $requested_page == "projects"}active{/if}" data-dropdown="dropdown">
<a href="#" class="dropdown-toggle" title="Projects">Projects</a>
<ul class="dropdown-menu">
<li><a href="{$base_uri}projects/" title="Projects Home">Home</a></li>
<li><a href="https://wiki.sihnon.net/" title="Sihnon Wiki">Sihnon Wiki</a></li>
<li><a href="{$base_uri}projects/gentoo-overlay/" title="Gentoo Portage Overlay">Gentoo Overlay</a></li>
<li><a href="{$base_uri}projects/sabayon-crepo/" title="Sabayon Entropy Community Repositories">Sabayon Community Repos</a></li>
<li><a href="{$base_uri}projects/ripping-cluster/" title="RippingCluster">RippingCluster</a></li>
<li><a href="{$base_uri}projects/status-board/" title="StatusBoard">StatusBoard</a></li>
<li><a href="{$base_uri}projects/3yp/" title="Third Year Project">Third Year Project</a></li>
</ul>
</li>
</ul>

View File

@@ -0,0 +1,23 @@
<h2>Opensource Projects</h2>
<dl>
<dt><a href="https://wiki.sihnon.net/" title="Sihnon Wiki">Sihnon Wiki</a></dt>
<dd>Documentation for various systems I've configured; mostly for personal reference, but may be useful to others.</dd>
<dt><a href="{$base_uri}projects/gentoo-overlay/" title="Gentoo Overlay">Gentoo Portage Overlay</a></dt>
<dd>Personally developed software, or miscellaneous ebuilds that can't be found in any other overlay.</dd>
<dt><a href="{$base_uri}projects/sabayon-crepo/" title="Sabayon Community Repositories">Sabayon Entropy Community Repositories</a></dt>
<dd>Personally developed software, or miscellaneous packages for Sabayon Linux that can't be found in the main repositories.</dd>
<dt><a href="{$base_uri}projects/ripping-cluster/" title="RippingCluster WebUI">RippingCluster WebUI</a></dt>
<dd>Web-based UI for transcoding DVDs on a cluster of multiple nodes.</dd>
<dt><a href="{$base_uri}projects/status-board/" title="StatusBoard">StatusBoard</a></dt>
<dd>PHP/Web tool for displaying service status and incident details.</dd>
</dl>
<h2>University Projects</h2>
<dl>
<dt><a href="{$base_uri}projects/3yp/" title="Third Year Project">Third Year Project</a></dt>
<dd>A cross-platform, zero-config file sharing client using public keys and a web of trust for password-less authentication and access control.</dd>
</dl>

View File

@@ -0,0 +1,16 @@
<h2>Third Year Project</h2>
<p>
I am still working on my project, but in the mean time I have some binaries available for user testing.
Please report any bugs you might find on the <a href="https://bugs.sihnon.net/" title="Sihnon Bug Tracker">bug tracker</a>.
</p>
<p>
These binaries are solely for testing purposes, and may not be redistributed from anywhere other than this site.
I may find time to produce a source package in the future.
<ul>
<li><a href="{$base_uri}files/rsdss_1.01_i386.deb" title="Ubuntu x86 deb package">rsdss-1.01-i386.deb</a></li>
<li><a href="{$base_uri}files/rsdss-1.0.1-i386.tar.bz2" title="Linux x86 binary archive">rsdss-1.0.1-i386.tar.bz2</a></li>
<li><a href="{$base_uri}files/rsdss-1.0.2-win32.zip" title="Windows 32-bit zip archive">rsdss-1.0.2-win32.zip</a></li>
</ul>
</p>

View File

@@ -0,0 +1,22 @@
<h2>Gentoo Portage Overlay</h2>
<p>
The ebuilds in this overlay have mostly been taken from the Gentoo
bugzilla, where the packages haven't yet made it into the portage tree. There
are also a couple of version-bumped packages, and packages to install homegrown
software on local machines. Feel free to use these packages, but do so at your
own risk.
</p>
<p>
You can install this overlay using layman. Add the the
<a href="https://dev.sihnon.net/projects/gentoo-overlay/layman.xml" title="Sihnon overlay url">Sihnon overlay url</a>
to your overlays variable in <code class="file">/etc/layman/layman.cfg</code>. Then update
the list of overlays with <code class="root">layman -L</code> and add the Sihnon overlay
with <code class="root">layman -a sihnon</code>.
</p>
<p>
The contents of the overlay can be browsed in the
<a href="https://git.sihnon.net/cgit.cgi/gentoo-overlay.git/" title="Sihnon Git repository browser">Sihnon Git repository browser</a>.
</p>

View File

@@ -0,0 +1,36 @@
<h2>RippingCluster</h2>
<p>
RippingCluster provides a web interface for managing a cluster of nodes for transcoding DVDs. The application provides the means
to list available sources and prepare a list of titles to rip over a collection of nodes using HandBrake and Gearman.
</p>
<div class="container">
<div class="row">
<div class="span8 column">
<img class="span8" src="{$base_uri}images/ripping-cluster/overview.png" alt="RippingCluster Overview" />
</div>
<div class="span8 column">
<h3>Features</h3>
<ul>
<li>Multiple machines can participate in transcoding jobs using a worker daemon.</li>
<li>Single WebUI to prepare and manage jobs.</li>
<li>Batch multiple titles from a single DVD source (perfect for TV boxsets).</li>
<li>Select audio and subtitle streams to include with the rip.</li>
</ul>
</div>
</div>
<div class="row">
<div class="span16">
<h3>Source</h3>
<p>
View or clone a copy of the Git repositories from one of the mirrors listed below:
</p>
<ul>
<li><a href="https://git.sihnon.net/cgit.cgi/handbrake-cluster-webui.git/" title="Sihnon Mirror">Sihnon Mirror</a></li>
<li><a href="https://github.com/optiz0r/handbrake-cluster-webui/" title="GitHub Mirror">GitHub Mirror</a></li>
</ul>
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<h2>Sabayon Entropy Community Repositories</h2>
<p>
I have a collection of community repositories for both amd64 and i686 architectures. These contain version-bumped or modified packages already
present in the upstream repositories, packages available in Gentoo but not Sabayon, and locally-developed software. Feel free to add these if any
of the packages inside might be of use, but please be aware they are provided as-is with no guarantees.
</p>
<dl>
<dt><a href="http://packages.sihnon.net/standard/packages.sihnon.net/" title="Common Repository">packages.sihnon.net</a></dt>
<dd>Packages suitable for machines of any designation.</dd>
<dt><a href="http://packages.sihnon.net/standard/server.packages.sihnon.net/" title="Server Repository">server.packages.sihnon.net</a></dt>
<dd>Packages suitable for server machines (without X).</dd>
<dt><a href="http://packages.sihnon.net/standard/desktop.packages.sihnon.net/" title="Desktop Repository">desktop.packages.sihnon.net</a></dt>
<dd>Packages suitable for desktop machines (with X).</dd>
</dl>

View File

@@ -0,0 +1,37 @@
<h2>StatusBoard</h2>
<p>
StatusBoard is a simple PHP web-based tool for displaying the status of services. Administrators can manually record incidents and provide
estimated end times and simple descriptions. This tool is suitable for exposing status information to customers or other third parties and
does not need to be connected to internal systems.
</p>
<div class="container">
<div class="row">
<div class="span8 column">
<h3>Features</h3>
<ul>
<li>Customisable list of Services and Sites.</li>
<li>Manual reporting and status changes for Incidents.</li>
<li>Multiple severity levels.</li>
<li>Full admin UI.</li>
</ul>
</div>
<div class="span8 column">
<img class="span8" src="{$base_uri}images/status-board/overview.png" alt="StatusBoard Overview" />
</div>
</div>
<div class="row">
<div class="span16">
<h3>Source</h3>
<p>
View or clone a copy of the Git repositories from one of the mirrors listed below:
</p>
<ul>
<li><a href="https://git.sihnon.net/cgit.cgi/status-board.git/" title="Sihnon Mirror">Sihnon Mirror</a></li>
<li><a href="https://github.com/optiz0r/status-board/" title="GitHub Mirror">GitHub Mirror</a></li>
</ul>
</div>
</div>
</div>

View File

@@ -1,98 +0,0 @@
<?php
/*
* Template
* This file contains the common html elements for all pages in the site.
* _template variables contain the information from the acutal page to be displayed
*/
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><?php echo $_template['title']; ?></title>
<link rel="stylesheet" type="text/css" href="<?php echo $_meta['base-dir']; ?>/resources/normal.css" />
<script language="javascript" type="text/javascript" src="<?php echo $_meta['base-dir']; ?>/resources/email.js"></script>
<?php
// If we have a redirection, implement a meta refresh here
if( $_template['redirect-to'] ) {
?>
<meta http-equiv="refresh" content="5;url=<?php echo $_template['redirect-to']; ?>" />
<?php
}
// Add any header info from the page
echo $_template['head'];
?>
</head>
<body>
<div class="header">
<h1>Ben Roberts</h1>
<p class="subtitle">MEng Computer Science @ ecs.soton.ac.uk</p>
</div>
<div class="sidebar">
<ul>
<li><a href="<?php echo $_req->construct('page','home'); ?>" title="Homepage">Home</a></li>
<li><a href="<?php echo $_meta['base-dir']; ?>/files/BenRobertsCv.pdf" title="Curriculum Vitae">Curriculum Vitae</a></li>
<li><a href="<?php echo $_req->construct('page','projects'); ?>" title="Projects">Projects</a></li>
<?php if( $_session->is_logged_in() ) { ?>
<li><a href="<?php echo $_req->construct('page','logout'); ?>" title="Logout">Logout</a></li>
<?php } ?>
</ul>
</div>
<div class="page">
<?php
// Display any page annoucements
if( count($_template['messages']) > 0 ) {
foreach( $_template['messages'] as $_message ) {
?>
<p class="message">
<?php echo $_message; ?>
</p>
<?php
}
}
?>
<h2><?php echo $_template['title']; ?></h2>
<?php
// Include the main page content
echo $_template['page'];
// Display any redirections
if( $_template['redirect-to'] ) {
?>
<p class="information">
About to be redirected... If nothing happens after 10 seconds, please click <a href="<?php echo $_template['redirect-to']; ?>" title="Redirecting">here</a>.
</p>
<?php
}
?>
</div>
<div class="footer">
<p>
Copyright &copy; 2008-2010 by Ben Roberts. All rights reserved unless otherwise specified.
</p>
<p>
This file was last modified <?php echo date("j F Y", filemtime($_page)); ?>.
</p>
</div>
<!-- Piwik -->
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://miranda.sihnon.net/logs/" : "http://miranda.sihnon.net/logs/");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
</script><script type="text/javascript">
try {
var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 3);
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
</script><noscript><p><img src="http://miranda.sihnon.net/logs/piwik.php?idsite=3" style="border:0" alt=""/></p></noscript>
<!-- End Piwik Tag -->
</body>
</html>