Refactored based on comments and feedback (3rd round)

This commit is contained in:
Antonio Ramirez
2013-05-29 09:20:45 +02:00
27 changed files with 1555 additions and 139 deletions

View File

@@ -48,4 +48,11 @@ return array(
YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js',
),
),
'yii/maskedinput' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'jquery.maskedinput.js',
),
'depends' => array('yii/jquery'),
),
);

View File

@@ -0,0 +1,338 @@
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function($) {
function getPasteEvent() {
var el = document.createElement('input'),
name = 'onpaste';
el.setAttribute(name, '');
return (typeof el[name] === 'function')?'paste':'input';
}
var pasteEventName = getPasteEvent() + ".mask",
ua = navigator.userAgent,
iPhone = /iphone/i.test(ua),
android=/android/i.test(ua),
caretTimeoutId;
$.mask = {
//Predefined character definitions
definitions: {
'9': "[0-9]",
'a': "[A-Za-z]",
'*': "[A-Za-z0-9]"
},
dataName: "rawMaskFn",
placeholder: '_',
};
$.fn.extend({
//Helper Function for Caret positioning
caret: function(begin, end) {
var range;
if (this.length === 0 || this.is(":hidden")) {
return;
}
if (typeof begin == 'number') {
end = (typeof end === 'number') ? end : begin;
return this.each(function() {
if (this.setSelectionRange) {
this.setSelectionRange(begin, end);
} else if (this.createTextRange) {
range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', begin);
range.select();
}
});
} else {
if (this[0].setSelectionRange) {
begin = this[0].selectionStart;
end = this[0].selectionEnd;
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
begin = 0 - range.duplicate().moveStart('character', -100000);
end = begin + range.text.length;
}
return { begin: begin, end: end };
}
},
unmask: function() {
return this.trigger("unmask");
},
mask: function(mask, settings) {
var input,
defs,
tests,
partialPosition,
firstNonMaskPos,
len;
if (!mask && this.length > 0) {
input = $(this[0]);
return input.data($.mask.dataName)();
}
settings = $.extend({
placeholder: $.mask.placeholder, // Load default placeholder
completed: null
}, settings);
defs = $.mask.definitions;
tests = [];
partialPosition = len = mask.length;
firstNonMaskPos = null;
$.each(mask.split(""), function(i, c) {
if (c == '?') {
len--;
partialPosition = i;
} else if (defs[c]) {
tests.push(new RegExp(defs[c]));
if (firstNonMaskPos === null) {
firstNonMaskPos = tests.length - 1;
}
} else {
tests.push(null);
}
});
return this.trigger("unmask").each(function() {
var input = $(this),
buffer = $.map(
mask.split(""),
function(c, i) {
if (c != '?') {
return defs[c] ? settings.placeholder : c;
}
}),
focusText = input.val();
function seekNext(pos) {
while (++pos < len && !tests[pos]);
return pos;
}
function seekPrev(pos) {
while (--pos >= 0 && !tests[pos]);
return pos;
}
function shiftL(begin,end) {
var i,
j;
if (begin<0) {
return;
}
for (i = begin, j = seekNext(end); i < len; i++) {
if (tests[i]) {
if (j < len && tests[i].test(buffer[j])) {
buffer[i] = buffer[j];
buffer[j] = settings.placeholder;
} else {
break;
}
j = seekNext(j);
}
}
writeBuffer();
input.caret(Math.max(firstNonMaskPos, begin));
}
function shiftR(pos) {
var i,
c,
j,
t;
for (i = pos, c = settings.placeholder; i < len; i++) {
if (tests[i]) {
j = seekNext(i);
t = buffer[i];
buffer[i] = c;
if (j < len && tests[j].test(t)) {
c = t;
} else {
break;
}
}
}
}
function keydownEvent(e) {
var k = e.which,
pos,
begin,
end;
//backspace, delete, and escape get special treatment
if (k === 8 || k === 46 || (iPhone && k === 127)) {
pos = input.caret();
begin = pos.begin;
end = pos.end;
if (end - begin === 0) {
begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
end=k===46?seekNext(end):end;
}
clearBuffer(begin, end);
shiftL(begin, end - 1);
e.preventDefault();
} else if (k == 27) {//escape
input.val(focusText);
input.caret(0, checkVal());
e.preventDefault();
}
}
function keypressEvent(e) {
var k = e.which,
pos = input.caret(),
p,
c,
next;
if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
return;
} else if (k) {
if (pos.end - pos.begin !== 0){
clearBuffer(pos.begin, pos.end);
shiftL(pos.begin, pos.end-1);
}
p = seekNext(pos.begin - 1);
if (p < len) {
c = String.fromCharCode(k);
if (tests[p].test(c)) {
shiftR(p);
buffer[p] = c;
writeBuffer();
next = seekNext(p);
if(android){
setTimeout($.proxy($.fn.caret,input,next),0);
}else{
input.caret(next);
}
if (settings.completed && next >= len) {
settings.completed.call(input);
}
}
}
e.preventDefault();
}
}
function clearBuffer(start, end) {
var i;
for (i = start; i < end && i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
}
}
}
function writeBuffer() { input.val(buffer.join('')); }
function checkVal(allow) {
//try to place characters where they belong
var test = input.val(),
lastMatch = -1,
i,
c;
for (i = 0, pos = 0; i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
while (pos++ < test.length) {
c = test.charAt(pos - 1);
if (tests[i].test(c)) {
buffer[i] = c;
lastMatch = i;
break;
}
}
if (pos > test.length) {
break;
}
} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
pos++;
lastMatch = i;
}
}
if (allow) {
writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
input.val("");
clearBuffer(0, len);
} else {
writeBuffer();
input.val(input.val().substring(0, lastMatch + 1));
}
return (partialPosition ? i : firstNonMaskPos);
}
input.data($.mask.dataName,function(){
return $.map(buffer, function(c, i) {
return tests[i]&&c!=settings.placeholder ? c : null;
}).join('');
});
if (!input.attr("readonly"))
input
.one("unmask", function() {
input
.unbind(".mask")
.removeData($.mask.dataName);
})
.bind("focus.mask", function() {
clearTimeout(caretTimeoutId);
var pos,
moveCaret;
focusText = input.val();
pos = checkVal();
caretTimeoutId = setTimeout(function(){
writeBuffer();
if (pos == mask.length) {
input.caret(0, pos);
} else {
input.caret(pos);
}
}, 10);
})
.bind("blur.mask", function() {
checkVal();
if (input.val() != focusText)
input.change();
})
.bind("keydown.mask", keydownEvent)
.bind("keypress.mask", keypressEvent)
.bind(pasteEventName, function() {
setTimeout(function() {
var pos=checkVal(true);
input.caret(pos);
if (settings.completed && pos == input.val().length)
settings.completed.call(input);
}, 0);
});
checkVal(); //Perform initial check for existing values
});
}
});
})(jQuery);

View File

@@ -30,8 +30,8 @@ class ActionFilter extends Behavior
public function events()
{
return array(
'beforeAction' => 'beforeFilter',
'afterAction' => 'afterFilter',
Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
Controller::EVENT_AFTER_ACTION => 'afterFilter',
);
}

View File

@@ -278,6 +278,15 @@ class Application extends Module
return $this->getComponent('cache');
}
/**
* Returns the formatter component.
* @return \yii\base\Formatter the formatter application component.
*/
public function getFormatter()
{
return $this->getComponent('formatter');
}
/**
* Returns the request component.
* @return \yii\web\Request|\yii\console\Request the request component
@@ -333,6 +342,9 @@ class Application extends Module
'errorHandler' => array(
'class' => 'yii\base\ErrorHandler',
),
'formatter' => array(
'class' => 'yii\base\Formatter',
),
'i18n' => array(
'class' => 'yii\i18n\I18N',
),

View File

@@ -151,43 +151,13 @@ class Controller extends Component
/**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with.
* @throws InvalidRequestException if there are missing parameters.
*/
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
return array();
}
/**
@@ -271,18 +241,6 @@ class Controller extends Component
return array();
}
/**
* Validates the parameter being bound to actions.
* This method is invoked when parameters are being bound to the currently requested action.
* Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param Action $action the currently requested action
* @param array $missingParams the names of the missing parameters
* @param array $unknownParams the unknown parameters (name => value)
*/
public function validateActionParams($action, $missingParams, $unknownParams)
{
}
/**
* @return string the controller ID that is prefixed with the module ID (if any).
*/

View File

@@ -0,0 +1,292 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use DateTime;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
/**
* Formatter provides a set of commonly used data formatting methods.
*
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
* The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends Component
{
/**
* @var string the default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'Y/m/d';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'h:i:s A';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'Y/m/d h:i:s A';
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text display for false, the second element for true. Defaults to <code>array('No', 'Yes')</code>.
*/
public $booleanFormat;
/**
* @var string the character displayed as the decimal point when formatting a number.
*/
public $decimalSeparator = '.';
/**
* @var string the character displayed as the thousands separator character when formatting a number.
*/
public $thousandSeparator = ',';
/**
* Initializes the component.
*/
public function init()
{
if (empty($this->booleanFormat)) {
$this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes'));
}
}
/**
* Formats the value as is without any formatting.
* This method simply returns back the parameter without any format.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asRaw($value)
{
return $value;
}
/**
* Formats the value as an HTML-encoded plain text.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asText($value)
{
return Html::encode($value);
}
/**
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asNtext($value)
{
return nl2br(Html::encode($value));
}
/**
* Formats the value as HTML-encoded text paragraphs.
* Each text paragraph is enclosed within a `<p>` tag.
* One or multiple consecutive empty lines divide two paragraphs.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asParagraphs($value)
{
return str_replace('<p></p>', '',
'<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>'
);
}
/**
* Formats the value as HTML text.
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
* Use [[asRaw()]] if you do not want any purification of the value.
* @param mixed $value the value to be formatted
* @param array|null $config the configuration for the HTMLPurifier class.
* @return string the formatted result
*/
public function asHtml($value, $config = null)
{
return HtmlPurifier::process($value, $config);
}
/**
* Formats the value as a mailto link.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asEmail($value)
{
return Html::mailto($value);
}
/**
* Formats the value as an image tag.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asImage($value)
{
return Html::img($value);
}
/**
* Formats the value as a hyperlink.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asUrl($value)
{
$url = $value;
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$url = 'http://' . $url;
}
return Html::a(Html::encode($value), $url);
}
/**
* Formats the value as a boolean.
* @param mixed $value the value to be formatted
* @return string the formatted result
* @see booleanFormat
*/
public function asBoolean($value)
{
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
}
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->dateFormat : $format, $value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[timeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->timeFormat : $format, $value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[datetimeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->datetimeFormat : $format, $value);
}
/**
* Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
* @param mixed $value the datetime value to be normalized.
* @return mixed the normalized datetime value
*/
protected function normalizeDatetimeValue($value)
{
if (is_string($value)) {
if (ctype_digit($value) || $value[0] === '-' && ctype_digit(substr($value, 1))) {
return (int)$value;
} else {
return strtotime($value);
}
} elseif ($value instanceof DateTime) {
return $value->getTimestamp();
} else {
return (int)$value;
}
}
/**
* Formats the value as an integer.
* @param mixed $value the value to be formatted
* @return string the formatting result.
*/
public function asInteger($value)
{
if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
return $matches[1];
} else {
$value = (int)$value;
return "$value";
}
}
/**
* Formats the value as a double number.
* Property [[decimalSeparator]] will be used to represent the decimal point.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatting result.
* @see decimalSeparator
*/
public function asDouble($value, $decimals = 2)
{
return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
}
/**
* Formats the value as a number with decimal and thousand separators.
* This method calls the PHP number_format() function to do the formatting.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatted result
* @see decimalSeparator
* @see thousandSeparator
*/
public function asNumber($value, $decimals = 0)
{
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
}
}

View File

@@ -1,26 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* InvalidRequestException represents an exception caused by incorrect end user request.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InvalidRequestException extends UserException
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Request');
}
}

View File

@@ -8,7 +8,7 @@
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\base\ArrayHelper;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
@@ -32,17 +32,22 @@ class Dropdown extends Widget
* // optional, url of the item link
* 'url' => '',
* // optional the HTML attributes of the item link
* 'urlOptions'=> array(...),
* 'linkOptions'=> array(...),
* // optional the HTML attributes of the item
* 'options'=> array(...),
* // optional, an array of items that configure a sub menu of the item
* // note: if `items` is set, then `url` of the parent item will be ignored and automatically set to "#"
* // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown
* // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727
* 'items'=> array(...)
* )
* ```
* Additionally, you can also configure a dropdown item as string.
*/
public $items = array();
/**
* @var boolean whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
@@ -60,43 +65,41 @@ class Dropdown extends Widget
*/
public function run()
{
echo Html::beginTag('ul', $this->options) . "\n";
echo $this->renderContents() . "\n";
echo Html::endTag('ul') . "\n";
echo $this->renderItems() . "\n";
$this->registerPlugin('dropdown');
}
/**
* Renders dropdown contents as specified on [[items]].
* Renders dropdown items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException
*/
protected function renderContents()
protected function renderItems()
{
$contents = array();
$items = array();
foreach ($this->items as $item) {
if (is_string($item)) {
$contents[] = $item;
$items[] = $item;
continue;
}
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', array());
$urlOptions = ArrayHelper::getValue($item, 'urlOptions', array());
$urlOptions['tabindex'] = '-1';
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
$linkOptions['tabindex'] = '-1';
if (isset($item['items'])) {
$this->addCssClass($options, 'dropdown-submenu');
$content = Html::a($item['label'], '#', $urlOptions) . $this->dropdown($item['items']);
$content = Html::a($label, '#', $linkOptions) . $this->dropdown($item['items']);
} else {
$content = Html::a($item['label'], ArrayHelper::getValue($item, 'url', '#'), $urlOptions);
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
}
$contents[] = Html::tag('li', $content , $options);
$items[] = Html::tag('li', $content , $options);
}
return implode("\n", $contents);
return Html::tag('ul', implode("\n", $items), $this->options);
}
/**

View File

@@ -344,7 +344,7 @@ class Html
/**
* Generates a hyperlink tag.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]]
* such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks.
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
@@ -366,7 +366,7 @@ class Html
/**
* Generates a mailto hyperlink.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]]
* such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used.

View File

@@ -0,0 +1,232 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
use Yii;
use IntlDateFormatter;
use NumberFormatter;
use DateTime;
use yii\base\InvalidConfigException;
/**
* Formatter is the localized version of [[\yii\base\Formatter]].
*
* Formatter requires the PHP "intl" extension to be installed. Formatter supports localized
* formatting of date, time and numbers, based on the current [[locale]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends \yii\base\Formatter
{
/**
* @var string the locale ID that is used to localize the date and number formatting.
* If not set, [[\yii\base\Application::language]] will be used.
*/
public $locale;
/**
* @var string the default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'short';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'short';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'short';
/**
* @var array the options to be set for the NumberFormatter objects. Please refer to
*/
public $numberFormatOptions = array();
/**
* Initializes the component.
* This method will check if the "intl" PHP extension is installed and set the
* default value of [[locale]].
* @throws InvalidConfigException if the "intl" PHP extension is not installed.
*/
public function init()
{
if (!extension_loaded('intl')) {
throw new InvalidConfigException('The "intl" PHP extension is not install. It is required to format data values in localized formats.');
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
parent::init();
}
private $_dateFormats = array(
'short' => IntlDateFormatter::SHORT,
'medium' => IntlDateFormatter::MEDIUM,
'long' => IntlDateFormatter::LONG,
'full' => IntlDateFormatter::FULL,
);
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->dateFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->timeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->datetimeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a decimal number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asDecimal($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value);
}
/**
* Formats the value as a currency number.
* @param mixed $value the value to be formatted
* @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asCurrency($value, $currency = 'USD', $format = null)
{
return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency);
}
/**
* Formats the value as a percent number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asPercent($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value);
}
/**
* Formats the value as a scientific number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asScientific($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value);
}
/**
* Creates a number formatter based on the given type and format.
* @param integer $type the type of the number formatter
* @param string $format the format to be used
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
{
$formatter = new NumberFormatter($this->locale, $type);
if ($format !== null) {
$formatter->setPattern($format);
}
if (!empty($this->numberFormatOptions)) {
foreach ($this->numberFormatOptions as $name => $attribute) {
$formatter->setAttribute($name, $attribute);
}
}
return $formatter;
}
}

View File

@@ -43,6 +43,6 @@ return array(
'mandatory' => false,
'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator.'
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.'
),
);

View File

@@ -8,6 +8,8 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\base\InlineAction;
/**
* Controller is the base class of Web controllers.
@@ -18,6 +20,48 @@ use Yii;
*/
class Controller extends \yii\base\Controller
{
/**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param \yii\base\Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with.
* @throws HttpException if there are missing parameters.
*/
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
}
/**
* Creates a URL using the given route and parameters.
*

View File

@@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\ActionEvent;
use yii\base\Behavior;
use yii\base\HttpException;
/**
* VerbFilter is an action filter that filters by HTTP request methods.
*
* It allows to define allowed HTTP request methods for each action and will throw
* an HTTP 405 error when the method is not allowed.
*
* To use VerbFilter, declare it in the `behaviors()` method of your controller class.
* For example, the following declarations will define a typical set of allowed
* request methods for REST CRUD actions.
*
* ~~~
* public function behaviors()
* {
* return array(
* 'verbs' => array(
* 'class' => \yii\web\VerbFilter::className(),
* 'actions' => array(
* 'index' => array('get'),
* 'view' => array('get'),
* 'create' => array('get', 'post'),
* 'update' => array('get', 'put', 'post'),
* 'delete' => array('post', 'delete'),
* ),
* ),
* );
* }
* ~~~
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class VerbFilter extends Behavior
{
/**
* @var array this property defines the allowed request methods for each action.
* For each action that should only support limited set of request methods
* you add an entry with the action id as array key and an array of
* allowed methods (e.g. GET, HEAD, PUT) as the value.
* If an action is not listed all request methods are considered allowed.
*/
public $actions = array();
/**
* Declares event handlers for the [[owner]]'s events.
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
return array(
Controller::EVENT_BEFORE_ACTION => 'beforeAction',
);
}
/**
* @param ActionEvent $event
* @return boolean
* @throws \yii\base\HttpException when the request method is not allowed.
*/
public function beforeAction($event)
{
$action = $event->action->id;
if (isset($this->actions[$action])) {
$verb = Yii::$app->getRequest()->getRequestMethod();
$allowed = array_map('strtoupper', $this->actions[$action]);
if (!in_array($verb, $allowed)) {
$event->isValid = false;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
header('Allow: ' . implode(', ', $allowed));
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
}
}
return $event->isValid;
}
}

View File

@@ -134,8 +134,8 @@ class ActiveForm extends Widget
$id = $this->options['id'];
$options = Json::encode($this->getClientOptions());
$attributes = Json::encode($this->attributes);
$this->view->registerAssetBundle('yii/form');
$this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
$this->getView()->registerAssetBundle('yii/form');
$this->getView()->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
}
echo Html::endForm();
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Widget;
use yii\base\Model;
use yii\base\InvalidConfigException;
/**
* InputWidget is the base class for widgets that collect user inputs.
*
* An input widget can be associated with a data model and an attribute,
* or a name and a value. If the former, the name and the value will
* be generated automatically.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InputWidget extends Widget
{
/**
* @var Model the data model that this widget is associated with.
*/
public $model;
/**
* @var string the model attribute that this widget is associated with.
*/
public $attribute;
/**
* @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
*/
public $name;
/**
* @var string the input value.
*/
public $value;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
if (!$this->hasModel() && $this->name === null) {
throw new InvalidConfigException("Either 'name' or 'model' and 'attribute' properties must be specified.");
}
parent::init();
}
/**
* @return boolean whether this widget is associated with a data model.
*/
protected function hasModel()
{
return $this->model instanceof Model && $this->attribute !== null;
}
}

View File

@@ -0,0 +1,136 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* MaskedInput generates a masked text input.
*
* MaskedInput is similar to [[Html::textInput()]] except that
* an input mask will be used to force users to enter properly formatted data,
* such as phone numbers, social security numbers.
*
* To use MaskedInput, you must set the [[mask]] property. The following example
* shows how to use MaskedInput to collect phone numbers:
*
* ~~~
* echo MaskedInput::widget(array(
* 'name' => 'phone',
* 'mask' => '999-999-9999',
* ));
* ~~~
*
* The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class MaskedInput extends InputWidget
{
/**
* @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
*
* - `a`: represents an alpha character (A-Z,a-z)
* - `9`: represents a numeric character (0-9)
* - `*`: represents an alphanumeric character (A-Z,a-z,0-9)
* - `?`: anything listed after '?' within the mask is considered optional user input
*
* Additional characters can be defined by specifying the [[charMap]] property.
*/
public $mask;
/**
* @var array the mapping between mask characters and the corresponding patterns.
* For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input.
* Defaults to null, meaning using the map as described in [[mask]].
*/
public $charMap;
/**
* @var string the character prompting for user input. Defaults to underscore '_'.
*/
public $placeholder;
/**
* @var string a JavaScript function callback that will be invoked when user finishes the input.
*/
public $completed;
/**
* @var array the HTML attributes for the input tag.
*/
public $options = array();
/**
* Initializes the widget.
* @throws InvalidConfigException if the "mask" property is not set.
*/
public function init()
{
parent::init();
if (empty($this->mask)) {
throw new InvalidConfigException('The "mask" property must be set.');
}
if (!isset($this->options['id'])) {
$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
}
/**
* Runs the widget.
*/
public function run()
{
if ($this->hasModel()) {
echo Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
echo Html::textInput($this->name, $this->value, $this->options);
}
$this->registerClientScript();
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$options = $this->getClientOptions();
$options = empty($options) ? '' : ',' . Json::encode($options);
$js = '';
if (is_array($this->charMap) && !empty($this->charMap)) {
$js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
}
$id = $this->options['id'];
$js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
$this->getView()->registerAssetBundle('yii/maskedinput');
$this->getView()->registerJs($js);
}
/**
* @return array the options for the text field
*/
protected function getClientOptions()
{
$options = array();
if ($this->placeholder !== null) {
$options['placeholder'] = $this->placeholder;
}
if ($this->completed !== null) {
if ($this->completed instanceof JsExpression) {
$options['completed'] = $this->completed;
} else {
$options['completed'] = new JsExpression($this->completed);
}
}
return $options;
}
}