Browse Source

Merge branch 'master' into 13379-js-tests

tags/2.0.12
Alexey Rogachev 8 years ago
parent
commit
058d2f6ef6
  1. 44
      docs/guide/security-best-practices.md
  2. 4
      framework/CHANGELOG.md
  3. 8
      framework/base/Security.php
  4. 7
      framework/helpers/BaseHtml.php
  5. 19
      framework/helpers/BaseStringHelper.php
  6. 199
      framework/messages/tg/yii.php
  7. 7
      tests/framework/helpers/HtmlTest.php
  8. 2
      tests/framework/helpers/StringHelperTest.php

44
docs/guide/security-best-practices.md

@ -2,6 +2,9 @@ Security best practices
=======================
Below we'll review common security principles and describe how to avoid threats when developing applications using Yii.
Most of these priciples are not unique to Yii alone but apply to website or software development in general,
so we you will also find links for further reading on the general ideas behind these.
Basic principles
----------------
@ -28,6 +31,11 @@ if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
In Yii, most probably you'll use [form validation](input-validation.md) to do alike checks.
Further reading on the topic:
- <https://www.owasp.org/index.php/Data_Validation>
- <https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet>
### Escape output
@ -36,6 +44,13 @@ should escape `<`, `>` and alike special characters. In context of JavaScript or
Since it's error-prone to escape everything manually Yii provides various tools to perform escaping for different
contexts.
Further reading on the topic:
- <https://www.owasp.org/index.php/Command_Injection>
- <https://www.owasp.org/index.php/Code_Injection>
- <https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29>
Avoiding SQL injections
-----------------------
@ -100,6 +115,10 @@ $rowCount = $connection->createCommand($sql)->queryScalar();
You can get details about the syntax in [Quoting Table and Column Names](db-dao.md#quoting-table-and-column-names).
Further reading on the topic:
- <https://www.owasp.org/index.php/SQL_Injection>
Avoiding XSS
------------
@ -130,6 +149,11 @@ If it should be HTML we could get some help from HtmlPurifier:
Note that HtmlPurifier processing is quite heavy so consider adding caching.
Further reading on the topic:
- <https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29>
Avoiding CSRF
-------------
@ -185,6 +209,10 @@ class SiteController extends Controller
}
```
Further reading on the topic:
- <https://www.owasp.org/index.php/CSRF>
Avoiding file exposure
----------------------
@ -195,33 +223,41 @@ environments it could be impossible to achieve so we'll end up with all the code
If it's the case don't forget to deny access to everything except `web`. If it can't be done consider hosting your
application elsewhere.
Avoiding debug info and tools at production
Avoiding debug info and tools in production
-------------------------------------------
In debug mode Yii shows quite verbose errors which are certainly helpful for development. The thing is that these
verbose errors are handy for attacker as well since these could reveal database structure, configuration values and
parts of your code. Never run production applications with `YII_DEBUG` set to `true` in your `index.php`.
You should never enable Gii at production. It could be used to get information about database structure, code and to
You should never enable Gii or the Debug toolbar in production. It could be used to get information about database structure, code and to
simply rewrite code with what's generated by Gii.
Debug toolbar should be avoided at production unless really necessary. It exposes all the application and config
details possible. If you absolutely need it check twice that access is properly restricted to your IP only.
Further reading on the topic:
- <https://www.owasp.org/index.php/Exception_Handling>
- <https://www.owasp.org/index.php/Top_10_2007-Information_Leakage>
Using secure connection over TLS
--------------------------------
Yii provides features that rely on cookies and/or PHP sessions. These can be vulnerable in case your connection is
compromised. The risk is reduced if the app uses secure connection via TLS.
compromised. The risk is reduced if the app uses secure connection via TLS (often referred to as [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security)).
Please refer to your webserver documentation for instructions on how to configure it. You may also check example configs
provided by H5BP project:
provided by the H5BP project:
- [Nginx](https://github.com/h5bp/server-configs-nginx)
- [Apache](https://github.com/h5bp/server-configs-apache).
- [IIS](https://github.com/h5bp/server-configs-iis).
- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd).
Secure Server configuration
---------------------------

4
framework/CHANGELOG.md

@ -4,6 +4,8 @@ Yii Framework 2 Change Log
2.0.12 under development
--------------------------
- Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002)
- Bug #7946 Fixed a bug when the `form` attribute was not propagated to the hidden input of the checkbox (Kolyunya)
- Bug #13087: Fixed getting active validators for safe attribute (developeruz)
- Bug #13571: Fix `yii\db\mssql\QueryBuilder::checkIntegrity` for all tables (boboldehampsink)
- Bug #11230: Include `defaultRoles` in `yii\rbac\DbManager->getRolesByUser()` results (developeruz)
@ -35,6 +37,8 @@ Yii Framework 2 Change Log
- Enh #13221: Make `\yii\db\QueryTrait::limit()` and `\yii\db\QueryTrait::offset()` methods work with `\yii\db\Expression` (Ni-san)
- Enh #13144: Refactored `yii\db\Query::queryScalar()` (Alex-Code)
- Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire, arogachev)
- Enh #13650: Improved `yii\base\Security::hkdf()` to take advantage of native `hash_hkdf()` implementation in PHP >= 7.1.2 (charlesportwoodii)
- Bug #13379: Fixed `applyFilter` function in `yii.gridView.js` to work correctly when params in `filterUrl` are indexed (SilverFire)
2.0.11.2 February 08, 2017

8
framework/base/Security.php

@ -273,6 +273,14 @@ class Security extends Component
*/
public function hkdf($algo, $inputKey, $salt = null, $info = null, $length = 0)
{
if (function_exists('hash_hkdf')) {
$outputKey = hash_hkdf($algo, $inputKey, $length, $info, $salt);
if ($outputKey === false) {
throw new InvalidParamException('Invalid parameters to hash_hkdf()');
}
return $outputKey;
}
$test = @hash_hmac($algo, '', '', true);
if (!$test) {
throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo);

7
framework/helpers/BaseHtml.php

@ -60,6 +60,7 @@ class BaseHtml
'href',
'src',
'srcset',
'form',
'action',
'method',
@ -742,7 +743,11 @@ class BaseHtml
$value = array_key_exists('value', $options) ? $options['value'] : '1';
if (isset($options['uncheck'])) {
// add a hidden field so that if the checkbox is not selected, it still submits a value
$hidden = static::hiddenInput($name, $options['uncheck']);
$hiddenOptions = [];
if (isset($options['form'])) {
$hiddenOptions['form'] = $options['form'];
}
$hidden = static::hiddenInput($name, $options['uncheck'], $hiddenOptions);
unset($options['uncheck']);
} else {
$hidden = '';

19
framework/helpers/BaseStringHelper.php

@ -160,10 +160,8 @@ class BaseStringHelper
$truncated = [];
foreach ($tokens as $token) {
if ($token instanceof \HTMLPurifier_Token_Start) { //Tag begins
if ($totalCount < $count) {
$openTokens[$token->name] = isset($openTokens[$token->name]) ? $openTokens[$token->name] + 1 : 1;
$truncated[] = $token;
}
$openTokens[$token->name] = isset($openTokens[$token->name]) ? $openTokens[$token->name] + 1 : 1;
$truncated[] = $token;
} elseif ($token instanceof \HTMLPurifier_Token_Text && $totalCount <= $count) { //Text
if (false === $encoding) {
preg_match('/^(\s*)/um', $token->data, $prefixSpace) ?: $prefixSpace = ['',''];
@ -178,12 +176,23 @@ class BaseStringHelper
} elseif ($token instanceof \HTMLPurifier_Token_End) { //Tag ends
if (!empty($openTokens[$token->name])) {
$openTokens[$token->name]--;
if ($openTokens[$token->name] <= 0) {
unset($openTokens[$token->name]);
}
$truncated[] = $token;
}
} elseif ($token instanceof \HTMLPurifier_Token_Empty) { //Self contained tags, i.e. <img/> etc.
$truncated[] = $token;
}
if (0 === $openTokens && $totalCount >= $count) {
if ($totalCount >= $count) {
if (0 < count($openTokens)) {
foreach (array_reverse($openTokens) as $name => $countTag) {
while ($countTag > 0) {
$truncated[] = new \HTMLPurifier_Token_End($name);
$countTag--;
}
}
}
break;
}
}

199
framework/messages/tg/yii.php

@ -1,114 +1,129 @@
<?php
/**
* Message translations.
*
* This file is automatically generated by 'yii message' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE: this file must be saved in UTF-8 encoding.
*/
* Message translations.
*
* This file is automatically generated by 'yii message/extract' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE: this file must be saved in UTF-8 encoding.
*/
return [
'Powered by {yii}' => 'Дар {yii} кор мекунад',
'Unknown alias: -{name}' => 'Тахаллуси номаълум: -{name}',
'Yii Framework' => 'Yii Framework',
'(not set)' => '(супориш дода нашуд)',
' and ' => ' ва ',
'An internal server error occurred.' => 'Хатои дохилии сервер рух дод.',
'Are you sure you want to delete this item?' => 'Оё шумо дар ҳақиқат мехоҳед, ки ин элементро нест кунед?',
'Error' => 'Иштибоҳ',
'File upload failed.' => 'Фарокашии файл. имконнопазир гашт.',
'Home' => 'Саҳифаи асосӣ',
'Invalid data received for parameter "{param}".' => 'Маънои нодурусти параметри "{param}".',
'Login Required' => 'Вуруд талаб карда мешавад.',
'Missing required arguments: {params}' => 'Далелҳои лозимӣ вуҷуд надоранд: {params}',
'Missing required parameters: {params}' => 'Параметрҳои лозимӣ вуҷуд надоранд: {params}',
'No' => 'Не',
'No results found.' => 'Ҳеҷ чиз ёфт нашуд.',
'Only files with these MIME types are allowed: {mimeTypes}.' => 'Барои фарокашии файлҳо танҳо бо намудҳои зерини MIME иҷозат аст: {mimeTypes}.',
'Only files with these extensions are allowed: {extensions}.' => 'Барои фарокашии файлҳо танҳо тавассути зиёдкуни зерин иҷозат аст: {extensions}.',
'Page not found.' => 'Саҳифа ёфт нашуд.',
'Please fix the following errors:' => 'Лутфан, хатогиҳои зеринро ислоҳ намоед:',
'Please upload a file.' => 'Лутфан, файлро бор кунед.',
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => 'Қайдҳо нишон дода шудаанд <b>{begin, number}-{end, number}</b> аз <b>{totalCount, number}</b>.',
'The combination {values} of {attributes} has already been taken.' => 'Комбинатсияи {values} параметрҳо {attributes} аллакай вуҷуд дорад.',
'The file "{file}" is not an image.' => 'Файли "{file}" тасвир нест.',
'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Ҳаҷми файли "{file}" азҳад зиёд калон аст. Андозаи он набояд аз {formattedLimit} зиёдтар бошад.',
'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Ҳаҷми файли "{file}" аз ҳад зиёд хурд аст. Он бояд аз {formattedLimit} калонтар бошад.',
'The format of {attribute} is invalid.' => 'Формати нодурусти маънӣ {attribute}.',
'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ҳаҷми файли "{file}" аз ҳад зиёд калон аст. Баландияш набояд аз {limit, number} {limit, plural, one{пиксел} few{пиксел} many{пиксел} other{пиксел}} зиёд бошад.',
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ҳаҷми файл "{file}" аз ҳад зиёд калон аст. Дарозияш набояд аз {limit, number} {limit, plural, one{пиксел} few{пиксел} many{пиксел} other{пиксел}} зиёд бошад.',
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ҳаҷми файл "{file}" аз ҳад зиёд хурд аст. Баландияш бояд аз {limit, number} {limit, plural, one{пиксел} few{пиксел} many{пиксел} other{пиксел}} зиёд бошад.',
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ҳаҷми файл "{file}" аз ҳад зиёд хурд аст. Дарозияш бояд аз {limit, number} {limit, plural, one{пиксел} few{пиксел} many{пиксел} other{пиксел}} зиёд бошад.',
'The requested view "{name}" was not found.' => 'Файл дархостшудаи ҷадвали "{name}" ёфт нашуд.',
'The verification code is incorrect.' => 'Рамзи нодурусти санҷишӣ.',
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Ҳамаги <b>{count, number}</b> {count, plural, one{қайд} few{қайд} many{қайдҳо} other{қайд}}.',
'Unable to verify your data submission.' => 'Санҷидани маълумоти фиристодаи Шумо муяссар нагардид.',
'Unknown option: --{name}' => 'Гузинаи номаълум: --{name}',
'Yes' => 'Ҳа',
'You are not allowed to perform this action.' => 'Шумо барои анҷом додани амали мазкур иҷозат надоред.',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ҳамаги {limit, number} аплод карда метавонед.',
'in {delta, plural, =1{a day} other{# days}}' => '{delta} рӯзи дигар',
'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} дақиқаи дигар',
'in {delta, plural, =1{a month} other{# months}}' => '{delta} моҳи дигар',
'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} сонияи дигар',
'in {delta, plural, =1{a year} other{# years}}' => '{delta} соли дигар',
'in {delta, plural, =1{an hour} other{# hours}}' => 'баъд аз {delta, plural, =1{соат} one{# соат} few{# соат} many{# соат} other{# соат}}',
'just now' => 'ҳоло',
'the input value' => 'ҷадвали воридшуда',
'{attribute} "{value}" has already been taken.' => 'Ҷадвали «{value}» барои {attribute} аллакай банд аст.',
'{attribute} cannot be blank.' => 'Ҳошияи «{attribute}» набояд холӣ бошад.',
'{attribute} contains wrong subnet mask.' => 'Маънои "{attribute}" дорои нодурусти ниқоби зершабака мебошад.',
'{attribute} is invalid.' => 'Ҷадвали {attribute} ғалат аст.',
'{attribute} is not a valid URL.' => 'Ҷадвали «{attribute}» URL-и нодуруст мебошад.',
'{attribute} is not a valid email address.' => 'Ҷадвали {attribute} сӯроғаи дурусти E-mail нест.',
'{attribute} is not in the allowed range.' => 'Ҷадвали «{attribute}» ба рӯйхати сӯроғаҳои диапазонҳои иҷозат додашуда дохил намешавад.',
'{attribute} must be "{requiredValue}".' => 'Ҷадвали «{attribute}» бояд ба «{requiredValue}» баробар бошад.',
'{attribute} must be a number.' => 'Ҷадвали {attribute} бояд адад бошад.',
'{attribute} must be a string.' => 'Ҷадвали {attribute} бояд сатр бошад.',
'{attribute} must be a valid IP address.' => 'Ҷадвали «{attribute}» бояд сӯроғаи дурусти IP бошад.',
'{attribute} must be an IP address with specified subnet.' => 'Ҷадвали «{attribute}» бояд сӯроғаи IP бо зершабака бошад.',
'{attribute} must be an integer.' => 'Ҷадвали {attribute} бояд адади бутун бошад.',
'{attribute} must be either "{true}" or "{false}".' => 'Маънои «{attribute}» бояд ба «{true}» ё «{false}» баробар бошад.',
'{attribute} must be equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд ба «{compareValueOrAttribute}» баробар бошад.',
'{attribute} must be greater than "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» бузургтар бошад.',
'{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» бузургтар ё ба он баробар бошад.',
'{attribute} must be less than "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» хурдтар бошад.',
'{attribute} must be less than or equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» бояд аз маънии «{compareValueOrAttribute}» хурдтар ё ба он баробар бошад.',
'{attribute} must be no greater than {max}.' => '{attribute} бояд аз {max} зиёд набошад.',
'{attribute} must be no less than {min}.' => '{attribute} бояд аз {min} кам набошад.',
'{attribute} must not be a subnet.' => 'Маънии «{attribute}» набояд зершабака бошад.',
'{attribute} must not be an IPv4 address.' => 'Маънии «{attribute}» набояд сӯроғаи IPv4 бошад.',
'{attribute} must not be an IPv6 address.' => 'Маънии «{attribute}» набояд сӯроғаи IPv6 бошад.',
'{attribute} must not be equal to "{compareValueOrAttribute}".' => 'Маънои «{attribute}» набояд ба «{compareValueOrAttribute}» баробар бошад.',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} хади ақал {min, number} рамз дошта бошад.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} хамаги {max, number} рамз дошта бошад.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} бояд {length, number} рамз дошта бошад.',
'{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# рӯз} few{# рӯз} many{# рӯз} other{# рӯз}}',
'{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# соат} few{# соат} many{# соат} other{# соат}}',
'{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# дақиқа} few{# дақиқа} many{# дақиқа} other{# дақиқа}}',
'{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# моҳ} few{# моҳ} many{# моҳ} other{# моҳ}}',
'{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# сония} few{# сония} many{# сония} other{# сония}}',
'{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# сол} few{# сол} many{# сол} other{# сол}}',
'{delta, plural, =1{a day} other{# days}} ago' => '{delta} рӯзи қабл',
'{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} дақиқаи қабл',
'{delta, plural, =1{a month} other{# months}} ago' => '{delta} моҳи қабл',
'{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} сонияи қабл',
'{delta, plural, =1{a year} other{# years}} ago' => '{delta} сол пеш',
'{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} соати қабл',
'{nFormatted} B' => '{nFormatted} B',
'{nFormatted} GB' => '{nFormatted} GB',
'{nFormatted} GiB' => '{nFormatted} GiB',
'{nFormatted} KB' => '{nFormatted} KB',
'{nFormatted} KiB' => '{nFormatted} KiB',
'{nFormatted} MB' => '{nFormatted} MB',
'{nFormatted} MiB' => '{nFormatted} MiB',
'{nFormatted} GB' => '{nFormatted} GB',
'{nFormatted} GiB' => '{nFormatted} GiB',
'{nFormatted} PB' => '{nFormatted} PB',
'{nFormatted} PiB' => '{nFormatted} PiB',
'{nFormatted} TB' => '{nFormatted} TB',
'{nFormatted} TiB' => '{nFormatted} TiB',
'{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} байт',
'{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} гибибайт',
'{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} гигабайт',
'{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} кибибайт',
'{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} килобайт',
'{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} мебибайт',
'{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} мегабайт',
'{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} гибибайт',
'{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} гигабайт',
'{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} пебибайт',
'{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} петабайт',
'{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} тебибайт',
'{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} терабайт',
'Are you sure you want to delete this item?' => 'Оё шумо дар ҳақиқат мехоҳед, ки ин нашрро нест кунед?',
'The requested view "{name}" was not found.' => 'Файл "{name}" барои манзур ёфт нашуд',
'(not set)' => '(танзим нашуда)',
'An internal server error occurred.' => 'Хатои дохилии сервер рух дод.',
'Delete' => 'Нест',
'Error' => 'Хато',
'File upload failed.' => 'Аплоди файл шикаст хурд.',
'Home' => 'Асосӣ',
'Invalid data received for parameter "{param}".' => 'Маълумоти номувофиқ барои параметри "{param}" гирифта шуд.',
'Login Required' => 'Вуруд маҷбурист',
'Missing required arguments: {params}' => 'Аргументи лозими вуҷд надорад: {params}',
'Missing required parameters: {params}' => 'Параметри лозими вуҷуд надорад: {params}',
'No' => 'На',
'No results found.' => 'Чизе ёфт нашуд.',
'Page not found.' => 'Саҳифа ёфт нашуд.',
'Please fix the following errors:' => 'Илтимос хатоҳои зеринро ислоҳ кунед:',
'Only files with these extensions are allowed: {extensions}.' => 'Танҳо файлҳои бо ин пасванд иҷоза аст: {extensions}.',
'Only files with these MIME types are allowed: {mimeTypes}.' => 'Фақат ин намуди файлҳо иҷозат аст: {mimeTypes}.',
'The format of {attribute} is invalid.' => 'Формати {attribute} ғалат буд.',
'Please upload a file.' => 'Илтимос файл аплод кунед.',
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => 'Манзури <b>{begin, number}-{end, number}</b> аз <b>{totalCount, number}</b>.',
'The file "{file}" is not an image.' => 'Файл "{file}" расм набуд.',
'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Файл "{file}" калон аст. Аз {formattedLimit} набояд калонтар бошад.',
'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Файл "{file}" хурд аст. Аз {formattedLimit} набояд хурдтар бошад.',
'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" баланд аст. Баландияш набояд аз {limit, number} зиёд бошад.',
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" паҳн аст. Паҳнияш набояд аз {limit, number} зиёд бошад.',
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Баландияш набояд аз {limit, number} хурд бошад.',
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Расми "{file}" хурд аст. Паҳнияш набояд аз {limit, number} хурд бошад.',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ҳамаги {limit, number} аплод карда метавонед.',
'The verification code is incorrect.' => 'Коди санҷиши ғалат аст.',
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Ҳамаги <b>{count, number}</b> нашр.',
'Unable to verify your data submission.' => 'Маълумоти фиристодаи шуморо санҷиш карда натавонистам.',
'Unknown option: --{name}' => 'Гузинаи номаълум: --{name}',
'Update' => 'Тағир',
'View' => 'Манзур',
'Yes' => 'Ҳа',
'just now' => 'ҳоло',
'the input value' => 'маълумоти вурудбуда',
'You are not allowed to perform this action.' => 'Шумо барои анҷоми ин амал дастнорасед.',
'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} сонияи дигар',
'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} дақиқаи дигар',
'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} соати дигар',
'in {delta, plural, =1{a day} other{# days}}' => '{delta} рӯзи дигар',
'in {delta, plural, =1{a month} other{# months}}' => '{delta} моҳи дигар',
'in {delta, plural, =1{a year} other{# years}}' => '{delta} соли дигар',
'{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} сонияи қабл',
'{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} дақиқаи қабл',
'{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} соати қабл',
'{delta, plural, =1{a day} other{# days}} ago' => '{delta} рӯзи қабл',
'{delta, plural, =1{a month} other{# months}} ago' => '{delta} моҳи қабл',
'{delta, plural, =1{a year} other{# years}} ago' => '{delta} сол пеш',
'{attribute} "{value}" has already been taken.' => '{attribute} "{value}" машғул аст.',
'{attribute} cannot be blank.' => '{attribute} набояд холи бошад.',
'{attribute} is invalid.' => '{attribute} ғалат аст.',
'{attribute} is not a valid URL.' => '{attribute} URL ғалат аст.',
'{attribute} is not a valid email address.' => '{attribute} E-mail одреси ғалат аст.',
'{attribute} must be "{requiredValue}".' => '{attribute} бояд "{requiredValue}" бошад.',
'{attribute} must be a number.' => '{attribute} бояд адад бошад.',
'{attribute} must be a string.' => '{attribute} бояд хат бошад.',
'{attribute} must be an integer.' => '{attribute} бояд адади комил бошад.',
'{attribute} must be either "{true}" or "{false}".' => '{attribute} бояд ё "{true}" ё "{false}" бошад.',
'{attribute} must be greater than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" калон бошад.',
'{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} бояд калон ё баробари "{compareValue}" бошад.',
'{attribute} must be less than "{compareValue}".' => '{attribute} бояд аз "{compareValue}" хурд бошад.',
'{attribute} must be less than or equal to "{compareValue}".' => '{attribute} бояд хурд ё баробари "{compareValue}" бошад.',
'{attribute} must be no greater than {max}.' => '{attribute} бояд аз {max} зиёд набошад.',
'{attribute} must be no less than {min}.' => '{attribute} бояд аз {min} кам набошад.',
'{attribute} must be repeated exactly.' => '{attribute} айнан бояд такрор шавад.',
'{attribute} must not be equal to "{compareValue}".' => '{attribute} бояд баробари "{compareValue}" набошад.',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} хади ақал {min, number} рамз дошта бошад.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} хамаги {max, number} рамз дошта бошад.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} бояд {length, number} рамз дошта бошад.',
];

7
tests/framework/helpers/HtmlTest.php

@ -378,6 +378,13 @@ class HtmlTest extends TestCase
'label' => 'ccc',
'value' => 2,
]));
$this->assertEquals('<input type="hidden" name="test" value="0" form="test-form"><label><input type="checkbox" class="a" name="test" value="2" form="test-form" checked> ccc</label>', Html::checkbox('test', true, [
'class' => 'a',
'uncheck' => '0',
'label' => 'ccc',
'value' => 2,
'form' => 'test-form',
]));
}
public function testDropDownList()

2
tests/framework/helpers/StringHelperTest.php

@ -117,6 +117,8 @@ class StringHelperTest extends TestCase
$this->assertEquals('<span><img src="image.png" />This is a test </span><strong>for</strong>...', StringHelper::truncate('<span><img src="image.png" />This is a test </span><strong>for a sentance</strong>', 18, '...', null, true));
$this->assertEquals('<p>This is a test</p><ul><li>bullet1</li><li>b</li></ul>...', StringHelper::truncate('<p>This is a test</p><ul><li>bullet1</li><li>bullet2</li><li>bullet3</li><li>bullet4</li></ul>', 22, '...', null, true));
$this->assertEquals('<div><ul><li>bullet1</li><li>b</li></ul></div>...', StringHelper::truncate('<div><ul><li>bullet1</li><li>bullet2</li><li>bullet3</li></ul><br></div>', 8, '...', null, true));
}
public function testTruncateWords()

Loading…
Cancel
Save