From 9ae0980221266ec69497da88d5ff7741ccdb72f1 Mon Sep 17 00:00:00 2001 From: Deon George Date: Mon, 29 Sep 2014 14:47:51 +1000 Subject: [PATCH] Updated bootstrap and many other items --- classes/Auth.php | 4 + classes/Controller/Welcome.php | 9 + classes/Database/MySQL.php | 4 + classes/Email.php | 4 + classes/Email/Exception.php | 3 + classes/Object.php | 4 +- classes/lnApp/Auth.php | 22 ++ classes/lnApp/Block.php | 6 +- classes/lnApp/Controller/Login.php | 174 +++++++- classes/lnApp/Controller/TemplateDefault.php | 53 +-- classes/lnApp/Database/MySQL.php | 20 + classes/lnApp/Email.php | 374 ++++++++++++++++++ classes/lnApp/Email/Exception.php | 9 + classes/lnApp/Form.php | 3 + classes/lnApp/HTTP/Exception.php | 4 +- classes/lnApp/ORM.php | 1 - config/debug.php | 9 +- email/login_activate.txt | 18 + email/login_reset.htm | 19 + email/login_reset.txt | 19 + media/theme/baseadmin/css/base-admin-3.css | 10 +- .../theme/bootstrap/js/bootstrap.validator.js | 260 ++++++++++++ media/theme/focusbusiness/css/custom.css | 8 + views/{pages => }/login.php | 10 +- views/theme/baseadmin/page.php | 1 + views/theme/focusbusiness/navbar.php | 5 +- views/theme/focusbusiness/page.php | 12 +- 27 files changed, 999 insertions(+), 66 deletions(-) create mode 100644 classes/Auth.php create mode 100644 classes/Controller/Welcome.php create mode 100644 classes/Database/MySQL.php create mode 100644 classes/Email.php create mode 100644 classes/Email/Exception.php create mode 100644 classes/lnApp/Auth.php create mode 100644 classes/lnApp/Database/MySQL.php create mode 100644 classes/lnApp/Email.php create mode 100644 classes/lnApp/Email/Exception.php create mode 100644 email/login_activate.txt create mode 100644 email/login_reset.htm create mode 100644 email/login_reset.txt create mode 100644 media/theme/bootstrap/js/bootstrap.validator.js rename views/{pages => }/login.php (91%) diff --git a/classes/Auth.php b/classes/Auth.php new file mode 100644 index 0000000..f46463b --- /dev/null +++ b/classes/Auth.php @@ -0,0 +1,4 @@ + diff --git a/classes/Controller/Welcome.php b/classes/Controller/Welcome.php new file mode 100644 index 0000000..2b532be --- /dev/null +++ b/classes/Controller/Welcome.php @@ -0,0 +1,9 @@ + diff --git a/classes/Email.php b/classes/Email.php new file mode 100644 index 0000000..23340e8 --- /dev/null +++ b/classes/Email.php @@ -0,0 +1,4 @@ + diff --git a/classes/Email/Exception.php b/classes/Email/Exception.php new file mode 100644 index 0000000..95b39b9 --- /dev/null +++ b/classes/Email/Exception.php @@ -0,0 +1,3 @@ +_model) : $x; + } +} diff --git a/classes/lnApp/Block.php b/classes/lnApp/Block.php index 2f3e29e..07be145 100644 --- a/classes/lnApp/Block.php +++ b/classes/lnApp/Block.php @@ -50,11 +50,11 @@ abstract class lnApp_Block extends HTMLRender { $record = self::$_data[$this->_x]; $output = ''; - $output .= sprintf('
',empty($record['span']) ? '12' : $record['span'],empty($record['scrollable']) ? '' : 'scrollable'); + $output .= sprintf('
',Arr::get($record,'span',12),Arr::get($record,'scrollable','')); $output .= '
'; if (! empty($record['title'])) - $output .= sprintf('
%s

%s

',empty($record['title_icon']) ? '' : sprintf(' ',$record['title_icon']),$record['title']); + $output .= sprintf('

%s

',Arr::get($record,'title_icon','fa-square'),Arr::get($record,'title','NO Title!')); $output .= '
'; @@ -65,7 +65,7 @@ abstract class lnApp_Block extends HTMLRender { $output .= Form::close(); 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 .= Form::close(); break; diff --git a/classes/lnApp/Controller/Login.php b/classes/lnApp/Controller/Login.php index 4991276..15361f7 100644 --- a/classes/lnApp/Controller/Login.php +++ b/classes/lnApp/Controller/Login.php @@ -13,6 +13,93 @@ class lnApp_Controller_Login extends Controller_TemplateDefault { 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() { $output = ''; @@ -24,9 +111,9 @@ class lnApp_Controller_Login extends Controller_TemplateDefault { HTTP::redirect(URL::link('user','welcome/index')); // 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 (Auth::instance()->login($_POST['username'],$_POST['password'])) { + if (Auth::instance()->login($this->request->post('username'),$this->request->post('password'))) { // Redirect to the user account if ($redir = Session::instance()->get('afterlogin')) { Session::instance()->delete('afterlogin'); @@ -48,7 +135,7 @@ class lnApp_Controller_Login extends Controller_TemplateDefault { else $oauthlogin = FALSE; - $output .= View::factory('pages/login') + $output .= View::factory('login') ->set('oauth',$oauthlogin); Style::factory() @@ -63,11 +150,92 @@ class lnApp_Controller_Login extends Controller_TemplateDefault { $this->template->shownavbar = FALSE; } + /** + * Method redirect when authenticated user doesnt have access to the url + */ public function action_noaccess() { SystemMessage::factory() ->title(_('No access to requested resource')) ->type('danger') ->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; + } } ?> diff --git a/classes/lnApp/Controller/TemplateDefault.php b/classes/lnApp/Controller/TemplateDefault.php index 046235e..57263f2 100644 --- a/classes/lnApp/Controller/TemplateDefault.php +++ b/classes/lnApp/Controller/TemplateDefault.php @@ -56,13 +56,7 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa * @return boolean */ protected function _auth_required() { - // If our global configurable is disabled, then continue - if (! Kohana::$config->load('config')->method_security) - 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__))); + return FALSE; } /** @@ -72,16 +66,6 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa * @uses meta */ 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 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.')); @@ -94,6 +78,9 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa 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 if ($this->_auth_required()) { if (PHP_SAPI === 'cli') @@ -187,30 +174,18 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa $this->check_cache(sha1($this->response->body())); } - /** - * Generate a view path to help View::factory() calls - * - * 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(); + protected function save(Model $o) { + try { + return $o->save(); - $path = $request->controller(); + } catch (ORM_Validation_Exception $e) { + SystemMessage::factory() + ->title('Record NOT updated') + ->type('danger') + ->body(join('
',array_values($e->errors('models')))); - if ($request->directory()) - $path .= ($path ? '/' : '').$request->directory(); - - if ($plugin) - $path .= ($path ? '/' : '').$plugin; - - $path .= ($path ? '/' : '').$request->action(); - - return strtolower($path); + return FALSE; + } } } ?> diff --git a/classes/lnApp/Database/MySQL.php b/classes/lnApp/Database/MySQL.php new file mode 100644 index 0000000..d75d76b --- /dev/null +++ b/classes/lnApp/Database/MySQL.php @@ -0,0 +1,20 @@ + diff --git a/classes/lnApp/Email.php b/classes/lnApp/Email.php new file mode 100644 index 0000000..ce4ca41 --- /dev/null +++ b/classes/lnApp/Email.php @@ -0,0 +1,374 @@ +'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; + } +} +?> diff --git a/classes/lnApp/Email/Exception.php b/classes/lnApp/Email/Exception.php new file mode 100644 index 0000000..2757806 --- /dev/null +++ b/classes/lnApp/Email/Exception.php @@ -0,0 +1,9 @@ +
'; + if ($classdiv) $output .= '
'; diff --git a/classes/lnApp/HTTP/Exception.php b/classes/lnApp/HTTP/Exception.php index 5ceff50..0ff8934 100644 --- a/classes/lnApp/HTTP/Exception.php +++ b/classes/lnApp/HTTP/Exception.php @@ -6,7 +6,7 @@ * @package lnApp * @category Modifications * @author Deon George - * @copyright (c) 2009-2013 Open Source Billing + * @copyright (c) 2009-2013 Deon George * @license http://dev.leenooks.net/license.html */ 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()); $output .= '
'; - $output .= HTML::anchor((array_key_exists('auth',Kohana::modules()) AND URL::admin_url() ? 'u/' : '').'welcome',' 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',' Back to Home',array('class'=>'btn btn-large btn-primary')); $output .= '
'; $output .= '
'; diff --git a/classes/lnApp/ORM.php b/classes/lnApp/ORM.php index 878f7d9..1c3b5fc 100644 --- a/classes/lnApp/ORM.php +++ b/classes/lnApp/ORM.php @@ -4,7 +4,6 @@ * This class overrides Kohana's ORM * * 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 * @category Modifications diff --git a/config/debug.php b/config/debug.php index d8ce698..ec4e76d 100644 --- a/config/debug.php +++ b/config/debug.php @@ -1,7 +1,7 @@ FALSE, // AJAX actions can only be run by ajax calls if set to FALSE 'etag'=>FALSE, // Force generating ETAGS + 'method_security'=>FALSE, // Debug Method Security '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 ); ?> diff --git a/email/login_activate.txt b/email/login_activate.txt new file mode 100644 index 0000000..e7833af --- /dev/null +++ b/email/login_activate.txt @@ -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$ diff --git a/email/login_reset.htm b/email/login_reset.htm new file mode 100644 index 0000000..ce70205 --- /dev/null +++ b/email/login_reset.htm @@ -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$

diff --git a/email/login_reset.txt b/email/login_reset.txt new file mode 100644 index 0000000..44f9e91 --- /dev/null +++ b/email/login_reset.txt @@ -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$ diff --git a/media/theme/baseadmin/css/base-admin-3.css b/media/theme/baseadmin/css/base-admin-3.css index f4f53eb..6fc8c39 100644 --- a/media/theme/baseadmin/css/base-admin-3.css +++ b/media/theme/baseadmin/css/base-admin-3.css @@ -38,11 +38,11 @@ select, textarea { font-family: 'Open Sans'; } -[class^="icon-"]:not(.ui-icon), -[class*="icon-"]:not(.ui-icon) { +[class^="fa-"]:not(.ui-icon), +[class*="fa-"]:not(.ui-icon) { background: none; } -[class^="icon-"] { +[class^="fa-"] { background: none\9; } .dropdown .dropdown-menu { @@ -707,8 +707,8 @@ h6 { line-height: 18px; text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5); } -.widget .widget-header [class^="icon-"], -.widget .widget-header [class*=" icon-"] { +.widget .widget-header [class^="fa-"], +.widget .widget-header [class*=" fa-"] { display: inline-block; margin-top: -3px; margin-left: 13px; diff --git a/media/theme/bootstrap/js/bootstrap.validator.js b/media/theme/bootstrap/js/bootstrap.validator.js new file mode 100644 index 0000000..3e8b0c8 --- /dev/null +++ b/media/theme/bootstrap/js/bootstrap.validator.js @@ -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 = $('