diff --git a/classes/Auth/ORM.php b/classes/Auth/ORM.php new file mode 100644 index 0000000..c1cc4aa --- /dev/null +++ b/classes/Auth/ORM.php @@ -0,0 +1,4 @@ + diff --git a/classes/Controller/User/Welcome.php b/classes/Controller/User/Welcome.php new file mode 100644 index 0000000..f557ced --- /dev/null +++ b/classes/Controller/User/Welcome.php @@ -0,0 +1,4 @@ + diff --git a/classes/Controller/Welcome.php b/classes/Controller/Welcome.php index 2b532be..c4be426 100644 --- a/classes/Controller/Welcome.php +++ b/classes/Controller/Welcome.php @@ -1,9 +1,4 @@ - diff --git a/classes/Model/Account.php b/classes/Model/Account.php new file mode 100644 index 0000000..4efeaeb --- /dev/null +++ b/classes/Model/Account.php @@ -0,0 +1,4 @@ + diff --git a/classes/Model/Auth/UserDefault.php b/classes/Model/Auth/UserDefault.php new file mode 100644 index 0000000..ad9a944 --- /dev/null +++ b/classes/Model/Auth/UserDefault.php @@ -0,0 +1,4 @@ + diff --git a/classes/Model/Country.php b/classes/Model/Country.php new file mode 100644 index 0000000..e38fcd6 --- /dev/null +++ b/classes/Model/Country.php @@ -0,0 +1,4 @@ + diff --git a/classes/Model/Language.php b/classes/Model/Language.php new file mode 100644 index 0000000..06bfda6 --- /dev/null +++ b/classes/Model/Language.php @@ -0,0 +1,4 @@ + diff --git a/classes/StaticList/Title.php b/classes/StaticList/Title.php new file mode 100644 index 0000000..374dc48 --- /dev/null +++ b/classes/StaticList/Title.php @@ -0,0 +1,4 @@ + diff --git a/classes/lnApp/Auth/ORM.php b/classes/lnApp/Auth/ORM.php new file mode 100644 index 0000000..d1bf280 --- /dev/null +++ b/classes/lnApp/Auth/ORM.php @@ -0,0 +1,140 @@ +_config = $config; + + if (PHP_SAPI !== 'cli') + parent::__construct($config); + } + + /** + * Logs a user in. + * + * @param string username + * @param string password + * @param boolean enable autologin + * @return boolean + */ + protected function _login($user,$password,$remember) { + if (! is_object($user)) { + $username = $user; + + // Load the user + $user = ORM::factory($this->_model); + $user->where('email','=',$username)->find(); + + // If no user loaded, return + if (! $user->loaded()) + return FALSE; + } + + // Create a hashed password + if (is_string($password)) + $password = $this->hash($password); + + // If we have the right password, we'll check the status of the account + if ($user->password === $password AND $user->active) { + // Record our session ID, we may need to update our DB when we get a new ID + $oldsess = session_id(); + + // Finish the login + $this->complete_login($user); + + // Do we need to update databases with our new sesion ID + $sct = Kohana::$config->load('config')->session_change_trigger; + if (session_id() != $oldsess AND count($sct)) + foreach ($sct as $t => $c) + if (Config::module_exist($t)) + foreach (ORM::factory(ucwords($t))->where($c,'=',$oldsess)->find_all() as $o) + $o->set('session_id',session_id()) + ->update(); + +//@TODO + if (! $user->has_any('group',ORM::factory('Group',array('name'=>'Registered Users'))->list_childgrps(TRUE))) + HTTP::redirect(URL::link('user','account/activate')); + + return TRUE; + } + + // Login failed + return FALSE; + } + + /** + * Determine if a user is authorised to view an account + * + * @param Model_Account Account Ojbect to validate if the current user has access + * @return boolean TRUE if authorised, FALSE if not. + */ + public function authorised(Model_Account $ao) { + return (($uo = $this->get_user()) AND $uo->loaded() AND ($uo == $ao OR in_array($ao->id,$uo->RTM->customers($uo->RTM)))); + } + + public function get_groups() { + return is_null($x=$this->get_user()) ? ORM::factory('Group')->where('id','=',0)->find_all() : $x->groups(); + } + + // Override Kohana Auth requirement to have a hash_key + public function hash($str) { + switch ($this->_config['hash_method']) { + case '' : return $str; + case 'md5': return md5($str); + default: return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']); + } + } + + /** + * OSB authentication is controlled via database queries. + * + * This method can be used to test two situations: + * 1) Is the user logged in? ($role == FALSE) + * 2) Can the user run the current controller->action ($role == TRUE) + * + * @param boolean If authentication should be done for this module:method (ie: controller:action). + * @return boolean + */ + public function logged_in($role=NULL,$debug=NULL) { + $status = FALSE; + + // If we are a CLI, we are not logged in + if (PHP_SAPI === 'cli') + return $status; + + // Get the user from the session + $uo = $this->get_user(); + + // If we are not a valid user object, then we are not logged in + if (is_object($uo) AND ($uo instanceof Model_Account) AND $uo->loaded()) + if (! empty($role)) { + if (($x = Request::current()->mmo()) instanceof Model) + // If the role has the authorisation to run the method + foreach ($x->group->find_all() as $go) + if ($go->id == 0 OR $uo->has_any('group',$go->list_childgrps(TRUE))) { + $status = TRUE; + break; + } + + // There is no role, so the method should be allowed to run as anonymous + } else + $status = TRUE; + + return $status; + } +} +?> diff --git a/classes/lnApp/Controller/TemplateDefault.php b/classes/lnApp/Controller/TemplateDefault.php index 57263f2..12905e7 100644 --- a/classes/lnApp/Controller/TemplateDefault.php +++ b/classes/lnApp/Controller/TemplateDefault.php @@ -56,7 +56,9 @@ abstract class lnApp_Controller_TemplateDefault extends Kohana_Controller_Templa * @return boolean */ protected function _auth_required() { - 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__))); } /** diff --git a/classes/lnApp/Controller/User/Welcome.php b/classes/lnApp/Controller/User/Welcome.php new file mode 100644 index 0000000..75ed0fa --- /dev/null +++ b/classes/lnApp/Controller/User/Welcome.php @@ -0,0 +1,19 @@ + diff --git a/classes/lnApp/Controller/Welcome.php b/classes/lnApp/Controller/Welcome.php new file mode 100644 index 0000000..82b15bd --- /dev/null +++ b/classes/lnApp/Controller/Welcome.php @@ -0,0 +1,19 @@ + diff --git a/classes/lnApp/Model/Account.php b/classes/lnApp/Model/Account.php new file mode 100644 index 0000000..9d92f04 --- /dev/null +++ b/classes/lnApp/Model/Account.php @@ -0,0 +1,152 @@ +array('far_key'=>'id'), + 'group'=>array('through'=>'account_group'), + ); + + protected $_has_one = array( + 'country'=>array('foreign_key'=>'id'), + 'currency'=>array('foreign_key'=>'id'), + 'language'=>array('foreign_key'=>'id'), + ); + + protected $_display_filters = array( + 'date_orig'=>array( + array('Site::Date',array(':value')), + ), + 'date_last'=>array( + array('Site::Date',array(':value')), + ), + 'active'=>array( + array('StaticList_YesNo::get',array(':value',TRUE)), + ), + ); + + protected $_form = array('id'=>'id','value'=>'name(TRUE)'); + + protected $_save_message = TRUE; + + /** + * Our account number format + */ + public function accnum() { + return sprintf('%s-%04s',Company::instance()->site(TRUE),$this->id); + } + + public function activate_code() { + return md5(sprintf('%s-%s-%s-%s',$this->accnum(),$this->date_orig,$this->date_last,$this->email)); + } + + public function activated() { + return $this->has_any('group',ORM::factory('Group',array('name'=>'Registered Users'))->list_childgrps(TRUE)); + } + + /** + * Get the groups that an account belongs to + */ + public function groups() { + $result = array(); + + foreach ($this->group->where_active()->find_all() as $go) + foreach ($go->list_parentgrps(TRUE) as $cgo) + if (empty($result[$cgo->id])) + $result[$cgo->id] = $cgo; + + return $result; + } + + public function log($message) { + // Log a message for this account + $alo = ORM::factory('Account_Log'); + $alo->account_id = $this->id; + $alo->ip = Request::$client_ip; + $alo->details = $message; + $alo->save(); + + return $alo->saved(); + } + + public function isAdmin() { + return FALSE; + } + + /** + * This function will extract the available methods for this account + * This is used both for menu options and method security + */ + public function methods() { + static $result = array(); + + // @todo We may want to optimise this with some session caching. + if ($result) + return $result; + + foreach ($this->groups() as $go) + foreach ($go->module_method->find_all() as $mmo) + if (empty($result[$mmo->id])) + $result[$mmo->id] = $mmo; + + Sort::MAsort($result,'module->name,menu_display'); + + return $result; + } + + /** + * Return an account name + */ + public function name() { + return trim(sprintf('%s %s',$this->first_name,$this->last_name)); + } + + /** + * Search for accounts matching a term + */ + public function list_autocomplete($term,$index,$value,array $label,array $limit=array(),array $options=NULL) { + $ao = Auth::instance()->get_user(); + + $this->clear(); + $this->where_active(); + + // Build our where clause + // First Name, Last name + if (preg_match('/\ /',$term)) { + list($fn,$ln) = explode(' ',$term,2); + + $this->where_open() + ->where('first_name','like','%'.$fn.'%') + ->and_where('last_name','like','%'.$ln.'%') + ->where_close(); + + } elseif (is_numeric($term)) { + $this->where('id','like','%'.$term.'%'); + + } elseif (preg_match('/\@/',$term)) { + $this->where('email','like','%'.$term.'%'); + + } else { + $this->where_open() + ->or_where('first_name','like','%'.$term.'%') + ->or_where('last_name','like','%'.$term.'%') + ->or_where('email','like','%'.$term.'%') + ->where_close(); + } + + // Restrict results to authorised accounts + // @todo + + return parent::list_autocomplete($term,$index,$value,$label,$limit,$options); + } +} +?> diff --git a/classes/lnApp/Model/Auth/UserDefault.php b/classes/lnApp/Model/Auth/UserDefault.php new file mode 100644 index 0000000..1df4d00 --- /dev/null +++ b/classes/lnApp/Model/Auth/UserDefault.php @@ -0,0 +1,40 @@ + array( + array('not_empty'), + array('min_length', array(':value', 4)), + array('max_length', array(':value', 127)), + array('email'), + ), + ); + } + + /** + * Complete our login + * + * For some database logins, we may not want to record the user last login + * details in the repository, so we just override that parent function + * here. + * + * We can also do some other post-login actions here. + */ + public function complete_login() { + return $this->log('Logged In'); + } + + abstract public function isAdmin(); +} +?> diff --git a/classes/lnApp/Model/Country.php b/classes/lnApp/Model/Country.php new file mode 100644 index 0000000..fe20968 --- /dev/null +++ b/classes/lnApp/Model/Country.php @@ -0,0 +1,23 @@ +'ASC', + ); + + protected $_form = array('id'=>'id','value'=>'name'); + + public static function icon() { + return HTML::image(sprintf('media/img/country/%s.png',strtolower($this->two_code)),array('alt'=>$this->currency->symbol)); + } +} +?> diff --git a/classes/lnApp/Model/Language.php b/classes/lnApp/Model/Language.php new file mode 100644 index 0000000..4a492f2 --- /dev/null +++ b/classes/lnApp/Model/Language.php @@ -0,0 +1,19 @@ +'ASC', + ); + + protected $_form = array('id'=>'id','value'=>'name'); +} +?> diff --git a/classes/lnApp/Site.php b/classes/lnApp/Site.php index 88520bd..7b9e050 100644 --- a/classes/lnApp/Site.php +++ b/classes/lnApp/Site.php @@ -33,7 +33,7 @@ abstract class lnApp_Site { } /** - * Return the site configured language + * Return the site configured id */ public static function ID($format=FALSE) { return $format ? sprintf('%02s',Kohana::$config->load('config')->id) : Kohana::$config->load('config')->id; @@ -43,6 +43,19 @@ abstract class lnApp_Site { * Return the site configured language */ public static function Language() { + foreach (Request::factory()->accept_lang() as $k=>$v) { + if (strlen($k) == 2) + $k = sprintf('%s_%s',strtolower($k),strtoupper($k)); + else { + list($k,$v) = preg_split('/[-_]/',$k,2); + $k = sprintf('%s_%s',strtolower($k),strtoupper($v)); + } + + if ($x=ORM::factory('Language',array('iso'=>$k))) + return $x; + } + + // @todo Return Default Language return Kohana::$config->load('config')->language; } diff --git a/classes/lnApp/StaticList/Title.php b/classes/lnApp/StaticList/Title.php new file mode 100644 index 0000000..b592526 --- /dev/null +++ b/classes/lnApp/StaticList/Title.php @@ -0,0 +1,28 @@ +_('Mr'), + 'ms'=>_('Ms'), + 'mrs'=>_('Mrs'), + 'miss'=>_('Miss'), + 'dr'=>_('Dr'), + 'prof'=>_('Prof') + ); + } + + public static function get($value) { + return self::factory()->_get($value); + } +} +?> diff --git a/classes/lnApp/URL.php b/classes/lnApp/URL.php index 0909b94..39e35fd 100644 --- a/classes/lnApp/URL.php +++ b/classes/lnApp/URL.php @@ -62,7 +62,7 @@ abstract class lnApp_URL extends Kohana_URL { case 'admin': $result[$k] = array('name'=>'Administrator','icon'=>'fa-globe'); break; - case 'user': $result[$k] = array('name'=>array_key_exists('auth',Kohana::modules()) ? Auth::instance()->get_user()->name() : 'Guest','icon'=>'fa-user'); + case 'user': $result[$k] = array('name'=>(array_key_exists('auth',Kohana::modules()) AND $x=Auth::instance()->get_user()) ? $x->name() : 'Guest','icon'=>'icon-user'); break; default: $result[$k] = array('name'=>$k,'icon'=>'fa-question-sign'); diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..3cfc2d4 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,17 @@ + 'ORM', + 'hash_method' => 'md5', +); +?> diff --git a/media/theme/bootstrap/css/custom.css b/media/theme/bootstrap/css/custom.css index ace2c48..8041a82 100644 --- a/media/theme/bootstrap/css/custom.css +++ b/media/theme/bootstrap/css/custom.css @@ -88,3 +88,111 @@ table .text-right { right: 0px; visibility: hidden; } + +#nav { + margin-bottom: .5em; +} + +#nav .container > #main-nav { + padding: 0; + margin: 0; + + border-top: 1px solid #DDD; +} + +#nav .container > #main-nav > li { + position: relative; + top: -1px; + + float: left; + + padding: 0 40px 0 0; + margin: 0; + + list-style: none; + + border-top: 1px solid #DDD; +} + +#nav .container > #main-nav > li:last-child { + padding-right: 0; +} + +#nav .container > #main-nav > li > a { + position: relative; + top: -1px; + + display: block; + + padding: 15px 5px 5px; + + color: #999; + + text-transform: uppercase; + + border-top: 1px solid transparent; +} + +#nav .container > #main-nav > li > a:hover, +#nav .container > #main-nav > li.dropdown.open > a { + color: #333; + + text-decoration: none; + + border-top-color: #333; + border-top-width: 1px; +} + +#nav .container > #main-nav > li.active a { + padding-top: 12px; + + color: #222; + + border-top-color: #F90; + border-top-width: 4px; +} + +#nav .container > #main-nav > li > a > .caret { + position: relative; + top: -2px; + + margin-left: .5em; +} + +#nav .dropdown-menu a:hover { + background-color: #F90; +} + +#nav .dropdown-menu > li > a { + padding: 6px 12px; +} + +#nav .dropdown-menu i { + margin-right: .5em; + + font-size: 14px; +} + +#nav .dropdown-menu::before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #CCC; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 9px; +} + +#nav .dropdown-menu::after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + position: absolute; + top: -6px; + left: 10px; +} + diff --git a/views/account/user/edit.php b/views/account/user/edit.php new file mode 100644 index 0000000..4089c0b --- /dev/null +++ b/views/account/user/edit.php @@ -0,0 +1,61 @@ +
+ Account Details + + date_last ? $o->display('date_last') : Site::date(time()),array('label'=>'Last Updated','class'=>'col-md-2','disabled')); ?> + + display('email'),array('label'=>'Email','class'=>'col-md-3','placeholder'=>'Email Address','type'=>'email','required','data-error'=>'Invalid EMAIL address')); ?> + +
+ +
+
+ display('title'),array('class'=>'form-control','required','nocg'=>TRUE)); ?> +
+
+ display('first_name'),array('class'=>'form-control col-md-2','placeholder'=>'First Name','required','nocg'=>TRUE)); ?> +
+
+ display('last_name'),array('class'=>'form-control col-md-2','placeholder'=>'Last Name','required','nocg'=>TRUE)); ?> +
+
+
+ +
+ + display('address1'),array('class'=>'col-md-6','placeholder'=>'Address Line 1','required')); ?> + + + display('address2'),array('class'=>'col-md-6','placeholder'=>'Address Line 2')); ?> + + +
+
+ display('city'),array('label'=>'City','placeholder'=>'City','required','nocg'=>TRUE)); ?> +
+ +
+ display('state'),array('label'=>'','class'=>'input-mini','placeholder'=>'State','required','nocg'=>TRUE)); ?> +
+ +
+ display('zip'),array('label'=>'','class'=>'input-mini','placeholder'=>'Post Code','required','nocg'=>TRUE)); ?> +
+
+
+ +
+ +
+ list_select(),$o->country_id,array('class'=>'form-control','required','nocg'=>TRUE)); ?> +
+
+ + +
+ +
+
+ + +
+
diff --git a/views/theme/bootstrap/navbar.php b/views/theme/bootstrap/navbar.php index bb41075..9ad033e 100644 --- a/views/theme/bootstrap/navbar.php +++ b/views/theme/bootstrap/navbar.php @@ -1,10 +1,18 @@ - $details) : - if ($x = Menu::items($type)) : ?> - - + + + diff --git a/views/theme/bootstrap/page.php b/views/theme/bootstrap/page.php index 7ceaee0..18b2b48 100644 --- a/views/theme/bootstrap/page.php +++ b/views/theme/bootstrap/page.php @@ -31,49 +31,27 @@ - + @@ -135,6 +113,7 @@ 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/js/typeahead.bundle.min.js'); echo HTML::script('media/js/search.js'); echo HTML::script('media/js/custom.js'); diff --git a/views/theme/focusbusiness/navbar.php b/views/theme/focusbusiness/navbar.php index aa2e4d3..e32d569 100644 --- a/views/theme/focusbusiness/navbar.php +++ b/views/theme/focusbusiness/navbar.php @@ -6,9 +6,9 @@ logged_in()) : ?> -
  • Customer Login
  • +
  • Login
  • -
  • Customer Login
  • +
  • Login
  • Register