Browse Source

helper refactor, libs added, tiny url added

euromark 14 years ago
parent
commit
be9276a828

+ 1 - 1
Controller/Component/CalendarComponent.php

@@ -42,7 +42,7 @@ class CalendarComponent extends Component {
 	/**
 	 * @return bool $success
 	 */
-	function ensureCalendarConsistency($year, $month, $span = 10) {
+	public function ensureCalendarConsistency($year, $month, $span = 10) {
 		if (!is_numeric($month)) {
 			$monthKeys = array_keys($this->monthList, $month);
 			$month = array_shift($monthKeys);

+ 13 - 9
Controller/Component/CommonComponent.php

@@ -381,14 +381,17 @@ class CommonComponent extends Component {
 
 
 	/**
+	 * Smart Referer Redirect - will try to use an existing referer first
+	 * otherwise it will use the default url
+	 * 
 	 * @param mixed $url
 	 * @param bool $allowSelf if redirect to the same controller/action (url) is allowed
 	 * @param int $status
 	 * returns nothing and automatically redirects
 	 * 2010-11-06 ms
 	 */
-	public function autoRedirect($whereTo, $allowSelf = true, $status = null) {
-		if ($allowSelf || $this->Controller->referer() != '/' . $this->Controller->request->url) {
+	public function autoRedirect($whereTo, $allowSelf = false, $status = null) {
+		if ($allowSelf || $this->Controller->referer(null, true) != '/' . $this->Controller->request->url) {
 			$this->Controller->redirect($this->Controller->referer($whereTo, true));
 		} else {
 			$this->Controller->redirect($whereTo, $status);
@@ -423,6 +426,10 @@ class CommonComponent extends Component {
 		}
 		if (!$conditionalAutoRedirect || empty($this->Controller->autoRedirectActions) || is_array($referer) && !empty($referer['action'])) {
 			$refererController = Inflector::camelize($referer['controller']);
+			# fixme
+			if (!isset($this->Controller->autoRedirectActions)) {
+				$this->Controller->autoRedirectActions = array();
+			}
 			foreach ($this->Controller->autoRedirectActions as $action) {
 				list($controller, $action) = pluginSplit($action);
 				if (!empty($controller) && $refererController != '*' && $refererController != $controller) {
@@ -454,8 +461,6 @@ class CommonComponent extends Component {
 		}
 	}
 
-
-
 	/**
 	 * Handler for passing some meta data to the view
 	 * uses CommonHelper to include them in the layout
@@ -479,8 +484,8 @@ class CommonComponent extends Component {
 		Configure::write('Meta.'.$type, $content);
 	}
 
-/*** Other helpers and debug features **/
 
+/*** Other helpers and debug features **/
 
 	/**
 	* Checks to see if there is a limit set for pagination results
@@ -520,7 +525,6 @@ class CommonComponent extends Component {
 		}
 	}
 
-
 	/**
 	 * set headers to cache this request
 	 * @param int $seconds
@@ -542,7 +546,7 @@ class CommonComponent extends Component {
 		if ($ref === null) {
 			$ref = env('HTTP_REFERER');
 		}
-		$base = FULL_BASE_URL.$this->Controller->webroot;
+		$base = FULL_BASE_URL . $this->Controller->webroot;
 		if (strpos($ref, $base) === 0) { // @ position 1 already the same
 			return false;
 		}
@@ -872,7 +876,7 @@ class CommonComponent extends Component {
 	 */
 	public function daysInMonth($year, $month) {
 		trigger_error('deprecated - use Tools.DatetimeLib instead');
-		App::uses('DatetimeLib', 'Tools.Lib');
+		App::uses('DatetimeLib', 'Tools.Utility');
 		return DatetimeLib::daysInMonth($year, $month);
 	}
 
@@ -969,7 +973,7 @@ class CommonComponent extends Component {
 	 */
 	public static function average($values, $precision = 0) {
 		trigger_error('deprecated - use Tools.NumberLib instead');
-		App::uses('NumberLib', 'Tools.Lib');
+		App::uses('NumberLib', 'Tools.Utility');
 		return NumberLib::average($values, $precision);
 	}
 

+ 92 - 0
Controller/TinyUrlsController.php

@@ -0,0 +1,92 @@
+<?php
+/*
+
+Apply this route (/Config/routes.php):
+
+Router::connect('/s/:id',
+	array('plugin'=>'tools', 'controller'=>'tiny_urls', 'action'=>'go'),
+	array('id'=>'[0-9a-zA-Z]+'));
+
+Result:
+/domain/s/ID
+
+*/
+
+class TinyUrlsController extends ToolsAppController {
+
+	//public $uses = array('Tools.TinyUrl');
+
+	public function beforeFilter() {
+		parent::beforeFilter();
+
+		if (isset($this->Auth)) {
+			$this->Auth->allow('go');
+		}
+	}
+
+
+	/****************************************************************************************
+	* ADMIN functions
+	****************************************************************************************/
+
+
+
+	/**
+	 * main redirect function
+	 * 2011-07-11 ms
+	 */
+	public function go() {
+		if (empty($this->request->params['id'])) {
+			throw new NotFoundException();
+		}
+		$entry = $this->TinyUrl->translate($this->request->params['id']);
+		if (empty($entry)) {
+			throw new NotFoundException();
+		}
+
+		//$message = $entry['TinyInt']['flash_message'];
+		$url = $entry['TinyUrl']['target'];
+
+		if (!empty($message)) {
+			$type = !empty($entry['TinyUrl']['flash_type']) ? $entry['TinyUrl']['flash_type'] : 'success';
+			$this->Common->flashMessage($message, $type);
+		}
+		$this->TinyUrl->up($entry['TinyUrl']['id'], array('field'=>'used', 'modify'=>true, 'timestampField'=>'last_used'));
+		$this->redirect($url, 301);
+	}
+
+
+	public function admin_index() {
+		//TODO
+
+		if ($this->Common->isPost()) {
+			$this->TinyUrl->set($this->request->data);
+			if ($this->TinyUrl->validates()) {
+				$id = $this->TinyUrl->generate($this->TinyUrl->data['TinyUrl']['url']);
+				$this->Common->flashMessage('New Key: '.h($id), 'success');
+				$url = $this->TinyUrl->urlByKey($id);
+				$this->set(compact('url'));
+				$this->request->data = array();
+			}
+		}
+
+		$tinyUrls = $this->TinyUrl->find('count', array('conditions'=>array()));
+
+		$this->set(compact('tinyUrls'));
+	}
+
+	public function admin_listing() {
+
+	}
+
+	public function admin_reset() {
+		if (!$this->Common->isPost()) {
+			throw new MethodNotAllowedException();
+		}
+		$this->TinyUrl->truncate();
+		$this->Common->flashMessage(__('Done'), 'success');
+		$this->Common->autoRedirect(array('action'=>'index'));
+	}
+
+}
+

+ 9 - 0
Controller/ToolsAppController.php

@@ -0,0 +1,9 @@
+<?php
+
+class ToolsAppController extends AppController {
+
+	public $components = array('Session', 'Tools.Common');
+	
+	public $helpers = array('Html', 'Form', 'Session', 'Tools.Common', 'Tools.Format', 'Tools.Datetime', 'Tools.Numeric');
+
+}

+ 719 - 0
Lib/Bootstrap/MyBootstrap.php

@@ -0,0 +1,719 @@
+<?php
+App::uses('Utility', 'Tools.Utility');
+
+/** BASIC STUFF **/
+
+//use FULL_BASE_URL (cake) instead of http_base?
+if (!empty($_SERVER['HTTP_HOST'])) {
+	define('HTTP_HOST', $_SERVER['HTTP_HOST']);
+	define('HTTP_BASE', 'http://' . HTTP_HOST); //FULL_BASE_URL
+} else {
+	define('HTTP_HOST', '');
+	define('HTTP_BASE', '');
+}
+
+if (!empty($_SERVER['PHP_SELF'])) {
+	define('HTTP_SELF', '' . $_SERVER['PHP_SELF']);
+}
+if (!empty($_SERVER['REQUEST_URI'])) {
+	define('HTTP_URI', '' . $_SERVER['REQUEST_URI']);
+}
+if (!empty($_SERVER['HTTP_REFERER'])) {
+	define('HTTP_REF', '' . $_SERVER['HTTP_REFERER']);
+}
+
+define('CHOWN_PUBLIC', 0770);
+
+
+# Useful when putting a string together that needs some "pretty" html-doc. source layouting
+# Only visible in SOURCE code (not in html layout in the browser)
+//define('LF',''); // line feed (depending on the system)
+define('LF', PHP_EOL); // line feed (depending on the system): \n or \n\r etc.
+# Alternativly NL,CR:
+define('NL', "\n"); // new line
+define('CR', "\r"); // carriage return
+
+define('TB', "\t"); // tabulator
+
+# Useful for html layouting
+# Visible in the Html Layout in the Browser
+define('BR', '<br />'); // line break
+
+# Make the app and l10n play nice with Windows.
+if (substr(PHP_OS, 0, 3) == 'WIN') { // || strpos(@php_uname(), 'ARCH')
+	define('WINDOWS', true);
+} else {
+	define('WINDOWS', false);
+}
+
+define('FORMAT_DB_DATETIME', 'Y-m-d H:i:s'); // date(...)
+define('FORMAT_DB_DATE', 'Y-m-d');
+define('FORMAT_DB_TIME', 'H:i:s');
+
+define('DEFAULT_DATETIME', '0000-00-00 00:00:00');
+define('DEFAULT_DATE', '0000-00-00');
+define('DEFAULT_TIME', '00:00:00');
+
+# deprecated (could be wrong, if timezone is modified)
+define('CURRENT_YEAR', date('Y'));
+define('CURRENT_MONTH', date('m'));
+define('CURRENT_DAY', date('d'));
+
+# workpaths
+define('FILES', APP . 'files' . DS);
+define('LOCALE', APP . 'locale' . DS);
+
+
+# Validation ## (minus should be "hyphen")
+/** Valid characters: letters only */
+define('VALID_ALPHA', '/^[a-zA-Z]+$/');
+
+/** Valid characters: letters,underscores only */
+define('VALID_ALPHA_UNDERSCORES', '/^[a-zA-Z_]+$/');
+
+/** Valid characters: letters,underscores,minus only */
+define('VALID_ALPHA_MINUS_UNDERSCORES', '/^[a-zA-Z_-]+$/');
+
+/** Valid characters: letters,spaces only */
+define('VALID_ALPHA_WHITESPACES', '/^[a-zA-Z ]+$/');
+
+/** Valid characters: letters,numbers,underscores only */
+define('VALID_ALPHANUMERIC_UNDERSCORES', '/^[\da-zA-Z_]+$/');
+
+/** Valid characters: letters,numbers,underscores,minus only */
+define('VALID_ALPHANUMERIC_MINUS_UNDERSCORES', '/^[\da-zA-Z_-]+$/');
+
+/** Valid characters: letters,numbers,spaces only */
+define('VALID_ALPHANUMERIC_WHITESPACES', '/^[\da-zA-Z ]+$/');
+
+/** Valid characters: letters,numbers,spaces,underscores only */
+define('VALID_ALPHANUMERIC_WHITESPACES_UNDERSCORES', '/^[\da-zA-Z _]+$/');
+
+/** Valid characters: numbers,underscores only */
+define('VALID_NUMERIC_UNDERSCORES', '/^[\d_]+$/');
+
+/** Valid characters: numbers,spaces only */
+define('VALID_NUMERIC_WHITESPACES', '/^[\d ]+$/');
+
+/** Valid characters: numbers,spaces,underscores only */
+define('VALID_NUMERIC_WHITESPACES_UNDERSCORES', '/^[\d _]+$/');
+
+/** Valid integers: > 0 */
+define('VALID_INTEGERS', '/^[\d]+$/'); //??
+
+
+if (!defined('FORMAT_NICE_YMDHMS')) {
+	define('FORMAT_NICE_YMDHMS','d.m.Y, H:i:s');
+	define('FORMAT_NICE_YMDHM','d.m.Y, H:i');
+	define('FORMAT_NICE_YM','m.Y');
+	define('FORMAT_NICE_YMD','d.m.Y');
+	define('FORMAT_NICE_MD','d.m.');
+	define('FORMAT_NICE_D','d'); # xx
+	define('FORMAT_NICE_W_NUM','w'); # xx (0=sunday to 6=saturday)
+	define('FORMAT_NICE_W_ABBR','D'); # needs manual translation
+	define('FORMAT_NICE_W_FULL','l'); # needs manual translation
+	define('FORMAT_NICE_M','m'); # xx
+	define('FORMAT_NICE_M_ABBR','M'); # needs manual translation
+	define('FORMAT_NICE_M_FULL','F'); # needs manual translation
+	define('FORMAT_NICE_Y_ABBR','y'); # xx
+	define('FORMAT_NICE_Y','Y'); # xxxx
+	define('FORMAT_NICE_HM','H:i');
+	define('FORMAT_NICE_HMS','H:i:s');
+	
+	# localDate strings
+	define('FORMAT_LOCAL_WA_YMDHMS','%a, %d.%m.%Y, %H:%M:%S');
+	define('FORMAT_LOCAL_WF_YMDHMS','%A, %d.%m.%Y, %H:%M:%S');
+	define('FORMAT_LOCAL_WA_YMDHM','%a, %d.%m.%Y, %H:%M');
+	define('FORMAT_LOCAL_WF_YMDHM','%A, %d.%m.%Y, %H:%M');
+	
+	define('FORMAT_LOCAL_YMDHMS','%d.%m.%Y, %H:%M:%S');
+	define('FORMAT_LOCAL_YMDHM','%d.%m.%Y, %H:%M');
+	define('FORMAT_LOCAL_YMD','%d.%m.%Y');
+	define('FORMAT_LOCAL_MD','%d.%m.');
+	define('FORMAT_LOCAL_D','%d'); # xx
+	define('FORMAT_LOCAL_W_NUM','%w'); # xx (0=sunday to 6=saturday)
+	define('FORMAT_LOCAL_W_ABBR','%a'); # needs translation
+	define('FORMAT_LOCAL_W_FULL','%A'); # needs translation
+	define('FORMAT_LOCAL_M','%m'); # xx
+	define('FORMAT_LOCAL_M_ABBR','%b'); # needs translation
+	define('FORMAT_LOCAL_M_FULL','%B'); # needs translation
+	define('FORMAT_LOCAL_Y_ABBR','%y'); # xx
+	define('FORMAT_LOCAL_Y','%Y'); # xxxx
+	define('FORMAT_LOCAL_H','%H');
+	define('FORMAT_LOCAL_S','%S');
+	define('FORMAT_LOCAL_HM','%H:%i');
+	define('FORMAT_LOCAL_HMS','%H:%M:%S');
+}
+
+
+/*** chars ***/
+
+/* see http://www.htmlcodetutorial.com/characterentities_famsupp_69.html */
+define('CHAR_LESS', '&lt;'); # <
+define('CHAR_GREATER', '&gt;'); # >
+define('CHAR_QUOTE', '&quot;'); # "
+define('CHAR_APOSTROPHE', '&#39'); # '
+define('CHAR_ARROWS', '&raquo;'); # »
+define('CHAR_ARROWS_R', '&#187;'); # »
+define('CHAR_ARROWS_L', '&#171;'); # «
+define('CHAR_AVERAGE', '&#216;'); # Ø
+define('CHAR_INFIN', '&infin;'); # 8
+define('CHAR_MILL', '&#137;'); # ‰ (per mille) / or &permil;
+define('CHAR_PLUSMN', '&plusmn;'); # 8
+define('CHAR_HELLIP', '&#8230;'); # … (horizontal ellipsis = three dot leader)
+define('CHAR_CIRCA', '&asymp;'); # ˜ (almost equal to)
+define('CHAR_CHECKBOX_EMPTY', '&#9744;]'); #
+define('CHAR_CHECKBOX_MAKRED', '&#9745'); #
+define('CHAR_CHECKMARK', '&#10003;');
+define('CHAR_CHECKMARK_BOLD', '&#10004;');
+define('CHAR_BALLOT', '&#10007;');
+define('CHAR_BALLOT_BOLD', '&#10008;');
+define('CHAR_ABOUT','&asymp;'); # … (horizontal ellipsis = three dot leader)
+
+/* not very often used */
+define('CHAR_RPIME', '&#8242;'); # ' (minutes)
+define('CHAR_DOUBLE_RPIME', '&#8243;'); # ? (seconds)
+
+
+/** BASIC FUNCTIONS **/
+
+/**
+ * own slug function
+ * 2010-11-07 ms
+ */
+function slug($string, $separator = null, $low = true) {
+	$additionalSlugElements = array(
+		'/º|°/' => 0,
+		'/¹/' => 1,
+		'/²/' => 2,
+		'/³/' => 3,
+		# new utf8 char "capitel ß" still missing here! '/.../' => 'SS', (TODO in 2009)
+		'/@/' => 'at',
+		'/æ/' => 'ae',
+		'/©/' => 'C',
+		'/ç|¢/' => 'c',
+		'/Ð/' => 'D',
+		'/€/' => 'EUR',
+		'/™/' => 'TM',
+		# more missing?
+	);
+
+	if ($separator === null) {
+		$separator = defined('SEO_SEPARATOR') ? SEO_SEPARATOR : '-';
+	}
+	$res = Inflector::slug($string, $separator, $additionalSlugElements);
+	if ($low) {
+		$res = strtolower($res);
+	}
+	return $res;
+}
+
+
+/**
+ * Since nl2br doesn't remove the line breaks when adding in the <br /> tags,
+ * it is necessary to strip those off before you convert all of the tags, otherwise you will get double spacing
+ * @param string $str
+ * @return string
+ * 2010-11-07 ms
+ */
+function br2nl($str) {
+	$str = preg_replace("/(\r\n|\r|\n)/", "", $str);
+	return preg_replace("=<br */?>=i", "\n", $str);
+}
+
+/**
+ * Replaces CRLF with spaces
+ *
+ * @param string $text Any text
+ * @return string Safe string without new lines
+ * 2010-11-14 ms
+ */
+function safenl($str) {
+	//$str = str_replace(chr(13).chr(10), " ", $str); # \r\n
+	//$str = str_replace(chr(13), " ", $str); # \r
+	//$str = str_replace(chr(10), " ", $str); # \n
+	$str = preg_replace("/(\r\n|\r|\n)/", " ", $str);
+	return $str;
+}
+
+/**
+ * @param array $keyValuePairs
+ * @return string $key
+ * like array_shift() only for keys and not values
+ * 2011-01-22 ms
+ */
+function arrayShiftKeys(&$array) {
+	trigger_error('deprecated - use Tools.Utility instead');
+	//TODO: improve?
+	foreach ($array as $key => $value) {
+		unset($array[$key]);
+		return $key;
+	}
+}
+
+/**
+ * Flattens an array, or returns FALSE on fail.
+ * 2011-07-02 ms
+ */
+function arrayFlatten($array) {
+	trigger_error('deprecated - use Tools.Utility instead');
+	
+	if (!is_array($array)) {
+	return false;
+	}
+	$result = array();
+	foreach ($array as $key => $value) {
+	if (is_array($value)) {
+		$result = array_merge($result, arrayFlatten($value));
+	} else {
+		$result[$key] = $value;
+	}
+	}
+	return $result;
+}
+
+
+/**
+ * convenience function to check on "empty()"
+ * 2009-06-15 ms
+ */
+function isEmpty($var = null) {
+	if (empty($var)) {
+		return true;
+	}
+	return false;
+}
+
+
+/**
+ * //TODO: use Debugger::exportVar() instead?
+ * of what type is the specific value
+ * @return type (NULL, array, bool, float, int, string, object, unknown) + value
+ * 2009-03-03 ms
+ */
+function returns($value) {
+	if ($value === null) {
+		return 'NULL';
+	} elseif (is_array($value)) {
+		return '(array)' . '<pre>' . print_r($value, true) . '</pre>';
+	} elseif ($value === true) {
+		return '(bool)TRUE';
+	} elseif ($value === false) {
+		return '(bool)FALSE';
+	} elseif (is_numeric($value) && is_float($value)) {
+		return '(float)' . $value;
+	} elseif (is_numeric($value) && is_int($value)) {
+		return '(int)' . $value;
+	} elseif (is_string($value)) {
+		return '(string)' . $value;
+	} elseif (is_object($value)) {
+		return '(object)' . get_class($value) . '<pre>' . print_r($value, true) .
+			'</pre>';
+	} else {
+		return '(unknown)' . $value;
+	}
+}
+
+function dump($var) {
+	if (class_exists('Debugger')) {
+		App::import('Core', 'Debugger');
+	}
+	return Debugger::dump($var);
+}
+
+/**
+ * Returns htmlentities - string
+ *
+ * ENT_COMPAT	= Will convert double-quotes and leave single-quotes alone.
+ * ENT_QUOTES	= Will convert both double and single quotes. !!!
+ * ENT_NOQUOTES= Will leave both double and single quotes unconverted.
+ */
+function ent($text) {
+	return (!empty($text) ? htmlentities($text, ENT_QUOTES, 'UTF-8') : '');
+}
+
+/**
+ * Convenience method for htmlspecialchars_decode
+ *
+ * @param string $text Text to wrap through htmlspecialchars_decode
+ * @return string Wrapped text
+ * 2011-04-03 ms
+ */
+function hDec($text, $quoteStyle = ENT_QUOTES) {
+	if (is_array($text)) {
+		return array_map('hDec', $text);
+	}
+	return htmlspecialchars_decode($text, $quoteStyle);
+}
+
+
+function entDec($text, $quoteStyle = ENT_QUOTES) {
+	return (!empty($text) ? html_entity_decode($text, $quoteStyle, 'UTF-8') :
+		'');
+}
+
+/**
+ * focus is on the filename (without path)
+ * 2011-06-02 ms
+ */
+function extractFileInfo($type = null, $filename) {
+	if ($info = extractPathInfo($type, $filename)) {
+		return $info;
+	}
+	$pos = strrpos($filename, '.');
+	$res = '';
+	switch ($type) {
+		case 'extension':
+		case 'ext':
+			$res = ($pos !== false) ? substr($filename, $pos+1) : '';
+			break;
+		case 'filename':
+		case 'file':
+			$res = ($pos !== false) ? substr($filename, 0, $pos) : '';
+			break;
+		default:
+			break;
+	}
+	return $res;
+}
+
+/**
+ * uses native PHP function to retrieve infos about a filename etc.
+ * @param string type (extension/ext, filename/file, basename/base, dirname/dir)
+ * @param string filename to check on
+ * //TODO: switch parameters!!!
+ * 2009-01-22 ms
+ */
+function extractPathInfo($type = null, $filename) {
+	switch ($type) {
+		case 'extension':
+		case 'ext':
+			$infoType = PATHINFO_EXTENSION;
+			break;
+		case 'filename':
+		case 'file':
+			$infoType = PATHINFO_FILENAME;
+			break;
+		case 'basename':
+		case 'base':
+			$infoType = PATHINFO_BASENAME;
+			break;
+		case 'dirname':
+		case 'dir':
+			$infoType = PATHINFO_DIRNAME;
+			break;
+		default:
+			$infoType = null;
+	}
+	return pathinfo($filename, $infoType);
+}
+
+
+/**
+ * Shows pr() messages, even with debug=0
+ *
+ * @param mixed $content
+ * @param bool $collapsedAndExpandable
+ * @param array $options
+ * - class, showHtml, showFrom, jquery, returns, debug
+ * 2011-01-19 ms
+ */
+function pre($var, $collapsedAndExpandable = false, $options = array()) {
+	$defaults = array(
+		'class' => 'cake-debug',
+		'showHtml' => false, # escape < and > (or manually escape with h() prior to calling this function)
+		'showFrom' => false, # display file + line
+		'jquery' => null, # auto - use jQuery (true/false to manually decide),
+		'returns' => false, # returns(),
+		'debug' => false # showOnlyOnDebug
+	);
+	$options = array_merge($defaults, $options);
+	if ($options['debug'] && !Configure::read('debug')) {
+		return '';
+	}
+	$res = '<div class="'.$options['class'].'">';
+
+	$pre = '';
+	if ($collapsedAndExpandable) {
+		$js = 'if (this.parentNode.getElementsByTagName(\'pre\')[0].style.display==\'block\') {this.parentNode.getElementsByTagName(\'pre\')[0].style.display=\'none\'} else {this.parentNode.getElementsByTagName(\'pre\')[0].style.display=\'block\'}';
+		$jsJquery = 'jQuery(this).parent().children(\'pre\').slideToggle(\'fast\')';
+		if ($options['jquery'] === true) {
+			$js = $jsJquery;
+		} elseif ($options['jquery'] !== false) {
+			# auto
+			$js = 'if (typeof jQuery == \'undefined\') {'.$js.'} else {'.$jsJquery.'}';
+		}
+		$res .= '<span onclick="'.$js.'" style="cursor: pointer; font-weight: bold">Debug</span>';
+		if ($options['showFrom']) {
+			$calledFrom = debug_backtrace();
+			$from = '<em>' . substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1) . '</em>';
+			$from .= ' (line <em>' . $calledFrom[0]['line'] . '</em>)';
+			$res .= '<div>'.$from.'</div>';
+		}
+		$pre = ' style="display: none"';
+	}
+
+	if ($options['returns']) {
+ 		$var = returns($var);
+	} else {
+		$var = print_r($var, true);
+	}
+	$res .= '<pre' . $pre . '>' . $var . '</pre>';
+	$res .= '</div>';
+	return $res;
+}
+
+/**
+ * Checks if the string [$haystack] contains [$needle]
+ * @param string $haystack  Input string.
+ * @param string $needle Needed char or string.
+ * @return boolean
+ */
+function contains($haystack, $needle, $caseSensitive = false) {
+	return (!$caseSensitive ? stripos($haystack, $needle) : strpos($haystack, $needle))
+		!== false;
+}
+
+/**
+ * Can compare two float values
+ * @deprecated use NumberLib::isFloatEqual
+ * @link http://php.net/manual/en/language.types.float.php
+ * @return boolean
+ */
+function isFloatEqual($x, $y, $precision = 0.0000001) { 
+	trigger_error('deprecated - use NumberLib::isFloatEqual instead');
+	return ($x+$precision >= $y) && ($x-$precision <= $y); 
+} 
+
+/**
+ * Checks if the string [$haystack] starts with [$needle]
+ * @param string $haystack  Input string.
+ * @param string $needle Needed char or string.
+ * @return boolean
+ */
+function startsWith($haystack, $needle, $caseSensitive = false) {
+	if ($caseSensitive) {
+		return (mb_strpos($haystack, $needle) === 0);
+	}
+	return (mb_stripos($haystack, $needle) === 0);
+}
+
+/**
+ * Checks if the String [$haystack] ends with [$needle]
+ * @param string $haystack  Input string.
+ * @param string $needle Needed char or string
+ * @return boolean
+ */
+function endsWith($haystack, $needle, $caseSensitive = false) {
+	if ($caseSensitive) {
+		return mb_strrpos($haystack, $needle) === mb_strlen($haystack) - mb_strlen($needle);
+	}
+	return mb_strripos($haystack, $needle) === mb_strlen($haystack) - mb_strlen($needle);
+}
+
+/* deprecated? */
+function isLoggedIn() {
+	return isset($_SESSION) && !empty($_SESSION['Auth']['User']['id']);
+}
+
+/* deprecated? */
+function uid($default = null) {
+	return (isset($_SESSION) && !empty($_SESSION['Auth']['User']['id'])) ? $_SESSION['Auth']['User']['id'] :
+		$default;
+}
+
+
+
+register_shutdown_function('shutdownFunction');
+
+/**
+ * own shutdown function - also logs fatal errors (necessary until cake2.2)
+ * 2010-10-17 ms
+ */
+function shutDownFunction() {
+	$error = error_get_last();
+	if (empty($error)) {
+		return;
+	}
+	$matching = array(
+		E_ERROR =>'E_ERROR',
+		E_WARNING => 'E_WARNING',
+		E_PARSE => 'E_',
+		E_NOTICE => 'E_',
+		E_CORE_ERROR => 'E_',
+		E_COMPILE_ERROR => 'E_',
+		E_COMPILE_WARNING => 'E_',
+		E_STRICT => 'E_STRICT',
+		E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
+		E_DEPRECATED => 'E_DEPRECATED',
+	);
+	App::uses('CakeLog', 'Log');
+	
+	if (in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) {
+		$error['type_name'] = 'Fatal Error';
+		$type = 'error';
+		
+	} elseif (Configure::read('Debug.log') && isset($matching[$error['type']])) {
+		$error['type_name'] = 'Error';
+		$type = 'notice';
+	}
+	
+	if (!isset($type)) {
+		return;
+	}
+	
+	App::uses('Debugger', 'Utility');
+	$trace = Debugger::trace(array('start' => 1, 'format' => 'log', 'args'=>true));
+	$path = Debugger::trimPath($error['file']);
+	
+	$message = $error['type_name'].' '.$matching[$error['type']].' in '.$path. ' (line '.$error['line'].'): ' . $error['message'];	
+	$message .= PHP_EOL . $trace;
+	App::uses('MyErrorHandler', 'Tools.Error');
+	$message .= MyErrorHandler::traceDetails();
+	
+	CakeLog::write($type, $message);
+}
+
+/*** < PHP5.3 ***/
+
+/**
+ * until PHP5.3 is the PHP version in use
+ * //BUGGY
+ * 2010-06-21 ms
+ */
+if (!function_exists('lcfirst')) {
+	function lcfirst($str) {
+		return (string) (mb_strtolower(mb_substr($str, 0, 1)) . mb_substr($str, 1));
+	}
+}
+
+class DebugTab {
+	public static $content = array();
+	public static $groups = array();
+}
+
+function debugTab($var = false, $display = false, $key = null) {
+	if (is_string($display)) {
+		$key = $display;
+		$display = true;
+	}
+	if (Configure::read('debug') > 0) {
+		$calledFrom = debug_backtrace();
+		if (is_string($key)) {
+			if (!isset(debugTab::$groups[$key])) {
+				DebugTab::$groups[$key] = array();
+			}
+			DebugTab::$groups[$key][] = array(
+				'debug' => print_r($var, true),
+				'file' => substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1),
+				'line' => $calledFrom[0]['line'],
+				'display' => $display
+			);
+		} else {
+			DebugTab::$content[] = array(
+				'debug' => print_r($var, true),
+				'file' => substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1),
+				'line' => $calledFrom[0]['line'],
+				'display' => $display
+			);
+		}
+	}
+	return true;
+}
+
+/**
+ * pretty_json
+ * 
+ * @link https://github.com/ndejong/pretty_json/blob/master/pretty_json.php
+ * @param string $json - the original JSON string
+ * @param string $ind - the string to indent with
+ * @return string
+ */ 
+function pretty_json($json, $ind = "\t") {
+
+	// Replace any escaped \" marks so we don't get tripped up on quotemarks_counter
+	$tokens = preg_split('|([\{\}\]\[,])|', str_replace('\"', '~~PRETTY_JSON_QUOTEMARK~~', $json), -1, PREG_SPLIT_DELIM_CAPTURE);
+
+	$indent = 0;
+	$result = "";
+	$quotemarks_counter = 0;
+	$next_token_use_prefix = true;
+
+	foreach ($tokens as $token) {
+
+		$quotemarks_counter = $quotemarks_counter + (count(explode('"', $token)) - 1);
+
+		if ($token == "") {
+			continue;
+		}
+
+		if ($next_token_use_prefix) {
+			$prefix = str_repeat($ind, $indent);
+		} else {
+			$prefix = null;
+		}
+
+		// Determine if the quote marks are open or closed
+		if ($quotemarks_counter & 1) {
+			// odd - thus quotemarks open
+			$next_token_use_prefix = false;
+			$new_line = null;
+		} else {
+			// even - thus quotemarks closed
+			$next_token_use_prefix = true;
+			$new_line = "\n";
+		}
+
+		if ($token == "{" || $token == "[") {
+			$indent++;
+			$result .= $token . $new_line;
+		} elseif ($token == "}" || $token == "]") {
+			$indent--;
+
+			if ($indent >= 0) {
+				$prefix = str_repeat($ind, $indent);
+			}
+
+			if ($next_token_use_prefix) {
+				$result .= $new_line . $prefix . $token;
+			} else {
+				$result .= $new_line . $token;
+			}
+		} elseif ($token == ",") {
+				$result .= $token . $new_line;
+		} else {
+			$result .= $prefix . $token;
+		}
+	}
+	$result = str_replace('~~PRETTY_JSON_QUOTEMARK~~', '\"', $result);
+	return $result;
+}
+
+/*** > PHP5.3 ***/
+
+/**
+ * replacement since it is deprecated in PHP5.3.3 (needs testing!!!)
+ *
+ * TODO: Write cool MimeLib to do this fucking Mime stuff in a better way
+ *		 and also build a mime type WITH the charset of the file/strig like:
+ *		 text/plain; charset=utf-8
+ * @deprecated This function has been deprecated as the PECL extension Fileinfo provides the same functionality (and more) in a much cleaner way
+ **/
+if (!function_exists('mime_content_type')) {
+	function mime_content_type($file, $method = 0) {
+		if (WINDOWS) {
+			return false;
+		}
+	if ($method == 0) {
+		ob_start();
+		system('/usr/bin/file -i -b ' . realpath($file));
+		$type = ob_get_clean();
+
+		$parts = explode(';', $type);
+
+		return trim($parts[0]);
+		} elseif ($method == 1) {
+		// another method here
+		}
+	}
+}

+ 112 - 0
Lib/Error/MyErrorHandler.php

@@ -0,0 +1,112 @@
+<?php
+App::uses('ErrorHandler', 'Error');
+App::uses('CakeRequest', 'Network');
+
+class MyErrorHandler extends ErrorHandler {
+	
+	/**
+	 * override core one with the following enhancements/fixes:
+	 * - 404s log to a different domain
+	 * - IP, Referer and Browser-Infos are added for better error debugging/tracing
+	 * 2011-12-21 ms
+	 */
+	public static function handleException(Exception $exception) {
+		$config = Configure::read('Exception');
+		
+		if (!empty($config['log'])) {
+			$log = LOG_ERR;	
+			$message = sprintf("[%s] %s\n%s\n%s",
+				get_class($exception),
+				$exception->getMessage(),
+				$exception->getTraceAsString(),
+				self::traceDetails()
+			);
+			if (in_array(get_class($exception), array('MissingControllerException', 'MissingActionException', 'PrivateActionException', 'NotFoundException'))) {
+				$log = '404';
+			}
+			CakeLog::write($log, $message);
+		}
+		$renderer = $config['renderer'];
+		if ($renderer !== 'ExceptionRenderer') {
+			list($plugin, $renderer) = pluginSplit($renderer, true);
+			App::uses($renderer, $plugin . 'Error');
+		}
+		try {
+			$error = new $renderer($exception);
+			$error->render();
+		} catch (Exception $e) {
+			set_error_handler(Configure::read('Error.handler')); // Should be using configured ErrorHandler
+			Configure::write('Error.trace', false); // trace is useless here since it's internal
+			$message = sprintf("[%s] %s\n%s\n%s", // Keeping same message format
+				get_class($e),
+				$e->getMessage(),
+				$e->getTraceAsString(),
+				self::traceDetails()
+			);
+			trigger_error($message, E_USER_ERROR);
+		}
+	}
+	
+	/**
+	 * override core one with the following enhancements/fixes:
+	 * - 404s log to a different domain
+	 * - IP, Referer and Browser-Infos are added for better error debugging/tracing
+	 * 2011-12-21 ms
+	 */
+	public static function handleError($code, $description, $file = null, $line = null, $context = null) {
+		if (error_reporting() === 0) {
+			return false;
+		}
+		$errorConfig = Configure::read('Error');
+		list($error, $log) = self::mapErrorCode($code);
+
+		$debug = Configure::read('debug');
+		if ($debug) {
+			$data = array(
+				'level' => $log,
+				'code' => $code,
+				'error' => $error,
+				'description' => $description,
+				'file' => $file,
+				'line' => $line,
+				'context' => $context,
+				'start' => 2,
+				'path' => Debugger::trimPath($file)
+			);
+			return Debugger::getInstance()->outputError($data);
+		} else {
+			$message = $error . ' (' . $code . '): ' . $description . ' in [' . $file . ', line ' . $line . ']';
+			if (!empty($errorConfig['trace'])) {
+				$trace = Debugger::trace(array('start' => 1, 'format' => 'log'));
+				$message .= "\nTrace:\n" . $trace . "\n";
+				$message .= self::traceDetails();
+			}
+			return CakeLog::write($log, $message);
+		}
+	}
+	
+	/**
+	 * append some more infos to better track down the error
+	 * @return string
+	 * 2011-12-21 ms
+	 */
+	public static function traceDetails() {
+		App::uses('CommonComponent', 'Tools.Controller/Component');
+		$currentUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'n/a';
+		$refererUrl = CommonComponent::getReferer(); //Router::getRequest()->url().'
+		App::uses('CakeSession', 'Model/Datasource');  
+		$uid = CakeSession::read('Auth.User.id');
+		if (empty($uid)) {
+			$uid = (!empty($_SESSION) && !empty($_SESSION['Auth']['User']['id'])) ? $_SESSION['Auth']['User']['id'] : null;
+		}
+		
+		$data = array(
+			@CakeRequest::clientIp(),
+			$currentUrl.(!empty($refererUrl) ? (' ('.$refererUrl.')') : ''), 
+			$uid,
+			env('HTTP_USER_AGENT')
+		);
+		return implode(' - ', $data);
+	}
+	
+}

+ 6 - 0
Lib/Error/MyExceptionRenderer.php

@@ -0,0 +1,6 @@
+<?php
+App::uses('ExceptionRenderer', 'Error');
+
+class MyExceptionRenderer extends ExceptionRenderer {
+
+}

+ 158 - 0
Lib/Routing/UrlCacheManager.php

@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * This class will statically hold in memory url's indexed by a custom hash
+ * 
+ * @licence MIT
+ * @modified Mark Scherer
+ * - now easier to integrate
+ * - optimization for `pageFiles` (still stores urls with only controller/action keys in global file)
+ * - can handle legacy `prefix` urls
+ * 
+ * 2012-02-13 ms
+ */
+class UrlCacheManager {
+	
+	/**
+	 * Holds all generated urls so far by the application indexed by a custom hash
+	 *
+	 */
+	public static $cache = array();
+	
+	/**
+	 * Holds all generated urls so far by the application indexed by a custom hash
+	 *
+	 */
+	public static $cachePage = array();
+	
+	/**
+	 * Holds all generated urls so far by the application indexed by a custom hash
+	 *
+	 */
+	public static $extras = array();
+	
+	/**
+	 * type for the current set (triggered by last get)
+	 */
+	public static $type = 'cache'; 
+	
+	/**
+	 * key for current get/set
+	 */
+	public static $key = null; 
+	
+	/**
+	 * cache key for pageFiles
+	 */
+	public static $cacheKey = 'url_map'; 
+	
+	/**
+	 * cache key for pageFiles
+	 */
+	public static $cachePageKey = null; 
+
+	/**
+	 * params that will always be present and will determine the global cache if pageFiles is used
+	 */
+	public static $paramFields = array('controller', 'plugin', 'action', 'prefix');
+
+	/**
+	 * should be called in beforeRender()
+	 * 
+	 */
+	public static function init(View $View) {
+		$params = $View->request->params;
+		if (Configure::read('UrlCache.pageFiles')) {
+		  $cachePageKey = '_misc';
+			if (is_object($View)) {
+				$path = $View->request->here;
+				if ($path == '/') {
+					$path = 'uc_homepage';
+				} else {
+					$path = strtolower(Inflector::slug($path));
+				}
+				if (empty($path)) {
+					$path = 'uc_error';
+				}
+				$cachePageKey = '_' . $path;
+			}
+			self::$cachePageKey = self::$cacheKey . $cachePageKey;
+			self::$cachePage = Cache::read(self::$cachePageKey, '_cake_core_');
+		}
+		self::$cache = Cache::read(self::$cacheKey, '_cake_core_');
+		
+		# still old "prefix true/false" syntax?
+		if (Configure::read('UrlCache.verbosePrefixes')) {
+			unset(self::$paramFields[3]);
+			self::$paramFields = array_merge(self::$paramFields, (array)Configure::read('Routing.prefixes'));
+		}
+		self::$extras = array_intersect_key($params, array_combine(self::$paramFields, self::$paramFields));
+		$defaults = array();
+		foreach (self::$paramFields as $field) {
+			$defaults[$field] = '';
+		}
+		self::$extras = array_merge($defaults, self::$extras);
+	}
+	
+	/**
+	 * should be called in afterLayout()
+	 * 
+	 */
+	public static function finalize() {
+		Cache::write(self::$cacheKey, self::$cache, '_cake_core_');
+		if (Configure::read('UrlCache.pageFiles') && !empty(self::$cachePage)) {
+			Cache::write(self::$cachePageKey, self::$cachePage, '_cake_core_');
+		}
+	}
+
+
+	/**
+	 * Returns the stored url if it was already generated, false otherwise
+	 *
+	 * @param string $key 
+	 * @return mixed
+	 */
+	public static function get($url, $full) {
+		$keyUrl = $url;
+		if (is_array($keyUrl)) {
+		  $keyUrl += self::$extras;
+		  # prevent different hashs on different orders
+		  ksort($keyUrl, SORT_STRING);
+		  # prevent different hashs on different types (int/string/bool)
+			foreach ($keyUrl as $key => $val) {
+				$keyUrl[$key] = (String) $val;
+			}
+		}
+		self::$key = md5(serialize($keyUrl) . $full);
+
+		if (Configure::read('UrlCache.pageFiles')) {
+			self::$type = 'cachePage';
+			if (is_array($keyUrl)) {
+				$res = array_diff_key($keyUrl, self::$extras);
+				if (empty($res)) {
+					self::$type = 'cache';
+				}
+			}
+			if (self::$type === 'cachePage') {
+				return isset(self::$cachePage[self::$key]) ? self::$cachePage[self::$key] : false;
+			}
+		}
+		return isset(self::$cache[self::$key]) ? self::$cache[self::$key] : false;
+	}
+
+	/**
+	 * Stores a ney key in memory cache
+	 *
+	 * @param string $key 
+	 * @param mixed data to be stored
+	 * @return void
+	 */
+	public static function set($data) {
+		if (Configure::read('UrlCache.pageFiles') && self::$type === 'cachePage') {
+			self::$cachePage[self::$key] = $data;
+		} else {
+			self::$cache[self::$key] = $data;
+		}
+	}
+	
+}

File diff suppressed because it is too large
+ 1138 - 0
Lib/Utility/DatetimeLib.php


+ 294 - 0
Lib/Utility/NumberLib.php

@@ -0,0 +1,294 @@
+<?php
+App::uses('CakeNumber', 'Utility');
+//TODO: rename to TimeLib or move the time stuff to a time lib???!!!
+
+/**
+ * 2011-03-07 ms
+ */
+class NumberLib extends CakeNumber {
+	
+	protected static $_currency = 'EUR';
+	
+	protected static $_symbolRight = '€';
+	
+	protected static $_symbolLeft = null;
+	
+	protected static $_decimalPoint = ',';
+	
+	protected static $_thousandsPoint = '.';
+
+	/**
+	 * Display price (or was price if available)
+	 * Without allowNegative it will always default all non-positive values to 0
+	 * 
+	 * @param price
+	 * @param specialPrice (outranks the price)
+	 * @param options
+	 * - places
+	 * - allowNegative (defaults to false - price needs to be > 0)
+	 * 
+	 * @deprecated use currency()
+	 * @return string
+	 * 2011-07-30 ms
+	 */
+	public static function price($price, $specialPrice = null, $formatOptions = array()) {
+		if ($specialPrice !== null && $specialPrice > 0) {
+			$val = $specialPrice;
+		} elseif ($price > 0 || !empty($formatOptions['allowNegative'])) {
+			$val = $price;
+		} else {
+			if (isset($formatOptions['default'])) {
+				return $formatOptions['default'];
+			}
+			$val = max(0, $price);
+		}
+		return self::money($val, $formatOptions);
+	}
+
+	/**
+	 * Convinience method to display the default currency
+	 * 
+	 * @return string
+	 * 2011-10-05 ms
+	 */
+	public static function money($amount, $formatOptions = array()) {
+		return self::currency($amount, null, $formatOptions);
+	}
+	
+	/**
+	 * format numeric values
+	 * should not be used for currencies
+	 * 
+	 * @param float $number
+	 * @param int $places (0 = int, 1..x places after dec, -1..-x places before dec)
+	 * @param array $option : currency=true/false, ... (leave empty for no special treatment)
+	 * //TODO: automize per localeconv() ?
+	 * 2009-04-03 ms
+	 */
+	public static function format($number, $formatOptions = array()) {
+		if (!is_numeric($number)) {
+			$default = '---';
+			if (!empty($options['default'])) {
+				$default = $options['default'];
+			}
+			return $default;
+		}
+		if ($formatOptions === false) {
+			$formatOptions = array();
+		}
+		$options = array('before' => '', 'after' => '', 'places' => 2, 'thousands' => self::$_thousandsPoint, 'decimals' => self::$_decimalPoint, 'escape' => false);
+		$options = am($options, $formatOptions);
+		//$options = array;
+
+		if (!empty($options['currency'])) {
+			if (!empty(self::$_symbolRight)) { 
+				$options['after'] = ' ' . self::$_symbolRight;
+			} elseif (!empty(self::$_symbolLeft)) {
+				$options['before'] = self::$_symbolLeft . ' ';
+			}
+		} 
+		/*
+		else {
+			if (!empty($formatOptions['after'])) {
+				$options['after'] = $formatOptions['after'];
+			}
+			if (!empty($formatOptions['before'])) {
+				$options['before'] = $formatOptions['before'];
+			}
+		}
+		if (!empty($formatOptions['thousands'])) {
+			$options['thousands'] = $formatOptions['thousands'];
+		}
+		if (!empty($formatOptions['decimals'])) {
+			$options['decimals'] = $formatOptions['decimals'];
+		}
+		*/		
+		if ($options['places'] < 0) {
+			$number = round($number, $options['places']);
+		}
+		$sign = '';
+		if ($number > 0 && !empty($options['signed'])) {
+			$sign = '+';
+		}
+		return $sign . parent::format($number, $options);
+	}
+
+	/**
+	 * Correct the default for European countries
+	 * 2012-04-08 ms
+	 */
+	public static function currency($number, $currency = null, $formatOptions = array()) {
+		if ($currency === null) {
+			$currency = self::$_currency;
+		}
+		$options = array(
+			'wholeSymbol' => self::$_symbolRight, 'wholePosition' => 'after', 'negative' => '-', 'positive'=> '+', 'escape' => true
+		);
+		$options = am($options, $formatOptions);
+		
+		if (!empty($options['wholeSymbol'])) {
+			if ($options['wholePosition'] == 'after') { 
+				$options['wholeSymbol'] = ' ' . self::$_symbolRight;
+			} elseif ($options['wholePosition'] == 'before') {
+				$options['wholeSymbol'] = self::$_symbolLeft . ' ';
+			}
+		}
+		$sign = '';
+		if ($number > 0 && !empty($options['signed'])) {
+			$sign = $options['positive'];
+		}
+		return $sign . parent::currency($number, $currency, $options);
+	}
+
+	/**
+	 * Formats a number with a level of precision.
+	 *
+	 * @param float $number	A floating point number.
+	 * @param integer $precision The precision of the returned number.
+	 * @param string $decimals
+	 * @return float Formatted float.
+	 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::precision
+	 */
+	public static function precision($number, $precision = 3, $decimals = '.') {
+		$number = parent::precision($number, $precision);
+		if ($decimals != '.' && $precision > 0) {
+			$number = str_replace('.', $decimals, $number);
+		}
+		return $number;
+	}
+	
+	/**
+	 * Returns a formatted-for-humans file size.
+	 *
+	 * @param integer $size Size in bytes
+	 * @return string Human readable size
+	 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
+	 */
+	public static function toReadableSize($size, $decimals = '.') {
+		$size = parent::toReadableSize($size);
+		if ($decimals != '.') {
+			$size = str_replace('.', $decimals, $size);
+		}
+		return $size;
+	}
+
+	/**
+	 * Formats a number into a percentage string.
+	 *
+	 * @param float $number A floating point number
+	 * @param integer $precision The precision of the returned number
+	 * @param string $decimals
+	 * @return string Percentage string
+	 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toPercentage
+	 */
+	public static function toPercentage($number, $precision = 2, $decimals = '.') {
+		return self::precision($number, $precision, $decimals) . '%';
+	}
+
+	/**
+	 * get the rounded average
+	 * @param array $values: int or float values
+	 * @param int $precision
+	 * @return int $average
+	 * 2009-09-05 ms
+	 */
+	public static function average($values, $precision = 0) {
+		$average = round(array_sum($values) / count($values), $precision);
+		return $average;
+	}
+	
+	/**
+	 * @access public
+	 * @param float $number
+	 * @param float $increment
+	 * @return float $result
+	 * 2011-04-14 lb
+	 */
+	public static function roundTo($number, $increments = 1.0) {
+		$precision = self::getDecimalPlaces($increments);
+		$res = round($number, $precision);
+		if ($precision <= 0) {
+			$res = (int)$res;
+		}
+		return $res;
+	}
+
+	/**
+	 * @access public
+	 * @param float $number
+	 * @param int $increment
+	 * @return float $result
+	 * 2011-04-14 lb
+	 */
+	public static function roundUpTo($number, $increments = 1) {
+		return (ceil($number / $increments) * $increments);
+	}
+
+	/**
+	 * @access public
+	 * @param float $number
+	 * @param int $increment
+	 * @return float $result
+	 * 2011-04-14 lb
+	 */
+	public static function roundDownTo($number, $increments = 1) {
+		return (floor($number / $increments) * $increments);
+	}
+
+	/**
+	 * @access public
+	 * @param float $number
+	 * @return int $decimalPlaces
+	 * 2011-04-15 lb
+	 */
+	public static function getDecimalPlaces($number) {
+		$decimalPlaces = 0;
+		while ($number > 1 && $number != 0) {
+			$number /= 10;
+			$decimalPlaces -= 1;
+		}
+		while ($number < 1 && $number != 0) {
+			$number *= 10;
+			$decimalPlaces += 1;
+		}
+		return $decimalPlaces;
+	}
+
+	/**
+	 * Returns the English ordinal suffix (th, st, nd, etc) of a number.
+	 *
+	 *     echo 2, Num::ordinal(2);   // "2nd"
+	 *     echo 10, Num::ordinal(10); // "10th"
+	 *     echo 33, Num::ordinal(33); // "33rd"
+	 *
+	 * @param   integer  number
+	 * @return  string
+	 */
+	public static function ordinal($number) {
+		if ($number % 100 > 10 and $number % 100 < 14) {
+			return 'th';
+		}
+		switch ($number % 10) {
+			case 1:
+				return 'st';
+			case 2:
+				return 'nd';
+			case 3:
+				return 'rd';
+			default:
+				return 'th';
+		}
+	}
+
+	/**
+	 * Can compare two float values
+	 * @link http://php.net/manual/en/language.types.float.php
+	 * @return boolean
+	 */
+	public static function isFloatEqual($x, $y, $precision = 0.0000001) {
+		return ($x+$precision >= $y) && ($x-$precision <= $y);
+	}
+
+}
+
+

+ 617 - 0
Lib/Utility/TextLib.php

@@ -0,0 +1,617 @@
+<?php
+
+/**
+ * TODO: extend the core String class some day?
+ * 
+ * 2010-08-31 ms
+ */
+class TextLib {
+
+	protected $text, $lenght, $char, $letter, $space, $word, $r_word, $sen, $r_sen, $para,
+		$r_para, $beautified;
+
+
+	public function __construct($text) {
+		$this->text = $text;
+	}
+	
+	/**
+	 * @param string $stringToCheck
+	 * @param tolerance (in %: 0 ... 1)
+	 * @return boolean $success
+	 * 2011-10-13 ms
+	 */
+	public function isScreamFont($str = null, $tolerance = 0.4) {
+		if ($str === null) {
+			$str = $this->text;
+		}
+		if (empty($str)) {
+			return false;
+		}
+		
+		$res = preg_match_all('/[A-ZÄÖÜ]/u', $str, $uppercase);
+		$uppercase = array_shift($uppercase);
+		//echo returns($uppercase);
+		
+		$res = preg_match_all('/[a-zäöüß]/u', $str, $lowercase);
+		$lowercase = array_shift($lowercase);
+		//echo returns($lowercase);
+		
+		if (($countUpper = count($uppercase)) && $countUpper >= count($lowercase)) {
+			return true;
+		}		
+		//TODO: tolerance
+		
+		return false;
+	}
+	
+	/**
+	 * @param string
+	 * @param string $additionalChars
+	 * - e.g. `-()0123456789`
+	 */
+	public function isWord($str = null, $additionalChars = null) {
+		return preg_match('/^\w+$/', $str);
+	}
+
+
+/* utf8 generell stuff */
+
+	/**
+	 * Tests whether a string contains only 7-bit ASCII bytes. This is used to
+	 * determine when to use native functions or UTF-8 functions.
+	 *
+	 *     $ascii = UTF8::is_ascii($str);
+	 *
+	 * @param   string  string to check
+	 * @return  bool
+	 */
+	public function isAscii($str = null) {
+		if ($str === null) {
+			$str = $this->text;
+		}
+		return !preg_match('/[^\x00-\x7F]/S', $str);
+	}
+
+	/**
+	 * Strips out device control codes in the ASCII range.
+	 *
+	 *     $str = UTF8::strip_ascii_ctrl($str);
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public function stripAsciiCtrl($str = null) {
+		if ($str === null) {
+			$str = $this->text;
+		}
+		return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+	}
+
+	/**
+	 * Strips out all non-7bit ASCII bytes.
+	 *
+	 *     $str = UTF8::strip_non_ascii($str);
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public function stripNonAscii($str = null) {
+		if ($str === null) {
+			$str = $this->text;
+		}
+		return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+	}
+
+
+	public function convertToOrd($str = null, $separator = '-') {
+		/*
+		if (!class_exists('UnicodeLib')) {
+			App::uses('UnicodeLib', 'Tools.Lib');
+		}
+		*/
+		if ($str === null) {
+			$str = $this->text;
+		}
+		$chars = preg_split('//', $str, -1);
+		$res = array();
+		foreach ($chars as $char) {
+			//$res[] = UnicodeLib::ord($char);
+			$res[] = ord($char);
+		}
+		return implode($separator, $res);
+	}
+
+	public function convertToOrdTable($str) {
+		$res = '<table><tr>';
+		$r = array('chr'=>array(), 'ord'=>array());
+		$chars = preg_split('//', $str, -1);
+		foreach ($chars as $char) {
+			//$res[] = UnicodeLib::ord($char);
+			$r['ord'][] = ord($char);
+			$r['chr'][] = $char;
+		}
+		$res .= '<th>'.implode('</th><th>', $r['chr']).'</th>';
+		$res .= '</tr>';
+		$res .= '<tr>';
+		$res .= '<td>'.implode('</th><th>', $r['ord']).'</td>';
+		$res .= '</tr></table>';
+		return $res;
+	}
+
+
+/* other */
+
+	/**
+	 * Explode a string of given tags into an array.
+	 */
+	public function explodeTags($tags) {
+		// This regexp allows the following types of user input:
+		// this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
+		$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
+		preg_match_all($regexp, $tags, $matches);
+		$typed_tags = array_unique($matches[1]);
+
+		$tags = array();
+		foreach ($typed_tags as $tag) {
+		// If a user has escaped a term (to demonstrate that it is a group,
+		// or includes a comma or quote character), we remove the escape
+		// formatting so to save the term into the database as the user intends.
+		$tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
+		if ($tag != "") {
+			$tags[] = $tag;
+		}
+		}
+
+		return $tags;
+	}
+
+
+	/**
+	 * Implode an array of tags into a string.
+	 */
+	public function implodeTags($tags) {
+		$encoded_tags = array();
+		foreach ($tags as $tag) {
+		// Commas and quotes in tag names are special cases, so encode them.
+		if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
+			$tag = '"'. str_replace('"', '""', $tag) .'"';
+		}
+
+		$encoded_tags[] = $tag;
+		}
+		return implode(', ', $encoded_tags);
+	}
+
+
+
+		/**
+	 * Prevents [widow words](http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin)
+	 * by inserting a non-breaking space between the last two words.
+	 *
+	 *     echo Text::widont($text);
+	 *
+	 * @param   string  text to remove widows from
+	 * @return  string
+	 */
+	public function widont($str = null) {
+		if ($str === null) {
+			$str = $this->text;
+		}
+		$str = rtrim($str);
+		$space = strrpos($str, ' ');
+
+		if ($space !== FALSE) {
+			$str = substr($str, 0, $space).'&nbsp;'.substr($str, $space + 1);
+		}
+
+		return $str;
+	}
+
+
+/* text object specific */
+
+	/**
+	 * @return array(char=>amount) for empty char or int amount for specific char
+	 * 2010-08-31 ms
+	 */
+	public function occurrences($char = null, $caseSensitive = false) {
+
+		if ($caseSensitive) {
+			$str = $this->text;
+		} else {
+			if ($char !== null) {
+				$char = strtolower($char);
+			}
+			$str = strtolower($this->text);
+		}
+
+		if ($char === null) {
+			$occ = array();
+			$str = str_split($str);
+			foreach ($str as $value) {
+				if (array_key_exists($value, $occ)) {
+					$occ[$value] += 1;
+				} else {
+					$occ[$value] = 1;
+				}
+			}
+			return $occ;
+
+		} else {
+
+			$occ = 0;
+			$pos = 0;
+			do {
+				$pos = strpos($str, $char, $pos);
+				if ($pos !== false) {
+					$occ++;
+					$pos++;
+				} else {
+					break;
+				}
+			} while (true);
+			return $occ;
+		}
+	}
+
+
+	/**
+	 * @return array(char=>amount) for empty char or int amount for specific char
+	 * 2010-08-31 ms
+	 */
+	public function maxOccurrences($caseSensitive = false) {
+
+		$arr = $this->occurrences(null, $caseSensitive);
+		$max = 0;
+		$occ = array();
+
+		foreach ($arr as $key => $value) {
+			if ($value === $max) {
+				$occ[$key] = $value;
+			} elseif ($value > $max) {
+				$max = $value;
+				$occ = array($key => $value);
+			}
+		}
+		echo returns($occ);
+		return $occ;
+	}
+
+
+	public function getLength() {
+		if (!$this->lenght) {
+			$this->lenght = mb_strlen($this->text);
+		}
+		return $this->lenght;
+	}
+
+	public function getCharacter() {
+		if (!$this->char) $this->char = mb_strlen(strtr($this->text, array("\n" => '', "\r" =>
+				'')));
+		return $this->char;
+	}
+
+	public function getLetter() {
+		if (!$this->letter) {
+			$l_text = mb_strtolower($this->text);
+			for ($i = 0; $i < $this->lenght; $i++)
+				if (mb_strpos("abcdefghijklmnopqrstuvwxyzäöü", $l_text[$i]) != false) $this->
+						letter++;
+		}
+		return $this->letter;
+	}
+
+	public function getSpace() {
+		if (!$this->space) $this->space = mb_substr_count($this->text, " ") +
+				mb_substr_count($this->text, "\t");
+		return $this->space;
+	}
+
+	public function getSymbol() {
+		return $this->getCharacter() - $this->getLetter() - $this->getSpace();
+	}
+
+	//TODO: improve it to work with case insensitivity and utf8 chars like é or î
+	public function getWord($parse = false) {
+		if (!$this->word && !$this->r_word) {
+			@preg_match_all("/[A-Za-zäöüÄÖÜß\-'\\\"]+/", $this->text, $m);
+			$this->word = count($m[0]);
+			$this->r_word = $m[0];
+		}
+		return $parse ? $this->r_word : $this->word;
+	}
+
+	/**
+	 * @param options
+	 * - min_char, max_char, case_sensititive, ...
+	 * 2010-10-09 ms
+	 */
+	public function words($options = array()) {
+		if (true || !$this->xr_word) {
+			$text = str_replace(array(PHP_EOL, NL, TB), ' ', $this->text);
+
+			$pieces = explode(' ', $text);
+			$pieces = array_unique($pieces);
+
+			# strip chars like . or ,
+			foreach ($pieces as $key => $piece) {
+				if (empty($options['case_sensitive'])) {
+					$piece = mb_strtolower($piece);
+				}
+				$search = array(',', '.', ';', ':', '#', '', '(', ')', '{', '}', '[', ']', '$', '%', '"', '!', '?', '<', '>', '=', '/');
+				$search = array_merge($search, array(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
+				$piece = str_replace($search, '', $piece);
+				$piece = trim($piece);
+
+				if (empty($piece) || !empty($options['min_char']) && mb_strlen($piece) < $options['min_char'] || !empty($options['max_char']) && mb_strlen($piece) > $options['max_char']) {
+					unset($pieces[$key]);
+				} else {
+					$pieces[$key] = $piece;
+				}
+			}
+			$pieces = array_unique($pieces);
+			//$this->xr_word = $pieces;
+		}
+		return $pieces;
+	}
+
+	/**
+	 * @param options
+	 * - min_char, max_char, case_sensititive, sort ('asc', 'desc', 'length', 'alpha', false), limit...
+	 * 2010-10-09 ms
+	 */
+	public function wordCount($options = array()) {
+		if (true || !$this->rr_word) {
+			$text = str_replace(array(NL, CR, PHP_EOL, TB), ' ', $this->text);
+			$res = array();
+			$search = array('*', '+', '~', ',', '.', ';', ':', '#', '', '(', ')', '{', '}', '[', ']', '$', '%', '“', '”', '—', '"', '‘', '’', '!', '?', '<', '>', '=', '/');
+			$search = array_merge($search, array(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
+			$text = str_replace($search, ' ', $text);
+
+			$pieces = explode(' ', $text);
+
+			//TODO: use array_count_values()?
+			foreach ($pieces as $key => $piece) {
+				if (empty($options['case_sensitive'])) {
+					$piece = mb_strtolower($piece);
+				}
+				$piece = trim($piece);
+
+				if (empty($piece) || !empty($options['min_char']) && mb_strlen($piece) < $options['min_char'] || !empty($options['max_char']) && mb_strlen($piece) > $options['max_char']) {
+					unset($pieces[$key]);
+					continue;
+				}
+
+				if (!array_key_exists($piece, $res)) {
+					$res[$piece] = 0;
+				}
+				$res[$piece]++;
+			}
+			if (!empty($options['sort'])) {
+				$sort = strtolower($options['sort']);
+				if ($sort == 'asc') {
+					asort($res);
+				} elseif ($sort == 'desc') {
+					arsort($res);
+				} elseif ($sort == 'length') {
+					//TODO:
+					//uasort($res, $callback);
+
+				} elseif ($sort == 'alpha') {
+					ksort($res);
+				}
+			}
+			if (!empty($options['limit'])) {
+				$res = array_slice($res, 0, (int)$options['limit'], true);
+			}
+
+			//$this->rr_word = $res;
+		}
+		return $res; // $this->rr_word;
+	}
+
+
+	public function getSentence($parse = false) {
+		if (!$this->sen && !$this->r_sen) {
+			@preg_match_all("/[^:|;|\!|\.]+(:|;|\!|\.| )+/", $this->text, $m);
+			$this->sen = count($m[0]);
+			foreach ($m[0] as $s) $this->r_sen[] = strtr(trim($s), array("\n" => '', "\r" =>
+					''));
+		}
+		return $parse ? $this->r_sen : $this->sen;
+	}
+
+	public function getParagraph($parse = false) {
+		if (!$this->para && !$this->r_para) {
+			@preg_match_all("/[^\n]+?(:|;|\!|\.| )+\n/s", strtr($this->text, array("\r" =>
+				'')) . "\n", $m);
+			$this->para = count($m[0]);
+			foreach ($m[0] as $p) $this->r_para[] = trim($p);
+		}
+		return $parse ? $this->r_para : $this->para;
+	}
+
+	public function beautify($wordwrap = false) {
+		if (!$this->beautified) {
+			$this->beautified = @preg_replace(array("/ {1,}/", "/\. {1,}\./", "/\. *(?!\.)/",
+				"/(,|:|;|\!|\)) */", "/(,|:|;|\!|\)|\.) *\r\n/", "/(\r\n) {3,}/"), array(" ", ".",
+				". ", "$1 ", "$1\r\n", "\r\n\r\n"), $this->text);
+		}
+		return $wordwrap ? wordwrap($this->beautified, $wordwrap) : $this->beautified;
+	}
+
+
+	/**
+	 * High ASCII to Entities
+	 *
+	 * Converts High ascii text and MS Word special characters to character entities
+	 *
+	 * @access	public
+	 * @param	string
+	 * @return	string
+	 */
+	public function ascii_to_entities($str) {
+		$count = 1;
+		$out = '';
+		$temp = array();
+
+		for ($i = 0, $s = strlen($str); $i < $s; $i++) {
+			$ordinal = ord($str[$i]);
+
+			if ($ordinal < 128) {
+				/*
+				If the $temp array has a value but we have moved on, then it seems only
+				fair that we output that entity and restart $temp before continuing. -Paul
+				*/
+				if (count($temp) == 1) {
+					$out .= '&#' . array_shift($temp) . ';';
+					$count = 1;
+				}
+
+				$out .= $str[$i];
+			} else {
+				if (count($temp) == 0) {
+					$count = ($ordinal < 224) ? 2 : 3;
+				}
+
+				$temp[] = $ordinal;
+
+				if (count($temp) == $count) {
+					$number = ($count == 3) ? (($temp['0'] % 16) * 4096) + (($temp['1'] % 64) * 64) + ($temp['2'] %
+						64) : (($temp['0'] % 32) * 64) + ($temp['1'] % 64);
+
+					$out .= '&#' . $number . ';';
+					$count = 1;
+					$temp = array();
+				}
+			}
+		}
+		return $out;
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Entities to ASCII
+	 *
+	 * Converts character entities back to ASCII
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	bool
+	 * @return	string
+	 */
+	public function entities_to_ascii($str, $all = true) {
+		if (preg_match_all('/\&#(\d+)\;/', $str, $matches)) {
+			for ($i = 0, $s = count($matches['0']); $i < $s; $i++) {
+				$digits = $matches['1'][$i];
+
+				$out = '';
+
+				if ($digits < 128) {
+					$out .= chr($digits);
+
+				} elseif ($digits < 2048) {
+					$out .= chr(192 + (($digits - ($digits % 64)) / 64));
+					$out .= chr(128 + ($digits % 64));
+				} else {
+					$out .= chr(224 + (($digits - ($digits % 4096)) / 4096));
+					$out .= chr(128 + ((($digits % 4096) - ($digits % 64)) / 64));
+					$out .= chr(128 + ($digits % 64));
+				}
+
+				$str = str_replace($matches['0'][$i], $out, $str);
+			}
+		}
+
+		if ($all) {
+			$str = str_replace(array("&amp;", "&lt;", "&gt;", "&quot;", "&apos;", "&#45;"),
+				array("&", "<", ">", "\"", "'", "-"), $str);
+		}
+
+		return $str;
+	}
+
+
+	/**
+	 * Reduce Double Slashes
+	 *
+	 * Converts double slashes in a string to a single slash,
+	 * except those found in http://
+	 *
+	 * http://www.some-site.com//index.php
+	 *
+	 * becomes:
+	 *
+	 * http://www.some-site.com/index.php
+	 *
+	 * @access	public
+	 * @param	string
+	 * @return	string
+	 */
+	public function reduce_double_slashes($str) {
+		return preg_replace("#([^:])//+#", "\\1/", $str);
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Reduce Multiples
+	 *
+	 * Reduces multiple instances of a particular character.  Example:
+	 *
+	 * Fred, Bill,, Joe, Jimmy
+	 *
+	 * becomes:
+	 *
+	 * Fred, Bill, Joe, Jimmy
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	string	the character you wish to reduce
+	 * @param	bool	TRUE/FALSE - whether to trim the character from the beginning/end
+	 * @return	string
+	 */
+	public function reduce_multiples($str, $character = ',', $trim = false) {
+		$str = preg_replace('#' . preg_quote($character, '#') . '{2,}#', $character, $str);
+
+		if ($trim === true) {
+			$str = trim($str, $character);
+		}
+
+		return $str;
+	}
+
+}
+
+
+/*
+
+//explode string, return word and number of repeation
+$r = explode('[spilit]', $value);
+
+//regex
+if ( preg_match('/([a-z]+)/', $r[0])) {
+
+preg_match_all( '/'. $r[0] .'/', $this -> checkString[$arrays], $match);
+} else {
+
+preg_match_all( '/\\'. $r[0] .'/', $this -> checkString[$arrays], $match);
+}
+
+//count chars
+if ( count($match[0]) <= $r[1]) {
+
+$this -> _is_valid[$arrays][$valData] = true;
+} else {
+
+$this -> _is_valid[$arrays][$valData] = false;
+
+//set errors array
+$this -> error[$arrays][] = $r[0] . $this -> error_max_time_char;
+}
+
+*/
+
+

+ 165 - 0
Lib/Utility/TimeLib.php

@@ -0,0 +1,165 @@
+<?php
+App::uses('CakeTime', 'Utility');
+
+/**
+ * 2012-04-06 ms
+ */
+class TimeLib extends CakeTime {
+
+	/**
+	 * hours, minutes
+	 * e.g. 9.3 => 9.5
+	 * 2010-11-03 ms
+	 */
+	public static function standardToDecimalTime($value) {
+		$base = (int)$value;
+		$tmp = $value-$base;
+
+		$tmp *= 100;
+		$tmp *= 1/60;
+
+		$value = $base+$tmp;
+		return $value;
+	}
+
+	/**
+	 * hours, minutes
+	 * e.g. 9.5 => 9.3
+	 * with pad=2: 9.30
+	 * 2010-11-03 ms
+	 */
+	public static function decimalToStandardTime($value, $pad = null, $decPoint = '.') {
+		$base = (int)$value;
+		$tmp = $value-$base;
+
+		$tmp /= 1/60;
+		$tmp /= 100;
+
+		$value = $base+$tmp;
+		if ($pad === null) {
+			return $value;
+		}
+		return number_format($value, $pad, $decPoint, '');
+	}
+
+	/**
+	 * parse 2,5 - 2.5 2:30 2:31:58 or even 2011-11-12 10:10:10
+	 * now supports negative values like -2,5 -2,5 -2:30 -:30 or -4
+	 * @param string
+	 * @return int: seconds
+	 * 2011-03-06 ms
+	 */
+	public static function parseTime($duration, $allowed = array(':', '.', ',')) {
+		if (empty($duration)) {
+			return 0;
+		}
+		$parts = explode(' ', $duration);
+		$duration = array_pop($parts);
+
+		if (strpos($duration, '.') !== false && in_array('.', $allowed)) {
+			$duration = self::decimalToStandardTime($duration, 2, ':');
+		} elseif (strpos($duration, ',') !== false && in_array(',', $allowed)) {
+			$duration = str_replace(',', '.', $duration);
+			$duration = self::decimalToStandardTime($duration, 2, ':');
+		}
+
+		# now there is only the time schema left...
+		$pieces = explode(':', $duration, 3);
+		$res = 0;
+		$hours = abs((int)$pieces[0])*HOUR;
+		//echo pre($hours);
+		$isNegative = (strpos((string)$pieces[0], '-') !== false ? true : false);
+
+		if (count($pieces) === 3) {
+			$res += $hours + ((int)$pieces[1])*MINUTE + ((int)$pieces[2])*SECOND;
+		} elseif (count($pieces) === 2) {
+			$res += $hours + ((int)$pieces[1])*MINUTE;
+		} else {
+			$res += $hours;
+		}
+		if ($isNegative) {
+			return -$res;
+		}
+		return $res;
+	}
+
+	/**
+	 * parse 2022-11-12 or 12.11.2022 or even 12.11.22
+	 * @param string $date
+	 * @return int: seconds
+	 * 2011-03-09 ms
+	 */
+	public static function parseDate($date, $allowed = array('.', '-')) {
+		$datePieces = explode(' ', $date, 2);
+		$date = array_shift($datePieces);
+
+		if (strpos($date, '.') !== false) {
+			$pieces = explode('.', $date);
+			$year = $pieces[2];
+			if (strlen($year) === 2) {
+				if ($year < 50) {
+					$year = '20'.$year;
+				} else {
+					$year = '19'.$year;
+				}
+			}
+			$date = mktime(0, 0, 0, $pieces[1], $pieces[0], $year);
+
+		} elseif (strpos($date, '-') !== false) {
+			//$pieces = explode('-', $date);
+			$date = strtotime($date);
+		} else {
+			return 0;
+		}
+		return $date;
+	}
+
+
+	/**
+	 * return strings like 2:30 (later //TODO: or 2:33:99) from seconds etc
+	 * @param int: seconds
+	 * @return string
+	 * 2011-03-06 ms
+	 */
+	public static function buildTime($duration, $mode = 'H:MM') {
+		if ($duration < 0) {
+			$duration = abs($duration);
+			$isNegative = true;
+		}
+
+		$minutes = $duration%HOUR;
+		$hours = ($duration - $minutes)/HOUR;
+		$res = (int)$hours.':'.str_pad(intval($minutes/MINUTE), 2, '0', STR_PAD_LEFT);
+		if (strpos($mode, 'SS') !== false) {
+			//TODO
+		}
+		if (!empty($isNegative)) {
+			$res = '-'.$res;
+		}
+		return $res;
+	}
+
+	/**
+	 * return strings like 2:33:99 from seconds etc
+	 * @param int: seconds
+	 * @return string
+	 * 2011-03-09 ms
+	 */
+	public static function buildDefaultTime($duration) {
+		$minutes = $duration%HOUR;
+		$duration = $duration - $minutes;
+		$hours = ($duration)/HOUR;
+
+		//$duration = $minutes*MINUTE;
+
+		$seconds = $minutes%MINUTE;
+		return self::pad($hours).':'.self::pad($minutes/MINUTE).':'.self::pad($seconds/SECOND);
+	}
+
+	public static function pad($value, $length = 2) {
+		return str_pad(intval($value), $length, '0', STR_PAD_LEFT);
+	}
+	
+}
+
+

+ 5 - 0
Model/ToolsAppModel.php

@@ -0,0 +1,5 @@
+<?php
+
+class ToolsAppModel extends AppModel {
+
+}

+ 262 - 0
Test/Case/Helper/DatetimeHelperTest.php

@@ -0,0 +1,262 @@
+<?php
+
+App::import('Helper', 'Tools.Datetime');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+App::uses('View', 'View');
+
+/**
+ * Datetime Test Case
+ *
+ * @package       cake.tests
+ * @subpackage    cake.tests.cases.libs.view.helpers
+ */
+class DatetimeHelperTest extends MyCakeTestCase {
+
+	public function setUp() {
+		$this->Datetime = new DatetimeHelper(new View(null));
+	}
+
+	/**
+	 * test user age
+	 *
+	 * 2012-04-10 ms
+	 */
+	public function testUserAge() {
+		$res = $this->Datetime->userAge('2010-01-01');
+		$this->assertTrue($res >= 2);
+	}
+
+
+	/**
+	 * test cweek
+	 *
+	 * 2009-03-11 ms
+	 */
+	public function testLengthOfTime() {
+
+		$this->assertEquals('6 '.__('Minutes').', 40 '.__('Seconds'), $this->Datetime->lengthOfTime(400));
+
+		$res = $this->Datetime->lengthOfTime(400, 'i');
+		pr($res);
+		$this->assertEquals('6 '.__('Minutes'), $res);
+		
+		$res = $this->Datetime->lengthOfTime(6*DAY);
+		pr($res);
+		$this->assertEquals('6 '.__('Days').', 0 '.__('Hours'), $res);
+
+		#TODO: more
+
+
+	}
+
+	public function testRelLengthOfTime() {
+		echo $this->_header(__FUNCTION__);
+		
+		$res = $this->Datetime->relLengthOfTime(date(FORMAT_DB_DATETIME, time()-3600));
+		pr($res);
+		$this->assertTrue(!empty($res));
+
+		$res = $this->Datetime->relLengthOfTime(date(FORMAT_DB_DATETIME, time()-4*DAY-5*HOUR), null, array('plural'=>'n'));
+		pr($res);
+		//$this->assertEquals($res, 'Vor 4 Tagen, 5 '.__('Hours'));
+		$this->assertEquals(__('%s ago', '4 '.__('Days').', '.'5 '.__('Hours')), $res);
+		
+		$res = $this->Datetime->relLengthOfTime(date(FORMAT_DB_DATETIME, time()+4*DAY+5*HOUR), null, array('plural'=>'n'));
+		pr($res);
+		$this->assertEquals(__('In %s', '4 '.__('Days').', '.'5 '.__('Hours')), $res);
+		
+		$res = $this->Datetime->relLengthOfTime(date(FORMAT_DB_DATETIME, time()), null, array('plural'=>'n'));
+		pr($res);
+		$this->assertEquals($res, __('justNow'));
+	}
+	
+	// Cake internal function...
+	public function testTimeAgoInWords() {
+		echo $this->_header(__FUNCTION__);
+		
+		$res = $this->Datetime->timeAgoInWords(date(FORMAT_DB_DATETIME, time()-4*DAY-5*HOUR));
+		pr($res);
+	}
+ 
+
+	public function testIsInRange() {
+		echo $this->_header(__FUNCTION__);
+		
+		$day = date(FORMAT_DB_DATETIME, time()+10*DAY);
+		
+		$this->assertTrue($this->Datetime->isInRange($day, 11*DAY));
+		$this->assertTrue($this->Datetime->isInRange($day, 10*DAY));
+		$this->assertFalse($this->Datetime->isInRange($day, 9*DAY));
+		
+		$day = date(FORMAT_DB_DATETIME, time()-78*DAY);
+		$this->assertTrue($this->Datetime->isInRange($day, 79*DAY));
+		$this->assertTrue($this->Datetime->isInRange($day, 78*DAY));
+		$this->assertFalse($this->Datetime->isInRange($day, 77*DAY));
+
+		#TODO: more
+
+
+	}
+
+
+/**
+ * test cweek
+ *
+ * @access public
+ * @return void
+ * 2009-03-11 ms
+ */
+	public function testCweek() {
+
+		$year = 2008;
+		$month = 12;
+		$day = 29;
+		$date = mktime(0,0,0,$month,$day,$year);
+		$this->assertEquals('01/'.$year, $this->Datetime->cweek($year.'-'.$month.'-'.$day));
+
+		$year = 2009;
+		$month = 1;
+		$day = 1;
+		$date = mktime(0,0,0,$month,$day,$year);
+		$this->assertEquals('01/'.$year, $this->Datetime->cweek($year.'-'.$month.'-'.$day));
+
+		$year = 2009;
+		$month = 1;
+		$day = 9;
+		$date = mktime(0,0,0,$month,$day,$year);
+		$this->assertEquals('02/'.$year, $this->Datetime->cweek($year.'-'.$month.'-'.$day.' 00:00:00'));
+
+		$year = 2009;
+		$month = 12;
+		$day = 26;
+		$date = mktime(0,0,0,$month,$day,$year);
+		$this->assertEquals('52/'.$year, $this->Datetime->cweek($year.'-'.$month.'-'.$day));
+
+
+	}
+
+
+/**
+ * test age
+ *
+ * @access public
+ * @return void
+ * 2009-03-11 ms
+ */
+	public function testAge() {
+		list($year,$month,$day) = explode('-',date('Y-m-d'));
+		$this->assertEquals('0', $this->Datetime->age($year.'-'.$month.'-'.$day, null));
+
+		list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('-10 years')));
+		$this->assertEquals('10', $this->Datetime->age($year.'-'.$month.'-'.$day, null));
+
+		list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('-10 years +1 day')));
+		$this->assertEquals('9', $this->Datetime->age($year.'-'.$month.'-'.$day, null));
+
+		list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('-10 years -1 day')));
+		$this->assertEquals('10', $this->Datetime->age($year.'-'.$month.'-'.$day, null));
+
+		# jahresübertritt
+		list($year,$month,$day) = explode('-','2005-12-01');
+		list($yearE,$monthE,$dayE) = explode('-','2008-02-29');
+		$this->assertEquals('2', $this->Datetime->age($year.'-'.$month.'-'.$day,$yearE.'-'.$monthE.'-'.$dayE));
+
+		list($year,$month,$day) = explode('-','2002-01-29');
+		list($yearE,$monthE,$dayE) = explode('-','2008-12-02');
+		$this->assertEquals('6', $this->Datetime->age($year.'-'.$month.'-'.$day, $yearE.'-'.$monthE.'-'.$dayE));
+
+		# schaltjahr
+		list($year,$month,$day) = explode('-','2005-02-29');
+		list($yearE,$monthE,$dayE) = explode('-','2008-03-01');
+		$this->assertEquals('3', $this->Datetime->age($year.'-'.$month.'-'.$day, $yearE.'-'.$monthE.'-'.$dayE));
+
+		list($year,$month,$day) = explode('-','2005-03-01');
+		list($yearE,$monthE,$dayE) = explode('-','2008-02-29');
+		$this->assertEquals('2', $this->Datetime->age($year.'-'.$month.'-'.$day, $yearE.'-'.$monthE.'-'.$dayE));
+
+		#zukunft
+		list($yearE,$monthE,$dayE) = explode('-',date('Y-m-d',strtotime('+10 years -1 day')));
+		$this->assertEquals('9', $this->Datetime->age(null, $yearE.'-'.$monthE.'-'.$dayE));
+
+		list($yearE,$monthE,$dayE) = explode('-',date('Y-m-d',strtotime('+10 years +1 day')));
+		$this->assertEquals('10', $this->Datetime->age(null, $yearE.'-'.$monthE.'-'.$dayE));
+		$birthday = '1985-04-08';
+
+		$relativeDate = '2010-04-07';
+		$this->assertEquals('24', $this->Datetime->age($birthday, $relativeDate));
+
+		$relativeDate = '2010-04-08';
+		$this->assertEquals('25', $this->Datetime->age($birthday, $relativeDate));
+
+		$relativeDate = '2010-04-09';
+		$this->assertEquals('25', $this->Datetime->age($birthday, $relativeDate));
+
+	}
+
+/**
+ * test IsInTheFuture
+ *
+ * @access public
+ * @return void
+ * 2010-02-18 ms
+ */
+
+	public function testIsInTheFuture() {
+		$testDate = date(FORMAT_DB_DATE, time()+2*DAY);
+		$is = $this->Datetime->isInTheFuture($testDate);
+		$this->assertTrue($is);
+	
+		$testDate = date(FORMAT_DB_DATETIME, time()-1*MINUTE);
+		$is = $this->Datetime->isInTheFuture($testDate);
+		$this->assertFalse($is);
+	}
+	
+/**
+ * test IsNotTodayAndInTheFuture
+ *
+ * @access public
+ * @return void
+ * 2010-02-18 ms
+ */	
+	
+	public function testIsNotTodayAndInTheFuture() {
+		$testDate = date(FORMAT_DB_DATE, time());
+		$is = $this->Datetime->isNotTodayAndInTheFuture($testDate);
+		$this->assertFalse($is);
+		
+		$testDate = date(FORMAT_DB_DATETIME, time()+1*DAY);
+		$is = $this->Datetime->isNotTodayAndInTheFuture($testDate);
+		$this->assertTrue($is);
+	} 
+	
+/**
+ * test IsDayAfterTomorrow
+ *
+ * @access public
+ * @return void
+ * 2010-02-18 ms
+ */		
+	
+	public function testIsDayAfterTomorrow() {
+		$testDate = date(FORMAT_DB_DATE, time()+2*DAY);
+		$is = $this->Datetime->isDayAfterTomorrow($testDate);
+		$this->assertTrue($is);
+		 
+		$testDate = date(FORMAT_DB_DATETIME, time()-1*MINUTE);
+		$is = $this->Datetime->isDayAfterTomorrow($testDate);
+		$this->assertFalse($is);
+	}
+	
+	
+
+
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+	public function tearDown() {
+		unset($this->Datetime);
+	}
+}

+ 15 - 15
Test/Case/Helper/NumericHelperTest.php

@@ -22,7 +22,9 @@ class NumericHelperTest extends MyCakeTestCase {
 
 
 /**
- * test cweek
+ * test format
+ * 
+ * TODO: move to NumberLib test?
  *
  * @access public
  * @return void
@@ -33,51 +35,49 @@ class NumericHelperTest extends MyCakeTestCase {
 		$expected = '22,00';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('22.30', 1);
+		$is = $this->Numeric->format('22.30', array('places'=>1));
 		$expected = '22,3';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('22.30', -1);
+		$is = $this->Numeric->format('22.30', array('places'=>-1));
 		$expected = '20';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('22.30', -2);
+		$is = $this->Numeric->format('22.30', array('places'=>-2));
 		$expected = '0';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('22.30', 3);
+		$is = $this->Numeric->format('22.30', array('places'=>3));
 		$expected = '22,300';
 		$this->assertEquals($expected, $is);
 
-		$is = $this->Numeric->format('abc', 2);
+		$is = $this->Numeric->format('abc', array('places'=>2));
 		$expected = '---';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('12.2', 'a');
+		/*
+		$is = $this->Numeric->format('12.2', array('places'=>'a'));
 		$expected = '12,20';
 		$this->assertEquals($expected, $is);
+		*/
 		
-		$is = $this->Numeric->format('22.3', 2, array('before'=>'EUR '));
+		$is = $this->Numeric->format('22.3', array('places'=>2, 'before'=>'EUR '));
 		$expected = 'EUR 22,30';
 		$this->assertEquals($expected, $is);
 
-		$is = $this->Numeric->format('22.3', 2, array('after'=>' EUR'));
+		$is = $this->Numeric->format('22.3', array('places'=>2, 'after'=>' EUR'));
 		$expected = '22,30 EUR';
 		$this->assertEquals($expected, $is);
 		
-		$is = $this->Numeric->format('22.3', 2, array('after'=>'x','before'=>'v'));
+		$is = $this->Numeric->format('22.3', array('places'=>2, 'after'=>'x','before'=>'v'));
 		$expected = 'v22,30x';
 		$this->assertEquals($expected, $is);
 		
 		#TODO: more		
-		
-		
-		
+				
 	}
 
 
-
-
 /**
  * tearDown method
  *

+ 196 - 0
Test/Case/Helper/TextExtHelperTest.php

@@ -0,0 +1,196 @@
+<?php
+
+App::import('Helper', 'Tools.TextExt');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+class TextExtHelperTest extends MyCakeTestCase {
+
+	public $Text;
+	
+	public function setUp() {
+		$this->Text = new TextExtHelper(new View(null));
+	}
+	
+	
+	public function testObject() {
+		$this->assertTrue(is_a($this->Text, 'TextExtHelper'));
+	}
+	
+	
+	public function testAutoLinkEmails() {
+		$text = 'Text with a url euro@euro.de and more';
+		$expected = 'Text with a url <a href="mailto:euro@euro.de">euro@euro.de</a> and more';
+		$result = $this->Text->autoLinkEmails($text, array());
+		$this->assertEquals($result, $expected);	
+		
+		$text = 'Text with a url euro@euro.de and more';
+		$expected = 'Text with a url <script language=javascript><!--
+	document.write(\'<a\'+ \' hre\'+ \'f="ma\'+ \'ilto:\'+ \'eu\'+ \'ro@\'+ \'euro\'+ \'.d\'+ \'e"\'+ \' t\'+ \'itle\'+ \'="\'+ \'Für \'+ \'den\'+ \' G\'+ \'ebra\'+ \'uch\'+ \' eines\'+ \' exte\'+ \'rn\'+ \'en E-\'+ \'Mail-P\'+ \'rogra\'+ \'mms"\'+ \' cl\'+ \'ass="e\'+ \'mail"\'+ \'>\');
+	//--></script>
+		e&#117;&#x72;o<span>@</span>e&#x75;&#x72;&#111;&#x2e;&#x64;&#x65;
+
+	<script language=javascript><!--
+	document.write(\'</a>\');
+	//--></script> and more\'';
+		$result = $this->Text->autoLinkEmails($text, array('obfuscate'=>true));
+		pr($text);
+		echo $result;
+		pr(h($result));
+		$this->assertNotEqual($result, $text);	
+	
+	}
+
+	public function testAutoLinkEmailsWithHtmlOrDangerousStrings() {
+		$text = 'Text <i>with a email</i> euro@euro.de and more';
+		$expected = 'Text &lt;i&gt;with a email&lt;/i&gt; <a href="mailto:euro@euro.de">euro@euro.de</a> and more';
+		$result = $this->Text->autoLinkEmails($text);
+		//pr(h($text));
+		$this->assertEquals($result, $expected);
+	}	
+	
+		
+	public function testStripProtocol() {
+		$urls = array(
+			'http://www.cakephp.org/bla/bla' => 'www.cakephp.org/bla/bla',
+			'www.cakephp.org' => 'www.cakephp.org'
+		);
+		
+		foreach ($urls as $url => $expected) {
+			$is = $this->Text->stripProtocol($url);
+			$this->assertEquals($is, $expected);
+		}
+	}
+	
+	public function testAutoLinkUrls() {
+		$texts = array(
+			'text http://www.cakephp.org/bla/bla some more text' => '',
+			'This is a test text with URL http://www.cakephp.org\tand some more text' => 'This is a test text with URL http://www.cakephp.org\tand some more text'
+		);
+		
+		foreach ($texts as $text => $expected) {
+			//$is = $this->Text->stripProtocol($url);
+			//$this->assertEquals($is, $expected);
+		}
+		
+		
+		$text = 'Text with a url www.cot.ag/cuIb2Q/eruierieriu-erjekr and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eruierieriu-erjekr">www.cot.ag/c...</a> and more';
+		$result = $this->Text->autoLinkUrls($text, array('maxLength'=>12));
+		$this->assertEquals($result, $expected);
+		
+		$text = 'Text with a url http://www.cot.ag/cuIb2Q/eru and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eru">www.cot.ag/cuIb2Q/eru</a> and more';
+		$result = $this->Text->autoLinkUrls($text, array('stripProtocol'=>true));
+		$this->assertEquals($result, $expected);
+		
+		$text = 'Text with a url http://www.cot.ag/cuIb2Q/eruierieriu-erjekr and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eruierieriu-erjekr">http://www.cot.ag/cuIb2Q/eruierieriu-erjekr</a> and more';
+		$result = $this->Text->autoLinkUrls($text, array('stripProtocol'=>false, 'maxLength'=>0));
+		$this->assertEquals($result, $expected);
+	
+		
+		$text = 'Text with a url www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer-werwerwe-werwerwer-werwerdfrffsd-werwer and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer-werwerwe-werwerwer-werwerdfrffsd-werwer">www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-w...</a> and more';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertEquals($result, $expected);
+			
+	}
+	
+	public function testAutoLinkUrlsWithEscapeFalse() {
+		$text = 'Text with a url www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer">www.cot.ag/cuIb2Q/er...</a> and more';
+		$result = $this->Text->autoLinkUrls($text, array('maxLength'=>20), array('escape'=>false));
+		$this->assertEquals($result, $expected);
+		
+		# not yet working
+		/*
+		$text = 'Text with a url www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q/eruierieriu-erjekrwerweuwrweir-werwer">www.cot.ag/cuIb2Q/er&hellip;</a> and more';
+		$result = $this->Text->autoLinkUrls($text, array('maxLength'=>20), array('escape'=>false, 'html'=>true));
+		$this->assertEquals($result, $expected);
+		*/
+		
+		$text = '<h3>google<h3> a http://maps.google.de/maps?f=d&source=s_d&saddr=m%C3%BCnchen&daddr=Berlin&hl=de&geocode=FXaL3gIdGrOwACnZX4yj-XWeRzF9mLF9SrgMAQ%3BFY1xIQMdSKTMACkBWQM_N06oRzFwO15bRiAhBA&mra=ls&sll=52.532932,13.41156&sspn=0.77021,2.348328&g=berlin&ie=UTF8&t=h&z=6 link';
+		$expected = '&lt;h3&gt;google&lt;h3&gt; a <a href="http://maps.google.de/maps?f=d&amp;source=s_d&amp;saddr=m%C3%BCnchen&amp;daddr=Berlin&amp;hl=de&amp;geocode=FXaL3gIdGrOwACnZX4yj-XWeRzF9mLF9SrgMAQ%3BFY1xIQMdSKTMACkBWQM_N06oRzFwO15bRiAhBA&amp;mra=ls&amp;sll=52.532932,13.41156&amp;sspn=0.77021,2.348328&amp;g=berlin&amp;ie=UTF8&amp;t=h&amp;z=6">maps.google.de/maps?f=d&amp;source...</a> link';
+		$result = $this->Text->autoLinkUrls($text, array('maxLength'=>30));
+		$this->assertEquals($result, $expected);
+		
+	}
+	
+	public function testAutoLinkUrlsWithHtmlOrDangerousStrings() {
+		$text = 'Text <i>with a url</i> www.cot.ag?id=2&sub=3 and more';
+		$expected = 'Text &lt;i&gt;with a url&lt;/i&gt; <a href="http://www.cot.ag?id=2&amp;sub=3">www.cot.ag?id=2&amp;sub=3</a> and more';
+		$result = $this->Text->autoLinkUrls($text);
+		//pr(h($text));
+		$this->assertEquals($result, $expected);
+	}
+	
+	/**
+	 * combined (emails + urls)
+	 * 2011-04-03 ms
+	 */
+	public function testAutoLink() {
+		$text = 'Text <i>with a url</i> www.cot.ag?id=2&sub=3 and some email@domain.com more';
+		$expected = 'Text &lt;i&gt;with a url&lt;/i&gt; <a href="http://www.cot.ag?id=2&amp;sub=3">www.cot.ag?id=2&amp;sub=3</a> and some <a href="mailto:email@domain.com">email@domain.com</a> more';
+		$result = $this->Text->autoLink($text);
+		pr(h($text));
+		$this->assertEquals($result, $expected);
+	}
+	
+/* from cake */
+
+	/**
+	 * test invalid email addresses.
+	 *
+	 * @return void
+	 */
+	public function testAutoLinkEmailInvalid() {
+		$result = $this->Text->autoLinkEmails('this is a myaddress@gmx-de test');
+		$expected = 'this is a myaddress@gmx-de test';
+		$this->assertEquals($expected, $result);
+		
+		$result = $this->Text->autoLink('this is a myaddress@gmx-de test');
+		$expected = 'this is a myaddress@gmx-de test';
+		$this->assertEquals($expected, $result);
+	}
+
+
+	
+	public function testAutoLinkUrlsWithCakeTests() {
+		$text = 'This is a test text';
+		$expected = 'This is a test text';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertEquals($result, $expected);
+
+		$text = 'This is a test that includes (www.cakephp.org)';
+		$expected = 'This is a test that includes (<a href="http://www.cakephp.org">www.cakephp.org</a>)';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertEquals($result, $expected);
+
+		$text = 'Text with a partial www.cakephp.org URL';
+		$expected = 'Text with a partial <a href="http://www.cakephp.org"\s*>www.cakephp.org</a> URL';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertRegExp('#^' . $expected . '$#', $result);
+
+		$text = 'Text with a partial www.cakephp.org URL';
+		$expected = 'Text with a partial <a href="http://www.cakephp.org" \s*class="link">www.cakephp.org</a> URL';
+		$result = $this->Text->autoLinkUrls($text, array(), array('class' => 'link'));
+		$this->assertRegExp('#^' . $expected . '$#', $result);
+
+		$text = 'Text with a partial WWW.cakephp.org URL';
+		$expected = 'Text with a partial <a href="http://WWW.cakephp.org"\s*>WWW.cakephp.org</a> URL';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertRegExp('#^' . $expected . '$#', $result);
+
+		$text = 'Text with a partial WWW.cakephp.org &copy; URL';
+		$expected = 'Text with a partial <a href="http://WWW.cakephp.org"\s*>WWW.cakephp.org</a> &copy; URL';
+		$result = $this->Text->autoLinkUrls($text, array('escape' => false), array('escape' => false));
+		$this->assertRegExp('#^' . $expected . '$#', $result);
+
+		$text = 'Text with a url www.cot.ag/cuIb2Q and more';
+		$expected = 'Text with a url <a href="http://www.cot.ag/cuIb2Q">www.cot.ag/cuIb2Q</a> and more';
+		$result = $this->Text->autoLinkUrls($text);
+		$this->assertEquals($result, $expected);
+	}
+	
+}

+ 0 - 267
Test/Case/Lib/NumberLibTest.php

@@ -1,267 +0,0 @@
-<?php
-
-App::uses('NumberLib', 'Tools.Lib');
-App::uses('MyCakeTestCase', 'Tools.Lib');
-
-class NumberLibTest extends MyCakeTestCase {
-
-	public $NumberLib = null;
-
-	public function startTest() {
-		//$this->NumberLib = new NumberLib();
-	}
-
-	/**
-	 *2011-04-14 lb
-	 */
-	public function testRoundTo() {
-		//increment = 10
-		$values = array(
-			'22' => 20,
-			'15' => 20,
-			'3.4' => 0,
-			'6' => 10,
-			'-3.12' => 0,
-			'-10' => -10
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::roundTo($was, 10);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-		//increment = 0.1
-		$values2 = array(
-			'22' => 22,
-			'15.234' => 15.2,
-			'3.4' => 3.4,
-			'6.131' => 6.1,
-			'-3.17' => -3.2,
-			'-10.99' => -11
-		);
-		foreach ($values2 as $was => $expected) {
-			$is = NumberLib::roundTo($was, 0.1);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-	}
-
-	/**
-	 *2011-04-14 lb
-	 */
-	public function testRoundUpTo() {
-		//increment = 10
-		$values = array(
-			'22.765' => 30,
-			'15.22' => 20,
-			'3.4' => 10,
-			'6' => 10,
-			'-3.12' => 0,
-			'-10' => -10
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::roundUpTo($was, 10);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-		//increment = 5
-		$values = array(
-			'22' => 25,
-			'15.234' => 20,
-			'3.4' => 5,
-			'6.131' => 10,
-			'-3.17' => 0,
-			'-10.99' => -10
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::roundUpTo($was, 5);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-	}
-
-
-	/**
-	 *2011-04-14 lb
-	 */
-	public function testRoundDownTo() {
-		//increment = 10
-		$values = array(
-			'22.765' => 20,
-			'15.22' => 10,
-			'3.4' => 0,
-			'6' => 0,
-			'-3.12' => -10,
-			'-10' => -10
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::roundDownTo($was, 10);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-		//increment = 3
-		$values = array(
-			'22' => 21,
-			'15.234' => 15,
-			'3.4' => 3,
-			'6.131' => 6,
-			'-3.17' => -6,
-			'-10.99' => -12
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::roundDownTo($was, 3);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-	}
-
-	/**
-	 *2011-04-15 lb
-	 */
-	public function testGetDecimalPlaces() {
-		$values = array(
-			'100' => -2,
-			'0.0001' => 4,
-			'10' => -1,
-			'0.1' => 1,
-			'1' => 0,
-			'0.001' => 3
-		);
-		foreach ($values as $was => $expected) {
-			$is = NumberLib::getDecimalPlaces($was, 10);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-	}
-
-
-	public function testParseDate() {
-		echo $this->_header(__FUNCTION__);
-		$tests = array(
-			'2010-12-11' => 1292022000,
-			'2010-01-02' => 1262386800,
-			'10-01-02' => 1262386800,
-			'2.1.2010' => 1262386800,
-			'2.1.10' => 1262386800,
-			'02.01.10' => 1262386800,
-			'02.01.2010' => 1262386800,
-			'02.01.2010 22:11' => 1262386800,
-			'2010-01-02 22:11' => 1262386800,
-		);
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::parseDate($was);
-			//pr($is);
-			pr(date(FORMAT_NICE_YMDHMS, $is));
-			$this->assertEquals($is, $expected, null, $was);
-		}
-	}
-
-
-	public function testParseTime() {
-		echo $this->_header(__FUNCTION__);
-		$tests = array(
-			'2:4' => 7440,
-			'2:04' => 7440,
-			'2' => 7200,
-			'1,5' => 3600+1800,
-			'1.5' => 3600+1800,
-			'1.50' => 3600+1800,
-			'1.01' => 3660,
-			':4' => 240,
-			':04' => 240,
-			':40' => 40*MINUTE,
-			'1:2:4' => 1*HOUR+2*MINUTE+4*SECOND,
-			'01:2:04' => 1*HOUR+2*MINUTE+4*SECOND,
-			'0:2:04' => 2*MINUTE+4*SECOND,
-			'::4' => 4*SECOND,
-			'::04' => 4*SECOND,
-			'::40' => 40*SECOND,
-			'2011-11-12 10:10:10' => 10*HOUR+10*MINUTE+10*SECOND,
-		);
-
-		# positive
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::parseTime($was);
-			//pr($is);
-			$this->assertEquals($is, $expected, null, $was);
-		}
-
-		unset($tests['2011-11-12 10:10:10']);
-		# negative
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::parseTime('-'.$was);
-			//pr($is);
-			$this->assertEquals($is, -$expected, null, '-'.$was.' ['.$is.' => '.(-$expected).']');
-		}
-	}
-
-	public function testBuildTime() {
-		echo $this->_header(__FUNCTION__);
-		$tests = array(
-			7440 => '2:04',
-			7220 => '2:00', # 02:00:20 => rounded to 2:00:00
-			5400 => '1:30',
-			3660 => '1:01',
-		);
-
-		# positive
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::buildTime($was);
-			pr($is);
-			$this->assertEquals($is, $expected);
-		}
-
-		# negative
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::buildTime(-$was);
-			pr($is);
-			$this->assertEquals($is, '-'.$expected);
-		}
-	}
-
-	public function testBuildDefaultTime() {
-		echo $this->_header(__FUNCTION__);
-		$tests = array(
-			7440 => '02:04:00',
-			7220 => '02:00:20',
-			5400 => '01:30:00',
-			3660 => '01:01:00',
-			1*HOUR+2*MINUTE+4*SECOND => '01:02:04',
-		);
-
-		foreach ($tests as $was => $expected) {
-			$is = NumberLib::buildDefaultTime($was);
-			pr($is);
-			$this->assertEquals($is, $expected);
-		}
-	}
-
-	/**
-	 * basic
-	 */
-	public function testStandardDecimal() {
-		echo $this->_header(__FUNCTION__);
-		$value = '9.30';
-		$is = NumberLib::standardToDecimalTime($value);
-		$this->assertEquals(round($is, 2), '9.50');
-
-		$value = '9.3';
-		$is = NumberLib::standardToDecimalTime($value);
-		$this->assertEquals(round($is, 2), '9.50');
-	}
-
-
-	public function testDecimalStandard() {
-		echo $this->_header(__FUNCTION__);
-		$value = '9.50';
-		$is = NumberLib::decimalToStandardTime($value);
-		$this->assertEquals(round($is, 2), '9.3');
-
-		$value = '9.5';
-		$is = NumberLib::decimalToStandardTime($value);
-		pr($is);
-		$this->assertEquals($is, '9.3');
-
-		$is = NumberLib::decimalToStandardTime($value, 2);
-		pr($is);
-		$this->assertEquals($is, '9.30');
-
-		$is = NumberLib::decimalToStandardTime($value, 2, ':');
-		pr($is);
-		$this->assertEquals($is, '9:30');
-	}
-
-
-}

+ 19 - 17
Test/Case/Lib/DatetimeLibTest.php

@@ -1,6 +1,6 @@
 <?php
 
-App::uses('DatetimeLib', 'Tools.Lib');
+App::uses('DatetimeLib', 'Tools.Utility');
 App::uses('MyCakeTestCase', 'Tools.Lib');
 
 class DatetimeLibTest extends MyCakeTestCase {
@@ -17,18 +17,18 @@ class DatetimeLibTest extends MyCakeTestCase {
 		unset($this->Datetime);
 	}
 
-	public function testParse() {
+	public function testParseLocalizedDate() {
 		$this->out($this->_header(__FUNCTION__));
 		
-		$ret = $this->Datetime->parseDate('15-Feb-2009', 'j-M-Y', 'start');
+		$ret = $this->Datetime->parseLocalizedDate('15-Feb-2009', 'j-M-Y', 'start');
 		pr($ret);
 		$this->assertEquals($ret, '2009-02-15 00:00:00');
 		
 		# problem when not passing months or days as well - no way of knowing how exact the date was
-		$ret = $this->Datetime->parseDate('2009', 'Y', 'start');
+		$ret = $this->Datetime->parseLocalizedDate('2009', 'Y', 'start');
 		pr($ret);
 		//$this->assertEquals($ret, '2009-01-01 00:00:00');
-		$ret = $this->Datetime->parseDate('Feb 2009', 'M Y', 'start');
+		$ret = $this->Datetime->parseLocalizedDate('Feb 2009', 'M Y', 'start');
 		pr($ret);
 		//$this->assertEquals($ret, '2009-02-01 00:00:00');
 		
@@ -43,11 +43,11 @@ class DatetimeLibTest extends MyCakeTestCase {
 			array('12/2009', array('2009-12-01 00:00:00', '2009-12-31 23:59:59')),
 		);
 		foreach ($values as $v) {
-			$ret = $this->Datetime->parseDate($v[0], null, 'start');
+			$ret = $this->Datetime->parseLocalizedDate($v[0], null, 'start');
 			pr($ret);
 			$this->assertEquals($ret, $v[1][0]);
 			
-			$ret = $this->Datetime->parseDate($v[0], null, 'end');
+			$ret = $this->Datetime->parseLocalizedDate($v[0], null, 'end');
 			pr($ret);
 			$this->assertEquals($ret, $v[1][1]);
 		}
@@ -83,13 +83,15 @@ class DatetimeLibTest extends MyCakeTestCase {
 		$values = array(
 			array(__('Today'), "(Model.field >= '".date(FORMAT_DB_DATE)." 00:00:00') AND (Model.field <= '".date(FORMAT_DB_DATE)." 23:59:59')"),
 			array(__('Yesterday').' '.__('until').' '.__('Today'), "(Model.field >= '".date(FORMAT_DB_DATE, time()-DAY)." 00:00:00') AND (Model.field <= '".date(FORMAT_DB_DATE)." 23:59:59')"),
-			array(__('Tomorrow').' '.__('until').' '.__('The day after tomorrow'), "(Model.field >= '".date(FORMAT_DB_DATE, time()+DAY)." 00:00:00') AND (Model.field <= '".date(FORMAT_DB_DATE, time()+2*DAY)." 23:59:59')"),
+			array(__('Today').' '.__('until').' '.__('Tomorrow'), "(Model.field >= '".date(FORMAT_DB_DATE, time())." 00:00:00') AND (Model.field <= '".date(FORMAT_DB_DATE, time()+DAY)." 23:59:59')"),
+			array(__('Yesterday').' '.__('until').' '.__('Tomorrow'), "(Model.field >= '".date(FORMAT_DB_DATE, time()-DAY)." 00:00:00') AND (Model.field <= '".date(FORMAT_DB_DATE, time()+DAY)." 23:59:59')"),
 		);
 		
 		foreach ($values as $v) {
 			$ret = $this->Datetime->periodAsSql($v[0], 'Model.field');
-			pr($ret);
-			$this->assertEquals($ret, $v[1]);
+			pr($v[1]);
+			pr($ret); ob_flush();
+			$this->assertSame($v[1], $ret);
 			
 		}
 	}
@@ -104,7 +106,7 @@ class DatetimeLibTest extends MyCakeTestCase {
 		
 		foreach ($values as $v) {
 			$ret = $this->Datetime->difference($v[0], $v[1]);
-			$this->assertEquals($ret, $v[2]);
+			$this->assertEquals($v[2], $ret);
 		}
 	}
 	
@@ -121,7 +123,7 @@ class DatetimeLibTest extends MyCakeTestCase {
 			$ret = $this->Datetime->ageBounds($v[0], $v[1], true, '2011-07-06'); //TODO: relative time
 			pr($ret);
 			if (isset($v[2])) {
-				$this->assertSame($ret, $v[2]);
+				$this->assertSame($v[2], $ret);
 				pr($this->Datetime->age($v[2]['min']));
 				pr($this->Datetime->age($v[2]['max']));
 				$this->assertEquals($v[0], $this->Datetime->age($v[2]['max']));
@@ -146,14 +148,14 @@ class DatetimeLibTest extends MyCakeTestCase {
 			$is = $this->Datetime->ageByYear(2000, $month);
 			$this->out($is);
 			//$this->assertEquals($is, (date('Y')-2001).'/'.(date('Y')-2000), null, '2000/'.$month);
-			$this->assertEquals($is, (date('Y')-2001), null, '2000/'.$month);
+			$this->assertSame($is, (date('Y')-2001), null, '2000/'.$month);
 		}
 		
 		if (($month = date('n')-1) >= 1) {
 			$is = $this->Datetime->ageByYear(2000, $month);
 			$this->out($is);
 			//$this->assertEquals($is, (date('Y')-2001).'/'.(date('Y')-2000), null, '2000/'.$month);
-			$this->assertEquals($is, (date('Y')-2000), null, '2000/'.$month);
+			$this->assertSame($is, (date('Y')-2000), null, '2000/'.$month);
 		}
 	}
 	
@@ -328,7 +330,7 @@ class DatetimeLibTest extends MyCakeTestCase {
 			$ret = $this->Datetime->cweekBeginning($v[0], $v[1]);
 			$this->out($ret);
 			$this->out($this->Datetime->niceDate($ret, 'D').' '.$this->Datetime->niceDate($ret, FORMAT_NICE_YMDHMS));
-			$this->assertEquals($ret, $v[2], null, $v[1].'/'.$v[0]);
+			$this->assertSame($v[2], $ret, null, $v[1].'/'.$v[0]);
 		}
 	}
 	
@@ -345,7 +347,7 @@ class DatetimeLibTest extends MyCakeTestCase {
 			$ret = $this->Datetime->cweekEnding($year);
 			$this->out($ret);
 			$this->out($this->Datetime->niceDate($ret, 'D').' '.$this->Datetime->niceDate($ret, FORMAT_NICE_YMDHMS));
-			$this->assertEquals($ret, $expected);
+			$this->assertSame($ret, $expected);
 		}
 		
 		$values = array(
@@ -360,7 +362,7 @@ class DatetimeLibTest extends MyCakeTestCase {
 			$ret = $this->Datetime->cweekEnding($v[0], $v[1]);
 			$this->out($ret);
 			$this->out($this->Datetime->niceDate($ret, 'D').' '.$this->Datetime->niceDate($ret, FORMAT_NICE_YMDHMS));
-			$this->assertEquals($ret, $v[2], null, $v[1].'/'.$v[0]);
+			$this->assertSame($v[2], $ret, null, $v[1].'/'.$v[0]);
 		}
 	}
 

+ 186 - 0
Test/Case/Lib/Utility/NumberLibTest.php

@@ -0,0 +1,186 @@
+<?php
+
+App::uses('NumberLib', 'Tools.Utility');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+class NumberLibTest extends MyCakeTestCase {
+
+	public $NumberLib = null;
+
+	public function startTest() {
+		//$this->NumberLib = new NumberLib();
+	}
+	
+	public function testMoney() {
+		$is = NumberLib::money(22.11);
+		$expected = '22,11 €';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::money(-22.11);
+		$expected = '-22,11 €';
+		$this->assertSame($expected, $is);		
+	}
+	
+	public function testPrice() {
+		$is = NumberLib::price(22.11);
+		$expected = '22,11 €';
+		$this->assertSame($expected, $is);	
+		
+		$is = NumberLib::price(-22.11);
+		$expected = '0,00 €';
+		$this->assertSame($expected, $is);		
+	}
+	
+	public function testCurrency() {
+		$is = NumberLib::currency(22.11);
+		$expected = '22,11 €';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::currency(-22.11);
+		$expected = '-22,11 €';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::currency(-22.11, 'EUR', array('signed'=>true));
+		$expected = '-22,11 €';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::currency(22.11, 'EUR', array('signed'=>true));
+		$expected = '+22,11 €';
+		$this->assertSame($expected, $is);
+	}
+	
+	/**
+	 * 2012-04-06 ms
+	 */
+	public function testToPercentage() {
+		$is = NumberLib::toPercentage(22.11, 2, '.');
+		$expected = '22.11%';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::toPercentage(22.11, 2, ',');
+		$expected = '22,11%';
+		$this->assertSame($expected, $is);
+		
+		$is = NumberLib::toPercentage(22.11, 0, ',');
+		$expected = '22%';
+		$this->assertSame($expected, $is);
+	}
+
+	/**
+	 *2011-04-14 lb
+	 */
+	public function testRoundTo() {
+		//increment = 10
+		$values = array(
+			'22' => 20,
+			'15' => 20,
+			'3.4' => 0,
+			'6' => 10,
+			'-3.12' => 0,
+			'-10' => -10
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::roundTo($was, 10);
+			//echo returns($expected); echo returns($is); echo BR; ob_flush();
+			
+			$this->assertSame($expected, $is, null, $was);
+		}
+		//increment = 0.1
+		$values2 = array(
+			'22' => 22.0,
+			'15.234' => 15.2,
+			'3.4' => 3.4,
+			'6.131' => 6.1,
+			'-3.17' => -3.2,
+			'-10.99' => -11.0
+		);
+		foreach ($values2 as $was => $expected) {
+			$is = NumberLib::roundTo($was, 0.1);
+			$this->assertSame($expected, $is, null, $was);
+		}
+	}
+
+	/**
+	 *2011-04-14 lb
+	 */
+	public function testRoundUpTo() {
+		//increment = 10
+		$values = array(
+			'22.765' => 30.0,
+			'15.22' => 20.0,
+			'3.4' => 10.0,
+			'6' => 10.0,
+			'-3.12' => -0.0,
+			'-10' => -10.0
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::roundUpTo($was, 10);
+			$this->assertSame($expected, $is, null, $was);
+		}
+		//increment = 5
+		$values = array(
+			'22' => 25.0,
+			'15.234' => 20.0,
+			'3.4' => 5.0,
+			'6.131' => 10.0,
+			'-3.17' => -0.0,
+			'-10.99' => -10.0
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::roundUpTo($was, 5);
+			$this->assertSame($expected, $is, null, $was);
+		}
+	}
+
+
+	/**
+	 *2011-04-14 lb
+	 */
+	public function testRoundDownTo() {
+		//increment = 10
+		$values = array(
+			'22.765' => 20.0,
+			'15.22' => 10.0,
+			'3.4' => 0.0,
+			'6' => 0.0,
+			'-3.12' => -10.0,
+			'-10' => -10.0
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::roundDownTo($was, 10);
+			$this->assertSame($expected, $is, null, $was);
+		}
+		//increment = 3
+		$values = array(
+			'22' => 21.0,
+			'15.234' => 15.0,
+			'3.4' => 3.0,
+			'6.131' => 6.0,
+			'-3.17' => -6.0,
+			'-10.99' => -12.0
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::roundDownTo($was, 3);
+			$this->assertSame($expected, $is, null, $was);
+		}
+	}
+
+	/**
+	 *2011-04-15 lb
+	 */
+	public function testGetDecimalPlaces() {
+		$values = array(
+			'100' => -2,
+			'0.0001' => 4,
+			'10' => -1,
+			'0.1' => 1,
+			'1' => 0,
+			'0.001' => 3
+		);
+		foreach ($values as $was => $expected) {
+			$is = NumberLib::getDecimalPlaces($was, 10);
+			$this->assertSame($expected, $is); //, null, $was
+		}
+	}
+
+}

+ 1 - 1
Test/Case/Lib/TextLibTest.php

@@ -1,6 +1,6 @@
 <?php
 
-App::uses('TextLib', 'Tools.Lib');
+App::uses('TextLib', 'Tools.Utility');
 
 /**
  * 2010-07-14 ms

+ 152 - 0
Test/Case/Lib/Utility/TimeLibTest.php

@@ -0,0 +1,152 @@
+<?php
+
+App::uses('TimeLib', 'Tools.Utility');
+App::uses('MyCakeTestCase', 'Tools.Lib');
+
+class TimeLibTest extends MyCakeTestCase {
+
+	public $TimeLib = null;
+
+	public function startTest() {
+		//$this->TimeLib = new TimeLib();
+	}
+	
+
+	public function testParseDate() {
+		echo $this->_header(__FUNCTION__);
+		$tests = array(
+			'2010-12-11' => 1292022000,
+			'2010-01-02' => 1262386800,
+			'10-01-02' => 1262386800,
+			'2.1.2010' => 1262386800,
+			'2.1.10' => 1262386800,
+			'02.01.10' => 1262386800,
+			'02.01.2010' => 1262386800,
+			'02.01.2010 22:11' => 1262386800,
+			'2010-01-02 22:11' => 1262386800,
+		);
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::parseDate($was);
+			//pr($is);
+			pr(date(FORMAT_NICE_YMDHMS, $is));
+			$this->assertSame($expected, $is); //, null, $was
+		}
+	}
+
+
+	public function testParseTime() {
+		echo $this->_header(__FUNCTION__);
+		$tests = array(
+			'2:4' => 7440,
+			'2:04' => 7440,
+			'2' => 7200,
+			'1,5' => 3600+1800,
+			'1.5' => 3600+1800,
+			'1.50' => 3600+1800,
+			'1.01' => 3660,
+			':4' => 240,
+			':04' => 240,
+			':40' => 40*MINUTE,
+			'1:2:4' => 1*HOUR+2*MINUTE+4*SECOND,
+			'01:2:04' => 1*HOUR+2*MINUTE+4*SECOND,
+			'0:2:04' => 2*MINUTE+4*SECOND,
+			'::4' => 4*SECOND,
+			'::04' => 4*SECOND,
+			'::40' => 40*SECOND,
+			'2011-11-12 10:10:10' => 10*HOUR+10*MINUTE+10*SECOND,
+		);
+
+		# positive
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::parseTime($was);
+			//pr($is);
+			$this->assertEquals($expected, $is); //null, $was
+		}
+
+		unset($tests['2011-11-12 10:10:10']);
+		# negative
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::parseTime('-'.$was);
+			//pr($is);
+			$this->assertEquals($is, -$expected); //, null, '-'.$was.' ['.$is.' => '.(-$expected).']'
+		}
+	}
+
+	public function testBuildTime() {
+		echo $this->_header(__FUNCTION__);
+		$tests = array(
+			7440 => '2:04',
+			7220 => '2:00', # 02:00:20 => rounded to 2:00:00
+			5400 => '1:30',
+			3660 => '1:01',
+		);
+
+		# positive
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::buildTime($was);
+			pr($is);
+			$this->assertEquals($expected, $is);
+		}
+
+		# negative
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::buildTime(-$was);
+			pr($is);
+			$this->assertEquals($is, '-'.$expected);
+		}
+	}
+
+	public function testBuildDefaultTime() {
+		echo $this->_header(__FUNCTION__);
+		$tests = array(
+			7440 => '02:04:00',
+			7220 => '02:00:20',
+			5400 => '01:30:00',
+			3660 => '01:01:00',
+			1*HOUR+2*MINUTE+4*SECOND => '01:02:04',
+		);
+
+		foreach ($tests as $was => $expected) {
+			$is = TimeLib::buildDefaultTime($was);
+			pr($is);
+			$this->assertEquals($expected, $is);
+		}
+	}
+
+	/**
+	 * basic
+	 */
+	public function testStandardDecimal() {
+		echo $this->_header(__FUNCTION__);
+		$value = '9.30';
+		$is = TimeLib::standardToDecimalTime($value);
+		$this->assertEquals(round($is, 2), '9.50');
+
+		$value = '9.3';
+		$is = TimeLib::standardToDecimalTime($value);
+		$this->assertEquals(round($is, 2), '9.50');
+	}
+
+
+	public function testDecimalStandard() {
+		echo $this->_header(__FUNCTION__);
+		$value = '9.50';
+		$is = TimeLib::decimalToStandardTime($value);
+		$this->assertEquals(round($is, 2), '9.3');
+
+		$value = '9.5';
+		$is = TimeLib::decimalToStandardTime($value);
+		pr($is);
+		$this->assertEquals($is, '9.3');
+
+		$is = TimeLib::decimalToStandardTime($value, 2);
+		pr($is);
+		$this->assertEquals($is, '9.30');
+
+		$is = TimeLib::decimalToStandardTime($value, 2, ':');
+		pr($is);
+		$this->assertEquals($is, '9:30');
+	}
+
+
+}

+ 2 - 2
View/Helper/CommonHelper.php

@@ -113,7 +113,7 @@ class CommonHelper extends AppHelper {
 	public function metaAlternate($url, $lang, $full = false) {
 		$canonical = $this->Html->url($url, $full);
 		//return $this->Html->meta('canonical', $canonical, array('rel'=>'canonical', 'type'=>null, 'title'=>null));
-		$lang = (array) $lang;
+		$lang = (array)$lang;
 		$res = array();
 		foreach ($lang as $language => $countries) {
 			if (is_numeric($language)) {
@@ -121,7 +121,7 @@ class CommonHelper extends AppHelper {
  			} else {
  				$language .= '-';
  			}
- 			$countries = (array) $countries;
+ 			$countries = (array)$countries;
 	 		foreach ($countries as $country) {
 	 			$l = $language.$country;
 	 			$res[] = $this->Html->meta('alternate', $url, array('rel'=>'alternate', 'hreflang'=>$l, 'type'=>null, 'title'=>null)).PHP_EOL;

+ 12 - 6
View/Helper/DatetimeHelper.php

@@ -1,8 +1,12 @@
 <?php
 
-App::uses('DatetimeLib', 'Tools.Lib');
+App::uses('DatetimeLib', 'Tools.Utility');
 App::uses('TimeHelper', 'View/Helper');
 
+/**
+ * TODO: make extend TimeLib some day?
+ * 2012-04-09 ms
+ */
 class DatetimeHelper extends TimeHelper {
 	
 	public $helpers = array('Html');
@@ -13,6 +17,7 @@ class DatetimeHelper extends TimeHelper {
 	protected $daylightSavings = false;
 
 	public function __construct($View = null, $settings = array()) {
+		$settings = Set::merge(array('engine' => 'Tools.DatetimeLib'), $settings);
 		parent::__construct($View, $settings);
 
 		$i18n = Configure::read('Localization');
@@ -22,11 +27,10 @@ class DatetimeHelper extends TimeHelper {
 		if (!empty($i18n['daylight_savings'])) {
 			$this->daylightSavings = (bool)$i18n['daylight_savings'];
 		}
-
-		$this->Datetime = new DatetimeLib();
+		//$this->Datetime = new DatetimeLib();
 	}
 
-
+	/*
 	public function __call($method, $params) {
 
 		if (!method_exists($this, 'call__')) {
@@ -34,6 +38,7 @@ class DatetimeHelper extends TimeHelper {
 		}
 		return call_user_func_array(array($this->Datetime, $method), $params);
 	}
+	*/
 
 
 	/**
@@ -63,7 +68,7 @@ class DatetimeHelper extends TimeHelper {
 		if ((int)$date === 0) {
 			return $default;
 		}
-		$age = $this->Datetime->age($date, null);
+		$age = $this->age($date, null);
 		if ($age >= 1 && $age <= 99) {
 			return $age;
 			}
@@ -78,7 +83,7 @@ class DatetimeHelper extends TimeHelper {
 	 * 2009-11-22 ms
 	 */
 	public function localDateMarkup($dateString = null, $format = null, $options = array()) {
-		$date = $this->Datetime->localDate($dateString, $format, $options);
+		$date = $this->localDate($dateString, $format, $options);
 		$date = '<span'.($this->isToday($dateString,(isset($options['userOffset'])?$options['userOffset']:null))?' class="today"':'').'>'.$date.'</span>';
 		return $date;
 	}
@@ -172,6 +177,7 @@ class DatetimeHelper extends TimeHelper {
 		if (isset($this->Html)) {
 			return $this->Html->tag('span', $niceDate, $attr);
 		}
+		trigger_error('HtmlHelper not found');
 		$a = array();
 		foreach ($attr as $key => $val) {
 			$a[] = $key.'="'.$val.'"';

+ 17 - 127
View/Helper/NumericHelper.php

@@ -1,24 +1,34 @@
 <?php
 
-App::import('Helper', 'Number');
-
+App::uses('NumberHelper', 'View/Helper');
+//App::uses('NumberLib', 'Tools.Utility');
+
+/**
+ * Todo: rename to MyNumberHelper some day?
+ * Aliasing it then as Number again in the project
+ * 
+ * 2012-04-08 ms
+ */
 class NumericHelper extends NumberHelper {
-	public $helpers = array('Session');
-
-	protected $_settings = array(
 	
+	//public $helpers = array();
+	/*
+	protected $_settings = array(
 	);
 
+
 	protected $code = null;
 	protected $places = 0;
 	protected $symbolRight = null;
 	protected $symbolLeft = null;
 	protected $decimalPoint = '.';
 	protected $thousandsPoint = ',';
+	*/
 
 	public function __construct($View = null, $settings = array()) {
+		$settings = Set::merge(array('engine' => 'Tools.NumberLib'), $settings);
 		parent::__construct($View, $settings);	
-			
+		/*	
 		$i18n = Configure::read('Currency');
 		if (!empty($i18n['code'])) {
 			$this->code = $i18n['code'];
@@ -38,127 +48,7 @@ class NumericHelper extends NumberHelper {
 		if (isset($i18n['thousands'])) {
 			$this->thousandsPoint = $i18n['thousands'];
 		}
-	}
-	
-	/**
-	 * like price but with negative values allowed
-	 * @return string
-	 * 2011-10-05 ms
-	 */
-	public function money($amount, $places = null, $formatOptions = array()) {
-		$formatOptions['allowNegative'] = true;
-		return $this->price($amount, null, $places, $formatOptions);
-	}
-	
-	/**
-	 * @param price
-	 * @param specialPrice (outranks the price)
-	 * @param places
-	 * @param options
-	 * - allowNegative (defaults to false - price needs to be > 0)
-	 * - currency (defaults to true)
-	 * @return string
-	 * 2011-07-30 ms
-	 */
-	public function price($price, $specialPrice = null, $places = null, $formatOptions = array()) {
-		if ($specialPrice !== null && (float)$specialPrice > 0) {
-			$val = $specialPrice;
-		} elseif ((float)$price > 0 || !empty($formatOptions['allowNegative'])) {
-			$val = $price;
-		} else {
-			if (isset($formatOptions['default'])) {
-				return $formatOptions['default'];
-			}
-			$val = $price;
-		}
-
-		if ($places === null) {
-			$places = 2;
-		}
-		$options = array('currency' => true);
-		if (!empty($formatOptions)) {
-			$options = array_merge($options, $formatOptions); # Set::merge not neccessary
-		}
-		
-		
-		return $this->format($val, $places, $options); // ->currency()
-	}
-
-	/**
-	 * format numeric values
-	 * @param float $number
-	 * @param int $places (0 = int, 1..x places after dec, -1..-x places before dec)
-	 * @param array $option : currency=true/false, ... (leave empty for no special treatment)
-	 * //TODO: automize per localeconv() ?
-	 * 2009-04-03 ms
-	 */
-	public function format($number, $places = null, $formatOptions = array()) {
-		if (!is_numeric($number)) {
-			return '---';
-		}
-		if (!is_integer($places)) {
-			$places = 2;
-		}
-		$options = array('before' => '', 'after' => '', 'places' => $places, 'thousands' => $this->thousandsPoint, 'decimals' => $this->
-			decimalPoint, 'escape' => false);
-
-		if (!empty($formatOptions['currency'])) {
-			if (!empty($this->symbolRight)) {
-				$options['after'] = ' ' . $this->symbolRight;
-			} elseif (!empty($this->symbolLeft)) {
-				$options['before'] = $this->symbolLeft . ' ';
-			} else {
-
-			}
-		} else {
-			if (!empty($formatOptions['after'])) {
-				$options['after'] = $formatOptions['after'];
-			}
-			if (!empty($formatOptions['before'])) {
-				$options['before'] = $formatOptions['before'];
-			}
-		}
-
-		if (!empty($formatOptions['thousands'])) {
-			$options['thousands'] = $formatOptions['thousands'];
-		}
-		if (!empty($formatOptions['decimals'])) {
-			$options['decimals'] = $formatOptions['decimals'];
-		}
-		if ($places < 0) {
-			$number = round($number, $places);
-		}
-		return parent::format($number, $options);
+		*/
 	}
 
-	/**
-	 * Returns the English ordinal suffix (th, st, nd, etc) of a number.
-	 *
-	 *     echo 2, Num::ordinal(2);   // "2nd"
-	 *     echo 10, Num::ordinal(10); // "10th"
-	 *     echo 33, Num::ordinal(33); // "33rd"
-	 *
-	 * @param   integer  number
-	 * @return  string
-	 */
-	public static function ordinal($number) {
-		if ($number % 100 > 10 and $number % 100 < 14) {
-			return 'th';
-		}
-		switch ($number % 10) {
-			case 1:
-				return 'st';
-			case 2:
-				return 'nd';
-			case 3:
-				return 'rd';
-			default:
-				return 'th';
-		}
-	}
-
-
-	public function currency($number, $currency = 'EUR', $options = array()) {
-		return parent::currency($number, $currency, $options);
-	}
 }