',Arr::get($record,'span',12),Arr::get($record,'scrollable',''));
$output .= '
';
if (! empty($record['title']))
- $output .= sprintf('',empty($record['title_icon']) ? '' : sprintf('
',$record['title_icon']),$record['title']);
+ $output .= sprintf('',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 .= '