Updated bootstrap and many other items

This commit is contained in:
Deon George 2014-09-29 14:47:51 +10:00
parent 85150c655c
commit 9ae0980221
27 changed files with 999 additions and 66 deletions

4
classes/Auth.php Normal file
View File

@ -0,0 +1,4 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
abstract class Auth extends lnApp_Auth {}
?>

View File

@ -0,0 +1,9 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Welcome extends Controller_TemplateDefault {
protected $auth_required = FALSE;
public function action_index() {
throw HTTP_Exception::factory(500,'Site not setup!');
}
} // End Welcome

View File

@ -0,0 +1,4 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
class Database_MySQL extends lnApp_Database_MySQL {}
?>

4
classes/Email.php Normal file
View File

@ -0,0 +1,4 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
class Email extends lnApp_Email {}
?>

View File

@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Email_Exception extends lnApp_Email_Exception {}

View File

@ -6,8 +6,8 @@
* @package lnApp * @package lnApp
* @category Helpers * @category Helpers
* @author Deon George * @author Deon George
* @copyright (c) 2009-2013 Open Source Billing * @copyright (c) 2009-2013 Deon George
* @license http://dev.osbill.net/license.html * @license http://dev.leenooks.net/license.html
*/ */
class Object { class Object {
/** /**

22
classes/lnApp/Auth.php Normal file
View File

@ -0,0 +1,22 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class overrides Kohana's Auth
*
* This file contains enhancements for Kohana, that should be considered upstream and maybe havent been yet.
*
* @package lnApp
* @category Modifications
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
abstract class lnApp_Auth extends Kohana_Auth {
protected $_model = 'Account';
public function get_user($default=NULL) {
$x = parent::get_user($default);
return (! $x) ? ORM::factory($this->_model) : $x;
}
}

View File

@ -50,11 +50,11 @@ abstract class lnApp_Block extends HTMLRender {
$record = self::$_data[$this->_x]; $record = self::$_data[$this->_x];
$output = ''; $output = '';
$output .= sprintf('<div class="col-md-%s %s">',empty($record['span']) ? '12' : $record['span'],empty($record['scrollable']) ? '' : 'scrollable'); $output .= sprintf('<div class="col-md-%s %s">',Arr::get($record,'span',12),Arr::get($record,'scrollable',''));
$output .= '<div class="widget stacked">'; $output .= '<div class="widget stacked">';
if (! empty($record['title'])) if (! empty($record['title']))
$output .= sprintf('<div class="widget-header">%s<h3>%s</h3></div>',empty($record['title_icon']) ? '' : sprintf(' <i class="fa %s"></i>',$record['title_icon']),$record['title']); $output .= sprintf('<div class="widget-header"><i class="fa %s"></i><h3>%s</h3></div>',Arr::get($record,'title_icon','fa-square'),Arr::get($record,'title','NO Title!'));
$output .= '<div class="widget-content">'; $output .= '<div class="widget-content">';
@ -65,7 +65,7 @@ abstract class lnApp_Block extends HTMLRender {
$output .= Form::close(); $output .= Form::close();
break; break;
case 'form-horizontal': $output .= Form::open(NULL,Arr::merge(array('class'=>'form-horizontal','role'=>'form'),(empty($record['id']) ? array() : array('id'=>$record['id'])))); case 'form-horizontal': $output .= Form::open(NULL,Arr::merge(array('class'=>'form-horizontal','role'=>'form','data-toggle'=>'validator'),(empty($record['id']) ? array() : array('id'=>$record['id']))));
$output .= (string)$record['body']; $output .= (string)$record['body'];
$output .= Form::close(); $output .= Form::close();
break; break;

View File

@ -13,6 +13,93 @@
class lnApp_Controller_Login extends Controller_TemplateDefault { class lnApp_Controller_Login extends Controller_TemplateDefault {
protected $auth_required = FALSE; protected $auth_required = FALSE;
/**
* Activate an account so that it can login and use the site
*/
public function action_activate() {
if ($this->request->post()) {
$ao = ORM::factory('Account',array('id'=>$this->request->param('id'),'email'=>$this->request->post('email')));
if ($ao->loaded()) {
if ($ao->activated())
HTTP::redirect('login');
elseif ($ao->activate_code() == $this->request->post('code')) {
$go = ORM::factory('Group',array('name'=>'Registered Users'));
$ago = ORM::factory('Account_Group',array('account_id'=>$ao,'group_id'=>$go));
if (! $ago->loaded()) {
$ago->account_id=$ao;
$ago->group_id=$go;
}
$ago->active = TRUE;
$ago->save();
SystemMessage::factory()
->title(_('Account Activated'))
->type('info')
->body(_('Your account has been activated.'));
}
}
} elseif (! $this->request->param('id'))
HTTP::redirect('login/activate_resend');
Block::factory()
->title('Activate account')
->title_icon('fa-wrench')
->type('form-horizontal')
->body(View::factory('login/activate')->set('o',Session::instance()->get_once('activate')));
}
/**
* Send the account activation code to the email address, validating the email address
*/
public function action_activate_resend() {
if ($this->request->post('email')) {
$ao = ORM::factory('Account',array('email'=>$this->request->post('email')));
if ($ao->loaded()) {
if ($ao->activated())
HTTP::redirect('login');
else {
$co = Company::instance();
// Send our email with the token
$email = Email::factory('login_activate')
->set('SITE',URL::base(TRUE,TRUE))
->set('SITE_ADMIN',$co->admin()->name())
->set('CODE',$ao->activate_code())
->set('EMAIL',$ao->email)
->set('ID',$ao->id)
->set('USER_NAME',$ao->name());
$email->to = array('email'=>array($ao->email=>$ao->name()));
$email->from = array('email'=>array($co->admin()->email=>$co->admin()->name()));
$email->subject = 'Activation Code for '.$co->name();
$email->deliver();
// Log the password reset
$ao->log('Activation code sent');
Session::instance()->set('activate',$ao);
}
}
HTTP::redirect('login/activate/'.$ao->id);
} else {
Block::factory()
->title('Activate account')
->title_icon('fa-wrench')
->type('form-horizontal')
->body(View::factory('login/activate_resend'));
}
}
/**
* Login to the site
*/
public function action_index() { public function action_index() {
$output = ''; $output = '';
@ -24,9 +111,9 @@ class lnApp_Controller_Login extends Controller_TemplateDefault {
HTTP::redirect(URL::link('user','welcome/index')); HTTP::redirect(URL::link('user','welcome/index'));
// If there is a post and $_POST is not empty // If there is a post and $_POST is not empty
if ($_POST) { if ($this->request->post()) {
// If the post data validates using the rules setup in the user model // If the post data validates using the rules setup in the user model
if (Auth::instance()->login($_POST['username'],$_POST['password'])) { if (Auth::instance()->login($this->request->post('username'),$this->request->post('password'))) {
// Redirect to the user account // Redirect to the user account
if ($redir = Session::instance()->get('afterlogin')) { if ($redir = Session::instance()->get('afterlogin')) {
Session::instance()->delete('afterlogin'); Session::instance()->delete('afterlogin');
@ -48,7 +135,7 @@ class lnApp_Controller_Login extends Controller_TemplateDefault {
else else
$oauthlogin = FALSE; $oauthlogin = FALSE;
$output .= View::factory('pages/login') $output .= View::factory('login')
->set('oauth',$oauthlogin); ->set('oauth',$oauthlogin);
Style::factory() Style::factory()
@ -63,11 +150,92 @@ class lnApp_Controller_Login extends Controller_TemplateDefault {
$this->template->shownavbar = FALSE; $this->template->shownavbar = FALSE;
} }
/**
* Method redirect when authenticated user doesnt have access to the url
*/
public function action_noaccess() { public function action_noaccess() {
SystemMessage::factory() SystemMessage::factory()
->title(_('No access to requested resource')) ->title(_('No access to requested resource'))
->type('danger') ->type('danger')
->body(_('You do not have access to the requested resource, please contact your administrator.')); ->body(_('You do not have access to the requested resource, please contact your administrator.'));
} }
/**
* Register for an account on the site
*/
public function action_register() {
$ao = ORM::factory('Account',$this->request->param('id'));
if ($this->request->post() AND $ao->values($this->request->post())->changed() AND (! $this->save($ao)))
$ao->reload()->values($this->request->post());
if ($ao->loaded())
HTTP::redirect('login');
Block::factory()
->type('form-horizontal')
->title('Register Account')
->title_icon('fa-edit')
->body(View::factory('account/user/edit')->set('o',$ao));
}
/**
* Enable user password reset
*/
public function action_reset() {
// Minutes to keep our token
$token_expire = 15;
$co = Company::instance();
// If user already signed-in
if (Auth::instance()->logged_in())
HTTP::redirect('welcome/index');
// If the user posted their details to reset their password
if ($this->request->post()) {
// If the username is correct, create a method token
if ($ao=ORM::factory('Account',array('email'=>$this->request->post('username'))) AND $ao->loaded()) {
$mmto = ORM::factory('Module_Method_Token')
->method(array('account','user:resetpassword'))
->account($ao)
->uses(2)
->expire(time()+$token_expire*60);
if ($mmto->generate()) {
// Send our email with the token
$email = Email::factory('login_reset')
->set('SITE',URL::base(TRUE,TRUE))
->set('SITE_ADMIN',$co->admin()->name())
->set('TOKEN',$mmto->token)
->set('TOKEN_EXPIRE_MIN',$token_expire)
->set('USER_NAME',$mmto->account->name());
$email->to = array('email'=>array($mmto->account->email=>$mmto->account->name()));
$email->from = array('email'=>array($co->admin()->email=>$co->admin()->name()));
$email->subject = 'Login Reset Token for '.$co->name();
$email->deliver();
// Log the password reset
$ao->log('Password reset token sent');
}
// Redirect to our password reset, the Auth will validate the token.
} elseif ($this->request->post('token')) {
HTTP::redirect(URL::link('user','account/resetpassword?token='.$this->request->post('token')));
}
// Show our token screen even if the email was invalid.
if ($this->request->post('username'))
$output = View::factory('login/reset_sent');
else
HTTP::redirect('login');
} else {
$output = View::factory('login/reset');
}
$this->template->content = $output;
$this->template->shownavbar = FALSE;
}
} }
?> ?>

View File

@ -56,13 +56,7 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa
* @return boolean * @return boolean
*/ */
protected function _auth_required() { protected function _auth_required() {
// If our global configurable is disabled, then continue
if (! Kohana::$config->load('config')->method_security)
return FALSE; return FALSE;
return (($this->auth_required !== FALSE && Auth::instance()->logged_in(NULL,get_class($this).'|'.__METHOD__) === FALSE) ||
(is_array($this->secure_actions) && array_key_exists($this->request->action(),$this->secure_actions) &&
! Auth::instance()->logged_in($this->secure_actions[$this->request->action()],get_class($this).'|'.__METHOD__)));
} }
/** /**
@ -72,16 +66,6 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa
* @uses meta * @uses meta
*/ */
public function before() { public function before() {
if ($this->auth_required) {
if (! count($this->secure_actions) OR (! isset($this->secure_actions[Request::current()->action()])))
throw HTTP_Exception::factory(403,'Class has no security defined :class, or no security configured for :method',array(':class'=>get_class($this),':method'=>Request::current()->action()));
$this->ao = Auth::instance()->get_user();
if (! is_null($this->ao) AND (is_string($this->ao) OR ! $this->ao->loaded()))
throw HTTP_Exception::factory(501,'Account doesnt exist :account ?',array(':account'=>(is_string($this->ao) OR is_null($this->ao)) ? $this->ao : Auth::instance()->get_user()->id));
}
// Actions that start with ajax, should only be ajax // Actions that start with ajax, should only be ajax
if (! Kohana::$config->load('debug')->ajax AND preg_match('/^ajax/',Request::current()->action()) AND ! Request::current()->is_ajax()) if (! Kohana::$config->load('debug')->ajax AND preg_match('/^ajax/',Request::current()->action()) AND ! Request::current()->is_ajax())
throw HTTP_Exception::factory(412,_('Unable to fulfil request.')); throw HTTP_Exception::factory(412,_('Unable to fulfil request.'));
@ -94,6 +78,9 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa
return; return;
} }
if ($this->ao AND $this->ao->loaded() AND ! $this->ao->activated() AND ($this->request->controller() != 'Account' OR $this->request->action() != 'activate'))
HTTP::redirect('login/activate');
// Check user auth and role // Check user auth and role
if ($this->_auth_required()) { if ($this->_auth_required()) {
if (PHP_SAPI === 'cli') if (PHP_SAPI === 'cli')
@ -187,30 +174,18 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa
$this->check_cache(sha1($this->response->body())); $this->check_cache(sha1($this->response->body()));
} }
/** protected function save(Model $o) {
* Generate a view path to help View::factory() calls try {
* return $o->save();
* The purpose of this method is to ensure that we have a consistant
* layout for our view files, including those that are needed by
* plugins
*
* @param string Plugin Name (optional)
* @deprecated
*/
public function viewpath($plugin='') {
$request = Request::current();
$path = $request->controller(); } catch (ORM_Validation_Exception $e) {
SystemMessage::factory()
->title('Record NOT updated')
->type('danger')
->body(join('<br/>',array_values($e->errors('models'))));
if ($request->directory()) return FALSE;
$path .= ($path ? '/' : '').$request->directory(); }
if ($plugin)
$path .= ($path ? '/' : '').$plugin;
$path .= ($path ? '/' : '').$request->action();
return strtolower($path);
} }
} }
?> ?>

View File

@ -0,0 +1,20 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* MySQL database connection.
*
* Modified for MDB, so that ORM can use enhanced SQL queries. This has been
* achieved by simply removing the identifier backtick.
* (IE: SELECT function()...)
*
* @package lnApp
* @category Modifications
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
abstract class lnApp_Database_MySQL extends Kohana_Database_MySQL {
// MySQL uses a backtick for identifiers
protected $_identifier = '';
}
?>

374
classes/lnApp/Email.php Normal file
View File

@ -0,0 +1,374 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides email template handling in the same manor as views.
*
* @package lnApp
* @category Helpers
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
abstract class lnApp_Email extends Kohana_Email {
// Array of global variables
protected static $_global_data = array();
// View filename
protected $_file = array();
// Array of local variables
protected $_data = array();
// Email Details
protected $_email = array();
// Our email subject key
private $_key;
private $_mime = array(
'txt'=>'text/plain',
'htm'=>'text/html',
);
/**
* Sets the initial view filename and local data. Views should almost
* always only be created using [Email::factory].
*
* $email = new Email($file);
*
* @param string $file view filename
* @param array $data array of values
* @return void
* @uses Email::set_filename
*/
public function __construct($file=NULL,array $data=NULL) {
$this->_key = $file;
if ($file !== NULL)
$this->set_filename($file);
// Add the values to the current data
if ($data !== NULL)
$this->_data = $data + $this->_data;
}
/**
* Magic method, searches for the given variable and returns its value.
* Local variables will be returned before global variables.
*
* $value = $email->foo;
*
* [!!] If the variable has not yet been set, an exception will be thrown.
*
* @param string $key variable name
* @return mixed
* @throws Kohana_Exception
*/
public function & __get($key) {
if (array_key_exists($key,$this->_data))
return $this->_data[$key];
elseif (array_key_exists($key,$this->_email))
return $this->_email[$key];
elseif (array_key_exists($key,Email::$_global_data))
return Email::$_global_data[$key];
else
throw new Kohana_Exception('View variable is not set: :var',array(':var'=>$key));
}
/**
* Magic method, determines if a variable is set.
*
* isset($email->foo);
*
* [!!] `NULL` variables are not considered to be set by [isset](http://php.net/isset).
*
* @param string $key variable name
* @return boolean
*/
public function __isset($key) {
return (isset($this->_data[$key]) OR isset($this->_email[$key]) OR isset(Email::$_global_data[$key]));
}
/**
* Magic method, calls [Email::set] with the same parameters.
*
* $email->foo = 'something';
*
* @param string $key variable name
* @param mixed $value value
* @return void
*/
public function __set($key,$value) {
switch ($key) {
case 'from':
case 'bcc':
case 'to':
if (! is_array($value) OR ! array_intersect(array('email','account'),array_keys($value)))
throw new Email_Exception('Values for to should be an array of either "mail" or "account", however :value was given',
array(':value'=>serialize($value)));
case 'subject':
$this->_email[$key] = $value;
break;
default:
$this->set($key,$value);
}
}
/**
* Magic method, returns the output of [Email::render].
*
* @return string
* @uses Email::render
*/
public function __toString() {
$e = Email::connect();
try {
return (string)$this->render();
} catch (Exception $e) {
/**
* Display the exception message.
*
* We use this method here because it's impossible to throw an
* exception from __toString().
*/
$error_response = Kohana_Exception::_handler($e);
return $error_response->body();
}
}
/**
* Magic method, unsets a given variable.
*
* unset($email->foo);
*
* @param string $key variable name
* @return void
*/
public function __unset($key) {
unset($this->_data[$key],$this->_email[$key],Email::$_global_data[$key]);
}
/**
* Assigns a value by reference. The benefit of binding is that values can
* be altered without re-setting them. It is also possible to bind variables
* before they have values. Assigned values will be available as a
* variable within the view file:
*
* // This reference can be accessed as $ref within the view
* $email->bind('ref', $bar);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @return $this
*/
public function bind($key,&$value) {
$this->_data[$key] =& $value;
return $this;
}
/**
* Assigns a global variable by reference, similar to [Email::bind], except
* that the variable will be accessible to all views.
*
* Email::bind_global($key,$value);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @return void
*/
public static function bind_global($key,&$value) {
Email::$_global_data[$key] =& $value;
}
/**
* Captures the output that is generated when a view is included.
* The view data will be extracted to make local variables. This method
* is static to prevent object scope resolution.
*
* $output = Email::capture($file,$data);
*
* @param string $kohana_view_filename filename
* @param array $kohana_view_data variables
* @return string
*/
protected static function capture($kohana_view_filename,array $kohana_view_data) {
return Email::complete(file_get_contents($kohana_view_filename),Arr::merge(Email::$_global_data,$kohana_view_data));
}
/**
* Exchange the variables for values
*/
private static function complete($output,$data) {
foreach (Email::variables($output) as $v)
$output = str_replace('$'.$v.'$',$data[$v],$output);
return $output;
}
/**
* Deliver the email
*/
public function deliver(array $admin=array()) {
// @todo - Setup queue mode
return Email::connect()->send($this->render(NULL,$admin));
}
/**
* Get email details
*/
private function email($key) {
if (is_array($this->_email[$key]) AND isset($this->_email[$key]['email']))
return $this->_email[$key]['email'];
else
return (is_array($this->_email[$key]) AND isset($this->_email[$key]['account'])) ? $this->_email[$key]['account'] : $this->_email[$key];
}
/**
* Returns a new Email object. If you do not define the "file" parameter,
* you must call [Email::set_filename].
*
* $email = Email::factory($file);
*
* @param string $file email filename
* @param array $data array of values
* @return Email
*/
public static function factory($file=NULL,array $data=NULL) {
return new Email($file,$data);
}
/**
* Renders the view object to a string. Global and local data are merged
* and extracted to create local variables within the view file.
*
* $output = $email->render();
*
* [!!] Global variables with the same key name as local variables will be
* overwritten by the local variable.
*
* @param string $file view filename
* @return string
* @throws Email_Exception
* @uses Email::capture
*/
public function render($file=NULL,array $admin=array()) {
if ($file !== NULL)
$this->set_filename($file);
if (empty($this->_file))
throw new Email_Exception('You must set the file to use within your email before rendering');
if ($x=array_diff(array('to','from','subject'),array_keys($this->_email)))
throw new Email_Exception('You are missing :missing from the Email',array(':missing'=>join('|',$x)));
$sm = Swift_Message::newInstance()
->setFrom($this->email('from'))
->setSubject($this->email('subject'));
foreach ($this->_file as $file) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$sm->addPart(Email::capture($file,$this->_data),array_key_exists($ext,$this->_mime) ? $this->_mime[$ext] : File::mime($file));
}
// Our list of BCC recipients
if (Kohana::$config->load('debug')->email_bcc_admin AND $x=Arr::merge($this->email('bcc'),Kohana::$config->load('debug')->email_bcc_admin))
$sm->setBcc($x);
$sm->setTo(($admin OR ($admin = Config::testmail($this->_key))) ? $admin : $this->email('to'));
return $sm;
}
/**
* Assigns a variable by name. Assigned values will be available as a
* variable within the view file:
*
* // This value can be accessed as $foo within the view
* $email->set('foo','my value');
*
* You can also use an array to set several values at once:
*
* // Create the values $food and $beverage in the view
* $email->set(array('food' => 'bread', 'beverage' => 'water'));
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return $this
*/
public function set($key,$value=NULL) {
if (is_array($key))
foreach ($key as $name => $value)
$this->_data[$name] = $value;
else
$this->_data[$key] = $value;
return $this;
}
/**
* Sets the view filename.
*
* $email->set_filename($file);
*
* @param string $file view filename
* @return Email
* @throws Email_Exception
*/
public function set_filename($file) {
foreach (array_keys($this->_mime) as $ext)
if ($path=Kohana::find_file('email',$file,$ext))
// Store the file path locally
array_push($this->_file,$path);
if (! $this->_file)
throw new Email_Exception('The requested email :file could not be found',array(':file'=>$file));
return $this;
}
/**
* Sets a global variable, similar to [Email::set], except that the
* variable will be accessible to all views.
*
* Email::set_global($name, $value);
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return void
*/
public static function set_global($key,$value=NULL) {
if (is_array($key))
foreach ($key as $key2 => $value)
Email::$_global_data[$key2] = $value;
else
Email::$_global_data[$key] = $value;
}
/**
* Extract the variables in the text
*/
public static function variables($output) {
$results = array();
$matches = array();
preg_match_all('/\$([A-Z0-9_]+)\$/U',$output,$matches,PREG_OFFSET_CAPTURE);
foreach ($matches[1] as $k => $v)
if (! in_array($v[0],$results))
array_push($results,$v[0]);
return $results;
}
}
?>

View File

@ -0,0 +1,9 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package lnApp
* @category Exceptions
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license
*/
class lnApp_Email_Exception extends Kohana_Exception {}

View File

@ -51,6 +51,9 @@ abstract class lnApp_Form extends Kohana_Form {
$output .= '%s'; $output .= '%s';
if (in_array('required',$attributes))
$output .= '<div class="help-block with-errors"></div>';
if ($classdiv) if ($classdiv)
$output .= '</div>'; $output .= '</div>';

View File

@ -6,7 +6,7 @@
* @package lnApp * @package lnApp
* @category Modifications * @category Modifications
* @author Deon George * @author Deon George
* @copyright (c) 2009-2013 Open Source Billing * @copyright (c) 2009-2013 Deon George
* @license http://dev.leenooks.net/license.html * @license http://dev.leenooks.net/license.html
*/ */
abstract class lnApp_HTTP_Exception extends Kohana_HTTP_Exception { abstract class lnApp_HTTP_Exception extends Kohana_HTTP_Exception {
@ -20,7 +20,7 @@ abstract class lnApp_HTTP_Exception extends Kohana_HTTP_Exception {
->set('message',$this->getMessage()); ->set('message',$this->getMessage());
$output .= '<div class="error-actions">'; $output .= '<div class="error-actions">';
$output .= HTML::anchor((array_key_exists('auth',Kohana::modules()) AND URL::admin_url() ? 'u/' : '').'welcome','<i class="fa fa-chevron-left"></i> Back to Home',array('class'=>'btn btn-large btn-primary')); $output .= HTML::anchor(((array_key_exists('auth',Kohana::modules()) AND URL::admin_url()) ? 'u/' : '').'welcome','<i class="fa fa-chevron-left"></i> Back to Home',array('class'=>'btn btn-large btn-primary'));
$output .= '</div>'; $output .= '</div>';
$output .= '</div></div></div>'; $output .= '</div></div></div>';

View File

@ -4,7 +4,6 @@
* This class overrides Kohana's ORM * This class overrides Kohana's ORM
* *
* This file contains enhancements for Kohana, that should be considered upstream and maybe havent been yet. * This file contains enhancements for Kohana, that should be considered upstream and maybe havent been yet.
* It also contains some functionality for OSB, which cannot be covered in ORM_OSB.
* *
* @package lnApp * @package lnApp
* @category Modifications * @category Modifications

View File

@ -1,7 +1,7 @@
<?php defined('SYSPATH') or die('No direct access allowed.'); <?php defined('SYSPATH') or die('No direct access allowed.');
/** /**
* WWZ Configuration - Debug Settings * lnApp Configuration - Debug Settings
* *
* @package lnApp * @package lnApp
* @category Configuration * @category Configuration
@ -14,6 +14,13 @@ return array
( (
'ajax'=>FALSE, // AJAX actions can only be run by ajax calls if set to FALSE 'ajax'=>FALSE, // AJAX actions can only be run by ajax calls if set to FALSE
'etag'=>FALSE, // Force generating ETAGS 'etag'=>FALSE, // Force generating ETAGS
'method_security'=>FALSE, // Debug Method Security
'site'=>FALSE, // Glogal site debug 'site'=>FALSE, // Glogal site debug
'email_bcc_admin'=>array(
// 'deon@leenooks.vpn' => 'Deon George',
), // BCC this person on all emails
'email_admin_only'=>array(
// 'login_reset'=>array('deon@leenooks.vpn'=>'Deon George'),
), // Send emails for this template to just this person
); );
?> ?>

18
email/login_activate.txt Normal file
View File

@ -0,0 +1,18 @@
Dear $USER_NAME$,
You (or someone who knows your email) has recently submitted a request to activate your account
If this was you, you can click the link below to activate your account.
$SITE$u/login/activate?id=$ID$&email=$EMAIL$&code=$CODE$
If the link above does not display as a link in your browser, you will need to paste it into the address bar of your browser instead.
If your browser is still on the activation page, and asking for a Code, yours is
$CODE$
If you did not request a password retrieval at our site, then you can safetly ignore and delete this email.
Thank you,
$SITE_ADMIN$

19
email/login_reset.htm Normal file
View File

@ -0,0 +1,19 @@
<p>Dear $USER_NAME$,<br />
<br />
You (or someone who knows your email) has recently submitted a password retrieval request at our site.<br />
<br />
If this was you, you can click the link below to change your password. <br />
The link expires in $TOKEN_EXPIRE_MIN$ minutes so be sure to do this right away:<br />
<br />
<a href="$SITE$u/account/resetpassword?token=$TOKEN$">$SITE$u/account/resetpassword?token=$TOKEN$</a><br />
<br />
If the link above does not display as a link in your browser, you will need to paste it into the address bar of your browser instead.<br />
<br />
If your browser is still on the password reset page, and asking for a Pass Code, yours is<br />
<br />
$TOKEN$<br />
<br />
If you did not request a password retrieval at our site, then you can safetly ignore and delete this email.<br />
<br />
Thank you,<br />
$SITE_ADMIN$</p>

19
email/login_reset.txt Normal file
View File

@ -0,0 +1,19 @@
Dear $USER_NAME$,
You (or someone who knows your email) has recently submitted a password retrieval request at our site.
If this was you, you can click the link below to change your password.
The link expires in $TOKEN_EXPIRE_MIN$ minutes so be sure to do this right away:
$SITE$u/account/resetpassword?token=$TOKEN$
If the link above does not display as a link in your browser, you will need to paste it into the address bar of your browser instead.
If your browser is still on the password reset page, and asking for a Pass Code, yours is
$TOKEN$
If you did not request a password retrieval at our site, then you can safetly ignore and delete this email.
Thank you,
$SITE_ADMIN$

View File

@ -38,11 +38,11 @@ select,
textarea { textarea {
font-family: 'Open Sans'; font-family: 'Open Sans';
} }
[class^="icon-"]:not(.ui-icon), [class^="fa-"]:not(.ui-icon),
[class*="icon-"]:not(.ui-icon) { [class*="fa-"]:not(.ui-icon) {
background: none; background: none;
} }
[class^="icon-"] { [class^="fa-"] {
background: none\9; background: none\9;
} }
.dropdown .dropdown-menu { .dropdown .dropdown-menu {
@ -707,8 +707,8 @@ h6 {
line-height: 18px; line-height: 18px;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5); text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
} }
.widget .widget-header [class^="icon-"], .widget .widget-header [class^="fa-"],
.widget .widget-header [class*=" icon-"] { .widget .widget-header [class*=" fa-"] {
display: inline-block; display: inline-block;
margin-top: -3px; margin-top: -3px;
margin-left: 13px; margin-left: 13px;

View File

@ -0,0 +1,260 @@
/* ========================================================================
* Bootstrap (plugin): validator.js v0.5.0
* ========================================================================
* The MIT License (MIT)
*
* Copyright (c) 2013 Spiceworks, Inc.
* Made by Cina Saffary (@1000hz) in the style of Bootstrap 3 era @fat
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* ======================================================================== */
+function ($) {
'use strict';
// VALIDATOR CLASS DEFINITION
// ==========================
var Validator = function (element, options) {
this.$element = $(element)
this.options = options
this.$element.attr('novalidate', true) // disable automatic native validation
this.toggleSubmit()
this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this))
this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this))
this.$element.find('[data-match]').each(function () {
var $this = $(this)
var target = $this.data('match')
$(target).on('input.bs.validator', function (e) {
$this.val() && $this.trigger('input')
})
})
}
Validator.DEFAULTS = {
delay: 500,
html: false,
errors: {
match: 'Does not match',
minlength: 'Not long enough'
}
}
Validator.VALIDATORS = {
native: function ($el) {
var el = $el[0]
return el.checkValidity ? el.checkValidity() : true
},
match: function ($el) {
var target = $el.data('match')
return !$el.val() || $el.val() === $(target).val()
},
minlength: function ($el) {
var minlength = $el.data('minlength')
return !$el.val() || $el.val().length >= minlength
}
}
Validator.prototype.validateInput = function (e) {
var $el = $(e.target)
var prevErrors = $el.data('bs.validator.errors')
var errors
if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]')
this.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}))
if (e.isDefaultPrevented()) return
var self = this
this.runValidators($el).done(function (errors) {
$el.data('bs.validator.errors', errors)
errors.length ? self.showErrors($el) : self.clearErrors($el)
if (!prevErrors || errors.toString() !== prevErrors.toString()) {
e = errors.length
? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors})
: $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors})
self.$element.trigger(e)
}
self.toggleSubmit()
self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}))
})
}
Validator.prototype.runValidators = function ($el) {
var errors = []
var validators = [Validator.VALIDATORS.native]
var deferred = $.Deferred()
var options = this.options
$el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject()
$el.data('bs.validator.deferred', deferred)
function getErrorMessage(key) {
return $el.data(key + '-error')
|| $el.data('error')
|| key == 'native' && $el[0].validationMessage
|| options.errors[key]
}
$.each(Validator.VALIDATORS, $.proxy(function (key, validator) {
if (($el.data(key) || key == 'native') && !validator.call(this, $el)) {
var error = getErrorMessage(key)
!~errors.indexOf(error) && errors.push(error)
}
}, this))
if (!errors.length && $el.val() && $el.data('remote')) {
this.defer($el, function () {
$.get($el.data('remote'), [$el.attr('name'), $el.val()].join('='))
.fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) })
.always(function () { deferred.resolve(errors)})
})
} else deferred.resolve(errors)
return deferred.promise()
}
Validator.prototype.validate = function () {
var delay = this.options.delay
this.options.delay = 0
this.$element.find(':input').trigger('input')
this.options.delay = delay
return this
}
Validator.prototype.showErrors = function ($el) {
var method = this.options.html ? 'html' : 'text'
this.defer($el, function () {
var $group = $el.closest('.form-group')
var $block = $group.find('.help-block.with-errors')
var errors = $el.data('bs.validator.errors')
if (!errors.length) return
errors = $('<ul/>')
.addClass('list-unstyled')
.append($.map(errors, function (error) { return $('<li/>')[method](error) }))
$block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html())
$block.empty().append(errors)
$group.addClass('has-error')
})
}
Validator.prototype.clearErrors = function ($el) {
var $group = $el.closest('.form-group')
var $block = $group.find('.help-block.with-errors')
$block.html($block.data('bs.validator.originalContent'))
$group.removeClass('has-error')
}
Validator.prototype.hasErrors = function () {
function fieldErrors() {
return !!($(this).data('bs.validator.errors') || []).length
}
return !!this.$element.find(':input:enabled').filter(fieldErrors).length
}
Validator.prototype.isIncomplete = function () {
function fieldIncomplete() {
return this.type === 'checkbox' ? !this.checked :
this.type === 'radio' ? !$('[name="' + this.name + '"]:checked').length :
$.trim(this.value) === ''
}
return !!this.$element.find(':input[required]:enabled').filter(fieldIncomplete).length
}
Validator.prototype.onSubmit = function (e) {
this.validate()
if (this.isIncomplete() || this.hasErrors()) e.preventDefault()
}
Validator.prototype.toggleSubmit = function () {
var $btn = this.$element.find('input[type="submit"], button[type="submit"]')
$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
.css({'pointer-events': 'all', 'cursor': 'pointer'})
}
Validator.prototype.defer = function ($el, callback) {
if (!this.options.delay) return callback()
window.clearTimeout($el.data('bs.validator.timeout'))
$el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay))
}
// VALIDATOR PLUGIN DEFINITION
// ===========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option)
var data = $this.data('bs.validator')
if (!data) $this.data('bs.validator', (data = new Validator(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.validator
$.fn.validator = Plugin
$.fn.validator.Constructor = Validator
// VALIDATOR NO CONFLICT
// =====================
$.fn.validator.noConflict = function () {
$.fn.validator = old
return this
}
// VALIDATOR DATA-API
// ==================
$(window).on('load', function () {
$('form[data-toggle="validator"]').each(function () {
var $form = $(this)
Plugin.call($form, $form.data())
})
})
}(jQuery);

View File

@ -29,3 +29,11 @@
#footer #footer-logo { #footer #footer-logo {
height: 65px; height: 65px;
} }
.modal-dialog {
height: 400px;
}
#wrapper {
width: 980px;
}

View File

@ -1,7 +1,7 @@
<div class="center-block" style="width: 350px;"> <div class="center-block" style="width: 350px;">
<div class="content clearfix"> <div class="content clearfix">
<form method="post" action="<?php echo URL::site('login'); ?>"> <form method="post" action="<?php echo URL::site('login'); ?>">
<br> <br/>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="panel-title">Sign In</div> <div class="panel-title">Sign In</div>
@ -48,7 +48,11 @@
<div class="login-extra"> <div class="login-extra">
<!-- Don't have an account? <a href="./signup.html">Sign Up</a><br/> --> <!-- Don't have an account? <a href="./signup.html">Sign Up</a><br/> -->
</div> <!-- /login-extra --> </div> <!-- /login-extra -->
<div class="modal hide fade" id="modal-reset" role="dialog" aria-hidden="true"> <div class="modal fade" id="modal-reset" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body"></div> <div class="modal-body"></div>
</div> </div>
</div>
</div>
<div class="fb-root"></div> <div class="fb-root"></div>

View File

@ -136,6 +136,7 @@
echo HTML::script(Site::Protocol('cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.min.js')); echo HTML::script(Site::Protocol('cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.min.js'));
} }
echo HTML::script('media/theme/bootstrap/js/bootstrap.validator.js');
echo HTML::script('media/theme/baseadmin/js/backtotop.js'); echo HTML::script('media/theme/baseadmin/js/backtotop.js');
echo HTML::script('media/js/typeahead.bundle.min.js'); echo HTML::script('media/js/typeahead.bundle.min.js');
echo HTML::script('media/js/search.js'); echo HTML::script('media/js/search.js');

View File

@ -1,16 +1,19 @@
<ul id="main-nav"> <ul id="main-nav">
<li class="active"><a href="<?php echo URL::site(''); ?>">Home</a></li> <li class="active"><a href="<?php echo URL::site(''); ?>">Home</a></li>
<?php if (class_exists('Company') AND $x=Company::instance()->faq()) : ?> <?php if (class_exists('Company') AND $x=Company::instance()->faq()) : ?>
<li><a href="<?php echo $x; ?>">Faq</a></li> <li><a href="<?php echo $x; ?>">Faq</a></li>
<?php endif ?> <?php endif ?>
<?php if (array_key_exists('auth',Kohana::modules()) AND Auth::instance()->logged_in()) : ?> <?php if (array_key_exists('auth',Kohana::modules()) AND Auth::instance()->logged_in()) : ?>
<li><a href="<?php echo URL::site('login'); ?>">Customer Login</a></li> <li><a href="<?php echo URL::site('login'); ?>">Customer Login</a></li>
<?php else : ?> <?php else : ?>
<li><a href="<?php echo URL::site('login'); ?>" data-toggle="modal" data-target="#modal-login">Customer Login</a></li> <li><a href="<?php echo URL::site('login'); ?>" data-toggle="modal" data-target="#modal-login">Customer Login</a></li>
<li><a href="<?php echo URL::site('login/register'); ?>">Register</a></li>
<?php endif ?> <?php endif ?>
</ul> </ul>
<div class="modal fade" id="modal-login" role="dialog" aria-hidden="true" aria-labelledby="myModalLabel"> <div class="modal fade" id="modal-login" role="dialog" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"></div> <div class="modal-body"></div>

View File

@ -16,12 +16,10 @@
<?php <?php
if (Kohana::$environment >= Kohana::TESTING OR Request::current()->secure()) { if (Kohana::$environment >= Kohana::TESTING OR Request::current()->secure()) {
echo HTML::style('media/theme/bootstrap/css/bootstrap.min.css'); echo HTML::style('media/theme/bootstrap/css/bootstrap.min.css');
echo HTML::style('media/theme/bootstrap/css/bootstrap-responsive.min.css');
echo HTML::style('media/vendor/font-awesome/css/font-awesome.min.css'); echo HTML::style('media/vendor/font-awesome/css/font-awesome.min.css');
} else { } else {
echo HTML::style(Site::Protocol('netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap.min.css')); echo HTML::style(Site::Protocol('maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'));
echo HTML::style(Site::Protocol('netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css')); echo HTML::style(Site::Protocol('maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css'));
echo HTML::style(Site::Protocol('netdna.bootstrapcdn.com/font-awesome/3.0.2/css/font-awesome.css'));
} }
echo HTML::style(Site::Protocol('fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,800italic,400,600,800')); echo HTML::style(Site::Protocol('fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,800italic,400,600,800'));
@ -53,6 +51,7 @@
<div id="footer"> <div id="footer">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<!--
<div class="grid-4"> <div class="grid-4">
<h1 id="footer-logo">Focus Business</h1> <h1 id="footer-logo">Focus Business</h1>
<p></p> <p></p>
@ -74,7 +73,8 @@
<a href="javascript:;" class="social-icon social-icon-facebook">Facebook</a> <a href="javascript:;" class="social-icon social-icon-facebook">Facebook</a>
</li> </li>
</ul> </ul>
</div> <!-- /grid-4 --> </div>
-->
<div class="grid-4"> <div class="grid-4">
<h3><span class="slash">//</span> Contact Us</h3> <h3><span class="slash">//</span> Contact Us</h3>
@ -98,7 +98,7 @@
echo HTML::script('media/theme/bootstrap/js/bootstrap.min.js'); echo HTML::script('media/theme/bootstrap/js/bootstrap.min.js');
} else { } else {
echo HTML::script(Site::Protocol('code.jquery.com/jquery-1.9.1.min.js')); echo HTML::script(Site::Protocol('code.jquery.com/jquery-1.9.1.min.js'));
echo HTML::script(Site::Protocol('netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js')); echo HTML::script(Site::Protocol('maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js'));
} }
echo HTML::script('media/theme/focusbusiness/js/focus.js'); echo HTML::script('media/theme/focusbusiness/js/focus.js');