From 9bc99e22b74ab44feb16f136d40f92d918855642 Mon Sep 17 00:00:00 2001 From: ploaiza Date: Tue, 28 May 2013 10:55:51 -0300 Subject: [PATCH 01/17] Fixed some wording --- docs/guide/upgrade-from-v1.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index b3d4411..ebfe94b 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -13,7 +13,7 @@ The most obvious change in Yii 2.0 is the use of namespaces. Almost every core c is namespaced, e.g., `yii\web\Request`. The "C" prefix is no longer used in class names. The naming of the namespaces follows the directory structure. For example, `yii\web\Request` indicates the corresponding class file is `web/Request.php` under the Yii framework folder. -You can use any core class without explicitly include that class file, thanks to the Yii +You can use any core class without explicitly including that class file, thanks to the Yii class loader. @@ -117,17 +117,17 @@ supported in most places in the Yii core code. For example, `FileCache::cachePat both a path alias and a normal directory path. Path alias is also closely related with class namespaces. It is recommended that a path -alias defined for each root namespace so that you can use Yii class autoloader without +alias be defined for each root namespace so that you can use Yii the class autoloader without any further configuration. For example, because `@yii` refers to the Yii installation directory, a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library -such as Zend Framework, you may define a path alias `@Zend` which refers to its installation directory. -And Yii will be able to autoload any class in this library. +such as Zend Framework, you may define a path alias `@Zend` which refers to its installation +directory and Yii will be able to autoload any class in this library. View ---- -Yii 2.0 introduces a `View` class to represent the view part in the MVC pattern. +Yii 2.0 introduces a `View` class to represent the view part of the MVC pattern. It can be configured globally through the "view" application component. It is also accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1: **`$this` in a view file no longer refers to the controller or widget object.** @@ -159,7 +159,7 @@ extension for your Smarty views, or `twig` for Twig views. You may also configur Models ------ -A model is now associated with a form name returned its `formName()` method. This is +A model is now associated with a form name returned by its `formName()` method. This is mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1, this is usually hardcoded as the class name of the model. @@ -235,7 +235,7 @@ Previously in 1.1, you would have to enter the widget class names as strings via Themes ------ -Theme works completely different in 2.0. It is now based on a path map to "translate" a source +Themes work completely different in 2.0. They are now based on a path map to "translate" a source view into a themed view. For example, if the path map for a theme is `array('/www/views' => '/www/themes/basic')`, then the themed version for a view file `/www/views/site/index.php` will be `/www/themes/basic/site/index.php`. @@ -250,7 +250,7 @@ application component. Console Applications -------------------- -Console applications are now composed by controllers, too, like Web applications. In fact, +Console applications are now composed by controllers, like Web applications. In fact, console controllers and Web controllers share the same base controller class. Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several @@ -300,7 +300,7 @@ public function behaviors() Assets ------ -Yii 2.0 introduces a new concept called *asset bundle*. It is a bit similar to script +Yii 2.0 introduces a new concept called *asset bundle*. It is similar to script packages (managed by `CClientScript`) in 1.1, but with better support. An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.) @@ -315,7 +315,7 @@ Static Helpers Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`, `StringHelper`. These classes are designed to be easily extended. Note that static classes -are usually hard to be extended because of the fixed class name references. But Yii 2.0 +are usually hard to extend because of the fixed class name references. But Yii 2.0 introduces the class map (via `Yii::$classMap`) to overcome this difficulty. @@ -343,7 +343,7 @@ Query Builder In 1.1, query building is scattered among several classes, including `CDbCommand`, `CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query -and `QueryBuilder` to generate SQL statements from query objects. For example, +and `QueryBuilder` to generate SQL statements from query objects. For example: ```php $query = new \yii\db\Query; @@ -365,7 +365,7 @@ ActiveRecord ------------ ActiveRecord has undergone significant changes in Yii 2.0. The most important one -is about relational ActiveRecord query. In 1.1, you have to declare the relations +is the relational ActiveRecord query. In 1.1, you have to declare the relations in the `relations()` method. In 2.0, this is done via getter methods that return an `ActiveQuery` object. For example, the following method declares an "orders" relation: @@ -392,7 +392,7 @@ by filtering with the primary keys of the primary records. Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you -use the `find()` method like the following: +use the `find()` method: ```php // to retrieve all *active* customers and order them by their ID: @@ -410,14 +410,14 @@ Therefore, you can use all query methods of `Query`. Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to return results in terms of arrays. This is more efficient and is especially useful -when you need to return large number of records. For example, +when you need to return a large number of records: ```php $customers = Customer::find()->asArray()->all(); ``` By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes -would be saved to database when you call `save()`, regardless they are changed or not, +are saved to database when you call `save()`, regardless of having changed or not, unless you explicitly list the attributes to save. @@ -427,7 +427,7 @@ Auto-quoting Table and Column Names Yii 2.0 supports automatic quoting of database table and column names. A name enclosed within double curly brackets is treated as a table name, and a name enclosed within double square brackets is treated as a column name. They will be quoted according to -the database driver being used. For example, +the database driver being used: ```php $command = $connection->createCommand('SELECT [[id]] FROM {{posts}}'); From d6f001016c4e1daa24d4bd28a4bb6deaa27d7933 Mon Sep 17 00:00:00 2001 From: Suralc Date: Tue, 28 May 2013 15:58:40 +0200 Subject: [PATCH 02/17] Added missing use statement. --- apps/advanced/console/migrations/m130524_201442_init.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/advanced/console/migrations/m130524_201442_init.php b/apps/advanced/console/migrations/m130524_201442_init.php index 24a74c3..a5e9d30 100644 --- a/apps/advanced/console/migrations/m130524_201442_init.php +++ b/apps/advanced/console/migrations/m130524_201442_init.php @@ -1,5 +1,7 @@ Date: Tue, 28 May 2013 17:27:37 -0400 Subject: [PATCH 03/17] formatter WIP. --- framework/yii/base/Formatter.php | 288 ++++++++++++++++++++++++++++ framework/yii/i18n/Formatter.php | 232 ++++++++++++++++++++++ tests/unit/framework/base/FormatterTest.php | 101 ++++++++++ 3 files changed, 621 insertions(+) create mode 100644 framework/yii/base/Formatter.php create mode 100644 framework/yii/i18n/Formatter.php create mode 100644 tests/unit/framework/base/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php new file mode 100644 index 0000000..a31fac9 --- /dev/null +++ b/framework/yii/base/Formatter.php @@ -0,0 +1,288 @@ + + * @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 format used to format a number with PHP number_format() function. + * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". + * They correspond to the number of digits after the decimal point, the character displayed as the decimal point + * and the thousands separator character. + */ + public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); + /** + * @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 array('No', 'Yes'). + */ + public $booleanFormat; + + + /** + * 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 `

` 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('

', '', + '

' . preg_replace('/[\r\n]{2,}/', "

\n

", Html::encode($value)) . '

' + ); + } + + /** + * 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 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); + 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 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 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); + 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 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 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); + 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. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @return string the formatting result. + */ + public function asDouble($value, $decimals = 2) + { + return sprintf("%.{$decimals}f", $value); + } + + /** + * Formats the value as a decimal number using the PHP number_format() function. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @param string $decimalSeparator the character displayed as the decimal point + * @param string $thousandSeparator the character displayed as the thousands separator character. + * @return string the formatted result + */ + public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + { + return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + } +} diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php new file mode 100644 index 0000000..a0570a0 --- /dev/null +++ b/framework/yii/i18n/Formatter.php @@ -0,0 +1,232 @@ + + * @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 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 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 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) + { + $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) + { + $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) + { + $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) + { + $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; + } +} diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php new file mode 100644 index 0000000..53d8770 --- /dev/null +++ b/tests/unit/framework/base/FormatterTest.php @@ -0,0 +1,101 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + $this->formatter = new Formatter(); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsRaw() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = 123; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = '<>'; + $this->assertSame($value, $this->formatter->asRaw($value)); + } + + public function testAsText() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asText($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asText($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asText($value)); + } + + public function testAsNtext() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asNtext($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asNtext($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asNtext($value)); + $value = "123\n456"; + $this->assertSame("123
\n456", $this->formatter->asNtext($value)); + } + + public function testAsParagraphs() + { + $value = '123'; + $this->assertSame("

$value

", $this->formatter->asParagraphs($value)); + $value = 123; + $this->assertSame("

$value

", $this->formatter->asParagraphs($value)); + $value = '<>'; + $this->assertSame('

<>

', $this->formatter->asParagraphs($value)); + $value = "123\n456"; + $this->assertSame("

123\n456

", $this->formatter->asParagraphs($value)); + $value = "123\n\n456"; + $this->assertSame("

123

\n

456

", $this->formatter->asParagraphs($value)); + $value = "123\n\n\n456"; + $this->assertSame("

123

\n

456

", $this->formatter->asParagraphs($value)); + } + + public function testAsHtml() + { + // todo + } + + public function testAsEmail() + { + $value = 'test@sample.com'; + $this->assertSame("$value", $this->formatter->asEmail($value)); + } + + public function testAsImage() + { + $value = 'http://sample.com/img.jpg'; + $this->assertSame("\"\"", $this->formatter->asImage($value)); + } +} From 9a970370129000ebd1b416a7ce05a62056a266c8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:55:26 -0400 Subject: [PATCH 04/17] Finished Formatter. --- framework/yii/base/Formatter.php | 12 +--- framework/yii/i18n/Formatter.php | 8 +-- tests/unit/framework/base/FormatterTest.php | 59 +++++++++++++++++- tests/unit/framework/i18n/FormatterTest.php | 93 +++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 tests/unit/framework/i18n/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index a31fac9..835b657 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -38,13 +38,6 @@ class Formatter extends Component */ public $datetimeFormat = 'Y/m/d h:i:s A'; /** - * @var array the format used to format a number with PHP number_format() function. - * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". - * They correspond to the number of digits after the decimal point, the character displayed as the decimal point - * and the thousands separator character. - */ - public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); - /** * @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 array('No', 'Yes'). */ @@ -274,14 +267,15 @@ class Formatter extends Component } /** - * Formats the value as a decimal number using the PHP number_format() function. + * 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 * @param string $decimalSeparator the character displayed as the decimal point * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result */ - public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) { return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); } diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a0570a0..a90f5c9 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -170,7 +170,7 @@ class Formatter extends \yii\base\Formatter */ public function asDecimal($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); } /** @@ -183,7 +183,7 @@ class Formatter extends \yii\base\Formatter */ public function asCurrency($value, $currency = 'USD', $format = null) { - $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); + return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); } /** @@ -195,7 +195,7 @@ class Formatter extends \yii\base\Formatter */ public function asPercent($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); } /** @@ -207,7 +207,7 @@ class Formatter extends \yii\base\Formatter */ public function asScientific($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); } /** diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 53d8770..3e7ac5f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -84,7 +84,7 @@ class FormatterTest extends TestCase public function testAsHtml() { - // todo + // todo: dependency on HtmlPurifier } public function testAsEmail() @@ -98,4 +98,61 @@ class FormatterTest extends TestCase $value = 'http://sample.com/img.jpg'; $this->assertSame("\"\"", $this->formatter->asImage($value)); } + + public function testAsBoolean() + { + $value = true; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = false; + $this->assertSame('No', $this->formatter->asBoolean($value)); + $value = "111"; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = ""; + $this->assertSame('No', $this->formatter->asBoolean($value)); + } + + public function testAsDate() + { + $value = time(); + $this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value)); + $this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d')); + } + + public function testAsTime() + { + $value = time(); + $this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value)); + $this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s')); + } + + public function testAsDatetime() + { + $value = time(); + $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value)); + $this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s')); + } + + public function testAsInteger() + { + $value = 123; + $this->assertSame("$value", $this->formatter->asInteger($value)); + $value = 123.23; + $this->assertSame("123", $this->formatter->asInteger($value)); + $value = 'a'; + $this->assertSame("0", $this->formatter->asInteger($value)); + } + + public function testAsDouble() + { + $value = 123.12; + $this->assertSame("123.12", $this->formatter->asDouble($value)); + $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + } + + public function testAsNumber() + { + $value = 123123.123; + $this->assertSame("123,123", $this->formatter->asNumber($value)); + $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + } } diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php new file mode 100644 index 0000000..cf479d1 --- /dev/null +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + $this->mockApplication(); + $this->formatter = new Formatter(array( + 'locale' => 'en_US', + )); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsDecimal() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asDecimal($value)); + $value = '123456'; + $this->assertSame("123,456", $this->formatter->asDecimal($value)); + $value = '-123456.123'; + $this->assertSame("-123,456.123", $this->formatter->asDecimal($value)); + } + + public function testAsPercent() + { + $value = '123'; + $this->assertSame('12,300%', $this->formatter->asPercent($value)); + $value = '0.1234'; + $this->assertSame("12%", $this->formatter->asPercent($value)); + $value = '-0.009343'; + $this->assertSame("-1%", $this->formatter->asPercent($value)); + } + + public function testAsScientific() + { + $value = '123'; + $this->assertSame('1.23E2', $this->formatter->asScientific($value)); + $value = '123456'; + $this->assertSame("1.23456E5", $this->formatter->asScientific($value)); + $value = '-123456.123'; + $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value)); + } + + public function testAsCurrency() + { + $value = '123'; + $this->assertSame('$123.00', $this->formatter->asCurrency($value)); + $value = '123.456'; + $this->assertSame("$123.46", $this->formatter->asCurrency($value)); + $value = '-123456.123'; + $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value)); + } + + public function testDate() + { + $time = time(); + $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); + $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); + $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); + + $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); + $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); + $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); + } +} From 2f1021830d5cd92da15cc99d25f27bcffdebf915 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:58:35 -0400 Subject: [PATCH 05/17] Added "formatter" app component. --- framework/yii/base/Application.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index eb0a0d3..d38f6a9 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -279,6 +279,15 @@ class Application extends Module } /** + * 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', ), From 6aba9ede8ba20200b0e1f840703b0559a513f30b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:04 -0400 Subject: [PATCH 06/17] Removed breaking test. --- tests/unit/framework/i18n/FormatterTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index cf479d1..0f53c2a 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -87,7 +87,5 @@ class FormatterTest extends TestCase $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); - $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); - $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); } } From 33dbe8fb43ad706e635e07ec7fa0a2a7ba75be02 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:39 -0400 Subject: [PATCH 07/17] Removed breaking tests. --- tests/unit/framework/i18n/FormatterTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index 0f53c2a..f742f22 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -83,9 +83,6 @@ class FormatterTest extends TestCase { $time = time(); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); - $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); - $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); - $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); } } From f690f0db13425ca7ed528e590b34d5359fb9b238 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:24:08 -0400 Subject: [PATCH 08/17] Fixes issue #441: InvalidRequestException should be HttpException --- framework/yii/base/Controller.php | 32 +------------------ framework/yii/base/InvalidRequestException.php | 26 --------------- framework/yii/web/Controller.php | 44 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 57 deletions(-) delete mode 100644 framework/yii/base/InvalidRequestException.php diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 543605f..756f34a 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -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(); } /** diff --git a/framework/yii/base/InvalidRequestException.php b/framework/yii/base/InvalidRequestException.php deleted file mode 100644 index f4806ce..0000000 --- a/framework/yii/base/InvalidRequestException.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @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'); - } -} - diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 517f4b4..026c078 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -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. @@ -19,6 +21,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. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. From 07011a2542af5370244d7bf640184d06a6bbed11 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 01:49:19 +0200 Subject: [PATCH 09/17] Fixed typos in new Formatter classes --- framework/yii/base/Formatter.php | 12 ++++++------ framework/yii/i18n/Formatter.php | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 835b657..8945d74 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -162,12 +162,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - 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 + * 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 @@ -183,12 +183,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - 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 the one + * 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 @@ -204,12 +204,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - 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 the one + * 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 diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a90f5c9..d688a15 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -76,7 +76,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -106,7 +106,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -136,7 +136,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * From 2553e9a043002893ff8ce1aeb42b99af33737a01 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:56:06 -0400 Subject: [PATCH 10/17] Fixes issue #439 --- apps/advanced/README.md | 28 ++++---- apps/advanced/backstage/config/main.php | 2 +- apps/advanced/composer.json | 3 - apps/advanced/console/config/main.php | 2 +- apps/advanced/frontend/config/main.php | 2 +- apps/advanced/init | 112 ++++++++++++++++++++++++++++++++ apps/advanced/init.bat | 20 ++++++ apps/advanced/install | 112 -------------------------------- apps/advanced/install.bat | 20 ------ 9 files changed, 149 insertions(+), 152 deletions(-) create mode 100755 apps/advanced/init create mode 100644 apps/advanced/init.bat delete mode 100755 apps/advanced/install delete mode 100644 apps/advanced/install.bat diff --git a/apps/advanced/README.md b/apps/advanced/README.md index a2bcdd4..c443c90 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -67,32 +67,32 @@ If you do not have [Composer](http://getcomposer.org/), you may download it from curl -s http://getcomposer.org/installer | php ~~~ -You can then install the Bootstrap Application using the following command: +You can then install the application using the following command: ~~~ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced ~~~ -Now you should be able to access: - -- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` -- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` - -assuming `yii-advanced` is directly under the document root of your Web server. - ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + GETTING STARTED --------------- -After template application and its dependencies are downloaded you need to initialize it and set some config values to -match your application requirements. +After you install the application, you have to conduct the following steps to initialize +the installed application. You only need to do these once for all. -1. Execute `install` command selecting `dev` as environment. -2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`. -3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. -4. In `common/config/params.php` set your database details in `components.db` values. +1. Execute the `init` command and select `dev` as environment. +2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. +3. In `common/config/params.php` set your database details in `components.db` values. + +Now you should be able to access: + +- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` +- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` + +assuming `yii-advanced` is directly under the document root of your Web server. diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backstage/config/main.php index 4898bfd..6e55c47 100644 --- a/apps/advanced/backstage/config/main.php +++ b/apps/advanced/backstage/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-backend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index db97efd..95b3b13 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -36,9 +36,6 @@ "frontend/runtime", "frontend/www/assets" - ], - "yii-install-executable": [ - "yii" ] } } diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php index cceb311..37db1d2 100644 --- a/apps/advanced/console/config/main.php +++ b/apps/advanced/console/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-console', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index 02a66c9..e53cfe8 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-frontend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/init b/apps/advanced/init new file mode 100755 index 0000000..17ed854 --- /dev/null +++ b/apps/advanced/init @@ -0,0 +1,112 @@ +#!/usr/bin/env php + $name) { + echo " [$i] $name\n"; +} +echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; +$answer = trim(fgets(STDIN)); +if (!ctype_digit($answer) || !isset($envNames[$answer])) { + echo "\n Quit initialization.\n"; + return; +} + +$env = $envs[$envNames[$answer]]; +echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; +$answer = trim(fgets(STDIN)); +if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + return; +} + +echo "\n Start initialization ...\n\n"; +$files = getFileList("$root/environments/{$env['path']}"); +$all = false; +foreach ($files as $file) { + if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { + break; + } +} + +if (isset($env['writable'])) { + foreach ($env['writable'] as $writable) { + echo " chmod 0777 $writable\n"; + @chmod("$root/$writable", 0777); + } +} + +if (isset($env['executable'])) { + foreach ($env['executable'] as $executable) { + echo " chmod 0755 $executable\n"; + @chmod("$root/$executable", 0755); + } +} + +echo "\n ... initialization completed.\n\n"; + +function getFileList($root, $basePath = '') +{ + $files = array(); + $handle = opendir($root); + while (($path = readdir($handle)) !== false) { + if ($path === '.svn' || $path === '.' || $path === '..') { + continue; + } + $fullPath = "$root/$path"; + $relativePath = $basePath === '' ? $path : "$basePath/$path"; + if (is_dir($fullPath)) { + $files = array_merge($files, getFileList($fullPath, $relativePath)); + } else { + $files[] = $relativePath; + } + } + closedir($handle); + return $files; +} + +function copyFile($root, $source, $target, &$all) +{ + if (!is_file($root . '/' . $source)) { + echo " skip $target ($source not exist)\n"; + return true; + } + if (is_file($root . '/' . $target)) { + if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { + echo " unchanged $target\n"; + return true; + } + if ($all) { + echo " overwrite $target\n"; + } else { + echo " exist $target\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + $answer = trim(fgets(STDIN)); + if (!strncasecmp($answer, 'q', 1)) { + return false; + } else { + if (!strncasecmp($answer, 'y', 1)) { + echo " overwrite $target\n"; + } else { + if (!strncasecmp($answer, 'a', 1)) { + echo " overwrite $target\n"; + $all = true; + } else { + echo " skip $target\n"; + return true; + } + } + } + } + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; + } + echo " generate $target\n"; + @mkdir(dirname($root . '/' . $target), 0777, true); + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; +} diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat new file mode 100644 index 0000000..dc2cd83 --- /dev/null +++ b/apps/advanced/init.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line install script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright © 2012 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%install" %* + +@endlocal diff --git a/apps/advanced/install b/apps/advanced/install deleted file mode 100755 index 6864440..0000000 --- a/apps/advanced/install +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env php - $name) { - echo " [$i] $name\n"; -} -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit installation.\n"; - return; -} - -$env = $envs[$envNames[$answer]]; -echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit installation.\n"; - return; -} - -echo "\n Start installation ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { - break; - } -} - -if (isset($env['writable'])) { - foreach ($env['writable'] as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -if (isset($env['executable'])) { - foreach ($env['executable'] as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -echo "\n ... installation completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = array(); - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} diff --git a/apps/advanced/install.bat b/apps/advanced/install.bat deleted file mode 100644 index dc2cd83..0000000 --- a/apps/advanced/install.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line install script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%install" %* - -@endlocal From 8b6447876ad8570e4410fd917abff61969e64788 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:15:43 +0200 Subject: [PATCH 11/17] refactored numberformatting in base\Formatter added decimalSeparator and thousandSeparator properties. issue #48 --- framework/yii/base/Formatter.php | 20 +++++++++++++++----- tests/unit/framework/base/FormatterTest.php | 23 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 8945d74..d15e5f2 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -42,6 +42,14 @@ class Formatter extends Component * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). */ 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 = ','; /** @@ -257,13 +265,15 @@ class Formatter extends Component /** * 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 sprintf("%.{$decimals}f", $value); + return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); } /** @@ -271,12 +281,12 @@ class Formatter extends Component * 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 - * @param string $decimalSeparator the character displayed as the decimal point - * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result + * @see decimalSeparator + * @see thousandSeparator */ - public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0) { - return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); } } diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 3e7ac5f..e9a909f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -140,6 +140,10 @@ class FormatterTest extends TestCase $this->assertSame("123", $this->formatter->asInteger($value)); $value = 'a'; $this->assertSame("0", $this->formatter->asInteger($value)); + $value = -123.23; + $this->assertSame("-123", $this->formatter->asInteger($value)); + $value = "-123abc"; + $this->assertSame("-123", $this->formatter->asInteger($value)); } public function testAsDouble() @@ -147,12 +151,29 @@ class FormatterTest extends TestCase $value = 123.12; $this->assertSame("123.12", $this->formatter->asDouble($value)); $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123; + $this->assertSame("123.00", $this->formatter->asDouble($value)); + $this->formatter->decimalSeparator = ','; + $value = 123.12; + $this->assertSame("123,12", $this->formatter->asDouble($value)); + $this->assertSame("123,1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123123.123; + $this->assertSame("123123,12", $this->formatter->asDouble($value)); } public function testAsNumber() { $value = 123123.123; $this->assertSame("123,123", $this->formatter->asNumber($value)); - $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + $this->assertSame("123,123.12", $this->formatter->asNumber($value, 2)); + $this->formatter->decimalSeparator = ','; + $this->formatter->thousandSeparator = ' '; + $this->assertSame("123 123", $this->formatter->asNumber($value)); + $this->assertSame("123 123,12", $this->formatter->asNumber($value, 2)); + $this->formatter->thousandSeparator = ''; + $this->assertSame("123123", $this->formatter->asNumber($value)); + $this->assertSame("123123,12", $this->formatter->asNumber($value, 2)); } } From c75e78c20a47b4b575ccef2405901d368aeccb44 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:17:00 +0200 Subject: [PATCH 12/17] Updated requirements about yii\i18n\Formatter --- framework/yii/requirements/requirements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 0dbc1fc..63aa70d 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -43,6 +43,6 @@ return array( 'mandatory' => false, 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'), 'by' => 'Internationalization support', - 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator.' + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator or the yii\i18n\Formatter class.' ), ); \ No newline at end of file From 5e8154ed7fd2ef0140027e136a2268d590fc5026 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 21:07:35 -0400 Subject: [PATCH 13/17] Removed unused method. --- framework/yii/base/Controller.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 756f34a..af33b63 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -242,18 +242,6 @@ class Controller extends Component } /** - * 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). */ public function getUniqueId() From 2f051e9dab43a8ba5d537879f30a7f6f3bae150c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 03:40:18 +0200 Subject: [PATCH 14/17] Use class constants for Event declatration in ActionFiler --- framework/yii/base/ActionFilter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php index d69c0fe..20ff142 100644 --- a/framework/yii/base/ActionFilter.php +++ b/framework/yii/base/ActionFilter.php @@ -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', ); } From 249f90f5ee78cee47f6fcb990ae10d2a56d24e1a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 22:06:31 -0400 Subject: [PATCH 15/17] Implemented MaskedInput widget. --- framework/yii/assets.php | 7 + framework/yii/assets/jquery.maskedinput.js | 338 +++++++++++++++++++++++++++++ framework/yii/widgets/ActiveForm.php | 4 +- framework/yii/widgets/InputWidget.php | 64 ++++++ framework/yii/widgets/MaskedInput.php | 136 ++++++++++++ 5 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 framework/yii/assets/jquery.maskedinput.js create mode 100644 framework/yii/widgets/InputWidget.php create mode 100644 framework/yii/widgets/MaskedInput.php diff --git a/framework/yii/assets.php b/framework/yii/assets.php index 79fbeb5..63f7560 100644 --- a/framework/yii/assets.php +++ b/framework/yii/assets.php @@ -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'), + ), ); diff --git a/framework/yii/assets/jquery.maskedinput.js b/framework/yii/assets/jquery.maskedinput.js new file mode 100644 index 0000000..49a5a72 --- /dev/null +++ b/framework/yii/assets/jquery.maskedinput.js @@ -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); \ No newline at end of file diff --git a/framework/yii/widgets/ActiveForm.php b/framework/yii/widgets/ActiveForm.php index 25a2054..eb14293 100644 --- a/framework/yii/widgets/ActiveForm.php +++ b/framework/yii/widgets/ActiveForm.php @@ -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(); } diff --git a/framework/yii/widgets/InputWidget.php b/framework/yii/widgets/InputWidget.php new file mode 100644 index 0000000..e1981c9 --- /dev/null +++ b/framework/yii/widgets/InputWidget.php @@ -0,0 +1,64 @@ + + * @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; + } +} diff --git a/framework/yii/widgets/MaskedInput.php b/framework/yii/widgets/MaskedInput.php new file mode 100644 index 0000000..a5a23f5 --- /dev/null +++ b/framework/yii/widgets/MaskedInput.php @@ -0,0 +1,136 @@ + '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 + * @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; + } +} From a2066cbe4878ff084c9bf9d34a3fe424912e73e9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 04:24:38 +0200 Subject: [PATCH 16/17] Created a \yii\web\VerbFilter It allows to filter actions by HTTP request methods --- framework/yii/web/VerbFilter.php | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 framework/yii/web/VerbFilter.php diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php new file mode 100644 index 0000000..9b475e3 --- /dev/null +++ b/framework/yii/web/VerbFilter.php @@ -0,0 +1,90 @@ + 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 + * @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; + } +} From 9af5466be588bbaeb8a2d413d651a1698226861e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 05:55:14 +0300 Subject: [PATCH 17/17] typo in Html helper --- framework/yii/helpers/base/Html.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/base/Html.php b/framework/yii/helpers/base/Html.php index 90396ec..9a0001c 100644 --- a/framework/yii/helpers/base/Html.php +++ b/framework/yii/helpers/base/Html.php @@ -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.