Browse Source

qlogin and fixes

euromark 14 years ago
parent
commit
560d168a8b

+ 382 - 0
Controller/AuthExtComponent.php

@@ -0,0 +1,382 @@
+<?php
+
+if (!defined('USER_ROLE_KEY')) {
+	define('USER_ROLE_KEY', 'Role');
+}
+if (!defined('USER_INFO_KEY')) {
+	define('USER_INFO_KEY', 'Info');
+}
+if (!defined('USER_RIGHT_KEY')) {
+	define('USER_RIGHT_KEY', 'Right');
+}
+if (!defined('CLASS_USER')) {
+	define('CLASS_USER', 'User');
+}
+
+App::uses('AuthComponent', 'Controller/Component');
+
+/**
+ * Important:
+ * index the ACO on alias, index the Aro on model+id
+ *
+ * Extends AuthComponent with the following addons:
+ * - allows multiple roles per user
+ * - auto-raises login counter and sets last_login date
+ * - preps the session data according to completeAuth() method (adds parent data etc)
+ * - dynamic login scope validation
+ * 
+ * @author Mark Scherer
+ * @cakephp 2.0
+ * @license MIT
+ * 2011-12-18 ms
+ */
+class AuthExtComponent extends AuthComponent {
+
+	public $intermediateModel = 'RoleUser';
+	public $roleModel = 'Role';
+
+	public $fieldKey = 'role_id';
+	
+	public $loginAction = array('controller' => 'account', 'action' => 'login', 'admin' => false, 'plugin' => false);
+	public $loginRedirect = array('controller' => 'overview', 'action' => 'home', 'admin' => false, 'plugin' => false);
+	public $autoRedirect = false;
+	public $loginError = null;
+	
+	public $settings = array(
+		'multi' => null, # null=auto - yes/no multiple roles (HABTM table between users and roles)
+		'parentModelAlias' => USER_ROLE_KEY,
+		'userModel' => CLASS_USER
+	);
+
+	# field name in DB , if none is specified there will be no floodProtection
+	public $floodProtection = null;
+
+
+	public function __construct(ComponentCollection $Collection, $settings = array()) {
+		$settings = array_merge($this->settings, (array) $settings, (array) Configure::read('Auth'));
+		$this->Controller = $Collection->getController();
+		parent::__construct($Collection, $settings);
+		
+		# auto-select multi if necessary
+		if ($this->settings['multi'] === null) {
+			$Model = $this->getModel();
+			if (!empty($Model->hasMany)) {
+				foreach ($Model->hasMany as $name => $relation) {
+					if ($name != $this->roleModel) {
+						continue;
+					}
+					$this->settings['multi'] = false;
+					return;
+				}
+			}
+			$Model = $this->getModel();
+			if (!empty($Model->hasAndBelongsToMany)) {
+				foreach ($Model->hasAndBelongsToMany as $name => $relation) {
+					if ($name != $this->roleModel) {
+						continue;
+					}
+					$this->settings['multi'] = true;
+					return;
+				}
+			}
+			//$this->log('AuthExt component not configured properly (auto select multi failed)');
+		}
+	}
+
+	public function initialize(Controller $Controller) {
+		$this->Controller = $Controller;
+
+		parent::initialize($Controller);
+	}
+	
+	/**
+	 * 2.1 fix for allowing * as wildcard
+	 * 2012-01-10 ms
+	 */
+	public function allow($action = null) {
+		if (((array) $action) === array('*')) {
+			parent::allow();
+			return;
+		}
+		$args = func_get_args();
+		if (empty($args) || $action === null) {
+			parent::allow();
+		}
+		parent::allow($args);
+	}
+
+	public function login($user = null) {
+		$Model = $this->getModel();
+		$this->_setDefaults();
+
+		if (empty($user)) {
+			$user = $this->identify($this->request, $this->response);
+		}
+		if (empty($user)) {
+			$this->loginError = __('invalidLoginCredentials');
+			return false;
+		}
+
+		# custom checks
+		if (isset($user['active'])) {
+			if (empty($user['active'])) {
+				$this->loginError = __('Account not active yet');
+				return false;
+			}
+			if (!empty($user['suspended'])) {
+				$this->loginError = __('Account wurde vorübergehend gesperrt');
+				if (!empty($user['suspended_reason'])) {
+					$this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
+				}
+				return false;
+			}
+		} else {
+			if (isset($user['status']) && empty($user['status'])) {
+				$this->loginError = __('Account not active yet');
+				return false;
+			}
+			if (isset($user['status']) && defined('User::STATUS_PENDING') && $user['status'] == User::STATUS_PENDING) {
+				$this->loginError = __('Account wurde noch nicht freigeschalten');
+				return false;
+			}
+			if (isset($user['status']) && defined('User::STATUS_SUSPENDED') && $user['status'] == User::STATUS_SUSPENDED) {
+				$this->loginError = 'Account wurde vorübergehend gesperrt';
+				if (!empty($user['suspended_reason'])) {
+					$this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
+				}
+				return false;
+			}
+			if (isset($user['status']) && defined('User::STATUS_DEL') && $user['status'] == User::STATUS_DEL) {
+				$this->loginError = 'Account wurde gelöscht';
+				if (!empty($user['suspended_reason'])) {
+					$this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
+				}
+				return false;
+			}
+			if (isset($user['status']) && defined('User::STATUS_ACTIVE') && $user['status'] != User::STATUS_ACTIVE) {
+				$this->loginError = __('Unknown Error');
+				return false;
+			}
+		}
+		if (isset($user['email_confirmed']) && empty($user['email_confirmed'])) {
+			$this->loginError = __('Email not active yet');
+			return false;
+		}
+		
+		if ($user) {
+			# update login counter
+			if (isset($user['logins'])) {
+				$user['logins'] = $user['logins'] + 1;
+				if (method_exists($Model, 'loginUpdate')) {
+					$Model->loginUpdate($user);
+				}
+			}
+			
+			$this->Session->renew();
+			$this->Session->write(self::$sessionKey, $user);
+			$this->Session->write(self::$sessionKey, $this->completeAuth($user));
+		}
+		return $this->loggedIn();
+	}
+
+	/**
+	 * return array $user or bool false on failure
+	 * 2011-11-03 ms
+	 */
+	public function completeAuth($user) {
+		$Model = $this->getModel();
+		if (!is_array($user)) {
+			$user = $Model->get($user);
+			if (!$user) {
+				return false;
+			}
+			$user = array_shift($user);
+		}
+				
+		if (isset($Model->hasMany[$this->intermediateModel]['className'])) {
+			$with = $Model->hasMany[$this->intermediateModel]['className'];
+		} elseif (isset($Model->belongsTo[$this->roleModel]['className'])) {
+			$with = $Model->belongsTo[$this->roleModel]['className'];
+		}
+		if (empty($with) && $this->settings['parentModelAlias'] !== false) {
+			trigger_error('No relation from user to role found');
+			return false;
+		}
+
+		$completeAuth = array($this->settings['userModel']=>$user);
+
+		# roles
+		if (!empty($with)) {
+			list($plugin, $withModel) = pluginSplit($with);
+			if (!isset($this->{$withModel})) {
+				$this->{$withModel} = ClassRegistry::init($with);
+			}
+			# only for multi
+			
+			if ($this->settings['multi'] || !isset($completeAuth[$this->settings['userModel']]['role_id'])) {
+				$parentModelAlias = $this->settings['parentModelAlias'];
+				$completeAuth[$this->settings['userModel']][$parentModelAlias] = array(); # default: no roles!
+				$roles = $this->{$withModel}->find('list', array('fields' => array($withModel.'.role_id'), 'conditions' => array($withModel.'.user_id' => $user['id'])));
+				if (!empty($roles)) {
+					//$primaryRole = $this->user($this->fieldKey);
+					// retrieve associated role that are not the primary one
+		
+					# MAYBE USEFUL FOR GUEST!!!
+					//$roles = set::extract('/'.$with.'['.$this->fieldKey.'!='.$primaryRole.']/'.$this->fieldKey, $roles);
+		
+					// add the suplemental roles id under the Auth session key
+		
+					$completeAuth[$this->settings['userModel']][$parentModelAlias] = $roles; // or USER_ROLE_KEY
+					//pr($completeAuth);
+				}
+			} else {
+				//$completeAuth[$this->settings['userModel']][$parentModelAlias][] = $completeAuth[$this->settings['userModel']]['role_id'];
+			}
+		}
+		
+		# deprecated!
+		if (isset($Model->hasOne['UserInfo'])) {
+			$with = $Model->hasOne['UserInfo']['className'];
+			list($plugin, $withModel) = pluginSplit($with);
+			if (!isset($this->{$withModel})) {
+				$this->{$withModel} = ClassRegistry::init($with);
+			}
+			$infos = $this->{$withModel}->find('first', array('fields' => array(), 'conditions' => array($withModel.'.id' => $user['id'])));
+
+			$completeAuth[$this->settings['userModel']][USER_INFO_KEY] = array(); # default: no rights!
+			if (!empty($infos)) {
+				$completeAuth[$this->settings['userModel']][USER_INFO_KEY] = $infos[$with];
+				//pr($completeAuth);
+			}
+		}
+
+		# deprecated!
+		if (isset($Model->hasOne['UserRight'])) {
+			$with = $Model->hasOne['UserRight']['className'];
+			list($plugin, $withModel) = pluginSplit($with);
+			if (!isset($this->{$withModel})) {
+				$this->{$withModel} = ClassRegistry::init($with);
+			}
+			$rights = $this->{$withModel}->find('first', array('fields' => array(), 'conditions' => array($withModel.'.id' => $user['id'])));
+
+			$completeAuth[$this->settings['userModel']][USER_RIGHT_KEY] = array(); # default: no rights!
+			if (!empty($rights)) {
+				// add the suplemental roles id under the Auth session key
+				$completeAuth[$this->settings['userModel']][USER_RIGHT_KEY] = $rights[$with];
+				//pr($completeAuth);
+			}
+		}
+
+		if (method_exists($Model, 'completeAuth')) {
+			$completeAuth = $Model->completeAuth($completeAuth);
+			return $completeAuth[$this->settings['userModel']];
+		}
+		return $completeAuth[$this->settings['userModel']];
+	}
+
+	/**
+	 * Main execution method.  Handles redirecting of invalid users, and processing
+	 * of login form data.
+	 *
+	 * @param Controller $controller A reference to the instantiating controller object
+	 * @return boolean
+	 */
+	public function startup($controller) {
+		if ($controller->name == 'CakeError') {
+			return true;
+		}
+
+		$methods = array_flip(array_map('strtolower', $controller->methods));
+		# fix: reverse camelCase first
+		$action = strtolower(Inflector::underscore($controller->request->params['action']));
+
+		$isMissingAction = (
+			$controller->scaffold === false &&
+			!isset($methods[$action])
+		);
+
+		if ($isMissingAction) {
+			return true;
+		}
+
+		if (!$this->_setDefaults()) {
+			return false;
+		}
+		$request = $controller->request;
+
+		$url = '';
+
+		if (isset($request->url)) {
+			$url = $request->url;
+		}
+		$url = Router::normalize($url);
+		$loginAction = Router::normalize($this->loginAction);
+
+		$allowedActions = $this->allowedActions;
+		$isAllowed = (
+			$this->allowedActions == array('*') ||
+			in_array($action, array_map('strtolower', $allowedActions))
+		);
+
+		if ($loginAction != $url && $isAllowed) {
+			return true;
+		}
+
+		if ($loginAction == $url) {
+			if (empty($request->data)) {
+				if (!$this->Session->check('Auth.redirect') && !$this->loginRedirect && env('HTTP_REFERER')) {
+					$this->Session->write('Auth.redirect', $controller->referer(null, true));
+				}
+			}
+			return true;
+		} else {
+			if (!$this->_getUser()) {
+				if (!$request->is('ajax')) {
+					$this->flash($this->authError);
+					$this->Session->write('Auth.redirect', Router::reverse($request));
+					$controller->redirect($loginAction);
+					return false;
+				} elseif (!empty($this->ajaxLogin)) {
+					$controller->viewPath = 'Elements';
+					echo $controller->render($this->ajaxLogin, $this->RequestHandler->ajaxLayout);
+					$this->_stop();
+					return false;
+				} else {
+					$controller->redirect(null, 403);
+				}
+			}
+		}
+		if (empty($this->authorize) || $this->isAuthorized($this->user())) {
+			return true;
+		}
+
+		$this->flash($this->authError);
+		# redirect fix
+		$controller->redirect($controller->referer($this->loginRedirect), null, true);
+	}
+	
+	/**
+	 * Quickfix
+	 * TODO: improve - maybe use Authenticate
+	 * @return bool $success
+	 */
+	public function verifyUser($id, $pwd) {
+		$options = array(
+			'conditions' => array('id'=>$id, 'password'=>$this->password($pwd)),
+		);
+		return $this->getModel()->find('first', $options);
+		
+		$this->constructAuthenticate();
+		$this->request->data['User']['password'] = $pwd;
+		return $this->identify($this->request, $this->response);
+	}
+	
+	/**
+	 * returns the current User model
+	 * @return object $User
+	 */
+	public function getModel() {
+		return ClassRegistry::init(CLASS_USER); 
+	}
+
+}

+ 105 - 0
Controller/QloginController.php

@@ -0,0 +1,105 @@
+<?php
+if (!defined('CLASS_USER')) {
+	define('CLASS_USER', 'User');
+}
+
+App::uses('ToolsAppController', 'Tools.Controller');
+
+class QloginController extends ToolsAppController {
+
+	public $uses = array('Tools.Qlogin');
+	
+	public $components = array('Tools.Common');
+
+	public function beforeFilter() {		
+		parent::beforeFilter();
+		
+		if (isset($this->Auth)) {
+			$this->Auth->allow('go');
+		}
+	}
+
+
+	/****************************************************************************************
+	* ADMIN functions
+	****************************************************************************************/
+
+
+
+	/**
+	 * main login function
+	 * 2011-07-11 ms
+	 */
+	public function go($key) {
+		$entry = $this->Qlogin->translate($key);
+		$default = '/';
+		if ($this->Session->read('Auth.User.id') && isset($this->Auth->loginRedirect)) {
+			$default = $this->Auth->loginRedirect;
+		}
+
+		if (empty($entry)) {
+			$this->Common->flashMessage(__('Invalid Key'), 'error');
+			$this->Common->autoRedirect($default);
+		}
+		//die(returns($entry));
+		$uid = $entry['CodeKey']['user_id'];
+		$url = $entry['CodeKey']['url'];
+		
+		if (!$this->Session->read('Auth.User.id')) {
+			$this->User = ClassRegistry::init(CLASS_USER);
+			# needs to be logged in
+			$user = $this->User->get($uid);
+			if (!$user) {
+				$this->Common->flashMessage(__('Invalid Account'), 'error');
+				$this->Common->autoRedirect($default);
+			}
+			
+			if ($this->Auth->login($user['User'])) {
+				$this->Session->write('Auth.User.Login.qlogin', true);
+				if (!Configure::read('Qlogin.suppressMessage')) {
+					$this->Common->flashMessage(__('You successfully logged in via qlogin'), 'success');
+				}
+			}
+		}
+		$this->redirect($url);
+	}
+
+
+	public function admin_index() {
+		//TODO
+		
+		if ($this->Common->isPost()) {
+			$this->Qlogin->set($this->request->data);
+			if ($this->Qlogin->validates()) {
+				$id = $this->Qlogin->generate($this->Qlogin->data['Qlogin']['url'], $this->Qlogin->data['Qlogin']['user_id']);
+				$this->Common->flashMessage('New Key: '.h($id), 'success');
+				$url = $this->Qlogin->urlByKey($id);
+				$this->set(compact('url'));
+				$this->request->data = array();
+			}
+		}
+		$this->User = ClassRegistry::init(CLASS_USER);
+		$users = $this->User->find('list');
+		
+		$this->CodeKey = ClassRegistry::init('Tools.CodeKey');
+		$qlogins = $this->CodeKey->find('count', array('conditions'=>array('type'=>'qlogin')));
+		
+		$this->set(compact('users', 'qlogins'));
+	}
+	
+	public function admin_listing() {
+		
+	}
+
+	public function admin_reset() {
+		if (!$this->Common->isPost()) {
+			throw new MethodNotAllowedException();
+		}
+		$this->CodeKey = ClassRegistry::init('Tools.CodeKey');
+		$this->CodeKey->deleteAll(array('type'=>'qlogin'));
+		$this->Common->flashMessage(__('Success'), 'success');
+		$this->Common->autoRedirect(array('action'=>'index'));
+	}
+
+}
+

+ 130 - 0
Lib/ChmodLib.php

@@ -0,0 +1,130 @@
+<?php
+
+/*
+ *
+ * TODO: change names of convertFromOctal and convertToOctal
+ * 
+ *
+ */
+
+/**
+ * PHP5
+ * u=user, g=group, o=other
+ * 2010-06-21 ms
+ */
+class ChmodLib {
+
+	//protected $dir;
+	protected $modes = array('user' => 0 , 'group' => 0 , 'other' => 0);
+
+
+/*** calc octal ***/
+
+	 	/**
+	 * from Octal 0xxx back to STRING with leading zero added on leading zero = true
+	 * e.g. 0777 => 0777, '755' => 0755
+	 * @access static Chmod::convertFromOctal(mode, leadingZero)
+	 * 2009-07-26 ms
+	 */
+	public static function convertFromOctal($mode, $leadingZero = false) {
+		$res = (String)substr(sprintf('%o', $mode), -4);
+		if ($leadingZero===true) {
+			$res = '0'.$res;
+		}
+		return $res;
+	}
+
+	/**
+	 * from INT or STRING with or without leading 0 -> Octal 0xxx
+	 * @access static Chmod::converttoOctal(mode)
+	 * 2009-07-26 ms
+	 */
+	public static function convertToOctal($mode) {
+		return intval((string)$mode, 8);
+	}
+
+
+/*** set/get modes ***/
+
+	public function setUser($read, $write, $execute) {
+		$this->modes['user'] = $this->setMode($read,$write,$execute);
+	}
+
+	public function setGroup($read, $write, $execute) {
+		$this->modes['group'] = $this->setMode($read,$write,$execute);
+	}
+
+	public function setOther($read, $write, $execute) {
+		$this->modes['other'] = $this->setMode($read,$write,$execute);
+	}
+
+	/**
+	 * get mode as octal value or
+	 * @param options
+	 * - string: string/int/symbolic
+	 * 2010-06-21 ms
+	 */
+	public function getMode($options = array()) {
+		$mode = (string)($this->modes['user'] . $this->modes['group'] . $this->modes['other']);
+		if (!empty($options['type'])) {
+			if ($options['type'] == 'string') {
+				return $mode;
+			} elseif ($options['type'] == 'int') {
+				return (int)$mode;
+			} elseif ($options['type'] == 'symbolic') {
+				$mode = $this->symbol($this->modes['user']).$this->symbol($this->modes['group']).$this->symbol($this->modes['other']);
+				return $mode;
+			}
+		}
+		return intval($mode, 8);
+	}
+
+
+	/**
+	 * full table with all rights
+	 * //TODO
+	 * 2010-06-21 ms
+	 */
+	public function table() {
+		$res = array();
+
+
+		return $res;
+	}
+
+	/**
+	 * get symbol for
+	 * read(4) = 'r', write(2) = 'w', execute(1) = 'x'
+	 * e.g: 4 for = r--
+	 * 2010-06-21 ms
+	 */
+	protected function symbol($mode) {
+		$res = '---';
+		if ($mode == 7) {
+			$res = 'rwx';
+		} elseif ($mode == 6) {
+			$res = 'rw-';
+		} elseif ($mode == 5) {
+			$res = 'r-x';
+		} elseif ($mode == 4) {
+			$res = 'r--';
+		} elseif ($mode == 3) {
+			$res = '-wx';
+		} elseif ($mode == 2) {
+			$res = '-w-';
+		} elseif ($mode == 1) {
+			$res = '--x';
+		}
+		return $res;
+	}
+
+	protected function setMode($r, $w, $e) {
+		$mode = 0;
+		if ($r) $mode+=4;
+		if ($w) $mode+=2;
+		if ($e) $mode+=1;
+		return $mode;
+	}
+
+}
+

File diff suppressed because it is too large
+ 994 - 0
Lib/GeocodeLib.php


+ 1 - 1
Lib/DangerLib.php

@@ -8,7 +8,7 @@ App::uses('Xml', 'Utility');
  * used in configurations controller + debug helper
  * 2010-07-30 ms
  */
-class DangerLib {
+class HazardLib {
 
 	const URL = 'http://ha.ckers.org/xssAttacks.xml';
 

+ 1 - 1
Lib/ImapLib.php

@@ -761,4 +761,4 @@ class ImapFolderLib {
 	}
 
 
-}
+}

+ 24 - 46
Lib/MyCakeTestCase.php

@@ -98,37 +98,6 @@ abstract class MyCakeTestCase extends CakeTestCase {
 
 /*** assert mods ***/
 
-/** compatibility */
-
-	protected static function assertEqual($expected, $is, $title = null, $value = null, $message = '', $options = array()) {
-		$expectation = 'EQUAL';
-		self::_printTitle($expectation, $title, $options);
-		self::_printResults($expected, $is, $value, $options);
-		return parent::assertEqual($expected, $is, $message);
-	}
-
-	protected static function assertNotEqual($expected, $is, $title = null, $value = null, $message = '', $options = array()) {
-		$expectation = 'NOT EQUAL';
-		self::_printTitle($expectation, $title, $options);
-		self::_printResults($expected, $is, $value, $options);
-		return parent::assertNotEqual($expected, $is, $message);
-	}
-
-	protected static function assertPattern($expected, $is, $title = null, $value = null, $message = '', $options = array()) {
-		$expectation = 'PATTERN';
-		self::_printTitle($expectation, $title, $options);
-		self::_printResults($expected, $is, $value, $options);
-		return parent::assertNotEqual($expected, $is, $message);
-	}	
-
-	protected static function assertWithinMargin($result, $expected, $margin, $message = '') {
-		return parent::assertWithinMargin($result, $expected, $margin, $message);
-	}
-
-	protected static function assertIsA($object, $type, $message ='') {
-		return parent::assertIsA($object, $type, $message);
-	}
-
 /** enhanced **/
 
 	public static function assertNull($is, $title = null, $value = null, $message = '', $options = array()) {
@@ -151,6 +120,7 @@ abstract class MyCakeTestCase extends CakeTestCase {
 	 * 2009-07-09 ms
 	 */
 
+	//deprecated
 	public static function assertNotEmpty($is, $title = null, $value = null, $message = '') {
 		$expectation = 'NOT EMPTY';
 		self::_printTitle($expectation, $title);
@@ -158,6 +128,7 @@ abstract class MyCakeTestCase extends CakeTestCase {
 		return parent::assertTrue(!empty($is), $message);
 	}
 
+	//deprecated
 	public static function assertIsTrue($is, $title = null, $value = null, $message = '') {
 		$expectation = 'TRUE';
 		echo self::_title($expectation, $title);
@@ -165,6 +136,7 @@ abstract class MyCakeTestCase extends CakeTestCase {
 		return parent::assertTrue($is, $message);
 	}
 
+	//deprecated
 	public static function assertIsFalse($is, $title = null, $value = null, $message = '') {
 		$expectation = 'FALSE';
 		echo self::_title($expectation, $title);
@@ -184,20 +156,17 @@ abstract class MyCakeTestCase extends CakeTestCase {
 	}
 
 	public function _printTitle($expectation, $title = null) {
-		/*
-		if (!self::_reporter->params['show_passes']) {
+		if (empty($_SERVER['HTTP_HOST']) || !isset($_GET['show_passes']) || !$_GET['show_passes']) {
 			return false;
 		}
-		*/
 		echo self::_title($expectation, $title);
 	}
 
 	public function _printResults($expected, $is, $pre = null, $status = false) {
-		/*
-		if (!self::_reporter->params['show_passes']) {
+		if (empty($_SERVER['HTTP_HOST']) || !isset($_GET['show_passes']) || !$_GET['show_passes']) {
 			return false;
 		}
-		*/
+		
 		if ($pre !== null) {
 			echo 'value:';
 			pr ($pre);
@@ -211,11 +180,10 @@ abstract class MyCakeTestCase extends CakeTestCase {
 	}
 
 	public function _printResult($is, $pre = null, $status = false) {
-		/*
-		if (!self::_reporter->params['show_passes']) {
+		if (empty($_SERVER['HTTP_HOST']) || !isset($_GET['show_passes']) || !$_GET['show_passes']) {
 			return false;
 		}
-		*/
+		
 		if ($pre !== null) {
 			echo 'value:';
 			pr($pre);
@@ -224,15 +192,25 @@ abstract class MyCakeTestCase extends CakeTestCase {
 		pr($is);
 	}
 
-	public function out($array, $pre = true) {
+	/**
+	 * outputs debug information during a web tester (browser) test case
+	 * since PHPUnit>=3.6 swallowes all output by default 
+	 * this is a convenience output handler since debug() or pr() have no effect
+	 * @param mixed $data
+	 * @param bool $pre should a pre tag be enclosed around the output
+	 * @return void
+	 * 2011-12-04 ms
+	 */
+	public function out($data, $pre = true) {
+		if ($pre) {
+			$data = pre($data);
+		}
+		echo $data;
 		if (empty($_SERVER['HTTP_HOST'])) {
-			# cake shell!!!
+			# cli mode / shell access: use the --debug modifier if you are using the CLI interface
 			return;
 		}
-		if ($pre) {
-			$array = pre($array);
-		}
-		echo $array;
+		ob_flush();
 	}
 
 }

+ 269 - 0
Lib/MyHelper.php

@@ -0,0 +1,269 @@
+<?php
+
+App::uses('Helper', 'View');
+
+class MyHelper extends Helper {
+
+	public function  __construct($View = null, $settings = array()) {
+		if (class_exists('Packages')) {
+			Packages::initialize($this, __CLASS__);
+		}
+		parent::__construct($View, $settings);
+	}
+
+	# deprecated in 2.0?
+	public function initHelpers($additionalHelpers = array()) {
+		if (!empty($this->helpers)) {
+			$this->loadHelpers(array_merge($this->helpers, $additionalHelpers));
+		}
+	}
+
+	/**
+	 * manually
+	 */
+	public function loadHelpers($helpers = array(), $callbacks = false) {
+		foreach ((array)$helpers as $helper => $config) {
+			if (is_int($helper)) {
+				$helper = $config;
+				$config = array();
+			}
+			list($plugin, $helperName) = pluginSplit($helper);
+			if (isset($this->{$helperName})) {
+				continue;
+			}
+			App::import('Helper', $helper);
+			$helperFullName = $helperName.'Helper';
+			$this->{$helperName} = new $helperFullName($this->_View, (array)$config);
+
+			if ($callbacks) {
+				if (method_exists($helper, 'beforeRender')) {
+					$this->{$helperName}->beforeRender();
+				}
+			}
+		}
+	}
+
+	//TODO
+	/**
+	 * problems: what if inside plugin webroot? not easy to do...
+	 */
+	public function imageIfExists($path, $options = array(), $default = '---') {
+		if (startsWith($path, '/')) {
+			/*
+			$completePath = Router::url($path);
+			//echo (returns(file_exists($completePath)));
+			//die($completePath);
+			# problem with plugin files!!! needs "webroot" added after plugin name
+			if (!file_exists($completePath)) {
+				return $default;
+			}
+			*/
+		} else {
+			$completePath = Router::url($path);
+		}
+		if (!empty($completePath) && !file_exists($completePath)) {
+			return $default;
+		}
+		return $this->image($path, $options);
+	}
+
+	/**
+	 * display image tag from blob content
+	 * enhancement for HtmlHelper
+	 * @param binary $content
+	 * @param array $options
+	 * @return string $html imageTag
+	 * 2010-11-22 ms
+	 */
+	public function imageFromBlob($content, $options = array()) {
+		$text = 'data:image/png;base64,' . base64_encode($content);
+		$image = sprintf($this->_tags['image'], $text, $this->_parseAttributes($options, null, '', ' '));
+		return $image;
+	}
+
+	/**
+	 * HTML Helper extension for HTML5 time
+	 * The time element represents either a time on a 24 hour clock,
+	 * or a precise date in the proleptic Gregorian calendar,
+	 * optionally with a time and a time-zone offset.
+	 *
+	 * @param $content string
+	 * @param $options array
+	 *      'format' STRING: Use the specified TimeHelper method (or format()). FALSE: Generate the datetime. NULL: Do nothing.
+	 *      'datetime' STRING: If 'format' is STRING use as the formatting string. FALSE: Don't generate attribute
+	 *
+	 * //TODO: fixme
+	 * 2011-07-17 ms
+	 */
+	public function time($content, $options = array()) {
+		if (!isset($this->tags['time'])) {
+			$this->tags['time'] = '<time%s>%s</time>';
+		}
+		$options = array_merge(array(
+		'datetime' => '%Y-%m-%d %T',
+		'pubdate' => false,
+			'format' => '%Y-%m-%d %T',
+		), $options);
+
+		if ($options['format'] !== null) {
+		App::import('helper', 'Time');
+		$t = new TimeHelper(new View(null));
+		}
+		if ($options['format']) {
+			if (method_exists($t, $options['format'])) {
+				$content = $t->$options['format']($content);
+		} else {
+			$content = $t->i18nFormat($content, $options['format']);
+		}
+		$options['datetime'] = $t->i18nFormat(strtotime($content), $options['datetime']);
+		} elseif ($options['format'] === false && $options['datetime']) {
+			$options['datetime'] = $t->i18nFormat(strtotime($content), $options['datetime']);
+		}
+
+		if ($options['pubdate'])
+			$pubdate = true;
+
+		unset($options['format']);
+		unset($options['pubdate']);
+		$attributes = $this->_parseAttributes($options, array(0), ' ', '');
+
+		if (isset($pubdate))
+			$attributes .= ' pubdate';
+
+		return sprintf($this->tags['time'],  $attributes, $content);
+	}
+
+
+
+	# for convienience function Html::defaultLink()
+	protected $linkDefaults = null;
+
+	/*
+	# only one prefix at a time
+	public function url($url, $full = false) {
+		if (is_array($url)) {
+			$url['lang'] = 'deu';
+		}
+		return parent::url($url, $full);
+	}
+	*/
+
+	/**
+	 * convenience function for normal links
+	 * useful for layout links and links inside elements etc
+	 * @params same as Html::link($title, $url, $options, $confirmMessage)
+	 * 2010-01-23 ms
+	 */
+	public function defaultLink($title, $url=null, $options=array(), $confirmMessage=false) {
+		if ($this->linkDefaults === null) {
+			if (!class_exists('CommonComponent')) {
+				App::import('Component', 'Tools.Common');
+			}
+			$this->linkDefaults = CommonComponent::defaultUrlParams();
+		}
+		if (!defined('PREFIX_ADMIN')) {
+			define('PREFIX_ADMIN', 'admin');
+		}
+		if ($url !== null && is_array($url)) {
+			$url = array_merge($this->linkDefaults, $url);
+			if (!empty($url[PREFIX_ADMIN])) {
+				$options['rel'] = 'nofollow';
+			}
+		} elseif (is_array($title)) {
+			$title = array_merge($this->linkDefaults, $title);
+			if (!empty($title[PREFIX_ADMIN])) {
+				$options['rel'] = 'nofollow';
+			}
+		}
+		//$this->log($url, '404');
+		return $this->link($title, $url, $options, $confirmMessage);
+	}
+
+	/**
+	 * convenience function for normal urls
+	 * useful for layout urls and urls inside elements etc
+	 * @params same as Html::url($url, $full)
+	 * 2010-01-23 ms
+	 */
+	public function defaultUrl($url = null, $full = false) {
+		if ($this->linkDefaults === null) {
+			if (!class_exists('CommonComponent')) {
+				App::import('Component', 'Tools.Common');
+			}
+			$this->linkDefaults = CommonComponent::defaultUrlParams();
+		}
+		if ($url !== null && is_array($url)) {
+			$url = array_merge($this->linkDefaults, $url);
+		}
+		return $this->url($url, $full);
+	}
+
+
+	public $urlHere = null;
+	/**
+	 * Small Helper Function to retrieve CORRECT $this->here (as it should be) - CAKE BUG !? -> this is a fix
+	 * 2009-01-06 ms
+	 */
+	public function here() {
+		if (empty($this->urlHere) && isset($_GET['url'])) {
+			$this->urlHere = $_GET['url'];
+			if (strpos($this->urlHere, '/') !== 0) {
+				$this->urlHere = '/'.$this->urlHere;
+			}
+		}
+		return $this->urlHere;
+	}
+
+
+	/**
+	 * enhancement to htmlHelper which allows the crumbs protected array
+	 * to be cleared so that more than one set of crumbs can be generated in the same view.
+	 *
+	 * @return void
+	 * 2009-08-05 ms
+	 */
+	public function resetCrumbs() {
+		$this->_crumbs = array();
+	}
+
+
+
+/** deprecated */
+
+	/**
+	 * @deprecated
+	 */
+	public function nl2p($text, $options = array(), $enforceMaxLength = true) {
+		$pS = $this->Html->tag('p', null, $options);
+		$pE = '</p>';
+		if (!empty($text)) {
+			// Max length auto line break, if enabled
+			if ($enforceMaxLength) {
+				$maxLength = null;
+				if (isset($options['maxLength'])) {
+					$maxLength = (int)$options['maxLength'];
+				}
+				$text = $this->maxLength($text, $maxLength);
+			}
+			// Replace double newlines with <p>
+			$text = $pS . preg_replace('#(\r?\n) {2,}(\s+)?#u', $pE . $pS, $text) . $pE;
+			// Replace single newlines with <br>
+			$text = preg_replace('#\r?\n#u', BR, $text);
+			// Add newlines to sourcecode for sourcode readability
+			$text = preg_replace(
+				array(
+					'#' . $pE . '#u', // Matches $pE (like </p>)
+					'#' . BR . '#u', // Matches $br (like <br />)
+				),
+				array(
+					$pE . "\n",
+					BR . "\n",
+				),
+				$text);
+		}
+		return $text;
+	}
+
+
+}
+

File diff suppressed because it is too large
+ 1464 - 0
Lib/MyModel.php


+ 13 - 0
Locale/qlogin_de.po

@@ -0,0 +1,13 @@
+### Qlogin
+
+msgid "Invalid Key"
+msgstr "Ungültiger Schlüssel"
+
+msgid "Invalid Account"
+msgstr "Ungültiger Account"
+
+msgid "You successfully logged in via qlogin"
+msgstr "Du wurdest erfolgreich eingeloggt."
+
+msgid "Success"
+msgstr "Erfolg"

+ 223 - 0
Model/CodeKey.php

@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * TODO: rename to "Token" with display field "token"
+ * 
+ * @author Mark Scherer
+ * @cakephp 2.0
+ * @license MIT
+ * 2011-11-17 ms
+ */
+class CodeKey extends ToolsAppModel {
+
+	public $displayField = 'key';
+	public $order = array('CodeKey.created' => 'ASC');
+
+	protected $defaultLength = 22;
+	protected $validity = MONTH;
+
+	public $validate = array(
+		'type' => array(
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+			),
+		),
+		'key' => array(
+			'isUnique' => array(
+				'rule' => array('isUnique'),
+				'message' => 'key already exists',
+			),
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+			),
+		),
+		'content' => array(
+			'maxLength' => array(
+				'rule' => array('maxLength', 255),
+				'message' => array('valErrMaxCharacters %s', 255),
+				'allowEmpty' => true
+			),
+		),
+		'used' => array('numeric')
+	);
+
+	//public $types = array('activate');
+
+	/**
+	 * stores new key in DB
+	 * @param string type: neccessary
+	 * @param string key: optional key, otherwise a key will be generated
+	 * @param mixed user_id: optional (if used, only this user can use this key)
+	 * @param string content: up to 255 characters of content may be added (optional)
+	 * NOW: checks if this key is already used (should be unique in table)
+	 * @return string key on SUCCESS, boolean false otherwise
+	 * 2009-05-13 ms
+	 */
+	public function newKey($type, $key = null, $uid = null, $content = null) {
+		if (empty($type)) {		//  || !in_array($type,$this->types)
+			return false;
+		}
+
+		if (empty($key)) {
+			$key = $this->generateKey($this->defaultLength);
+			$keyLength = $this->defaultLength;
+		} else {
+			$keyLength = mb_strlen($key);
+		}
+
+		$data = array(
+			'type' => $type,
+			'user_id' => (string)$uid,
+			'content' => (string)$content,
+			'key' => $key,
+		);
+
+		$this->set($data);
+		$max = 99;
+		while (!$this->validates()) {
+			$data['key'] = $this->generateKey($keyLength);
+			$this->set($data);
+			$max--;
+			if ($max == 0) { //die('Exeption in CodeKey');
+			 	return false;
+			}
+		}
+
+		$this->create();
+		if ($this->save($data)) {
+			return $key;
+		}
+		return false;
+	}
+
+	/**
+	 * usesKey (only once!) - by KEY
+	 * @param string type: neccessary
+	 * @param string key: neccessary
+	 * @param mixed user_id: needs to be provided if this key has a user_id stored
+	 * @return ARRAY(content) if successfully used or if already used (used=1), FALSE else
+	 * 2009-05-13 ms
+	 */
+	public function useKey($type, $key, $uid = null, $treatUsedAsInvalid = false) {
+		if (empty($type) || empty($key)) {
+			return false;
+		}
+		$conditions = array('conditions'=>array($this->alias.'.key'=>$key,$this->alias.'.type'=>$type));
+		if (!empty($uid)) {
+			$conditions['conditions'][$this->alias.'.user_id'] = $uid;
+		}
+		$res = $this->find('first', $conditions);
+		if (empty($res)) {
+			return false;
+		} 
+		if (!empty($uid) && !empty($res[$this->alias]['user_id']) && $res[$this->alias]['user_id'] != $uid) {
+			// return $res; # more secure to fail here if user_id is not provided, but was submitted prev.
+			return false;
+		}
+		# already used?
+		if (!empty($res[$this->alias]['used'])) {
+			if ($treatUsedAsInvalid) {
+				return false;
+			}
+			# return true and let the application check what to do then 
+			return $res;
+		}
+		# actually spend key (set to used)
+		if ($this->spendKey($res[$this->alias]['id'])) {
+			return $res;
+		}
+		# no limit? we dont spend key then
+		if (!empty($res[$this->alias]['unlimited'])) {
+			return $res;
+		}
+		$this->log('VIOLATION in CodeKey Model (method useKey)');
+		return false;
+	}
+
+	/**
+	 * sets Key to "used" (only once!) - directly by ID
+	 * @param id of key to spend: neccessary
+	 * @return boolean true on success, false otherwise
+	 * 2009-05-13 ms
+	 */
+	public function spendKey($id = null) {
+		if (empty($id)) {
+			return false;
+		}
+		//$this->id = $id;
+		if ($this->updateAll(array($this->alias.'.used' => $this->alias.'.used + 1', $this->alias.'.modified'=>'"'.date(FORMAT_DB_DATETIME).'"'), array($this->alias.'.id'=>$id))) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * remove old/invalid keys
+	 * does not remove recently used ones (for proper feedback)!
+	 * @return boolean success
+	 * 2010-06-17 ms
+	 */
+	public function garbigeCollector() {
+		$conditions = array(
+			$this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity),
+		);
+		return $this->deleteAll($conditions, false);
+	}
+	
+	
+	/**
+	 * get admin stats
+	 * 2010-10-22 ms 
+	 */
+	public function stats() {
+		$keys = array();
+		$keys['unused_valid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>0, $this->alias.'.created >='=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
+		$keys['used_valid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>1, $this->alias.'.created >='=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
+		
+		$keys['unused_invalid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>0, $this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
+		$keys['used_invalid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>1, $this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
+		
+		$types = $this->find('all', array('conditions'=>array(), 'fields'=>array('DISTINCT type')));
+		$keys['types'] = !empty($types) ? Set::extract('/'.$this->alias.'/type', $types) : array();
+		return $keys;
+	}
+
+
+	/**
+	 * @param length (defaults to defaultLength)
+	 * @return string codekey
+	 * 2009-05-13 ms
+	 */
+	public function generateKey($length = null) {
+		if (empty($length)) {
+			$length = $defaultLength;
+		} else {
+			if ((class_exists('CommonComponent') || App::import('Component', 'Common')) && method_exists('CommonComponent', 'generatePassword')) {
+				return CommonComponent::generatePassword($length);
+			} else {
+				return $this->_generateKey($length);
+			}
+		}
+	}
+
+	/**
+	 * backup method - only used if no custom function exists
+	 * 2010-06-17 ms
+	 */
+	public function _generateKey($length = null) {
+		$chars = "234567890abcdefghijkmnopqrstuvwxyz"; // ABCDEFGHIJKLMNOPQRSTUVWXYZ
+		$i = 0;
+		$password = "";
+		$max = strlen($chars) - 1;
+
+		while ($i < $length) {
+			$password .= $chars[mt_rand(0, $max)];
+			$i++;
+		}
+		return $password;
+	}
+
+}
+

+ 100 - 0
Model/Qlogin.php

@@ -0,0 +1,100 @@
+<?php
+//TODO: later Auth Plugin
+
+/**
+ * Manage Quick Logins
+ * 
+ * @author Mark Scherer
+ * @cakephp 2.0
+ * @license MIT
+ * 2011-11-17 ms
+ */
+class Qlogin extends ToolsAppModel {
+
+	public $useTable = false;
+
+	public $validate = array(
+		'url' => array(
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+				'last' => true
+			),
+			'validateUrl' => array(
+				'rule' => array('validateUrl', array('deep'=>false, 'sameDomain'=>true, 'autoComplete'=>true)),
+				'message' => 'valErrInvalidQloginUrl',
+				'last' => true
+			)
+		),
+		'user_id' => array(
+			'notEmpty' => array(
+				'rule' => array('notEmpty'),
+				'message' => 'valErrMandatoryField',
+				'last' => true
+			),		
+			/*
+			'validateUnique' => array(
+				'rule' => array('validateUnique', array('url')),
+				'message' => 'key already exists',
+			),
+			*/
+		),
+	);
+	
+	protected function _useKey($key) {
+		if (!isset($this->CodeKey)) {
+			$this->CodeKey = ClassRegistry::init('Tools.CodeKey');
+		}
+		return $this->CodeKey->useKey('qlogin', $key);
+	}
+	
+	protected function _newKey($uid, $content) {
+		if (!isset($this->CodeKey)) {
+			$this->CodeKey = ClassRegistry::init('Tools.CodeKey');
+		}
+		return $this->CodeKey->newKey('qlogin', null, $uid, $content);
+	}
+	
+	public function translate($key) {
+		$res = $this->_useKey($key);
+		if (!$res) {
+			return false;
+		}
+		$res['CodeKey']['content'] = unserialize($res['CodeKey']['content']);
+		$res['CodeKey']['url'] = Router::url($res['CodeKey']['content'], true);
+		return $res;
+	}
+	
+	/**
+	 * generates a qlogin key
+	 * @param mixed $url
+	 * @param string $uid
+	 * @return string $key
+	 * 2011-07-12 ms
+	 */
+	public function generate($url, $uid) {
+		$content = serialize($url);
+		return $this->_newKey($uid, $content);
+	}
+	
+	public function urlByKey($key) {
+		return Router::url(array('admin'=>'', 'plugin'=>'tools', 'controller'=>'qlogin', 'action'=>'go', $key), true);
+	}
+	
+	/**
+	 * makes an absolute url string ready to input anywhere
+	 * uses generate() internally to get the key
+	 * @param mixed $url
+	 * @return string $url (absolute) 
+	 */
+	public function url($url, $uid = null) {
+		if ($uid === null) {
+			$uid = $this->Session->read('Auth.User.id');
+		}
+		$key = $this->generate($url, $uid);
+	 	return $this->urlByKey($key);
+	}
+	
+}
+
+

+ 61 - 0
Test/Case/Model/CodeKeyTest.php

@@ -0,0 +1,61 @@
+<?php
+
+App::import('Model', 'Tools.CodeKey');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+class CodeKeyTest extends MyCakeTestCase {
+	public $CodeKey = null;
+	//public $fixtures = array('app.code_key');
+
+	public function startTest() {
+		$this->CodeKey = ClassRegistry::init('Tools.CodeKey');
+	}
+
+	public function testCodeKeyInstance() {
+		$this->assertTrue(is_a($this->CodeKey, 'CodeKey'));
+	}
+
+
+	public function testGenerateKey() {
+		$key = $this->CodeKey->generateKey(4);
+		pr($key);
+		$this->assertTrue(!empty($key) && strlen($key) === 4);
+	}
+
+
+	public function testNewKeySpendKey() {
+		$key = $this->CodeKey->newKey('test', null, null, 'xyz');
+		$this->assertTrue(!empty($key));
+
+		$res = $this->CodeKey->useKey('test', $key);
+		pr($res);
+		$this->assertTrue(!empty($res));
+
+		$res = $this->CodeKey->useKey('test', $key);
+		pr($res);
+		$this->assertTrue(!empty($res) && !empty($res['CodeKey']['used']));
+
+		$res = $this->CodeKey->useKey('test', $key.'x');
+		$this->assertFalse($res);
+
+		$res = $this->CodeKey->useKey('testx', $key);
+		$this->assertFalse($res);
+	}
+
+	public function testGarbigeCollector() {
+		$data = array(
+			'created' => date(FORMAT_DB_DATETIME, time()-3*MONTH),
+			'type' => 'y',
+			'key' => 'x'
+		);
+		$this->CodeKey->create();
+		$this->CodeKey->save($data, false);
+		$count = $this->CodeKey->find('count');
+		$this->CodeKey->garbigeCollector();
+		$count2 = $this->CodeKey->find('count');
+		$this->assertTrue($count > $count2);
+	}
+
+
+
+}

+ 22 - 0
Test/Case/Model/ContactFormTest.php

@@ -0,0 +1,22 @@
+<?php
+
+//App::import('Model', 'Tools.ContactForm');
+
+class ContactFormTest extends CakeTestCase {
+	public $ContactForm = null;
+	//public $fixtures = array('app.code_key');
+
+	public function startTest() {
+		$this->ContactForm = ClassRegistry::init('Tools.ContactForm');
+	}
+
+	public function testContactInstance() {
+		$this->assertTrue(is_a($this->ContactForm, 'ContactForm'));
+	}
+
+
+	//TODO
+
+
+}
+

+ 47 - 0
Test/Case/Model/QloginTest.php

@@ -0,0 +1,47 @@
+<?php
+
+App::import('Model', 'Tools.Qlogin');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+App::uses('Router', 'Routing');
+
+class QloginTest extends MyCakeTestCase {
+	public $Qlogin = null;
+	//public $fixtures = array('app.code_key');
+
+	public function startTest() {
+		$this->Qlogin = ClassRegistry::init('Tools.Qlogin');
+	}
+
+	public function testQloginInstance() {
+		$this->assertTrue(is_a($this->Qlogin, 'Qlogin'));
+	}
+
+	public function testGenerate() {
+		$url = Router::url(array('admin'=>'', 'plugin'=>'tools', 'controller'=>'qlogin', 'action'=>'go'), true).'/';
+		pr($url);
+		$res = $this->Qlogin->url(array('controller'=>'test', 'action'=>'foo', 'bar'), 1);
+		pr($res);
+		$this->assertTrue(is_string($res) && !empty($res));
+		$this->assertTrue(strpos($res, $url) === 0);
+		
+		$res = $this->Qlogin->url('/test/foo/bar', 2);
+		pr($res);
+		$this->assertTrue(is_string($res) && !empty($res));
+	}
+
+	public function testUse() {
+		$key = $this->Qlogin->generate(array('controller'=>'test', 'action'=>'foo', 'bar'), 1);
+		$res = $this->Qlogin->translate($key);
+		echo returns($res);
+		pr($res);
+		
+		$key = $this->Qlogin->generate('/test/foo/bar', 2);
+		$res = $this->Qlogin->translate($key);
+		echo returns($res);
+		pr($res);
+	}
+	
+	//TODO
+
+
+}

+ 32 - 0
View/Qlogin/admin_index.ctp

@@ -0,0 +1,32 @@
+<div class="page form">
+
+<h2><?php echo __('Qlogins'); ?></h2>
+<?php echo $qlogins;?> <?php echo __('valid ones'); ?>
+<br /><br />
+<?php if (!empty($url)) { ?>
+<h3><?php echo __('Generated Link'); ?></h3>
+<code><?php echo h($url);?></code>
+<?php } ?>
+
+<h3><?php echo __('Add %s', __('Qlogin')); ?></h3>
+<?php echo $this->Form->create('Qlogin');?>
+	<fieldset>
+ 		<legend><?php echo __('Add %s', __('Qlogin')); ?></legend>
+	<?php
+		echo $this->Form->input('url', array('placeholder'=>'/controller/action/...'));
+		echo $this->Form->input('user_id', array('empty'=>'---'));
+	?>
+	</fieldset>
+<?php echo $this->Form->end(__('Submit'));?>
+</div>
+
+<br /><br />
+
+<div class="actions">
+	<ul>
+		<li><?php echo $this->Html->link(__('Reset %s', __('Qlogins')), array('action' => 'reset'), array(), __('Sure?'));?></li>
+	<?php if (false) { ?>
+		<li><?php echo $this->Html->link(__('List %s', __('Qlogins')), array('action' => 'listing'));?></li>
+	<?php } ?>	
+	</ul>
+</div>