From 77d9fad7012d977fd3eee050eb04e9959855827d Mon Sep 17 00:00:00 2001 From: sensorario Date: Mon, 6 May 2013 12:38:07 +0200 Subject: [PATCH 001/613] Removed unnecessary else --- framework/web/User.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/web/User.php b/framework/web/User.php index 88fc12d..e05d88a 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -460,8 +460,7 @@ class User extends Component $auth = Yii::$app->getAuthManager(); if ($auth !== null) { return $auth->checkAccess($this->getId(), $operation, $params); - } else { - return false; } + return false; } } From cde97847f425840948a4bfbf267693063c14e103 Mon Sep 17 00:00:00 2001 From: Suralc Date: Mon, 29 Jul 2013 15:04:44 +0200 Subject: [PATCH 002/613] BooleanValidatorTest --- .../framework/validators/BooleanValidatorTest.php | 53 ++++++++++++++++++++++ .../validators/DefaultValueValidatorTest.php | 32 +++++++++++++ .../framework/validators/FakedValidationModel.php | 20 ++++++++ 3 files changed, 105 insertions(+) create mode 100644 tests/unit/framework/validators/BooleanValidatorTest.php create mode 100644 tests/unit/framework/validators/DefaultValueValidatorTest.php create mode 100644 tests/unit/framework/validators/FakedValidationModel.php diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/unit/framework/validators/BooleanValidatorTest.php new file mode 100644 index 0000000..e13635d --- /dev/null +++ b/tests/unit/framework/validators/BooleanValidatorTest.php @@ -0,0 +1,53 @@ +assertTrue($val->validateValue(true)); + $this->assertTrue($val->validateValue(false)); + $this->assertTrue($val->validateValue('0')); + $this->assertTrue($val->validateValue('1')); + $this->assertTrue($val->validateValue(null)); + $this->assertTrue($val->validateValue(array())); + $val->strict = true; + $this->assertTrue($val->validateValue('0')); + $this->assertTrue($val->validateValue('1')); + $this->assertFalse($val->validateValue(true)); + $this->assertFalse($val->validateValue(false)); + $val->trueValue = true; + $val->falseValue = false; + $this->assertFalse($val->validateValue('0')); + $this->assertFalse($val->validateValue(array())); + $this->assertTrue($val->validateValue(true)); + $this->assertTrue($val->validateValue(false)); + } + + public function testValidateAttributeAndError() + { + $obj = new FakedValidationModel; + $obj->attrA = true; + $obj->attrB = '1'; + $obj->attrC = '0'; + $obj->attrD = array(); + $val = new BooleanValidator; + $val->validateAttribute($obj, 'attrA'); + $this->assertFalse(isset($obj->errors['attrA'])); + $val->validateAttribute($obj, 'attrC'); + $this->assertFalse(isset($obj->errors['attrC'])); + $val->strict = true; + $val->validateAttribute($obj, 'attrB'); + $this->assertFalse(isset($obj->errors['attrB'])); + $val->validateAttribute($obj, 'attrD'); + $this->assertTrue(isset($obj->errors['attrD'])); + } +} diff --git a/tests/unit/framework/validators/DefaultValueValidatorTest.php b/tests/unit/framework/validators/DefaultValueValidatorTest.php new file mode 100644 index 0000000..05a71b5 --- /dev/null +++ b/tests/unit/framework/validators/DefaultValueValidatorTest.php @@ -0,0 +1,32 @@ +value = 'test_value'; + $obj = new \stdclass; + $obj->attrA = 'attrA'; + $obj->attrB = null; + $obj->attrC = ''; + // original values to chek which attritubes where modified + $objB = clone $obj; + $val->validateAttribute($obj, 'attrB'); + $this->assertEquals($val->value, $obj->attrB); + $this->assertEquals($objB->attrA, $obj->attrA); + $val->value = 'new_test_value'; + $obj = clone $objB; // get clean object + $val->validateAttribute($obj, 'attrC'); + $this->assertEquals('new_test_value', $obj->attrC); + $this->assertEquals($objB->attrA, $obj->attrA); + $val->validateAttribute($obj, 'attrA'); + $this->assertEquals($objB->attrA, $obj->attrA); + } +} diff --git a/tests/unit/framework/validators/FakedValidationModel.php b/tests/unit/framework/validators/FakedValidationModel.php new file mode 100644 index 0000000..d3c0424 --- /dev/null +++ b/tests/unit/framework/validators/FakedValidationModel.php @@ -0,0 +1,20 @@ +errors[$attribute] = $message; + } +} \ No newline at end of file From 09c5e6146273310ab3f885c595cbabd8f27c202a Mon Sep 17 00:00:00 2001 From: Suralc Date: Mon, 29 Jul 2013 15:20:07 +0200 Subject: [PATCH 003/613] fix test --- tests/unit/framework/validators/BooleanValidatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/unit/framework/validators/BooleanValidatorTest.php index e13635d..655ef58 100644 --- a/tests/unit/framework/validators/BooleanValidatorTest.php +++ b/tests/unit/framework/validators/BooleanValidatorTest.php @@ -17,8 +17,8 @@ class BooleanValidatorTest extends TestCase $this->assertTrue($val->validateValue(false)); $this->assertTrue($val->validateValue('0')); $this->assertTrue($val->validateValue('1')); - $this->assertTrue($val->validateValue(null)); - $this->assertTrue($val->validateValue(array())); + $this->assertFalse($val->validateValue(null)); + $this->assertFalse($val->validateValue(array())); $val->strict = true; $this->assertTrue($val->validateValue('0')); $this->assertTrue($val->validateValue('1')); From fc1d2af36c28ff853faa59fc799bbf05f1ab9410 Mon Sep 17 00:00:00 2001 From: Suralc Date: Mon, 29 Jul 2013 16:21:07 +0200 Subject: [PATCH 004/613] UrlValidator Tests --- framework/yii/validators/UrlValidator.php | 2 + .../framework/validators/FakedValidationModel.php | 5 ++ .../unit/framework/validators/UrlValidatorTest.php | 89 ++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 tests/unit/framework/validators/UrlValidatorTest.php diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php index aa3dad1..c9b3a2b 100644 --- a/framework/yii/validators/UrlValidator.php +++ b/framework/yii/validators/UrlValidator.php @@ -54,7 +54,9 @@ class UrlValidator extends Validator { parent::init(); if ($this->enableIDN && !function_exists('idn_to_ascii')) { + // @codeCoverageIgnoreStart throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.'); + // @codeCoverageIgnoreEnd } if ($this->message === null) { $this->message = Yii::t('yii', '{attribute} is not a valid URL.'); diff --git a/tests/unit/framework/validators/FakedValidationModel.php b/tests/unit/framework/validators/FakedValidationModel.php index d3c0424..89632c7 100644 --- a/tests/unit/framework/validators/FakedValidationModel.php +++ b/tests/unit/framework/validators/FakedValidationModel.php @@ -17,4 +17,9 @@ class FakedValidationModel { $this->errors[$attribute] = $message; } + + public function resetErrors() + { + $this->errors = array(); + } } \ No newline at end of file diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/unit/framework/validators/UrlValidatorTest.php new file mode 100644 index 0000000..81ddf41 --- /dev/null +++ b/tests/unit/framework/validators/UrlValidatorTest.php @@ -0,0 +1,89 @@ +assertFalse($val->validateValue('google.de')); + $this->assertTrue($val->validateValue('http://google.de')); + $this->assertTrue($val->validateValue('https://google.de')); + $this->assertFalse($val->validateValue('htp://yiiframework.com')); + $this->assertTrue($val->validateValue('https://www.google.de/search?q=yii+framework&ie=utf-8&oe=utf-8' + .'&rls=org.mozilla:de:official&client=firefox-a&gws_rd=cr')); + $this->assertFalse($val->validateValue('ftp://ftp.ruhr-uni-bochum.de/')); + $this->assertFalse($val->validateValue('http://invalid,domain')); + $this->assertFalse($val->validateValue('http://äüö?=!"§$%&/()=}][{³²€.edu')); + } + + public function testValidateValueWithDefaultScheme() + { + $val = new UrlValidator(array('defaultScheme' => 'https')); + $this->assertTrue($val->validateValue('yiiframework.com')); + $this->assertTrue($val->validateValue('http://yiiframework.com')); + } + + public function testValidateWithCustomScheme() + { + $val = new UrlValidator(array( + 'validSchemes' => array('http', 'https', 'ftp', 'ftps'), + 'defaultScheme' => 'http', + )); + $this->assertTrue($val->validateValue('ftp://ftp.ruhr-uni-bochum.de/')); + $this->assertTrue($val->validateValue('google.de')); + $this->assertTrue($val->validateValue('http://google.de')); + $this->assertTrue($val->validateValue('https://google.de')); + $this->assertFalse($val->validateValue('htp://yiiframework.com')); + // relative urls not supported + $this->assertFalse($val->validateValue('//yiiframework.com')); + } + + public function testValidateWithIdn() + { + if(!function_exists('idn_to_ascii')) { + $this->markTestSkipped('intl package required'); + return; + } + $val = new UrlValidator(array( + 'enableIDN' => true, + )); + $this->assertTrue($val->validateValue('http://äüößìà.de')); + // converted via http://mct.verisign-grs.com/convertServlet + $this->assertTrue($val->validateValue('http://xn--zcack7ayc9a.de')); + } + + public function testValidateLength() + { + $url = 'http://' . str_pad('base', 2000, 'url') . '.de'; + $val = new UrlValidator; + $this->assertFalse($val->validateValue($url)); + } + + public function testValidateAttributeAndError() + { + $obj = new FakedValidationModel; + $obj->url = 'http://google.de'; + $val = new UrlValidator; + $val->validateAttribute($obj, 'url'); + $this->assertFalse(isset($obj->errors['url'])); + $this->assertSame('http://google.de', $obj->url); + $obj->resetErrors(); + $val->defaultScheme = 'http'; + $obj->url = 'google.de'; + $val->validateAttribute($obj, 'url'); + $this->assertFalse(isset($obj->errors['url'])); + $this->assertTrue(stripos($obj->url, 'http') !== false); + $obj->resetErrors(); + $obj->url = 'gttp;/invalid string'; + $val->validateAttribute($obj, 'url'); + $this->assertTrue(isset($obj->errors['url'])); + } +} From ec1dfeaa587878fc8ae0dd02fcb2b444d12419f2 Mon Sep 17 00:00:00 2001 From: Suralc Date: Mon, 29 Jul 2013 16:40:33 +0200 Subject: [PATCH 005/613] Ignoring javascript generating methods in coverage for now. --- framework/yii/validators/BooleanValidator.php | 1 + framework/yii/validators/CaptchaValidator.php | 1 + framework/yii/validators/CompareValidator.php | 1 + framework/yii/validators/EmailValidator.php | 1 + framework/yii/validators/NumberValidator.php | 1 + framework/yii/validators/RangeValidator.php | 1 + framework/yii/validators/RegularExpressionValidator.php | 1 + framework/yii/validators/RequiredValidator.php | 1 + framework/yii/validators/StringValidator.php | 1 + framework/yii/validators/UrlValidator.php | 1 + 10 files changed, 10 insertions(+) diff --git a/framework/yii/validators/BooleanValidator.php b/framework/yii/validators/BooleanValidator.php index d1e81b8..6b6fded 100644 --- a/framework/yii/validators/BooleanValidator.php +++ b/framework/yii/validators/BooleanValidator.php @@ -82,6 +82,7 @@ class BooleanValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/CaptchaValidator.php b/framework/yii/validators/CaptchaValidator.php index 2a16f57..379859c 100644 --- a/framework/yii/validators/CaptchaValidator.php +++ b/framework/yii/validators/CaptchaValidator.php @@ -97,6 +97,7 @@ class CaptchaValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/CompareValidator.php b/framework/yii/validators/CompareValidator.php index 8c9e587..9d427ff 100644 --- a/framework/yii/validators/CompareValidator.php +++ b/framework/yii/validators/CompareValidator.php @@ -181,6 +181,7 @@ class CompareValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @throws InvalidConfigException if CompareValidator::operator is invalid + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/EmailValidator.php b/framework/yii/validators/EmailValidator.php index c3d8480..dba1d7b 100644 --- a/framework/yii/validators/EmailValidator.php +++ b/framework/yii/validators/EmailValidator.php @@ -122,6 +122,7 @@ class EmailValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/NumberValidator.php b/framework/yii/validators/NumberValidator.php index 2fee133..2c96e49 100644 --- a/framework/yii/validators/NumberValidator.php +++ b/framework/yii/validators/NumberValidator.php @@ -117,6 +117,7 @@ class NumberValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/RangeValidator.php b/framework/yii/validators/RangeValidator.php index f89d420..5474d32 100644 --- a/framework/yii/validators/RangeValidator.php +++ b/framework/yii/validators/RangeValidator.php @@ -84,6 +84,7 @@ class RangeValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php index 4ae2099..84fd292 100644 --- a/framework/yii/validators/RegularExpressionValidator.php +++ b/framework/yii/validators/RegularExpressionValidator.php @@ -83,6 +83,7 @@ class RegularExpressionValidator extends Validator * containing a model form with this validator applied. * @return string the client-side validation script. * @throws InvalidConfigException if the "pattern" is not a valid regular expression + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/RequiredValidator.php b/framework/yii/validators/RequiredValidator.php index 9ca0ecb..6cab6a9 100644 --- a/framework/yii/validators/RequiredValidator.php +++ b/framework/yii/validators/RequiredValidator.php @@ -105,6 +105,7 @@ class RequiredValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php index 4cc9dba..ff8570e 100644 --- a/framework/yii/validators/StringValidator.php +++ b/framework/yii/validators/StringValidator.php @@ -129,6 +129,7 @@ class StringValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php index c9b3a2b..29663f1 100644 --- a/framework/yii/validators/UrlValidator.php +++ b/framework/yii/validators/UrlValidator.php @@ -121,6 +121,7 @@ class UrlValidator extends Validator * containing a model form with this validator applied. * @return string the client-side validation script. * @see \yii\Web\ActiveForm::enableClientValidation + * @codeCoverageIgnore */ public function clientValidateAttribute($object, $attribute, $view) { From 1c6b64e7172cedc2733bd37eb0f329bd1d3fd47a Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 31 Jul 2013 19:42:20 +0200 Subject: [PATCH 006/613] Added CompareValidatorTest and improved BooleanValidatorTest and UrlValidatorTest --- .../framework/validators/BooleanValidatorTest.php | 11 +- .../framework/validators/CompareValidatorTest.php | 160 +++++++++++++++++++++ .../framework/validators/FakedValidationModel.php | 30 ++-- .../unit/framework/validators/UrlValidatorTest.php | 26 ++-- 4 files changed, 197 insertions(+), 30 deletions(-) create mode 100644 tests/unit/framework/validators/CompareValidatorTest.php diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/unit/framework/validators/BooleanValidatorTest.php index 655ef58..e3fa3e5 100644 --- a/tests/unit/framework/validators/BooleanValidatorTest.php +++ b/tests/unit/framework/validators/BooleanValidatorTest.php @@ -1,5 +1,6 @@ assertTrue($val->validateValue(true)); $this->assertTrue($val->validateValue(false)); } - + public function testValidateAttributeAndError() { $obj = new FakedValidationModel; @@ -41,13 +42,13 @@ class BooleanValidatorTest extends TestCase $obj->attrD = array(); $val = new BooleanValidator; $val->validateAttribute($obj, 'attrA'); - $this->assertFalse(isset($obj->errors['attrA'])); + $this->assertFalse($obj->hasErrors('attrA')); $val->validateAttribute($obj, 'attrC'); - $this->assertFalse(isset($obj->errors['attrC'])); + $this->assertFalse($obj->hasErrors('attrC')); $val->strict = true; $val->validateAttribute($obj, 'attrB'); - $this->assertFalse(isset($obj->errors['attrB'])); + $this->assertFalse($obj->hasErrors('attrB')); $val->validateAttribute($obj, 'attrD'); - $this->assertTrue(isset($obj->errors['attrD'])); + $this->assertTrue($obj->hasErrors('attrD')); } } diff --git a/tests/unit/framework/validators/CompareValidatorTest.php b/tests/unit/framework/validators/CompareValidatorTest.php new file mode 100644 index 0000000..c91e7d6 --- /dev/null +++ b/tests/unit/framework/validators/CompareValidatorTest.php @@ -0,0 +1,160 @@ +setExpectedException('yii\base\InvalidConfigException'); + $val = new CompareValidator; + $val->validateValue('val'); + } + + public function testValidateValue() + { + $value = 18449; + // default config + $val = new CompareValidator(array('compareValue' => $value)); + $this->assertTrue($val->validateValue($value)); + $this->assertTrue($val->validateValue((string)$value)); + $this->assertFalse($val->validateValue($value + 1)); + foreach ($this->getOperationTestData($value) as $op => $tests) { + $val = new CompareValidator(array('compareValue' => $value)); + $val->operator = $op; + foreach ($tests as $test) { + $this->assertEquals($test[1], $val->validateValue($test[0])); + } + } + } + + protected function getOperationTestData($value) + { + return array( + '===' => array( + array($value, true), + array((string)$value, false), + array((float)$value, false), + array($value + 1, false), + ), + '!=' => array( + array($value, false), + array((string)$value, false), + array((float)$value, false), + array($value + 0.00001, true), + array(false, true), + ), + '!==' => array( + array($value, false), + array((string)$value, true), + array((float)$value, true), + array(false, true), + ), + '>' => array( + array($value, false), + array($value + 1, true), + array($value - 1, false), + ), + '>=' => array( + array($value, true), + array($value + 1, true), + array($value - 1, false), + ), + '<' => array( + array($value, false), + array($value + 1, false), + array($value - 1, true), + ), + '<=' => array( + array($value, true), + array($value + 1, false), + array($value - 1, true), + ), + + ); + } + + public function testValidateAttribute() + { + // invalid-array + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr = array('test_val'); + $val->validateAttribute($model, 'attr'); + $this->assertTrue($model->hasErrors('attr')); + $val = new CompareValidator(array('compareValue' => 'test-string')); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $val = new CompareValidator(array('compareAttribute' => 'attr_test_val')); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_val = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_val')); + $val = new CompareValidator(array('compareAttribute' => 'attr_test_val')); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_val = 'test-string-false'; + $val->validateAttribute($model, 'attr_test'); + $this->assertTrue($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_val')); + // assume: _repeat + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_repeat = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_repeat')); + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_repeat = 'test-string2'; + $val->validateAttribute($model, 'attr_test'); + $this->assertTrue($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_repeat')); + } + + public function testValidateAttributeOperators() + { + $value = 55; + foreach ($this->getOperationTestData($value) as $operator => $tests) { + $val = new CompareValidator(array('operator' => $operator, 'compareValue' => $value)); + foreach ($tests as $test) { + $model = new FakedValidationModel; + $model->attr_test = $test[0]; + $val->validateAttribute($model, 'attr_test'); + $this->assertEquals($test[1], !$model->hasErrors('attr_test')); + } + + } + } + + public function testEnsureMessageSetOnInit() + { + foreach ($this->getOperationTestData(1337) as $operator => $tests) { + $val = new CompareValidator(array('operator' => $operator)); + $this->assertTrue(strlen($val->message) > 1); + } + try { + $val = new CompareValidator(array('operator' => '<>')); + } catch (InvalidConfigException $e) { + return; + } + catch (\Exception $e) { + $this->fail('InvalidConfigException expected' . get_class($e) . 'received'); + return; + } + $this->fail('InvalidConfigException expected none received'); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/FakedValidationModel.php b/tests/unit/framework/validators/FakedValidationModel.php index 89632c7..15a2a94 100644 --- a/tests/unit/framework/validators/FakedValidationModel.php +++ b/tests/unit/framework/validators/FakedValidationModel.php @@ -2,24 +2,30 @@ namespace yiiunit\framework\validators; +use yii\base\Model; + /** * @codeCoverageIgnore */ -class FakedValidationModel +class FakedValidationModel extends Model { - public $errors = array(); - public function getAttributeLabel($attr) - { - return 'Attr-Label: '.$attr; - } - - public function addError($attribute, $message) + private $attr = array(); + + public function __get($name) { - $this->errors[$attribute] = $message; + if (stripos($name, 'attr') === 0) { + return isset($this->attr[$name]) ? $this->attr[$name] : null; + } + + return parent::__get($name); } - - public function resetErrors() + + public function __set($name, $value) { - $this->errors = array(); + if (stripos($name, 'attr') === 0) { + $this->attr[$name] = $value; + } else { + parent::__set($name, $value); + } } } \ No newline at end of file diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/unit/framework/validators/UrlValidatorTest.php index 81ddf41..f585590 100644 --- a/tests/unit/framework/validators/UrlValidatorTest.php +++ b/tests/unit/framework/validators/UrlValidatorTest.php @@ -70,20 +70,20 @@ class UrlValidatorTest extends TestCase public function testValidateAttributeAndError() { $obj = new FakedValidationModel; - $obj->url = 'http://google.de'; + $obj->attr_url = 'http://google.de'; $val = new UrlValidator; - $val->validateAttribute($obj, 'url'); - $this->assertFalse(isset($obj->errors['url'])); - $this->assertSame('http://google.de', $obj->url); - $obj->resetErrors(); + $val->validateAttribute($obj, 'attr_url'); + $this->assertFalse($obj->hasErrors('attr_url')); + $this->assertSame('http://google.de', $obj->attr_url); + $obj = new FakedValidationModel; $val->defaultScheme = 'http'; - $obj->url = 'google.de'; - $val->validateAttribute($obj, 'url'); - $this->assertFalse(isset($obj->errors['url'])); - $this->assertTrue(stripos($obj->url, 'http') !== false); - $obj->resetErrors(); - $obj->url = 'gttp;/invalid string'; - $val->validateAttribute($obj, 'url'); - $this->assertTrue(isset($obj->errors['url'])); + $obj->attr_url = 'google.de'; + $val->validateAttribute($obj, 'attr_url'); + $this->assertFalse($obj->hasErrors('attr_url')); + $this->assertTrue(stripos($obj->attr_url, 'http') !== false); + $obj = new FakedValidationModel; + $obj->attr_url = 'gttp;/invalid string'; + $val->validateAttribute($obj, 'attr_url'); + $this->assertTrue($obj->hasErrors('attr_url')); } } From 777ff792c4694926ce8b2795b5627be3c9bd34f4 Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 31 Jul 2013 20:12:45 +0200 Subject: [PATCH 007/613] DateValidatorTest --- .../framework/validators/DateValidatorTest.php | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/unit/framework/validators/DateValidatorTest.php diff --git a/tests/unit/framework/validators/DateValidatorTest.php b/tests/unit/framework/validators/DateValidatorTest.php new file mode 100644 index 0000000..445d57d --- /dev/null +++ b/tests/unit/framework/validators/DateValidatorTest.php @@ -0,0 +1,61 @@ +assertTrue($val->message !== null && strlen($val->message) > 1); + } + + public function testValidateValue() + { + $val = new DateValidator; + $this->assertTrue($val->validateValue('2013-09-13')); + $this->assertFalse($val->validateValue('31.7.2013')); + $this->assertFalse($val->validateValue('31-7-2013')); + $this->assertFalse($val->validateValue(time())); + $val->format = 'U'; + $this->assertTrue($val->validateValue(time())); + $val->format = 'd.m.Y'; + $this->assertTrue($val->validateValue('31.7.2013')); + $val->format = 'Y-m-!d H:i:s'; + $this->assertTrue($val->validateValue('2009-02-15 15:16:17')); + } + + public function testValidateAttribute() + { + // error-array-add + $val = new DateValidator; + $model = new FakedValidationModel; + $model->attr_date = '2013-09-13'; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + $model = new FakedValidationModel; + $model->attr_date = '1375293913'; + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); + //// timestamp attribute + $val = new DateValidator(array('timestampAttribute' => 'attr_timestamp')); + $model = new FakedValidationModel; + $model->attr_date = '2013-09-13'; + $model->attr_timestamp = true; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + $this->assertFalse($model->hasErrors('attr_timestamp')); + $this->assertEquals( + DateTime::createFromFormat($val->format, '2013-09-13')->getTimestamp(), + $model->attr_timestamp + ); + + + } +} \ No newline at end of file From 1ce7344e184bfe3e8c0eedd473aed4cc4e5806c6 Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 31 Jul 2013 21:14:14 +0200 Subject: [PATCH 008/613] Additional tests in EmailValidator. --- framework/yii/validators/EmailValidator.php | 2 ++ .../framework/validators/EmailValidatorTest.php | 30 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/framework/yii/validators/EmailValidator.php b/framework/yii/validators/EmailValidator.php index dba1d7b..116d8e3 100644 --- a/framework/yii/validators/EmailValidator.php +++ b/framework/yii/validators/EmailValidator.php @@ -64,7 +64,9 @@ class EmailValidator extends Validator { parent::init(); if ($this->enableIDN && !function_exists('idn_to_ascii')) { + // @codeCoverageIgnoreStart throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.'); + // @codeCoverageIgnoreEnd } if ($this->message === null) { $this->message = Yii::t('yii', '{attribute} is not a valid email address.'); diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php index 852b9f9..2a0de82 100644 --- a/tests/unit/framework/validators/EmailValidatorTest.php +++ b/tests/unit/framework/validators/EmailValidatorTest.php @@ -1,5 +1,6 @@ assertTrue($validator->validateValue('5011@gmail.com')); $this->assertFalse($validator->validateValue('test@example.com')); } + + public function testValidateAttribute() + { + $val = new EmailValidator(); + $model = new FakedValidationModel(); + $model->attr_email = '5011@gmail.com'; + $val->validateAttribute($model, 'attr_email'); + $this->assertFalse($model->hasErrors('attr_email')); + } + + public function testValidateValueIdn() + { + if (!function_exists('idn_to_ascii')) { + $this->markTestSkipped('Intl extension required'); + return; + } + $val = new EmailValidator(array('enableIDN' => true)); + $this->assertTrue($val->validateValue('5011@example.com')); + $this->assertTrue($val->validateValue('example@äüößìà.de')); + $this->assertTrue($val->validateValue('example@xn--zcack7ayc9a.de')); + } + + public function testValidateValueWithName() + { + $val = new EmailValidator(array('allowName' => true)); + $this->assertTrue($val->validateValue('test@example.com')); + $this->assertTrue($val->validateValue('John Smith ')); + $this->assertFalse($val->validateValue('John Smith ')); + } } From 467d88faf92c4b8232e6340eb2605f54bad7f84f Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 31 Jul 2013 22:48:19 +0200 Subject: [PATCH 009/613] Added StringValidatorTest --- .../framework/validators/FakedValidationModel.php | 5 + .../framework/validators/StringValidatorTest.php | 110 +++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 tests/unit/framework/validators/StringValidatorTest.php diff --git a/tests/unit/framework/validators/FakedValidationModel.php b/tests/unit/framework/validators/FakedValidationModel.php index 15a2a94..f30735e 100644 --- a/tests/unit/framework/validators/FakedValidationModel.php +++ b/tests/unit/framework/validators/FakedValidationModel.php @@ -28,4 +28,9 @@ class FakedValidationModel extends Model parent::__set($name, $value); } } + + public function getAttributeLabel($attr) + { + return $attr; + } } \ No newline at end of file diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php new file mode 100644 index 0000000..5336dfc --- /dev/null +++ b/tests/unit/framework/validators/StringValidatorTest.php @@ -0,0 +1,110 @@ +mockApplication(); + } + + public function testValidateValue() + { + $val = new StringValidator(); + $this->assertFalse($val->validateValue(array('not a string'))); + $this->assertTrue($val->validateValue('Just some string')); + } + + public function testValidateValueLength() + { + $val = new StringValidator(array('length' => 25)); + $this->assertTrue($val->validateValue(str_repeat('x', 25))); + $this->assertTrue($val->validateValue(str_repeat('€', 25))); + $this->assertFalse($val->validateValue(str_repeat('x', 125))); + $this->assertFalse($val->validateValue('')); + $val = new StringValidator(array('length' => array(25))); + $this->assertTrue($val->validateValue(str_repeat('x', 25))); + $this->assertTrue($val->validateValue(str_repeat('x', 1250))); + $this->assertFalse($val->validateValue(str_repeat('Ä', 24))); + $this->assertFalse($val->validateValue('')); + $val = new StringValidator(array('length' => array(10, 20))); + $this->assertTrue($val->validateValue(str_repeat('x', 15))); + $this->assertTrue($val->validateValue(str_repeat('x', 10))); + $this->assertTrue($val->validateValue(str_repeat('x', 20))); + $this->assertFalse($val->validateValue(str_repeat('x', 5))); + $this->assertFalse($val->validateValue(str_repeat('x', 25))); + $this->assertFalse($val->validateValue('')); + // make sure min/max are overridden + $val = new StringValidator(array('length' => array(10, 20), 'min' => 25, 'max' => 35)); + $this->assertTrue($val->validateValue(str_repeat('x', 15))); + $this->assertFalse($val->validateValue(str_repeat('x', 30))); + } + + public function testValidateValueMinMax() + { + $val = new StringValidator(array('min' => 10)); + $this->assertTrue($val->validateValue(str_repeat('x', 10))); + $this->assertFalse($val->validateValue('xxxx')); + $val = new StringValidator(array('max' => 10)); + $this->assertTrue($val->validateValue('xxxx')); + $this->assertFalse($val->validateValue(str_repeat('y', 20))); + $val = new StringValidator(array('min' => 10, 'max' => 20)); + $this->assertTrue($val->validateValue(str_repeat('y', 15))); + $this->assertFalse($val->validateValue('abc')); + $this->assertFalse($val->validateValue(str_repeat('b', 25))); + } + + public function testValidateAttribute() + { + $val = new StringValidator(); + $model = new FakedValidationModel(); + $model->attr_string = 'a tet string'; + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $val = new StringValidator(array('length' => 20)); + $model = new FakedValidationModel(); + $model->attr_string = str_repeat('x', 20); + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + $val = new StringValidator(array('max' => 2)); + $model = new FakedValidationModel(); + $model->attr_string = 'a'; + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + } + + public function testEnsureMessagesOnInit() + { + $val = new StringValidator(array('min' => 1, 'max' => 2)); + $this->assertTrue(is_string($val->message)); + $this->assertTrue(is_string($val->tooLong)); + $this->assertTrue(is_string($val->tooShort)); + } + + public function testCustomErrorMessageInValidateAttribute() + { + $val = new StringValidator(array( + 'min' => 5, + 'tooShort' => '{attribute} to short. Min is {min}', + )); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + $this->assertEquals('attr_string to short. Min is 5', $model->getErrors('attr_string')[0]); + } +} \ No newline at end of file From f7bb54be1c5d31af369b39514ddc7486f4d8c07b Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 31 Jul 2013 23:03:34 +0200 Subject: [PATCH 010/613] Test fix for Php 5.3 --- tests/unit/framework/validators/StringValidatorTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php index 5336dfc..e7c68bc 100644 --- a/tests/unit/framework/validators/StringValidatorTest.php +++ b/tests/unit/framework/validators/StringValidatorTest.php @@ -105,6 +105,7 @@ class StringValidatorTest extends TestCase $model->attr_string = 'abc'; $val->validateAttribute($model, 'attr_string'); $this->assertTrue($model->hasErrors('attr_string')); - $this->assertEquals('attr_string to short. Min is 5', $model->getErrors('attr_string')[0]); + $errorMsg = $model->getErrors('attr_string'); + $this->assertEquals('attr_string to short. Min is 5', $errorMsg[0]); } } \ No newline at end of file From 8f8de7cf604c292b799ad24acd95b22e48a3f6ab Mon Sep 17 00:00:00 2001 From: Suralc Date: Tue, 6 Aug 2013 13:13:58 +0200 Subject: [PATCH 011/613] Added NumberValidatorTest --- .../framework/validators/NumberValidatorTest.php | 155 +++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/unit/framework/validators/NumberValidatorTest.php diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/unit/framework/validators/NumberValidatorTest.php new file mode 100644 index 0000000..07a7a69 --- /dev/null +++ b/tests/unit/framework/validators/NumberValidatorTest.php @@ -0,0 +1,155 @@ +assertTrue(is_string($val->message)); + $this->assertTrue(is_null($val->max)); + $val = new NumberValidator(array('min' => -1, 'max' => 20, 'integerOnly' => true)); + $this->assertTrue(is_string($val->message)); + $this->assertTrue(is_string($val->tooSmall)); + $this->assertTrue(is_string($val->tooBig)); + } + + public function testValidateValueSimple() + { + $val = new NumberValidator(); + $this->assertTrue($val->validateValue(20)); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(-20)); + $this->assertTrue($val->validateValue('20')); + $this->assertTrue($val->validateValue(25.45)); + $this->assertFalse($val->validateValue('25,45')); + $this->assertFalse($val->validateValue('12:45')); + $val = new NumberValidator(array('integerOnly' => true)); + $this->assertTrue($val->validateValue(20)); + $this->assertTrue($val->validateValue(0)); + $this->assertFalse($val->validateValue(25.45)); + $this->assertTrue($val->validateValue('20')); + $this->assertFalse($val->validateValue('25,45')); + $this->assertTrue($val->validateValue('020')); + $this->assertTrue($val->validateValue(0x14)); + $this->assertFalse($val->validateValue('0x14')); // todo check this + } + + public function testValidateValueAdvanced() + { + $val = new NumberValidator(); + $this->assertTrue($val->validateValue('-1.23')); // signed float + $this->assertTrue($val->validateValue('-4.423e-12')); // signed float + exponent + $this->assertTrue($val->validateValue('12E3')); // integer + exponent + $this->assertFalse($val->validateValue('e12')); // just exponent + $this->assertFalse($val->validateValue('-e3')); + $this->assertFalse($val->validateValue('-4.534-e-12')); // 'signed' exponent + $this->assertFalse($val->validateValue('12.23^4')); // expression instead of value + $val = new NumberValidator(array('integerOnly' => true)); + $this->assertFalse($val->validateValue('-1.23')); + $this->assertFalse($val->validateValue('-4.423e-12')); + $this->assertFalse($val->validateValue('12E3')); + $this->assertFalse($val->validateValue('e12')); + $this->assertFalse($val->validateValue('-e3')); + $this->assertFalse($val->validateValue('-4.534-e-12')); + $this->assertFalse($val->validateValue('12.23^4')); + } + + public function testValidateValueMin() + { + $val = new NumberValidator(array('min' => 1)); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(-1)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue(PHP_INT_MAX + 1)); + $val = new NumberValidator(array('min' => 1), array('integerOnly' => true)); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(-1)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue(PHP_INT_MAX + 1)); + } + + public function testValidateValueMax() + { + $val = new NumberValidator(array('max' => 1.25)); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(1.5)); + $this->assertTrue($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue('125e-2')); + $val = new NumberValidator(array('max' => 1.25, 'integerOnly' => true)); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(1.5)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertFalse($val->validateValue('125e-2')); + } + + public function testValidateValueRange() + { + $val = new NumberValidator(array('min' => -10, 'max' => 20)); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(-10)); + $this->assertFalse($val->validateValue(-11)); + $this->assertFalse($val->validateValue(21)); + $val = new NumberValidator(array('min' => -10, 'max' => 20, 'integerOnly' => true)); + $this->assertTrue($val->validateValue(0)); + $this->assertFalse($val->validateValue(-11)); + $this->assertFalse($val->validateValue(22)); + $this->assertFalse($val->validateValue('20e-1')); + } + + public function testValidateAttribute() + { + $val = new NumberValidator(); + $model = new FakedValidationModel(); + $model->attr_number = '5.5e1'; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = '43^32'; //expression + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(array('min' => 10)); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 5; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(array('max' => 10)); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 15; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(array('max' => 10, 'integerOnly' => true)); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 3.43; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + } + + public function testEnsureCustomMessageIsSetOnValidateAttribute() + { + $val = new NumberValidator(array( + 'toSmall' => '{attribute} is to small.', + 'min' => 5 + )); + $model = new FakedValidationModel(); + $model->attr_number = 0; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $this->assertEquals(1, count($model->getErrors('attr_number'))); + $msgs = $model->getErrors('attr_number'); + $this->assertSame('attr_number is to small.', $msgs[0]); + } +} \ No newline at end of file From b5472c20e323ad2c51de8bf2af8c63d9e587f486 Mon Sep 17 00:00:00 2001 From: Suralc Date: Tue, 6 Aug 2013 15:21:29 +0200 Subject: [PATCH 012/613] Typo fix --- tests/unit/framework/validators/NumberValidatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/unit/framework/validators/NumberValidatorTest.php index 07a7a69..279b89e 100644 --- a/tests/unit/framework/validators/NumberValidatorTest.php +++ b/tests/unit/framework/validators/NumberValidatorTest.php @@ -141,7 +141,7 @@ class NumberValidatorTest extends TestCase public function testEnsureCustomMessageIsSetOnValidateAttribute() { $val = new NumberValidator(array( - 'toSmall' => '{attribute} is to small.', + 'tooSmall' => '{attribute} is to small.', 'min' => 5 )); $model = new FakedValidationModel(); From a81480d309cfea6a080982a5f4ffcdc3c97c0de2 Mon Sep 17 00:00:00 2001 From: Suralc Date: Tue, 6 Aug 2013 19:56:05 +0200 Subject: [PATCH 013/613] Improved ValidatorTest organization. --- .../validators/models/FakedValidationModel.php | 55 ++++++++++++++++++++++ .../framework/validators/BooleanValidatorTest.php | 3 +- .../framework/validators/CompareValidatorTest.php | 2 +- .../framework/validators/DateValidatorTest.php | 2 +- .../framework/validators/EmailValidatorTest.php | 1 + .../framework/validators/FakedValidationModel.php | 36 -------------- .../framework/validators/NumberValidatorTest.php | 1 + .../framework/validators/StringValidatorTest.php | 1 + .../unit/framework/validators/UrlValidatorTest.php | 3 +- 9 files changed, 62 insertions(+), 42 deletions(-) create mode 100644 tests/unit/data/validators/models/FakedValidationModel.php delete mode 100644 tests/unit/framework/validators/FakedValidationModel.php diff --git a/tests/unit/data/validators/models/FakedValidationModel.php b/tests/unit/data/validators/models/FakedValidationModel.php new file mode 100644 index 0000000..1e3366c --- /dev/null +++ b/tests/unit/data/validators/models/FakedValidationModel.php @@ -0,0 +1,55 @@ + 'reqTest'), + array('val_attr_c', 'integer'), + ); + } + + public function inlineVal($attribute, $params = array()) + { + return true; + } + + public function __get($name) + { + if (stripos($name, 'attr') === 0) { + return isset($this->attr[$name]) ? $this->attr[$name] : null; + } + + return parent::__get($name); + } + + public function __set($name, $value) + { + if (stripos($name, 'attr') === 0) { + $this->attr[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + public function getAttributeLabel($attr) + { + return $attr; + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/unit/framework/validators/BooleanValidatorTest.php index e3fa3e5..f37b39a 100644 --- a/tests/unit/framework/validators/BooleanValidatorTest.php +++ b/tests/unit/framework/validators/BooleanValidatorTest.php @@ -1,11 +1,10 @@ attr[$name]) ? $this->attr[$name] : null; - } - - return parent::__get($name); - } - - public function __set($name, $value) - { - if (stripos($name, 'attr') === 0) { - $this->attr[$name] = $value; - } else { - parent::__set($name, $value); - } - } - - public function getAttributeLabel($attr) - { - return $attr; - } -} \ No newline at end of file diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/unit/framework/validators/NumberValidatorTest.php index 279b89e..4868503 100644 --- a/tests/unit/framework/validators/NumberValidatorTest.php +++ b/tests/unit/framework/validators/NumberValidatorTest.php @@ -4,6 +4,7 @@ namespace yiiunit\framework\validators; use yii\validators\NumberValidator; +use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\TestCase; class NumberValidatorTest extends TestCase diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php index e7c68bc..c7d2889 100644 --- a/tests/unit/framework/validators/StringValidatorTest.php +++ b/tests/unit/framework/validators/StringValidatorTest.php @@ -4,6 +4,7 @@ namespace yiiunit\framework\validators; use yii\validators\StringValidator; +use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\TestCase; class StringValidatorTest extends TestCase diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/unit/framework/validators/UrlValidatorTest.php index f585590..7b618c1 100644 --- a/tests/unit/framework/validators/UrlValidatorTest.php +++ b/tests/unit/framework/validators/UrlValidatorTest.php @@ -1,10 +1,9 @@ Date: Tue, 13 Aug 2013 16:49:53 +0200 Subject: [PATCH 014/613] Added RequiredValidatorTest --- .../framework/validators/RequiredValidatorTest.php | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/unit/framework/validators/RequiredValidatorTest.php diff --git a/tests/unit/framework/validators/RequiredValidatorTest.php b/tests/unit/framework/validators/RequiredValidatorTest.php new file mode 100644 index 0000000..198727b --- /dev/null +++ b/tests/unit/framework/validators/RequiredValidatorTest.php @@ -0,0 +1,34 @@ +assertFalse($val->validateValue(null)); + $this->assertFalse($val->validateValue(array())); + $this->assertTrue($val->validateValue('not empty')); + $this->assertTrue($val->validateValue(array('with', 'elements'))); + } + + public function testValidateValueWithValue() + { + $val = new RequiredValidator(array('requiredValue' => 55)); + $this->assertTrue($val->validateValue(55)); + $this->assertTrue($val->validateValue("55")); + $this->assertTrue($val->validateValue("0x37")); + $this->assertFalse($val->validateValue("should fail")); + $this->assertTrue($val->validateValue(true)); + $val->strict = true; + $this->assertTrue($val->validateValue(55)); + $this->assertFalse($val->validateValue("55")); + $this->assertFalse($val->validateValue("0x37")); + $this->assertFalse($val->validateValue("should fail")); + $this->assertFalse($val->validateValue(true)); + } +} \ No newline at end of file From bae79cd76c6459198e0d5b79cbb6b09e08b089e1 Mon Sep 17 00:00:00 2001 From: Suralc Date: Tue, 13 Aug 2013 16:51:53 +0200 Subject: [PATCH 015/613] Added ValidatorTest --- tests/unit/data/validators/TestValidator.php | 44 ++++ .../validators/models/FakedValidationModel.php | 15 +- tests/unit/framework/validators/ValidatorTest.php | 227 +++++++++++++++++++++ 3 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 tests/unit/data/validators/TestValidator.php create mode 100644 tests/unit/framework/validators/ValidatorTest.php diff --git a/tests/unit/data/validators/TestValidator.php b/tests/unit/data/validators/TestValidator.php new file mode 100644 index 0000000..13f12dc --- /dev/null +++ b/tests/unit/data/validators/TestValidator.php @@ -0,0 +1,44 @@ +markAttributeValidated($attribute); + if ($this->_setErrorOnValidateAttribute == true) { + $this->addError($object, $attribute, sprintf('%s##%s', $attribute, get_class($object))); + } + } + + protected function markAttributeValidated($attr, $increaseBy = 1) + { + if (!isset($this->_validatedAttributes[$attr])) { + $this->_validatedAttributes[$attr] = 1; + } else { + $this->_validatedAttributes[$attr] = $this->_validatedAttributes[$attr] + $increaseBy; + } + } + + public function countAttributeValidations($attr) + { + return isset($this->_validatedAttributes[$attr]) ? $this->_validatedAttributes[$attr] : 0; + } + + public function isAttributeValidated($attr) + { + return isset($this->_validatedAttributes[$attr]); + } + + public function enableErrorOnValidateAttribute() + { + $this->_setErrorOnValidateAttribute = true; + } +} \ No newline at end of file diff --git a/tests/unit/data/validators/models/FakedValidationModel.php b/tests/unit/data/validators/models/FakedValidationModel.php index 1e3366c..d2e3f52 100644 --- a/tests/unit/data/validators/models/FakedValidationModel.php +++ b/tests/unit/data/validators/models/FakedValidationModel.php @@ -9,13 +9,24 @@ use yii\base\Model; */ class FakedValidationModel extends Model { - private $attr = array(); - public $val_attr_a; public $val_attr_b; public $val_attr_c; public $val_attr_d; + private $attr = array(); + /** + * @param array $attributes + * @return self + */ + public static function createWithAttributes($attributes = array()) + { + $m = new static(); + foreach ($attributes as $attribute => $value) { + $m->$attribute = $value; + } + return $m; + } public function rules() { diff --git a/tests/unit/framework/validators/ValidatorTest.php b/tests/unit/framework/validators/ValidatorTest.php new file mode 100644 index 0000000..f0a114b --- /dev/null +++ b/tests/unit/framework/validators/ValidatorTest.php @@ -0,0 +1,227 @@ + true, 'attr_runMe2' => true, 'attr_skip' => true), + $additionalAttributes + ); + return FakedValidationModel::createWithAttributes($attributes); + } + + public function testCreateValidator() + { + $model = FakedValidationModel::createWithAttributes(array('attr_test1' => 'abc', 'attr_test2' => '2013')); + /** @var $numberVal NumberValidator */ + $numberVal = TestValidator::createValidator('number', $model, array('attr_test1')); + $this->assertInstanceOf(NumberValidator::className(), $numberVal); + $numberVal = TestValidator::createValidator('integer', $model, array('attr_test2')); + $this->assertInstanceOf(NumberValidator::className(), $numberVal); + $this->assertTrue($numberVal->integerOnly); + $val = TestValidator::createValidator( + 'boolean', + $model, + 'attr_test1, attr_test2', + array('on' => array('a', 'b')) + ); + $this->assertInstanceOf(BooleanValidator::className(), $val); + $this->assertSame(array('a', 'b'), $val->on); + $this->assertSame(array('attr_test1', 'attr_test2'), $val->attributes); + $val = TestValidator::createValidator( + 'boolean', + $model, + 'attr_test1, attr_test2', + array('on' => 'a, b', 'except' => 'c,d,e') + ); + $this->assertInstanceOf(BooleanValidator::className(), $val); + $this->assertSame(array('a', 'b'), $val->on); + $this->assertSame(array('c', 'd', 'e'), $val->except); + $val = TestValidator::createValidator('inlineVal', $model, 'val_attr_a'); + $this->assertInstanceOf(InlineValidator::className(), $val); + $this->assertSame('inlineVal', $val->method); + } + + public function testValidate() + { + $val = new TestValidator(array('attributes' => array('attr_runMe1', 'attr_runMe2'))); + $model = $this->getTestModel(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithAttributeIntersect() + { + $val = new TestValidator(array('attributes' => array('attr_runMe1', 'attr_runMe2'))); + $model = $this->getTestModel(); + $val->validate($model, array('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithEmptyAttributes() + { + $val = new TestValidator(); + $model = $this->getTestModel(); + $val->validate($model, array('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $val->validate($model); + $this->assertFalse($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithError() + { + $val = new TestValidator(array('attributes' => array('attr_runMe1', 'attr_runMe2'), 'skipOnError' => false)); + $model = $this->getTestModel(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $val->validate($model, array('attr_runMe2')); + $this->assertEquals(2, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + $val = new TestValidator(array('attributes' => array('attr_runMe1', 'attr_runMe2'), 'skipOnError' => true)); + $model = $this->getTestModel(); + $val->enableErrorOnValidateAttribute(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + $val->validate($model, array('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + } + + public function testValidateWithEmpty() + { + $val = new TestValidator(array( + 'attributes' => array( + 'attr_runMe1', + 'attr_runMe2', + 'attr_empty1', + 'attr_empty2' + ), + 'skipOnEmpty' => true, + )); + $model = $this->getTestModel(array('attr_empty1' => '', 'attr_emtpy2' => ' ')); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_empty1')); + $this->assertFalse($val->isAttributeValidated('attr_empty2')); + $model->attr_empty1 = 'not empty anymore'; + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_empty1')); + $this->assertFalse($val->isAttributeValidated('attr_empty2')); + $val = new TestValidator(array( + 'attributes' => array( + 'attr_runMe1', + 'attr_runMe2', + 'attr_empty1', + 'attr_empty2' + ), + 'skipOnEmpty' => false, + )); + $model = $this->getTestModel(array('attr_empty1' => '', 'attr_emtpy2' => ' ')); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertTrue($val->isAttributeValidated('attr_empty1')); + $this->assertTrue($val->isAttributeValidated('attr_empty2')); + } + + public function testIsEmpty() + { + $val = new TestValidator(); + $this->assertTrue($val->isEmpty(null)); + $this->assertTrue($val->isEmpty(array())); + $this->assertTrue($val->isEmpty('')); + $this->assertFalse($val->isEmpty(5)); + $this->assertFalse($val->isEmpty(0)); + $this->assertFalse($val->isEmpty(new \stdClass())); + $this->assertFalse($val->isEmpty(' ')); + // trim + $this->assertTrue($val->isEmpty(' ', true)); + $this->assertTrue($val->isEmpty('', true)); + $this->assertTrue($val->isEmpty(" \t\n\r\0\x0B", true)); + $this->assertTrue($val->isEmpty('', true)); + $this->assertFalse($val->isEmpty('0', true)); + $this->assertFalse($val->isEmpty(0, true)); + $this->assertFalse($val->isEmpty('this ain\'t an empty value', true)); + } + + public function testValidateValue() + { + $this->setExpectedException( + 'yii\base\NotSupportedException', + TestValidator::className() . ' does not support validateValue().' + ); + $val = new TestValidator(); + $val->validateValue('abc'); + } + + public function testClientValidateAttribute() + { + $val = new TestValidator(); + $this->assertNull( + $val->clientValidateAttribute($this->getTestModel(), 'attr_runMe1', array()) + ); //todo pass a view instead of array + } + + public function testIsActive() + { + $val = new TestValidator(); + $this->assertTrue($val->isActive('scenA')); + $this->assertTrue($val->isActive('scenB')); + $val->except = array('scenB'); + $this->assertTrue($val->isActive('scenA')); + $this->assertFalse($val->isActive('scenB')); + $val->on = array('scenC'); + $this->assertFalse($val->isActive('scenA')); + $this->assertFalse($val->isActive('scenB')); + $this->assertTrue($val->isActive('scenC')); + } + + public function testAddError() + { + $val = new TestValidator(); + $m = $this->getTestModel(array('attr_msg_val' => 'abc')); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::abc', $errors[0]); + $m = $this->getTestModel(array('attr_msg_val' => array('bcc'))); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::array()', $errors[0]); + $m = $this->getTestModel(array('attr_msg_val' => 'abc')); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}::{param}', array('{param}' => 'param_value')); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::abc::param_value', $errors[0]); + } +} \ No newline at end of file From 8e48f01c85517edca2951d7b413ca291313b6cc3 Mon Sep 17 00:00:00 2001 From: Suralc Date: Wed, 14 Aug 2013 21:20:20 +0200 Subject: [PATCH 016/613] Add RegularExpressionValidatorTest --- .../validators/RegularExpressionValidatorTest.php | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/unit/framework/validators/RegularExpressionValidatorTest.php diff --git a/tests/unit/framework/validators/RegularExpressionValidatorTest.php b/tests/unit/framework/validators/RegularExpressionValidatorTest.php new file mode 100644 index 0000000..fc89139 --- /dev/null +++ b/tests/unit/framework/validators/RegularExpressionValidatorTest.php @@ -0,0 +1,48 @@ + '/^[a-zA-Z0-9](\.)?([^\/]*)$/m')); + $this->assertTrue($val->validateValue('b.4')); + $this->assertFalse($val->validateValue('b./')); + $this->assertFalse($val->validateValue(array('a', 'b'))); + $val->not = true; + $this->assertFalse($val->validateValue('b.4')); + $this->assertTrue($val->validateValue('b./')); + $this->assertFalse($val->validateValue(array('a', 'b'))); + } + + public function testValidateAttribute() + { + $val = new RegularExpressionValidator(array('pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m')); + $m = FakedValidationModel::createWithAttributes(array('attr_reg1' => 'b.4')); + $val->validateAttribute($m, 'attr_reg1'); + $this->assertFalse($m->hasErrors('attr_reg1')); + $m->attr_reg1 = 'b./'; + $val->validateAttribute($m, 'attr_reg1'); + $this->assertTrue($m->hasErrors('attr_reg1')); + } + + public function testMessageSetOnInit() + { + $val = new RegularExpressionValidator(array('pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m')); + $this->assertTrue(is_string($val->message)); + } + + public function testInitException() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new RegularExpressionValidator(); + $val->validateValue('abc'); + } + +} \ No newline at end of file From 5c8927af7d0e2d2748cbfba0b384257cf8299d19 Mon Sep 17 00:00:00 2001 From: Suralc Date: Sat, 17 Aug 2013 00:27:57 +0200 Subject: [PATCH 017/613] Added ExistValidatorTest and added new option to DatabaseTestCase --- tests/unit/data/mysql.sql | 32 ++++++++ tests/unit/data/postgres.sql | 29 +++++++ tests/unit/data/sqlite.sql | 33 +++++++- .../validators/models/ExistValidatorMainModel.php | 20 +++++ .../validators/models/ExistValidatorRefModel.php | 22 +++++ tests/unit/framework/db/DatabaseTestCase.php | 6 +- .../ExistValidatorPostgresTest.php | 10 +++ .../ExistValidatorSQliteTest.php | 10 +++ .../framework/validators/ExistValidatorTest.php | 94 ++++++++++++++++++++++ 9 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 tests/unit/data/validators/models/ExistValidatorMainModel.php create mode 100644 tests/unit/data/validators/models/ExistValidatorRefModel.php create mode 100644 tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php create mode 100644 tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorSQliteTest.php create mode 100644 tests/unit/framework/validators/ExistValidatorTest.php diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql index 2e9458e..9a31083 100644 --- a/tests/unit/data/mysql.sql +++ b/tests/unit/data/mysql.sql @@ -100,3 +100,35 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + + +/** + * (MySQL-)Database Schema for ExistValidatorTest + */ + +DROP TABLE IF EXISTS tbl_validator_exist_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_exist_ref CASCADE; + +CREATE TABLE `tbl_validator_exist_main` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `field1` VARCHAR(255), + PRIMARY KEY (`id`) +) ENGINE =InnoDB DEFAULT CHARSET =utf8; + +CREATE TABLE `tbl_validator_exist_ref` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `a_field` VARCHAR(255), + `ref` INT(11), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_3', 3); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_5', 5); diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index f8fb0eb..fc306da 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -92,3 +92,32 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + +/** + * (Postgres-)Database Schema for ExistValidatorTest + */ + +DROP TABLE IF EXISTS tbl_validator_exist_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_exist_ref CASCADE; + +CREATE TABLE tbl_validator_exist_main ( + id integer not null primary key, + field1 VARCHAR(255) +); + +CREATE TABLE tbl_validator_exist_ref ( + id integer not null primary key, + a_field VARCHAR(255), + ref integer +); + +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index f75bfa6..c1111da 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -85,4 +85,35 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); -INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); \ No newline at end of file +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + +/** + * (SqLite-)Database Schema for ExistValidatorTest + */ + +DROP TABLE IF EXISTS tbl_validator_exist_main; +DROP TABLE IF EXISTS tbl_validator_exist_ref; + +CREATE TABLE tbl_validator_exist_main ( + id INT(11) NOT NULL, + field1 VARCHAR(255), + PRIMARY KEY (id) +); + +CREATE TABLE tbl_validator_exist_ref ( + id INT(11) NOT NULL, + a_field VARCHAR(255), + ref INT(11), + PRIMARY KEY (id) +); + +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file diff --git a/tests/unit/data/validators/models/ExistValidatorMainModel.php b/tests/unit/data/validators/models/ExistValidatorMainModel.php new file mode 100644 index 0000000..5f3ff00 --- /dev/null +++ b/tests/unit/data/validators/models/ExistValidatorMainModel.php @@ -0,0 +1,20 @@ +hasMany(ExistValidatorRefModel::className(), array('ref' => 'id')); + } +} \ No newline at end of file diff --git a/tests/unit/data/validators/models/ExistValidatorRefModel.php b/tests/unit/data/validators/models/ExistValidatorRefModel.php new file mode 100644 index 0000000..6cafa39 --- /dev/null +++ b/tests/unit/data/validators/models/ExistValidatorRefModel.php @@ -0,0 +1,22 @@ +hasOne(ExistValidatorMainModel::className(), array('id' => 'ref')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 1fd2d56..c3d4206 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -8,6 +8,7 @@ abstract class DatabaseTestCase extends TestCase protected $database; protected $driverName = 'mysql'; protected $db; + protected $initializeAppWithDb = false; protected function setUp() { @@ -18,7 +19,10 @@ abstract class DatabaseTestCase extends TestCase $pdo_database = 'pdo_'.$this->driverName; if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { - $this->markTestSkipped('pdo and pdo_'.$pdo_database.' extension are required.'); + $this->markTestSkipped('pdo and '.$pdo_database.' extension are required.'); + } + if ($this->initializeAppWithDb === true) { + \Yii::$app->setComponent('db', $this->getConnection()); } } diff --git a/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php new file mode 100644 index 0000000..539b458 --- /dev/null +++ b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php @@ -0,0 +1,10 @@ +getComponent('db'); + } + + public function tearDown() + { + parent::tearDown(); + } + + public function testValidateValueExpectedException() + { + try { + $val = new ExistValidator(); + $result = $val->validateValue('ref'); + $this->fail('Exception should have been thrown at this time'); + } catch (Exception $e) { + $this->assertInstanceOf('yii\base\InvalidConfigException', $e); + $this->assertEquals('The "className" property must be set.', $e->getMessage()); + } + // combine to save the time creating a new db-fixture set (likely ~5 sec) + try { + $val = new ExistValidator(array('className' => ExistValidatorMainModel::className())); + $val->validateValue('ref'); + $this->fail('Exception should have been thrown at this time'); + } catch (Exception $e) { + $this->assertInstanceOf('yii\base\InvalidConfigException', $e); + $this->assertEquals('The "attributeName" property must be set.', $e->getMessage()); + } + } + + public function testValidateValue() + { + $val = new ExistValidator(array('className' => ExistValidatorRefModel::className(), 'attributeName' => 'id')); + $this->assertTrue($val->validateValue(2)); + $this->assertTrue($val->validateValue(5)); + $this->assertFalse($val->validateValue(99)); + $this->assertFalse($val->validateValue(array('1'))); + } + + public function testValidateAttribute() + { + // existing value on different table + $val = new ExistValidator(array('className' => ExistValidatorMainModel::className(), 'attributeName' => 'id')); + $m = ExistValidatorRefModel::find(array('id' => 1)); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors()); + // non-existing value on different table + $val = new ExistValidator(array('className' => ExistValidatorMainModel::className(), 'attributeName' => 'id')); + $m = ExistValidatorRefModel::find(array('id' => 6)); + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + // existing value on same table + $val = new ExistValidator(array('attributeName' => 'ref')); + $m = ExistValidatorRefModel::find(array('id' => 2)); + $val->validateAttribute($m, 'test_val'); + $this->assertFalse($m->hasErrors()); + // non-existing value on same table + $val = new ExistValidator(array('attributeName' => 'ref')); + $m = ExistValidatorRefModel::find(array('id' => 5)); + $val->validateAttribute($m, 'test_val_fail'); + $this->assertTrue($m->hasErrors('test_val_fail')); + // check for given value (true) + $val = new ExistValidator(); + $m = ExistValidatorRefModel::find(array('id' => 3)); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors()); + // check for given defaults (false) + $val = new ExistValidator(); + $m = ExistValidatorRefModel::find(array('id' => 4)); + $m->a_field = 'some new value'; + $val->validateAttribute($m, 'a_field'); + $this->assertTrue($m->hasErrors('a_field')); + } +} \ No newline at end of file From 0c45765e7d53bdb18e5f90aa6cb441329474bfb1 Mon Sep 17 00:00:00 2001 From: Suralc Date: Sun, 18 Aug 2013 14:39:22 +0200 Subject: [PATCH 018/613] Added tests for uncovered lines. --- tests/unit/framework/validators/DateValidatorTest.php | 5 ++++- tests/unit/framework/validators/ExistValidatorTest.php | 6 ++++++ tests/unit/framework/validators/NumberValidatorTest.php | 4 ++++ tests/unit/framework/validators/StringValidatorTest.php | 4 ++++ tests/unit/framework/validators/UrlValidatorTest.php | 6 ++++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/validators/DateValidatorTest.php b/tests/unit/framework/validators/DateValidatorTest.php index 30a515e..a551889 100644 --- a/tests/unit/framework/validators/DateValidatorTest.php +++ b/tests/unit/framework/validators/DateValidatorTest.php @@ -55,7 +55,10 @@ class DateValidatorTest extends TestCase DateTime::createFromFormat($val->format, '2013-09-13')->getTimestamp(), $model->attr_timestamp ); - + $val = new DateValidator(); + $model = FakedValidationModel::createWithAttributes(array('attr_date' => array())); + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); } } \ No newline at end of file diff --git a/tests/unit/framework/validators/ExistValidatorTest.php b/tests/unit/framework/validators/ExistValidatorTest.php index 6a1e5fe..930712c 100644 --- a/tests/unit/framework/validators/ExistValidatorTest.php +++ b/tests/unit/framework/validators/ExistValidatorTest.php @@ -90,5 +90,11 @@ class ExistValidatorTest extends DatabaseTestCase $m->a_field = 'some new value'; $val->validateAttribute($m, 'a_field'); $this->assertTrue($m->hasErrors('a_field')); + // check array + $val = new ExistValidator(array('attributeName' => 'ref')); + $m = ExistValidatorRefModel::find(array('id' => 2)); + $m->test_val = array(1,2,3); + $val->validateAttribute($m, 'test_val'); + $this->assertTrue($m->hasErrors('test_val')); } } \ No newline at end of file diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/unit/framework/validators/NumberValidatorTest.php index 4868503..fa158d3 100644 --- a/tests/unit/framework/validators/NumberValidatorTest.php +++ b/tests/unit/framework/validators/NumberValidatorTest.php @@ -137,6 +137,10 @@ class NumberValidatorTest extends TestCase $model->attr_number = 3.43; $val->validateAttribute($model, 'attr_number'); $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(array('min' => 1)); + $model = FakedValidationModel::createWithAttributes(array('attr_num' => array(1,2,3))); + $val->validateAttribute($model, 'attr_num'); + $this->assertTrue($model->hasErrors('attr_num')); } public function testEnsureCustomMessageIsSetOnValidateAttribute() diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php index c7d2889..8beaf16 100644 --- a/tests/unit/framework/validators/StringValidatorTest.php +++ b/tests/unit/framework/validators/StringValidatorTest.php @@ -86,6 +86,10 @@ class StringValidatorTest extends TestCase $model->attr_string = 'abc'; $val->validateAttribute($model, 'attr_string'); $this->assertTrue($model->hasErrors('attr_string')); + $val = new StringValidator(array('max' => 1)); + $model = FakedValidationModel::createWithAttributes(array('attr_str' => array('abc'))); + $val->validateAttribute($model, 'attr_str'); + $this->assertTrue($model->hasErrors('attr_str')); } public function testEnsureMessagesOnInit() diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/unit/framework/validators/UrlValidatorTest.php index 7b618c1..239c92b 100644 --- a/tests/unit/framework/validators/UrlValidatorTest.php +++ b/tests/unit/framework/validators/UrlValidatorTest.php @@ -29,6 +29,12 @@ class UrlValidatorTest extends TestCase $this->assertTrue($val->validateValue('yiiframework.com')); $this->assertTrue($val->validateValue('http://yiiframework.com')); } + + public function testValidateValueWithoutScheme() + { + $val = new UrlValidator(array('pattern' => '/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i')); + $this->assertTrue($val->validateValue('yiiframework.com')); + } public function testValidateWithCustomScheme() { From 3469b2cb60aa08093ef6fc2041e1583fc6df44b5 Mon Sep 17 00:00:00 2001 From: Suralc Date: Thu, 22 Aug 2013 16:12:09 +0200 Subject: [PATCH 019/613] RangeValidator tests. --- .../framework/validators/RangeValidatorTest.php | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/unit/framework/validators/RangeValidatorTest.php diff --git a/tests/unit/framework/validators/RangeValidatorTest.php b/tests/unit/framework/validators/RangeValidatorTest.php new file mode 100644 index 0000000..a22d190 --- /dev/null +++ b/tests/unit/framework/validators/RangeValidatorTest.php @@ -0,0 +1,70 @@ +setExpectedException('yii\base\InvalidConfigException', 'The "range" property must be set.'); + $val = new RangeValidator(array('range' => 'not an array')); + } + + public function testAssureMessageSetOnInit() + { + $val = new RangeValidator(array('range' => array())); + $this->assertTrue(is_string($val->message)); + } + + public function testValidateValue() + { + $val = new RangeValidator(array('range' => range(1, 10, 1))); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(0)); + $this->assertFalse($val->validateValue(11)); + $this->assertFalse($val->validateValue(5.5)); + $this->assertTrue($val->validateValue(10)); + $this->assertTrue($val->validateValue("10")); + $this->assertTrue($val->validateValue("5")); + } + + public function testValidateValueStrict() + { + $val = new RangeValidator(array('range' => range(1, 10, 1), 'strict' => true)); + $this->assertTrue($val->validateValue(1)); + $this->assertTrue($val->validateValue(5)); + $this->assertTrue($val->validateValue(10)); + $this->assertFalse($val->validateValue("1")); + $this->assertFalse($val->validateValue("10")); + $this->assertFalse($val->validateValue("5.5")); + } + + public function testValidateValueNot() + { + $val = new RangeValidator(array('range' => range(1, 10, 1), 'not' => true)); + $this->assertFalse($val->validateValue(1)); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(11)); + $this->assertTrue($val->validateValue(5.5)); + $this->assertFalse($val->validateValue(10)); + $this->assertFalse($val->validateValue("10")); + $this->assertFalse($val->validateValue("5")); + } + + public function testValidateAttribute() + { + $val = new RangeValidator(array('range' => range(1, 10, 1))); + $m = FakedValidationModel::createWithAttributes(array('attr_r1' => 5, 'attr_r2' => 999)); + $val->validateAttribute($m, 'attr_r1'); + $this->assertFalse($m->hasErrors()); + $val->validateAttribute($m, 'attr_r2'); + $this->assertTrue($m->hasErrors('attr_r2')); + $err = $m->getErrors('attr_r2'); + $this->assertTrue(stripos($err[0], 'attr_r2') !== false); + } +} \ No newline at end of file From 3b07e4affcb13c12cf04bb4924a769b324925829 Mon Sep 17 00:00:00 2001 From: Suralc Date: Fri, 23 Aug 2013 17:24:56 +0200 Subject: [PATCH 020/613] Added FileValidator tests. --- .../framework/validators/FileValidatorTest.php | 275 +++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 tests/unit/framework/validators/FileValidatorTest.php diff --git a/tests/unit/framework/validators/FileValidatorTest.php b/tests/unit/framework/validators/FileValidatorTest.php new file mode 100644 index 0000000..820afb5 --- /dev/null +++ b/tests/unit/framework/validators/FileValidatorTest.php @@ -0,0 +1,275 @@ +mockApplication(); + } + + public function testAssureMessagesSetOnInit() + { + $val = new FileValidator(); + foreach (array('message', 'uploadRequired', 'tooMany', 'wrongType', 'tooBig', 'tooSmall') as $attr) { + $this->assertTrue(is_string($val->$attr)); + } + } + + public function testTypeSplitOnInit() + { + $val = new FileValidator(array('types' => 'jpeg, jpg, gif')); + $this->assertEquals(array('jpeg', 'jpg', 'gif'), $val->types); + $val = new FileValidator(array('types' => 'jpeg')); + $this->assertEquals(array('jpeg'), $val->types); + $val = new FileValidator(array('types' => '')); + $this->assertEquals(array(), $val->types); + $val = new FileValidator(array('types' => array())); + $this->assertEquals(array(), $val->types); + $val = new FileValidator(); + $this->assertEquals(array(), $val->types); + $val = new FileValidator(array('types' => array('jpeg', 'exe'))); + $this->assertEquals(array('jpeg', 'exe'), $val->types); + } + + public function testGetSizeLimit() + { + $size = $this->sizeToBytes(ini_get('upload_max_filesize')); + $val = new FileValidator(); + $this->assertEquals($size, $val->getSizeLimit()); + $val->maxSize = $size + 1; // set and test if value is overridden + $this->assertEquals($size, $val->getSizeLimit()); + $val->maxSize = abs($size - 1); + $this->assertEquals($size - 1, $val->getSizeLimit()); + $_POST['MAX_FILE_SIZE'] = $size + 1; + $this->assertEquals($size - 1, $val->getSizeLimit()); + $_POST['MAX_FILE_SIZE'] = abs($size - 2); + $this->assertSame($_POST['MAX_FILE_SIZE'], $val->getSizeLimit()); + } + + protected function sizeToBytes($sizeStr) + { + switch (substr($sizeStr, -1)) { + case 'M': + case 'm': + return (int)$sizeStr * 1048576; + case 'K': + case 'k': + return (int)$sizeStr * 1024; + case 'G': + case 'g': + return (int)$sizeStr * 1073741824; + default: + return (int)$sizeStr; + } + } + + public function testValidateAttributeMultiple() + { + $val = new FileValidator(array('maxFiles' => 2)); + $m = FakedValidationModel::createWithAttributes(array('attr_files' => 'path')); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $m = FakedValidationModel::createWithAttributes(array('attr_files' => array())); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertSame($val->uploadRequired, current($m->getErrors('attr_files'))); + $m = FakedValidationModel::createWithAttributes( + array( + 'attr_files' => $this->createTestFiles( + array( + array( + 'name' => 'test_up_1.txt', + 'size' => 1024, + ), + array( + 'error' => UPLOAD_ERR_NO_FILE, + ), + ) + ) + ) + ); + $val->validateAttribute($m, 'attr_files'); + $this->assertFalse($m->hasErrors('attr_files')); + $m = FakedValidationModel::createWithAttributes( + array('attr_files' => $this->createTestFiles(array(array(''), array(''), array(''),))) + ); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors()); + $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'you can upload at most') !== false); + } + + /** + * @param array $params + * @return UploadedFile[] + */ + protected function createTestFiles($params = array()) + { + $rndString = function ($len = 10) { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $randomString = ''; + for ($i = 0; $i < $len; $i++) { + $randomString .= $characters[rand(0, strlen($characters) - 1)]; + } + return $randomString; + }; + $files = array(); + foreach ($params as $param) { + if (empty($param) && count($params) != 1) { + $files[] = array('no instance of UploadedFile'); + continue; + } + $name = isset($param['name']) ? $param['name'] : $rndString(); + $tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp') . $name; + if (is_readable($tempName)) { + $size = filesize($tempName); + } else { + $size = isset($param['size']) ? $param['size'] : rand( + 1, + $this->sizeToBytes(ini_get('upload_max_filesize')) + ); + } + $type = isset($param['type']) ? $param['type'] : 'text/plain'; + $error = isset($param['error']) ? $param['error'] : UPLOAD_ERR_OK; + if (count($params) == 1) { + $error = empty($param) ? UPLOAD_ERR_NO_FILE : $error; + return new UploadedFile($name, $tempName, $type, $size, $error); + } + $files[] = new UploadedFile($name, $tempName, $type, $size, $error); + } + return $files; + } + + public function testValidateAttribute() + { + // single File + $val = new FileValidator(); + $m = $this->createModelForAttributeTest(); + $val->validateAttribute($m, 'attr_files'); + $this->assertFalse($m->hasErrors()); + $val->validateAttribute($m, 'attr_files_empty'); + $this->assertTrue($m->hasErrors('attr_files_empty')); + $this->assertSame($val->uploadRequired, current($m->getErrors('attr_files_empty'))); + $m = $this->createModelForAttributeTest(); + // too big + $val = new FileValidator(array('maxSize' => 128)); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_files')), + str_ireplace(array('{file}', '{limit}'), array($m->attr_files->getName(), 128), $val->tooBig) + ) !== false + ); + // to Small + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(array('minSize' => 2048)); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_files')), + str_ireplace(array('{file}', '{limit}'), array($m->attr_files->getName(), 2048), $val->tooSmall) + ) !== false + ); + // UPLOAD_ERR_INI_SIZE/UPLOAD_ERR_FORM_SIZE + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_ini'); + $this->assertTrue($m->hasErrors('attr_err_ini')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_err_ini')), + str_ireplace( + array('{file}', '{limit}'), + array($m->attr_err_ini->getName(), $val->getSizeLimit()), + $val->tooBig + ) + ) !== false + ); + // UPLOAD_ERR_PARTIAL + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_part'); + $this->assertTrue($m->hasErrors('attr_err_part')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_part'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + } + + protected function createModelForAttributeTest() + { + return FakedValidationModel::createWithAttributes( + array( + 'attr_files' => $this->createTestFiles( + array( + array('name' => 'abc.jpg', 'size' => 1024, 'type' => 'image/jpeg'), + ) + ), + 'attr_files_empty' => $this->createTestFiles(array(array())), + 'attr_err_ini' => $this->createTestFiles(array(array('error' => UPLOAD_ERR_INI_SIZE))), + 'attr_err_part' => $this->createTestFiles(array(array('error' => UPLOAD_ERR_PARTIAL))), + 'attr_err_tmp' => $this->createTestFiles(array(array('error' => UPLOAD_ERR_NO_TMP_DIR))), + 'attr_err_write' => $this->createTestFiles(array(array('error' => UPLOAD_ERR_CANT_WRITE))), + 'attr_err_ext' => $this->createTestFiles(array(array('error' => UPLOAD_ERR_EXTENSION))), + + ) + ); + } + + public function testValidateAttributeErrPartial() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_part'); + $this->assertTrue($m->hasErrors('attr_err_part')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_part'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('File was only', $log['messages'][0][0]); + } + + public function testValidateAttributeErrCantWrite() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_write'); + $this->assertTrue($m->hasErrors('attr_err_write')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_write'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('Failed to write', $log['messages'][0][0]); + } + + public function testValidateAttributeErrExtension() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_ext'); + $this->assertTrue($m->hasErrors('attr_err_ext')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_ext'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('PHP extension', $log['messages'][0][0]); + } + + public function testValidateAttributeErrNoTmpDir() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_tmp'); + $this->assertTrue($m->hasErrors('attr_err_tmp')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_tmp'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('Missing the temporary folder', $log['messages'][0][0]); + } +} \ No newline at end of file From 7d5bb082ac32a0e3bf2df1a3a4fd52f85f264128 Mon Sep 17 00:00:00 2001 From: Suralc Date: Fri, 23 Aug 2013 22:33:39 +0200 Subject: [PATCH 021/613] FilterValidator test --- .../framework/validators/FilterValidatorTest.php | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/unit/framework/validators/FilterValidatorTest.php diff --git a/tests/unit/framework/validators/FilterValidatorTest.php b/tests/unit/framework/validators/FilterValidatorTest.php new file mode 100644 index 0000000..322f921 --- /dev/null +++ b/tests/unit/framework/validators/FilterValidatorTest.php @@ -0,0 +1,46 @@ +setExpectedException('yii\base\InvalidConfigException'); + $val = new FilterValidator(); + } + + public function testValidateAttribute() + { + $m = FakedValidationModel::createWithAttributes(array( + 'attr_one' => ' to be trimmed ', + 'attr_two' => 'set this to null', + 'attr_empty1' => '', + 'attr_empty2' => null + )); + $val = new FilterValidator(array('filter' => 'trim')); + $val->validateAttribute($m, 'attr_one'); + $this->assertSame('to be trimmed', $m->attr_one); + $val->filter = function ($value) { + return null; + }; + $val->validateAttribute($m, 'attr_two'); + $this->assertNull($m->attr_two); + $val->filter = array($this, 'notToBeNull'); + $val->validateAttribute($m, 'attr_empty1'); + $this->assertSame($this->notToBeNull(''), $m->attr_empty1); + $val->skipOnEmpty = true; + $val->validateAttribute($m, 'attr_empty2'); + $this->assertNotNull($m->attr_empty2); + } + + public function notToBeNull($value) + { + return 'not null'; + } +} \ No newline at end of file From 24e2f885037bca1de58cdf0b6e67e72966c6cbf3 Mon Sep 17 00:00:00 2001 From: Suralc Date: Fri, 23 Aug 2013 22:47:45 +0200 Subject: [PATCH 022/613] Renamed validator test models to be less specific. --- tests/unit/data/mysql.sql | 30 +++++++++++----------- tests/unit/data/postgres.sql | 30 +++++++++++----------- tests/unit/data/sqlite.sql | 30 +++++++++++----------- .../validators/models/ExistValidatorMainModel.php | 20 --------------- .../validators/models/ExistValidatorRefModel.php | 22 ---------------- .../validators/models/ValidatorTestMainModel.php | 21 +++++++++++++++ .../validators/models/ValidatorTestRefModel.php | 23 +++++++++++++++++ .../framework/validators/CompareValidatorTest.php | 5 ++++ .../framework/validators/ExistValidatorTest.php | 26 +++++++++---------- 9 files changed, 107 insertions(+), 100 deletions(-) delete mode 100644 tests/unit/data/validators/models/ExistValidatorMainModel.php delete mode 100644 tests/unit/data/validators/models/ExistValidatorRefModel.php create mode 100644 tests/unit/data/validators/models/ValidatorTestMainModel.php create mode 100644 tests/unit/data/validators/models/ValidatorTestRefModel.php diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql index 9a31083..9ed0d90 100644 --- a/tests/unit/data/mysql.sql +++ b/tests/unit/data/mysql.sql @@ -103,32 +103,32 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, /** - * (MySQL-)Database Schema for ExistValidatorTest + * (MySQL-)Database Schema for validator tests */ -DROP TABLE IF EXISTS tbl_validator_exist_main CASCADE; -DROP TABLE IF EXISTS tbl_validator_exist_ref CASCADE; +DROP TABLE IF EXISTS tbl_validator_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_ref CASCADE; -CREATE TABLE `tbl_validator_exist_main` ( +CREATE TABLE tbl_validator_main ( `id` INT(11) NOT NULL AUTO_INCREMENT, `field1` VARCHAR(255), PRIMARY KEY (`id`) ) ENGINE =InnoDB DEFAULT CHARSET =utf8; -CREATE TABLE `tbl_validator_exist_ref` ( +CREATE TABLE tbl_validator_ref ( `id` INT(11) NOT NULL AUTO_INCREMENT, `a_field` VARCHAR(255), `ref` INT(11), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_3', 3); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (a_field, ref) VALUES ('ref_to_5', 5); +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_3', 3); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_5', 5); diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index fc306da..c2702c9 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -94,30 +94,30 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); /** - * (Postgres-)Database Schema for ExistValidatorTest + * (Postgres-)Database Schema for validator tests */ -DROP TABLE IF EXISTS tbl_validator_exist_main CASCADE; -DROP TABLE IF EXISTS tbl_validator_exist_ref CASCADE; +DROP TABLE IF EXISTS tbl_validator_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_ref CASCADE; -CREATE TABLE tbl_validator_exist_main ( +CREATE TABLE tbl_validator_main ( id integer not null primary key, field1 VARCHAR(255) ); -CREATE TABLE tbl_validator_exist_ref ( +CREATE TABLE tbl_validator_ref ( id integer not null primary key, a_field VARCHAR(255), ref integer ); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index c1111da..8976519 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -88,32 +88,32 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); /** - * (SqLite-)Database Schema for ExistValidatorTest + * (SqLite-)Database Schema for validator tests */ -DROP TABLE IF EXISTS tbl_validator_exist_main; -DROP TABLE IF EXISTS tbl_validator_exist_ref; +DROP TABLE IF EXISTS tbl_validator_main; +DROP TABLE IF EXISTS tbl_validator_ref; -CREATE TABLE tbl_validator_exist_main ( +CREATE TABLE tbl_validator_main ( id INT(11) NOT NULL, field1 VARCHAR(255), PRIMARY KEY (id) ); -CREATE TABLE tbl_validator_exist_ref ( +CREATE TABLE tbl_validator_ref ( id INT(11) NOT NULL, a_field VARCHAR(255), ref INT(11), PRIMARY KEY (id) ); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (1, 'just a string1'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (2, 'just a string2'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (3, 'just a string3'); -INSERT INTO tbl_validator_exist_main (id, field1) VALUES (4, 'just a string4'); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); -INSERT INTO tbl_validator_exist_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file diff --git a/tests/unit/data/validators/models/ExistValidatorMainModel.php b/tests/unit/data/validators/models/ExistValidatorMainModel.php deleted file mode 100644 index 5f3ff00..0000000 --- a/tests/unit/data/validators/models/ExistValidatorMainModel.php +++ /dev/null @@ -1,20 +0,0 @@ -hasMany(ExistValidatorRefModel::className(), array('ref' => 'id')); - } -} \ No newline at end of file diff --git a/tests/unit/data/validators/models/ExistValidatorRefModel.php b/tests/unit/data/validators/models/ExistValidatorRefModel.php deleted file mode 100644 index 6cafa39..0000000 --- a/tests/unit/data/validators/models/ExistValidatorRefModel.php +++ /dev/null @@ -1,22 +0,0 @@ -hasOne(ExistValidatorMainModel::className(), array('id' => 'ref')); - } -} \ No newline at end of file diff --git a/tests/unit/data/validators/models/ValidatorTestMainModel.php b/tests/unit/data/validators/models/ValidatorTestMainModel.php new file mode 100644 index 0000000..6f2808b --- /dev/null +++ b/tests/unit/data/validators/models/ValidatorTestMainModel.php @@ -0,0 +1,21 @@ +hasMany(ValidatorTestRefModel::className(), array('ref' => 'id')); + } +} \ No newline at end of file diff --git a/tests/unit/data/validators/models/ValidatorTestRefModel.php b/tests/unit/data/validators/models/ValidatorTestRefModel.php new file mode 100644 index 0000000..aa3d199 --- /dev/null +++ b/tests/unit/data/validators/models/ValidatorTestRefModel.php @@ -0,0 +1,23 @@ +hasOne(ValidatorTestMainModel::className(), array('id' => 'ref')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/CompareValidatorTest.php b/tests/unit/framework/validators/CompareValidatorTest.php index 7ae0263..46ab383 100644 --- a/tests/unit/framework/validators/CompareValidatorTest.php +++ b/tests/unit/framework/validators/CompareValidatorTest.php @@ -77,6 +77,11 @@ class CompareValidatorTest extends TestCase array($value + 1, false), array($value - 1, true), ), + //'non-op' => array( + // array($value, false), + // array($value + 1, false), + // array($value - 1, false), + //), ); } diff --git a/tests/unit/framework/validators/ExistValidatorTest.php b/tests/unit/framework/validators/ExistValidatorTest.php index 930712c..40d0935 100644 --- a/tests/unit/framework/validators/ExistValidatorTest.php +++ b/tests/unit/framework/validators/ExistValidatorTest.php @@ -7,8 +7,8 @@ use Yii; use yii\base\Exception; use yii\validators\ExistValidator; use yiiunit\data\ar\ActiveRecord; -use yiiunit\data\validators\models\ExistValidatorMainModel; -use yiiunit\data\validators\models\ExistValidatorRefModel; +use yiiunit\data\validators\models\ValidatorTestMainModel; +use yiiunit\data\validators\models\ValidatorTestRefModel; use yiiunit\framework\db\DatabaseTestCase; class ExistValidatorTest extends DatabaseTestCase @@ -39,7 +39,7 @@ class ExistValidatorTest extends DatabaseTestCase } // combine to save the time creating a new db-fixture set (likely ~5 sec) try { - $val = new ExistValidator(array('className' => ExistValidatorMainModel::className())); + $val = new ExistValidator(array('className' => ValidatorTestMainModel::className())); $val->validateValue('ref'); $this->fail('Exception should have been thrown at this time'); } catch (Exception $e) { @@ -50,7 +50,7 @@ class ExistValidatorTest extends DatabaseTestCase public function testValidateValue() { - $val = new ExistValidator(array('className' => ExistValidatorRefModel::className(), 'attributeName' => 'id')); + $val = new ExistValidator(array('className' => ValidatorTestRefModel::className(), 'attributeName' => 'id')); $this->assertTrue($val->validateValue(2)); $this->assertTrue($val->validateValue(5)); $this->assertFalse($val->validateValue(99)); @@ -60,39 +60,39 @@ class ExistValidatorTest extends DatabaseTestCase public function testValidateAttribute() { // existing value on different table - $val = new ExistValidator(array('className' => ExistValidatorMainModel::className(), 'attributeName' => 'id')); - $m = ExistValidatorRefModel::find(array('id' => 1)); + $val = new ExistValidator(array('className' => ValidatorTestMainModel::className(), 'attributeName' => 'id')); + $m = ValidatorTestRefModel::find(array('id' => 1)); $val->validateAttribute($m, 'ref'); $this->assertFalse($m->hasErrors()); // non-existing value on different table - $val = new ExistValidator(array('className' => ExistValidatorMainModel::className(), 'attributeName' => 'id')); - $m = ExistValidatorRefModel::find(array('id' => 6)); + $val = new ExistValidator(array('className' => ValidatorTestMainModel::className(), 'attributeName' => 'id')); + $m = ValidatorTestRefModel::find(array('id' => 6)); $val->validateAttribute($m, 'ref'); $this->assertTrue($m->hasErrors('ref')); // existing value on same table $val = new ExistValidator(array('attributeName' => 'ref')); - $m = ExistValidatorRefModel::find(array('id' => 2)); + $m = ValidatorTestRefModel::find(array('id' => 2)); $val->validateAttribute($m, 'test_val'); $this->assertFalse($m->hasErrors()); // non-existing value on same table $val = new ExistValidator(array('attributeName' => 'ref')); - $m = ExistValidatorRefModel::find(array('id' => 5)); + $m = ValidatorTestRefModel::find(array('id' => 5)); $val->validateAttribute($m, 'test_val_fail'); $this->assertTrue($m->hasErrors('test_val_fail')); // check for given value (true) $val = new ExistValidator(); - $m = ExistValidatorRefModel::find(array('id' => 3)); + $m = ValidatorTestRefModel::find(array('id' => 3)); $val->validateAttribute($m, 'ref'); $this->assertFalse($m->hasErrors()); // check for given defaults (false) $val = new ExistValidator(); - $m = ExistValidatorRefModel::find(array('id' => 4)); + $m = ValidatorTestRefModel::find(array('id' => 4)); $m->a_field = 'some new value'; $val->validateAttribute($m, 'a_field'); $this->assertTrue($m->hasErrors('a_field')); // check array $val = new ExistValidator(array('attributeName' => 'ref')); - $m = ExistValidatorRefModel::find(array('id' => 2)); + $m = ValidatorTestRefModel::find(array('id' => 2)); $m->test_val = array(1,2,3); $val->validateAttribute($m, 'test_val'); $this->assertTrue($m->hasErrors('test_val')); From c189e5ad3f4e7fbdd4f1bc7a2edda1a2b54813f6 Mon Sep 17 00:00:00 2001 From: Suralc Date: Sat, 24 Aug 2013 00:05:27 +0200 Subject: [PATCH 023/613] UniqueValidator test. --- .../UniqueValidatorPostgresTest.php | 11 +++ .../UniqueValidatorSQliteTest.php | 11 +++ .../framework/validators/UniqueValidatorTest.php | 88 ++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php create mode 100644 tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorSQliteTest.php create mode 100644 tests/unit/framework/validators/UniqueValidatorTest.php diff --git a/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php new file mode 100644 index 0000000..9adc57c --- /dev/null +++ b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php @@ -0,0 +1,11 @@ +getComponent('db'); + } + + public function testAssureMessageSetOnInit() + { + $val = new UniqueValidator(); + $this->assertTrue(is_string($val->message)); + } + + public function testValidateAttributeDefault() + { + $val = new UniqueValidator(); + $m = ValidatorTestMainModel::find()->one(); + $val->validateAttribute($m, 'id'); + $this->assertFalse($m->hasErrors('id')); + $m = ValidatorTestRefModel::find(1); + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + // new record: + $m = new ValidatorTestRefModel(); + $m->ref = 5; + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + $m = new ValidatorTestRefModel(); + $m->ref = 12121; + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors('ref')); + $m->save(false); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors('ref')); + // array error + $m = FakedValidationModel::createWithAttributes(array('attr_arr' => array('a', 'b'))); + $val->validateAttribute($m, 'attr_arr'); + $this->assertTrue($m->hasErrors('attr_arr')); + } + + public function testValidateAttributeOfNonARModel() + { + $val = new UniqueValidator(array('className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref')); + $m = FakedValidationModel::createWithAttributes(array('attr_1' => 5, 'attr_2' => 1313)); + $val->validateAttribute($m, 'attr_1'); + $this->assertTrue($m->hasErrors('attr_1')); + $val->validateAttribute($m, 'attr_2'); + $this->assertFalse($m->hasErrors('attr_2')); + } + + public function testValidateNonDatabaseAttribute() + { + $val = new UniqueValidator(array('className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref')); + $m = ValidatorTestMainModel::find(1); + $val->validateAttribute($m, 'testMainVal'); + $this->assertFalse($m->hasErrors('testMainVal')); + $m = ValidatorTestMainModel::find(1); + $m->testMainVal = 4; + $val->validateAttribute($m, 'testMainVal'); + $this->assertTrue($m->hasErrors('testMainVal')); + } + + public function testValidateAttributeAttributeNotInTableException() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new UniqueValidator(); + $m = new ValidatorTestMainModel(); + $val->validateAttribute($m, 'testMainVal'); + } +} \ No newline at end of file From 06682d0ba9407279c06005ccf96e09d04e1f5002 Mon Sep 17 00:00:00 2001 From: Suralc Date: Sat, 24 Aug 2013 00:05:55 +0200 Subject: [PATCH 024/613] coverage improvements --- tests/unit/data/sqlite.sql | 10 ++++------ .../framework/validators/CompareValidatorTest.php | 6 ++++++ .../unit/framework/validators/FileValidatorTest.php | 17 +++++++++++++++++ .../framework/validators/RequiredValidatorTest.php | 20 ++++++++++++++++++++ .../framework/validators/UniqueValidatorTest.php | 1 + 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index 8976519..71eaf46 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -95,16 +95,14 @@ DROP TABLE IF EXISTS tbl_validator_main; DROP TABLE IF EXISTS tbl_validator_ref; CREATE TABLE tbl_validator_main ( - id INT(11) NOT NULL, - field1 VARCHAR(255), - PRIMARY KEY (id) + id INTEGER PRIMARY KEY , + field1 VARCHAR(255) ); CREATE TABLE tbl_validator_ref ( - id INT(11) NOT NULL, + id INTEGER PRIMARY KEY , a_field VARCHAR(255), - ref INT(11), - PRIMARY KEY (id) + ref INT(11) ); INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); diff --git a/tests/unit/framework/validators/CompareValidatorTest.php b/tests/unit/framework/validators/CompareValidatorTest.php index 46ab383..5a16a30 100644 --- a/tests/unit/framework/validators/CompareValidatorTest.php +++ b/tests/unit/framework/validators/CompareValidatorTest.php @@ -128,6 +128,12 @@ class CompareValidatorTest extends TestCase $val->validateAttribute($model, 'attr_test'); $this->assertTrue($model->hasErrors('attr_test')); $this->assertFalse($model->hasErrors('attr_test_repeat')); + // not existing op + $val = new CompareValidator(); + $val->operator = '<>'; + $model = FakedValidationModel::createWithAttributes(array('attr_o' => 5, 'attr_o_repeat' => 5 )); + $val->validateAttribute($model, 'attr_o'); + $this->assertTrue($model->hasErrors('attr_o')); } public function testValidateAttributeOperators() diff --git a/tests/unit/framework/validators/FileValidatorTest.php b/tests/unit/framework/validators/FileValidatorTest.php index 820afb5..4d74383 100644 --- a/tests/unit/framework/validators/FileValidatorTest.php +++ b/tests/unit/framework/validators/FileValidatorTest.php @@ -205,6 +205,23 @@ class FileValidatorTest extends TestCase $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); } + public function testValidateAttributeType() + { + $val = new FileValidator(array('types' => 'jpeg, jpg')); + $m = FakedValidationModel::createWithAttributes( + array( + 'attr_jpg' => $this->createTestFiles(array(array('name' => 'one.jpeg'))), + 'attr_exe' => $this->createTestFiles(array(array('name' => 'bad.exe'))), + ) + ); + $val->validateAttribute($m, 'attr_jpg'); + $this->assertFalse($m->hasErrors('attr_jpg')); + $val->validateAttribute($m, 'attr_exe'); + $this->assertTrue($m->hasErrors('attr_exe')); + $this->assertTrue(stripos(current($m->getErrors('attr_exe')), 'Only files with these extensions ') !== false); + } + + protected function createModelForAttributeTest() { return FakedValidationModel::createWithAttributes( diff --git a/tests/unit/framework/validators/RequiredValidatorTest.php b/tests/unit/framework/validators/RequiredValidatorTest.php index 198727b..22c9d0b 100644 --- a/tests/unit/framework/validators/RequiredValidatorTest.php +++ b/tests/unit/framework/validators/RequiredValidatorTest.php @@ -3,6 +3,7 @@ namespace yiiunit\framework\validators; use yii\validators\RequiredValidator; +use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\TestCase; class RequiredValidatorTest extends TestCase @@ -31,4 +32,23 @@ class RequiredValidatorTest extends TestCase $this->assertFalse($val->validateValue("should fail")); $this->assertFalse($val->validateValue(true)); } + + public function testValidateAttribute() + { + // empty req-value + $val = new RequiredValidator(); + $m = FakedValidationModel::createWithAttributes(array('attr_val' => null)); + $val->validateAttribute($m, 'attr_val'); + $this->assertTrue($m->hasErrors('attr_val')); + $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'blank') !== false); + $val = new RequiredValidator(array('requiredValue' => 55)); + $m = FakedValidationModel::createWithAttributes(array('attr_val' => 56)); + $val->validateAttribute($m, 'attr_val'); + $this->assertTrue($m->hasErrors('attr_val')); + $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'must be') !== false); + $val = new RequiredValidator(array('requiredValue' => 55)); + $m = FakedValidationModel::createWithAttributes(array('attr_val' => 55)); + $val->validateAttribute($m, 'attr_val'); + $this->assertFalse($m->hasErrors('attr_val')); + } } \ No newline at end of file diff --git a/tests/unit/framework/validators/UniqueValidatorTest.php b/tests/unit/framework/validators/UniqueValidatorTest.php index 325a15c..b0d3554 100644 --- a/tests/unit/framework/validators/UniqueValidatorTest.php +++ b/tests/unit/framework/validators/UniqueValidatorTest.php @@ -44,6 +44,7 @@ class UniqueValidatorTest extends DatabaseTestCase $val->validateAttribute($m, 'ref'); $this->assertTrue($m->hasErrors('ref')); $m = new ValidatorTestRefModel(); + $m->id = 7; $m->ref = 12121; $val->validateAttribute($m, 'ref'); $this->assertFalse($m->hasErrors('ref')); From c06e9cafe1b0b56057527e2bd832457abc1f3a35 Mon Sep 17 00:00:00 2001 From: callmez Date: Sun, 25 Aug 2013 04:07:30 +0800 Subject: [PATCH 025/613] Modal::clientOptions disable support. --- framework/yii/bootstrap/Modal.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/framework/yii/bootstrap/Modal.php b/framework/yii/bootstrap/Modal.php index 7dfac70..e1f042f 100644 --- a/framework/yii/bootstrap/Modal.php +++ b/framework/yii/bootstrap/Modal.php @@ -204,10 +204,12 @@ class Modal extends Widget 'tabindex' => -1, ), $this->options); Html::addCssClass($this->options, 'modal'); - - $this->clientOptions = array_merge(array( - 'show' => false, - ), $this->clientOptions); + + if ($this->clientOptions !== false) { + $this->clientOptions = array_merge(array( + 'show' => false, + ), $this->clientOptions); + } if ($this->closeButton !== null) { $this->closeButton = array_merge(array( From 9542fd24d4591ea392544952b26f9cfb9da6bf35 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 25 Sep 2013 12:58:27 +0200 Subject: [PATCH 026/613] try to fix: memcache testExpire fails randomly on travis issue #877 --- tests/unit/framework/caching/CacheTestCase.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 94894a3..dffcd67 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -13,6 +13,7 @@ function time() namespace yiiunit\framework\caching; +use yii\helpers\StringHelper; use yiiunit\TestCase; use yii\caching\Cache; @@ -147,7 +148,11 @@ abstract class CacheTestCase extends TestCase sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); // wait a bit more than 2 sec to avoid random test failure - usleep(2500000); + if ($_ENV['TRAVIS'] && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { + sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache + } else { + usleep(2500000); + } $this->assertFalse($cache->get('expire_test')); } From f3504f426dafb7b12e2f08238e15282cad3a29a6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 25 Sep 2013 13:00:13 +0200 Subject: [PATCH 027/613] fix test fail when not on travis --- tests/unit/framework/caching/CacheTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index dffcd67..480941f 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -148,7 +148,7 @@ abstract class CacheTestCase extends TestCase sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); // wait a bit more than 2 sec to avoid random test failure - if ($_ENV['TRAVIS'] && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { + if (isset($_ENV['TRAVIS']) && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache } else { usleep(2500000); From 220db270cfd3efd0b6f89197de615f8354c18c70 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 25 Sep 2013 21:20:58 -0400 Subject: [PATCH 028/613] Improved crud generator. --- framework/yii/gii/generators/crud/templates/views/index.php | 8 +++----- framework/yii/gii/generators/crud/templates/views/view.php | 4 ++-- framework/yii/grid/DataColumn.php | 11 +++++++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index df76581..1de548a 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -30,13 +30,11 @@ $this->params['breadcrumbs'][] = $this->title;

echo Html::encode($this->title); ?>

- echo $this->render('_search', array('model' => $searchModel)); ?> + indexWidgetType === 'grid' ? ' //' : ''); ?> echo $this->render('_search', array('model' => $searchModel)); ?> -
- -
+

echo Html::a('Create modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> -

+

indexWidgetType === 'grid'): ?> echo GridView::widget(array( diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index a3b6cad..d25a851 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -29,14 +29,14 @@ $this->params['breadcrumbs'][] = $this->title;

echo Html::encode($this->title); ?>

-
+

echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> echo Html::a('Delete', array('delete', ), array( 'class' => 'btn btn-danger', 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), 'data-method' => 'post', )); ?> -

+

echo DetailView::widget(array( 'model' => $model, diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php index 295dece..4ebbb8f 100644 --- a/framework/yii/grid/DataColumn.php +++ b/framework/yii/grid/DataColumn.php @@ -68,6 +68,12 @@ class DataColumn extends Column * - If you don't want a filter for this data column, set this value to be false. */ public $filter; + /** + * @var array the HTML attributes for the filter input fields. This property is used in combination with + * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to + * render the HTML attributes for the generated filter input fields. + */ + public $filterInputOptions = array('class' => 'form-control'); protected function renderHeaderCellContent() @@ -111,9 +117,10 @@ class DataColumn extends Column return $this->filter; } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && $this->attribute !== null) { if (is_array($this->filter)) { - return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, array('prompt' => '')); + $options = array_merge(array('prompt' => ''), $this->filterInputOptions); + return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options); } else { - return Html::activeTextInput($this->grid->filterModel, $this->attribute); + return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions); } } else { return parent::renderFilterCellContent(); From d9b256d73455ca771ca985efa7b23d7bfa8b7312 Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Thu, 26 Sep 2013 20:29:59 +0800 Subject: [PATCH 029/613] Fixed pagination not working before data loaded --- framework/yii/data/DataProvider.php | 1 + tests/unit/framework/data/ActiveDataProviderTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index b29f616..d75be6f 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -48,6 +48,7 @@ abstract class DataProvider extends Component implements DataProviderInterface if ($this->id !== null) { $this->_pagination->pageVar = $this->id . '-page'; } + $this->_pagination->totalCount = $this->getTotalCount(); } return $this->_pagination; } diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 3f65ebb..79c0a39 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -85,4 +85,21 @@ class ActiveDataProviderTest extends DatabaseTestCase $provider->refresh(); $this->assertEquals(2, count($provider->getModels())); } + + public function testPaginationBeforeModels() + { + $query = new Query; + $provider = new ActiveDataProvider(array( + 'db' => $this->getConnection(), + 'query' => $query->from('tbl_order')->orderBy('id'), + )); + $pagination = $provider->getPagination(); + $this->assertEquals(1, $pagination->getPageCount()); + $this->assertCount(3, $provider->getModels()); + + $provider->getPagination()->pageSize = 2; + $this->assertEquals(3, count($provider->getModels())); + $provider->refresh(); + $this->assertEquals(2, count($provider->getModels())); + } } From b7948e48406f21a059431a619c901b3cd88a8ec5 Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Thu, 26 Sep 2013 23:35:02 +0800 Subject: [PATCH 030/613] Removed unnecessary lines --- framework/yii/data/ActiveDataProvider.php | 1 - framework/yii/data/ArrayDataProvider.php | 1 - 2 files changed, 2 deletions(-) diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index aaf71b2..2fe0efb 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -156,7 +156,6 @@ class ActiveDataProvider extends DataProvider throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); } if (($sort = $this->getSort()) !== false) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index d6eaca7..9534803 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -114,7 +114,6 @@ class ArrayDataProvider extends DataProvider } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } From 45778a4bd7037e2797f29bc80d1c09cedbefffeb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 27 Sep 2013 20:36:47 +0400 Subject: [PATCH 031/613] Added ->send() to redirect phpdoc example --- framework/yii/web/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index e6505fd..b353e7c 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -581,7 +581,7 @@ class Response extends \yii\base\Response * In a controller action you may use this method like this: * * ~~~ - * return Yii::$app->getResponse()->redirect($url); + * return Yii::$app->getResponse()->redirect($url)->send(); * ~~~ * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: From 3e94fb479f1bb8cfe8669064e0a1489e3a508d88 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 27 Sep 2013 20:51:08 +0400 Subject: [PATCH 032/613] Fixed phpdoc --- framework/yii/helpers/Console.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index eeadcbc..c0b7b32 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -8,11 +8,8 @@ namespace yii\helpers; /** - * TODO adjust phpdoc - * Console View is the base class for console view components - * - * A console view provides functionality to create rich console application by allowing to format output - * by adding color and font style to it. + * Console helper provides useful methods for command line related tasks such as getting input or formatting and coloring + * output. * * @author Carsten Brandt * @since 2.0 From 907d24fb9e07b2a4fdf344a76e984cfb7f734d97 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:01:49 +0400 Subject: [PATCH 033/613] Advanced application: fixed console return codes --- apps/advanced/environments/dev/yii | 3 ++- apps/advanced/environments/prod/yii | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii index 9a257d7..e7d5f6c 100644 --- a/apps/advanced/environments/dev/yii +++ b/apps/advanced/environments/dev/yii @@ -24,4 +24,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii index 7f24a70..9e13eb2 100644 --- a/apps/advanced/environments/prod/yii +++ b/apps/advanced/environments/prod/yii @@ -24,4 +24,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); From 4ab2e986c6ccf6f1d89db1971e353b0c32d8df6d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:10:38 +0400 Subject: [PATCH 034/613] Advanced application: init script can now be executed in non-interactive input mode Usage: init --env=Development init --env=Production --- apps/advanced/README.md | 3 ++- apps/advanced/init | 72 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 6860c11..cdc10eb 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -103,7 +103,8 @@ GETTING STARTED 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 the `init` command and select `dev` as environment. +1. Execute the `init` command and select `dev` as environment. Alternatively you can execute it as `init --env=Development` +or `init --env=Production`. 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. diff --git a/apps/advanced/init b/apps/advanced/init index 17ed854..3a8f6a6 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -1,27 +1,49 @@ #!/usr/bin/env php $name) { - echo " [$i] $name\n"; +echo "Yii Application Initialization Tool v1.0\n\n"; + +$envName = null; +if (empty($params['env'])) { + echo "Which environment do you want the application to be initialized in?\n\n"; + foreach ($envNames as $i => $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) || !in_array($answer, range(0, count($envs) - 1))) { + echo "\n Quit initialization.\n"; + exit(1); + } + + if(isset($envNames[$answer])) { + $envName = $envNames[$answer]; + } +} +else { + $envName = $params['env']; } -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; + +if (!in_array($envName, $envNames)) { + $envsList = implode(', ', $envNames); + echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; + exit(2); } -$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; +$env = $envs[$envName]; + +if (empty($params['env'])) { + 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"; + exit(1); + } } echo "\n Start initialization ...\n\n"; @@ -110,3 +132,23 @@ function copyFile($root, $source, $target, &$all) file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); return true; } + +function getParams() +{ + $rawParams = array(); + if (isset($_SERVER['argv'])) { + $rawParams = $_SERVER['argv']; + array_shift($rawParams); + } + + $params = array(); + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params[] = $param; + } + } + return $params; +} From c4354d6be688f6323980c1d37c84fcfee86dfbc7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:14:19 +0400 Subject: [PATCH 035/613] Advanced application: init is now automatically called when installing via Composer --- apps/advanced/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 2d5b987..4c0fced 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -20,7 +20,8 @@ }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\InstallHandler::setPermissions", + "./init" ] }, "extra": { From abd775e5a58ee641866d1aae201445faa78222a8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:27:00 +0400 Subject: [PATCH 036/613] fixed typos --- extensions/composer/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/composer/README.md b/extensions/composer/README.md index 986fc6c..853d3c3 100644 --- a/extensions/composer/README.md +++ b/extensions/composer/README.md @@ -17,11 +17,11 @@ This is the yii2 composer installer. Installation ------------ -This extension offers you enhanced composer handling for your yii2-project. It will therefor require you to use composer. +This extension offers you enhanced Composer handling for your yii2-project. It will therefore require you to use Composer. -` +``` php composer.phar require yiisoft/yii2-composer "*" -` +``` *Note: You might have to run `php composer.phar selfupdate` before using this extension.* @@ -29,9 +29,9 @@ php composer.phar require yiisoft/yii2-composer "*" Usage & Documentation --------------------- -This extensions allows you to hook to certain composer events and prepare your yii2-app for usage. +This extension allows you to hook to certain composer events and automate preparing your Yii2 application for further usage. -After the package is installed, the composer.json file has to be modified to enable this extension. +After the package is installed, the `composer.json` file has to be modified to enable this extension. To see it in action take a look at the example apps in the repository: From f8ddb3d7be0c14ae362a1637baa9fee21042f3d5 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:27:23 +0400 Subject: [PATCH 037/613] Advanced application: added applying migrations to readme --- apps/advanced/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index cdc10eb..3903532 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -107,6 +107,7 @@ the installed application. You only need to do these once for all. or `init --env=Production`. 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. +4. Apply migrations with `yii migrate`. Now you should be able to access: From 76cab3ea6449ef90bbfade7683b1c83aa2336a35 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 02:01:06 +0400 Subject: [PATCH 038/613] Renamed base helper classes --- framework/yii/classes.php | 22 +- framework/yii/helpers/AbstractArrayHelper.php | 451 +++++++ framework/yii/helpers/AbstractConsole.php | 835 +++++++++++++ framework/yii/helpers/AbstractFileHelper.php | 329 +++++ framework/yii/helpers/AbstractHtml.php | 1599 ++++++++++++++++++++++++ framework/yii/helpers/AbstractHtmlPurifier.php | 34 + framework/yii/helpers/AbstractInflector.php | 480 +++++++ framework/yii/helpers/AbstractJson.php | 112 ++ framework/yii/helpers/AbstractMarkdown.php | 44 + framework/yii/helpers/AbstractSecurity.php | 285 +++++ framework/yii/helpers/AbstractStringHelper.php | 138 ++ framework/yii/helpers/AbstractVarDumper.php | 127 ++ framework/yii/helpers/ArrayHelper.php | 2 +- framework/yii/helpers/ArrayHelperBase.php | 451 ------- framework/yii/helpers/Console.php | 2 +- framework/yii/helpers/ConsoleBase.php | 835 ------------- framework/yii/helpers/FileHelper.php | 2 +- framework/yii/helpers/FileHelperBase.php | 329 ----- framework/yii/helpers/Html.php | 2 +- framework/yii/helpers/HtmlBase.php | 1599 ------------------------ framework/yii/helpers/HtmlPurifier.php | 2 +- framework/yii/helpers/HtmlPurifierBase.php | 34 - framework/yii/helpers/Inflector.php | 2 +- framework/yii/helpers/InflectorBase.php | 480 ------- framework/yii/helpers/Json.php | 2 +- framework/yii/helpers/JsonBase.php | 112 -- framework/yii/helpers/Markdown.php | 2 +- framework/yii/helpers/MarkdownBase.php | 44 - framework/yii/helpers/Security.php | 2 +- framework/yii/helpers/SecurityBase.php | 285 ----- framework/yii/helpers/StringHelper.php | 2 +- framework/yii/helpers/StringHelperBase.php | 138 -- framework/yii/helpers/VarDumper.php | 2 +- framework/yii/helpers/VarDumperBase.php | 127 -- 34 files changed, 4456 insertions(+), 4456 deletions(-) create mode 100644 framework/yii/helpers/AbstractArrayHelper.php create mode 100644 framework/yii/helpers/AbstractConsole.php create mode 100644 framework/yii/helpers/AbstractFileHelper.php create mode 100644 framework/yii/helpers/AbstractHtml.php create mode 100644 framework/yii/helpers/AbstractHtmlPurifier.php create mode 100644 framework/yii/helpers/AbstractInflector.php create mode 100644 framework/yii/helpers/AbstractJson.php create mode 100644 framework/yii/helpers/AbstractMarkdown.php create mode 100644 framework/yii/helpers/AbstractSecurity.php create mode 100644 framework/yii/helpers/AbstractStringHelper.php create mode 100644 framework/yii/helpers/AbstractVarDumper.php delete mode 100644 framework/yii/helpers/ArrayHelperBase.php delete mode 100644 framework/yii/helpers/ConsoleBase.php delete mode 100644 framework/yii/helpers/FileHelperBase.php delete mode 100644 framework/yii/helpers/HtmlBase.php delete mode 100644 framework/yii/helpers/HtmlPurifierBase.php delete mode 100644 framework/yii/helpers/InflectorBase.php delete mode 100644 framework/yii/helpers/JsonBase.php delete mode 100644 framework/yii/helpers/MarkdownBase.php delete mode 100644 framework/yii/helpers/SecurityBase.php delete mode 100644 framework/yii/helpers/StringHelperBase.php delete mode 100644 framework/yii/helpers/VarDumperBase.php diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 40ca225..22472d7 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -125,27 +125,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\ArrayHelperBase' => YII_PATH . '/helpers/ArrayHelperBase.php', + 'yii\helpers\AbstractArrayHelper' => YII_PATH . '/helpers/AbstractArrayHelper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\ConsoleBase' => YII_PATH . '/helpers/ConsoleBase.php', + 'yii\helpers\AbstractConsole' => YII_PATH . '/helpers/AbstractConsole.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\FileHelperBase' => YII_PATH . '/helpers/FileHelperBase.php', + 'yii\helpers\AbstractFileHelper' => YII_PATH . '/helpers/AbstractFileHelper.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\HtmlBase' => YII_PATH . '/helpers/HtmlBase.php', + 'yii\helpers\AbstractHtml' => YII_PATH . '/helpers/AbstractHtml.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\HtmlPurifierBase' => YII_PATH . '/helpers/HtmlPurifierBase.php', + 'yii\helpers\AbstractHtmlPurifier' => YII_PATH . '/helpers/AbstractHtmlPurifier.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\InflectorBase' => YII_PATH . '/helpers/InflectorBase.php', + 'yii\helpers\AbstractInflector' => YII_PATH . '/helpers/AbstractInflector.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\JsonBase' => YII_PATH . '/helpers/JsonBase.php', + 'yii\helpers\AbstractJson' => YII_PATH . '/helpers/AbstractJson.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\MarkdownBase' => YII_PATH . '/helpers/MarkdownBase.php', + 'yii\helpers\AbstractMarkdown' => YII_PATH . '/helpers/AbstractMarkdown.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\SecurityBase' => YII_PATH . '/helpers/SecurityBase.php', + 'yii\helpers\AbstractSecurity' => YII_PATH . '/helpers/AbstractSecurity.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\StringHelperBase' => YII_PATH . '/helpers/StringHelperBase.php', + 'yii\helpers\AbstractStringHelper' => YII_PATH . '/helpers/AbstractStringHelper.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\VarDumperBase' => YII_PATH . '/helpers/VarDumperBase.php', + 'yii\helpers\AbstractVarDumper' => YII_PATH . '/helpers/AbstractVarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', diff --git a/framework/yii/helpers/AbstractArrayHelper.php b/framework/yii/helpers/AbstractArrayHelper.php new file mode 100644 index 0000000..c26c1cd --- /dev/null +++ b/framework/yii/helpers/AbstractArrayHelper.php @@ -0,0 +1,451 @@ + + * @since 2.0 + */ +abstract class AbstractArrayHelper +{ + /** + * Converts an object or an array of objects into an array. + * @param object|array $object the object to be converted into an array + * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. + * The properties specified for each class is an array of the following format: + * + * ~~~ + * array( + * 'app\models\Post' => array( + * 'id', + * 'title', + * // the key name in array result => property name + * 'createTime' => 'create_time', + * // the key name in array result => anonymous function + * 'length' => function ($post) { + * return strlen($post->content); + * }, + * ), + * ) + * ~~~ + * + * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: + * + * ~~~ + * array( + * 'id' => 123, + * 'title' => 'test', + * 'createTime' => '2013-01-01 12:00AM', + * 'length' => 301, + * ) + * ~~~ + * + * @param boolean $recursive whether to recursively converts properties which are objects into arrays. + * @return array the array representation of the object + */ + public static function toArray($object, $properties = array(), $recursive = true) + { + if (!empty($properties) && is_object($object)) { + $className = get_class($object); + if (!empty($properties[$className])) { + $result = array(); + foreach ($properties[$className] as $key => $name) { + if (is_int($key)) { + $result[$name] = $object->$name; + } else { + $result[$key] = static::getValue($object, $name); + } + } + return $result; + } + } + if ($object instanceof Arrayable) { + $object = $object->toArray(); + if (!$recursive) { + return $object; + } + } + $result = array(); + foreach ($object as $key => $value) { + if ($recursive && (is_array($value) || is_object($value))) { + $result[$key] = static::toArray($value, true); + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while (!empty($args)) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + isset($res[$k]) ? $res[] = $v : $res[$k] = $v; + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array, the default value will be returned instead. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed the value of the element if found, default value otherwise + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } elseif (is_array($array)) { + return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $array->$key; + } + } + + /** + * Removes an item from an array and returns the value. If the key does not exist in the array, the default value + * will be returned instead. + * + * Usage examples, + * + * ~~~ + * // $array = array('type' => 'A', 'options' => array(1, 2)); + * // working with array + * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); + * // $array content + * // $array = array('options' => array(1, 2)); + * ~~~ + * + * @param array $array the array to extract value from + * @param string $key key name of the array element + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed|null the value of the element if found, default value otherwise + */ + public static function remove(&$array, $key, $default = null) + { + if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + $value = $array[$key]; + unset($array[$key]); + return $value; + } + return $default; + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // array( + * // '123' => array('id' => '123', 'data' => 'abc'), + * // '345' => array('id' => '345', 'data' => 'def'), + * // ) + * + * // using anonymous function + * $result = ArrayHelper::index($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = array(); + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: array( '123', '345') + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = array(); + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), + * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), + * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ) + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // array( + * // 'x' => array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ), + * // 'y' => array( + * // '345' => 'ccc', + * // ), + * // ) + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = array(); + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param boolean|array $descending whether to sort in descending or ascending order. When + * sorting by multiple keys with different descending orders, use an array of descending flags. + * @param integer|array $sortFlag the PHP sort flag. Valid values include + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. + * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) + * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. + * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter + * is used only when `$sortFlag` is `SORT_STRING`. + * When sorting by multiple keys with different case sensitivities, use an array of boolean values. + * @throws InvalidParamException if the $descending or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) + { + $keys = is_array($key) ? $key : array($key); + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($descending)) { + $descending = array_fill(0, $n, $descending); + } elseif (count($descending) !== $n) { + throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); + } + if (is_scalar($caseSensitive)) { + $caseSensitive = array_fill(0, $n, $caseSensitive); + } elseif (count($caseSensitive) !== $n) { + throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); + } + $args = array(); + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + $cs = $caseSensitive[$i]; + if (!$cs && ($flag === SORT_STRING)) { + if (defined('SORT_FLAG_CASE')) { + $flag = $flag | SORT_FLAG_CASE; + $args[] = static::getColumn($array, $key); + } else { + $column = array(); + foreach (static::getColumn($array, $key) as $k => $value) { + $column[$k] = mb_strtolower($value); + } + $args[] = $column; + } + } else { + $args[] = static::getColumn($array, $key); + } + $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + return $d; + } +} diff --git a/framework/yii/helpers/AbstractConsole.php b/framework/yii/helpers/AbstractConsole.php new file mode 100644 index 0000000..8131aae --- /dev/null +++ b/framework/yii/helpers/AbstractConsole.php @@ -0,0 +1,835 @@ + + * @since 2.0 + */ +abstract class AbstractConsole +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const RESET = 0; + const NORMAL = 0; + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows = 1) + { + echo "\033[" . (int)$rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows = 1) + { + echo "\033[" . (int)$rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps = 1) + { + echo "\033[" . (int)$steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps = 1) + { + echo "\033[" . (int)$steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines = 1) + { + echo "\033[" . (int)$lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines = 1) + { + echo "\033[" . (int)$lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row = null) + { + if ($row === null) { + echo "\033[" . (int)$column . 'G'; + } else { + echo "\033[" . (int)$row . ';' . (int)$column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll up + */ + public static function scrollUp($lines = 1) + { + echo "\033[" . (int)$lines . "S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll down + */ + public static function scrollDown($lines = 1) + { + echo "\033[" . (int)$lines . "T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with {@link restoreCursorPosition}. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use {@link showCursor} to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Returns the ANSI format code. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string The ANSI format code according to the given formatting constants. + */ + public static function ansiFormatCode($format) + { + return "\033[" . implode(';', $format) . 'm'; + } + + /** + * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @see ansiFormatCode() + * @see ansiFormatEnd() + */ + public static function beginAnsiFormat($format) + { + echo "\033[" . implode(';', $format) . 'm'; + } + + /** + * Resets any ANSI format set by previous method [[ansiFormatBegin()]] + * Any output after this will have default text format. + * This is equal to calling + * + * ```php + * echo Console::ansiFormatCode(array(Console::RESET)) + * ``` + */ + public static function endAnsiFormat() + { + echo "\033[0m"; + } + + /** + * Will return a string formatted with the given ANSI style + * + * @param string $string the string to be formatted + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string + */ + public static function ansiFormat($string, $format = array()) + { + $code = implode(';', $format); + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + } + + /** + * Returns the ansi format code for xterm foreground color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermFgColor($colorCode) + { + return '38;5;' . $colorCode; + } + + /** + * Returns the ansi format code for xterm background color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermBgColor($colorCode) + { + return '48;5;' . $colorCode; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function stripAnsiFormat($string) + { + return preg_replace('/\033\[[\d;?]*\w/', '', $string); + } + + /** + * Converts an ANSI formatted string to HTML + * @param $string + * @return mixed + */ + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + public static function ansiToHtml($string) + { + $tags = 0; + return preg_replace_callback( + '/\033\[[\d;]+m/', + function ($ansi) use (&$tags) { + $styleA = array(); + foreach (explode(';', $ansi) as $controlCode) { + switch ($controlCode) { + case self::FG_BLACK: + $style = array('color' => '#000000'); + break; + case self::FG_BLUE: + $style = array('color' => '#000078'); + break; + case self::FG_CYAN: + $style = array('color' => '#007878'); + break; + case self::FG_GREEN: + $style = array('color' => '#007800'); + break; + case self::FG_GREY: + $style = array('color' => '#787878'); + break; + case self::FG_PURPLE: + $style = array('color' => '#780078'); + break; + case self::FG_RED: + $style = array('color' => '#780000'); + break; + case self::FG_YELLOW: + $style = array('color' => '#787800'); + break; + case self::BG_BLACK: + $style = array('background-color' => '#000000'); + break; + case self::BG_BLUE: + $style = array('background-color' => '#000078'); + break; + case self::BG_CYAN: + $style = array('background-color' => '#007878'); + break; + case self::BG_GREEN: + $style = array('background-color' => '#007800'); + break; + case self::BG_GREY: + $style = array('background-color' => '#787878'); + break; + case self::BG_PURPLE: + $style = array('background-color' => '#780078'); + break; + case self::BG_RED: + $style = array('background-color' => '#780000'); + break; + case self::BG_YELLOW: + $style = array('background-color' => '#787800'); + break; + case self::BOLD: + $style = array('font-weight' => 'bold'); + break; + case self::ITALIC: + $style = array('font-style' => 'italic'); + break; + case self::UNDERLINE: + $style = array('text-decoration' => array('underline')); + break; + case self::OVERLINED: + $style = array('text-decoration' => array('overline')); + break; + case self::CROSSED_OUT: + $style = array('text-decoration' => array('line-through')); + break; + case self::BLINK: + $style = array('text-decoration' => array('blink')); + break; + case self::NEGATIVE: // ??? + case self::CONCEALED: + case self::ENCIRCLED: + case self::FRAMED: + // TODO allow resetting codes + break; + case 0: // ansi reset + $return = ''; + for ($n = $tags; $tags > 0; $tags--) { + $return .= ''; + } + return $return; + } + + $styleA = ArrayHelper::merge($styleA, $style); + } + $styleString[] = array(); + foreach ($styleA as $name => $content) { + if ($name === 'text-decoration') { + $content = implode(' ', $content); + } + $styleString[] = $name . ':' . $content; + } + $tags++; + return ' $value) { + echo " $key - $value\n"; + } + echo " ? - Show help\n"; + goto top; + } elseif (!in_array($input, array_keys($options))) { + goto top; + } + return $input; + } + + /** + * Displays and updates a simple progress bar on screen. + * + * @param integer $done the number of items that are completed + * @param integer $total the total value of items that are to be done + * @param integer $size the size of the status bar (optional) + * @see http://snipplr.com/view/29548/ + */ + public static function showProgress($done, $total, $size = 30) + { + static $start; + + // if we go over our bound, just ignore it + if ($done > $total) { + return; + } + + if (empty($start)) { + $start = time(); + } + + $now = time(); + + $percent = (double)($done / $total); + $bar = floor($percent * $size); + + $status = "\r["; + $status .= str_repeat("=", $bar); + if ($bar < $size) { + $status .= ">"; + $status .= str_repeat(" ", $size - $bar); + } else { + $status .= "="; + } + + $display = number_format($percent * 100, 0); + + $status .= "] $display% $done/$total"; + + $rate = ($now - $start) / $done; + $left = $total - $done; + $eta = round($rate * $left, 2); + + $elapsed = $now - $start; + + $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; + + static::stdout("$status "); + + flush(); + + // when done, send a newline + if ($done == $total) { + echo "\n"; + } + } +} diff --git a/framework/yii/helpers/AbstractFileHelper.php b/framework/yii/helpers/AbstractFileHelper.php new file mode 100644 index 0000000..5eab927 --- /dev/null +++ b/framework/yii/helpers/AbstractFileHelper.php @@ -0,0 +1,329 @@ + + * @author Alex Makarov + * @since 2.0 + */ +abstract class AbstractFileHelper +{ + /** + * Normalizes a file/directory path. + * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, + * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux + * will be normalized as '/home/demo'. + * @param string $path the file/directory path to be normalized + * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. + * @return string the normalized file/directory path + */ + public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) + { + return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_CN", the localized file will be looked for as + * "path/to/zh_CN/view.php". If the file is not found, the original file + * will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); + return is_file($desiredFile) ? $desiredFile : $file; + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will + * fall back to [[getMimeTypeByExtension()]]. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if (function_exists('finfo_open')) { + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + if ($info) { + $result = finfo_file($info, $file); + finfo_close($info); + if ($result !== false) { + return $result; + } + } + } + + return $checkExtension ? static::getMimeTypeByExtension($file) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path of the file that contains all available MIME type information. + * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + static $mimeTypes = array(); + if ($magicFile === null) { + $magicFile = __DIR__ . '/mimeTypes.php'; + } + if (!isset($mimeTypes[$magicFile])) { + $mimeTypes[$magicFile] = require($magicFile); + } + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$magicFile][$ext])) { + return $mimeTypes[$magicFile][$ext]; + } + } + return null; + } + + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be copied (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be copied + * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file copied from, while `$to` is the copy target. + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($from, $options)) { + if (is_file($from)) { + copy($from, $to); + if (isset($options['fileMode'])) { + @chmod($to, $options['fileMode']); + } + } else { + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); + } + } + } + closedir($handle); + } + + /** + * Removes a directory (and all its content) recursively. + * @param string $dir the directory to be deleted recursively. + */ + public static function removeDirectory($dir) + { + if (!is_dir($dir) || !($handle = opendir($dir))) { + return; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_file($path)) { + unlink($path); + } else { + static::removeDirectory($path); + } + } + closedir($handle); + rmdir($dir); + } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be returned (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be returned + * + * - only: array, list of patterns that the file paths should match if they want to be returned. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be returned. + * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. + * @return array files found under the directory. The file list is sorted. + */ + public static function findFiles($dir, $options = array()) + { + $list = array(); + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($path, $options)) { + if (is_file($path)) { + $list[] = $path; + } elseif (!isset($options['recursive']) || $options['recursive']) { + $list = array_merge($list, static::findFiles($path, $options)); + } + } + } + closedir($handle); + return $list; + } + + /** + * Checks if the given file path satisfies the filtering options. + * @param string $path the path of the file or directory to be checked + * @param array $options the filtering options. See [[findFiles()]] for explanations of + * the supported options. + * @return boolean whether the file or directory satisfies the filtering options. + */ + public static function filterPath($path, $options) + { + if (isset($options['filter'])) { + $result = call_user_func($options['filter'], $path); + if (is_bool($result)) { + return $result; + } + } + $path = str_replace('\\', '/', $path); + if ($isDir = is_dir($path)) { + $path .= '/'; + } + $n = StringHelper::strlen($path); + + if (!empty($options['except'])) { + foreach ($options['except'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return false; + } + } + } + + if (!$isDir && !empty($options['only'])) { + foreach ($options['only'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return true; + } + } + return false; + } + return true; + } + + /** + * Creates a new directory. + * + * This method is similar to the PHP `mkdir()` function except that + * it uses `chmod()` to set the permission of the created directory + * in order to avoid the impact of the `umask` setting. + * + * @param string $path path of the directory to be created. + * @param integer $mode the permission to be set for the created directory. + * @param boolean $recursive whether to create parent directories if they do not exist. + * @return boolean whether the directory is created successfully + */ + public static function createDirectory($path, $mode = 0775, $recursive = true) + { + if (is_dir($path)) { + return true; + } + $parentDir = dirname($path); + if ($recursive && !is_dir($parentDir)) { + static::createDirectory($parentDir, $mode, true); + } + $result = mkdir($path, $mode); + chmod($path, $mode); + return $result; + } +} diff --git a/framework/yii/helpers/AbstractHtml.php b/framework/yii/helpers/AbstractHtml.php new file mode 100644 index 0000000..37e926c --- /dev/null +++ b/framework/yii/helpers/AbstractHtml.php @@ -0,0 +1,1599 @@ + + * @since 2.0 + */ +abstract class AbstractHtml +{ + /** + * @var array list of void elements (element name => 1) + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = array( + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderTagAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); + + /** + * Encodes special characters into HTML entities. + * The [[yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, + * HTML entities in `$content` will not be further encoded. + * @return string the encoded content + * @see decode + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content, $doubleEncode = true) + { + return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated HTML tag + * @see beginTag + * @see endTag + */ + public static function tag($name, $content = '', $options = array()) + { + $html = "<$name" . static::renderTagAttributes($options) . '>'; + return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated start tag + * @see endTag + * @see tag + */ + public static function beginTag($name, $options = array()) + { + return "<$name" . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag + * @see tag + */ + public static function endTag($name) + { + return ""; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * @return string the generated style tag + */ + public static function style($content, $options = array()) + { + return static::tag('style', $content, $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * @return string the generated script tag + */ + public static function script($content, $options = array()) + { + return static::tag('script', $content, $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated link tag + * @see url + */ + public static function cssFile($url, $options = array()) + { + $options['rel'] = 'stylesheet'; + $options['href'] = static::url($url); + return static::tag('link', '', $options); + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated script tag + * @see url + */ + public static function jsFile($url, $options = array()) + { + $options['src'] = static::url($url); + return static::tag('script', '', $options); + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). + * Since most browsers only support "post" and "get", if other methods are given, they will + * be simulated using "post", and a hidden input will be added which contains the actual method type. + * See [[\yii\web\Request::restVar]] for more details. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated form start tag. + * @see endForm + */ + public static function beginForm($action = '', $method = 'post', $options = array()) + { + $action = static::url($action); + + $hiddenInputs = array(); + + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { + // simulate PUT, DELETE, etc. via POST + $hiddenInputs[] = static::hiddenInput($request->restVar, $method); + $method = 'post'; + } + if ($request->enableCsrfValidation) { + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); + } + } + + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddenInputs[] = static::hiddenInput( + urldecode(substr($pair, 0, $pos1)), + urldecode(substr($pair, $pos1 + 1)) + ); + } else { + $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if (!empty($hiddenInputs)) { + $form .= "\n" . implode("\n", $hiddenInputs); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return ''; + } + + /** + * 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 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 + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated hyperlink + * @see url + */ + public static function a($text, $url = null, $options = array()) + { + if ($url !== null) { + $options['href'] = static::url($url); + } + return static::tag('a', $text, $options); + } + + /** + * 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 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. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = array()) + { + $options['href'] = 'mailto:' . ($email === null ? $text : $email); + return static::tag('a', $text, $options); + } + + /** + * Generates an image tag. + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated image tag + */ + public static function img($src, $options = array()) + { + $options['src'] = static::url($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', '', $options); + } + + /** + * Generates a label tag. + * @param string $content label text. 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 [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = array()) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function button($content = 'Button', $options = array()) + { + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated submit button tag + */ + public static function submitButton($content = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + return static::button($content, $options); + } + + /** + * Generates a reset button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated reset button tag + */ + public static function resetButton($content = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + return static::button($content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = array()) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value === null ? null : (string)$value; + return static::tag('input', '', $options); + } + + /** + * Generates an input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function buttonInput($label = 'Button', $options = array()) + { + $options['type'] = 'button'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a submit input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function submitInput($label = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a reset input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag + */ + public static function resetInput($label = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = array()) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = array()) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = array()) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = array()) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = array()) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'radio')); + } else { + return $hidden . static::input('radio', $name, $value, $options); + } + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $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']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'checkbox')); + } else { + return $hidden . static::input('checkbox', $name, $value, $options); + } + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + { + $options['name'] = $name; + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = array(), $options = array()) + { + if (!isset($options['size'])) { + $options['size'] = 4; + } + if (!empty($options['multiple']) && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * @param array $options options (name => config) for the checkbox list container tag. + * The following options are specially handled: + * + * - tag: string, the tag name of the container element. + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input, respectively. + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $encode = !isset($options['encode']) || $options['encode']; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::checkbox($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input, respectively. + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = array(), $options = array()) + { + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::radio($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates an unordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated unordered list. An empty string is returned if `$items` is empty. + */ + public static function ul($items, $options = array()) + { + if (empty($items)) { + return ''; + } + $tag = isset($options['tag']) ? $options['tag'] : 'ul'; + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); + unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + $results = array(); + foreach ($items as $index => $item) { + if ($formatter !== null) { + $results[] = call_user_func($formatter, $item, $index); + } else { + $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); + } + } + return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + } + + /** + * Generates an ordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated ordered list. An empty string is returned if `$items` is empty. + */ + public static function ol($items, $options = array()) + { + $options['tag'] = 'ol'; + return static::ul($items, $options); + } + + /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. + * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display + * (after encoding). + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + unset($options['label'], $options['for']); + return static::label($label, $for, $options); + } + + /** + * Generates a tag that contains the first validation error of the specified model attribute. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded + * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * @return string the generated label tag + */ + public static function error($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $error = $model->getFirstError($attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag']); + return Html::tag($tag, Html::encode($error), $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = array()) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = array()) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = array()) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = array()) + { + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = array()) + { + $name = static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radio($name, $checked, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkbox($name, $checked, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::dropDownList($name, $checked, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::listBox($name, $checked, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkboxList($name, $checked, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radioList($name, $checked, $items, $options); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + { + $lines = array(); + if (isset($tagOptions['prompt'])) { + $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); + $lines[] = static::tag('option', $prompt, array('value' => '')); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = (string)$key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * Attributes whose values are of boolean type will be treated as + * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). + * And attributes whose values are null will not be rendered. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (so that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (is_bool($value)) { + if ($value) { + $html .= " $name"; + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + return $html; + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result + * is an absolute URL, it will be returned without any change further; Otherwise, the result + * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. + * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + $route = $url[0]; + $params = array_splice($url, 1); + if (Yii::$app->controller instanceof \yii\web\Controller) { + return Yii::$app->controller->createUrl($route, $params); + } else { + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + $url = Yii::getAlias($url); + if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { + return $url; + } else { + return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; + } + } + } + + /** + * Adds a CSS class to the specified options. + * If the CSS class is already in the options, it will not be added again. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added + */ + public static function addCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = ' ' . $options['class'] . ' '; + if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { + $options['class'] .= ' ' . $class; + } + } else { + $options['class'] = $class; + } + } + + /** + * Removes a CSS class from the specified options. + * @param array $options the options to be modified. + * @param string $class the CSS class to be removed + */ + public static function removeCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); + if (($index = array_search($class, $classes)) !== false) { + unset($classes[$index]); + } + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } + } + } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return mixed the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $index = $matches[3]; + if ($index === '') { + return $model->$attribute; + } else { + $value = $model->$attribute; + foreach (explode('][', trim($index, '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + return $value; + } + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputName($model, $attribute) + { + $formName = $model->formName(); + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $prefix = $matches[1]; + $attribute = $matches[2]; + $suffix = $matches[3]; + if ($formName === '' && $prefix === '') { + return $attribute . $suffix; + } elseif ($formName !== '') { + return $formName . $prefix . "[$attribute]" . $suffix; + } else { + throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = strtolower(static::getInputName($model, $attribute)); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } +} diff --git a/framework/yii/helpers/AbstractHtmlPurifier.php b/framework/yii/helpers/AbstractHtmlPurifier.php new file mode 100644 index 0000000..221fc37 --- /dev/null +++ b/framework/yii/helpers/AbstractHtmlPurifier.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +abstract class AbstractHtmlPurifier +{ + /** + * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content + * @param array|null $config + * @return string + */ + public static function process($content, $config = null) + { + $configInstance = \HTMLPurifier_Config::create($config); + $configInstance->autoFinalize = false; + $purifier=\HTMLPurifier::instance($configInstance); + $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + return $purifier->purify($content); + } +} diff --git a/framework/yii/helpers/AbstractInflector.php b/framework/yii/helpers/AbstractInflector.php new file mode 100644 index 0000000..27ee4f7 --- /dev/null +++ b/framework/yii/helpers/AbstractInflector.php @@ -0,0 +1,480 @@ + + * @since 2.0 + */ +abstract class AbstractInflector +{ + /** + * @var array the rules for converting a word into its plural form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $plurals = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ); + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $singulars = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ); + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + public static $specials = array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ); + /** + * @var array map of special chars and its translation. This is used by [[slug()]]. + */ + public static $transliteration = array( + '/ä|æ|ǽ/' => 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Ð|Ď|Đ/' => 'D', + '/ð|ď|đ/' => 'd', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', + '/Ĝ|Ğ|Ġ|Ģ/' => 'G', + '/ĝ|ğ|ġ|ģ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/ķ/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł/' => 'l', + '/Ñ|Ń|Ņ|Ň/' => 'N', + '/ñ|ń|ņ|ň|ʼn/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', + '/Ŕ|Ŗ|Ř/' => 'R', + '/ŕ|ŗ|ř/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š/' => 'S', + '/ś|ŝ|ş|ș|š|ſ/' => 's', + '/Ţ|Ț|Ť|Ŧ/' => 'T', + '/ţ|ț|ť|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', + '/Ý|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|ŷ/' => 'y', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f' + ); + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($word) + { + if (isset(self::$specials[$word])) { + return self::$specials[$word]; + } + foreach (static::$plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Returns the singular of the $word + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public static function singularize($word) + { + $result = array_search($word, self::$specials, true); + if ($result !== false) { + return $result; + } + foreach (static::$singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * @param string $words + * @param bool $ucAll whether to set all words to uppercase + * @return string + */ + public static function titleize($words, $ucAll = false) + { + $words = static::humanize(static::underscore($words), $ucAll); + return $ucAll ? ucwords($words) : ucfirst($words); + } + + /** + * Returns given word as CamelCased + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * @see variablize + * @param string $word the word to CamelCase + * @return string + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace(array( + '-', + '_', + '.' + ), ' ', preg_replace('/(? ' ', + '/\\s+/' => $replacement, + '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', + str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' + ); + return preg_replace(array_keys($map), array_values($map), $string); + } + + /** + * Converts a table name to its class name. For example, converts "people" to "Person" + * @param string $tableName + * @return string + */ + public static function classify($tableName) + { + return static::camelize(static::singularize($tableName)); + } + + /** + * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... + * @param int $number the number to get its ordinal value + * @return string + */ + public static function ordinalize($number) + { + if (in_array(($number % 100), range(11, 13))) { + return $number . 'th'; + } + switch ($number % 10) { + case 1: return $number . 'st'; + case 2: return $number . 'nd'; + case 3: return $number . 'rd'; + default: return $number . 'th'; + } + } +} diff --git a/framework/yii/helpers/AbstractJson.php b/framework/yii/helpers/AbstractJson.php new file mode 100644 index 0000000..cda71a0 --- /dev/null +++ b/framework/yii/helpers/AbstractJson.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +abstract class AbstractJson +{ + /** + * Encodes the given value into a JSON string. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * @param mixed $value the data to be encoded + * @param integer $options the encoding options. For more details please refer to + * [[http://www.php.net/manual/en/function.json-encode.php]] + * @return string the encoding result + */ + public static function encode($value, $options = 0) + { + $expressions = array(); + $value = static::processData($value, $expressions, uniqid()); + $json = json_encode($value, $options); + return empty($expressions) ? $json : strtr($json, $expressions); + } + + /** + * Decodes the given JSON string into a PHP data structure. + * @param string $json the JSON string to be decoded + * @param boolean $asArray whether to return objects in terms of associative arrays. + * @return mixed the PHP data + * @throws InvalidParamException if there is any decoding error + */ + public static function decode($json, $asArray = true) + { + if (is_array($json)) { + throw new InvalidParamException('Invalid JSON data.'); + } + $decode = json_decode((string)$json, $asArray); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + case JSON_ERROR_DEPTH: + throw new InvalidParamException('The maximum stack depth has been exceeded.'); + case JSON_ERROR_CTRL_CHAR: + throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); + case JSON_ERROR_SYNTAX: + throw new InvalidParamException('Syntax error.'); + case JSON_ERROR_STATE_MISMATCH: + throw new InvalidParamException('Invalid or malformed JSON.'); + case JSON_ERROR_UTF8: + throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); + default: + throw new InvalidParamException('Unknown JSON decoding error.'); + } + + return $decode; + } + + /** + * Pre-processes the data before sending it to `json_encode()`. + * @param mixed $data the data to be processed + * @param array $expressions collection of JavaScript expressions + * @param string $expPrefix a prefix internally used to handle JS expressions + * @return mixed the processed data + */ + protected static function processData($data, &$expressions, $expPrefix) + { + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); + } + } + return $data; + } elseif (is_object($data)) { + if ($data instanceof JsExpression) { + $token = "!{[$expPrefix=" . count($expressions) . ']}!'; + $expressions['"' . $token . '"'] = $data->expression; + return $token; + } else { + $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); + $result = array(); + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $result[$key] = static::processData($value, $expressions, $expPrefix); + } else { + $result[$key] = $value; + } + } + return $result; + } + } else { + return $data; + } + } +} diff --git a/framework/yii/helpers/AbstractMarkdown.php b/framework/yii/helpers/AbstractMarkdown.php new file mode 100644 index 0000000..6b76b44 --- /dev/null +++ b/framework/yii/helpers/AbstractMarkdown.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +abstract class AbstractMarkdown +{ + /** + * @var MarkdownExtra + */ + protected static $markdown; + + /** + * Converts markdown into HTML + * + * @param string $content + * @param array $config + * @return string + */ + public static function process($content, $config = array()) + { + if (static::$markdown === null) { + static::$markdown = new MarkdownExtra(); + } + foreach ($config as $name => $value) { + static::$markdown->{$name} = $value; + } + return static::$markdown->transform($content); + } +} diff --git a/framework/yii/helpers/AbstractSecurity.php b/framework/yii/helpers/AbstractSecurity.php new file mode 100644 index 0000000..d308b7f --- /dev/null +++ b/framework/yii/helpers/AbstractSecurity.php @@ -0,0 +1,285 @@ + + * @author Tom Worster + * @since 2.0 + */ +abstract class AbstractSecurity +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + srand(); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + mcrypt_generic_init($module, $key, $iv); + $encrypted = $iv . mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return $encrypted; + } + + /** + * Decrypts data + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $keys = array(); + if (is_file($keyFile)) { + $keys = require($keyFile); + } + } + if (!isset($keys[$name])) { + $keys[$name] = static::generateRandomKey($length); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 31) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} diff --git a/framework/yii/helpers/AbstractStringHelper.php b/framework/yii/helpers/AbstractStringHelper.php new file mode 100644 index 0000000..050481e --- /dev/null +++ b/framework/yii/helpers/AbstractStringHelper.php @@ -0,0 +1,138 @@ + + * @author Alex Makarov + * @since 2.0 + */ +abstract class AbstractStringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array by using `mb_strlen()`. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function strlen($string) + { + return mb_strlen($string, '8bit'); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array by using `mb_substr()`. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function substr($string, $start, $length) + { + return mb_substr($string, $start, $length, '8bit'); + } + + /** + * Returns the trailing name component of a path. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. + * This method was mainly created to work on php namespaces. When working with real + * file paths, php's `basename()` should work fine for you. + * Note: this method is not aware of the actual filesystem, or path components such as "..". + * @param string $path A path string. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string the trailing name component of the given path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function basename($path, $suffix = '') + { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + $path = mb_substr($path, 0, -$len); + } + $path = rtrim(str_replace('\\', '/', $path), '/\\'); + if (($pos = mb_strrpos($path, '/')) !== false) { + return mb_substr($path, $pos + 1); + } + return $path; + } + + /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** + * Compares two strings or string arrays, and return their differences. + * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. + * @param string|array $lines1 the first string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string|array $lines2 the second string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. + * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other + * formats, a string is returned. + * @throws InvalidParamException if the format is invalid. + */ + public static function diff($lines1, $lines2, $format = 'inline') + { + if (!is_array($lines1)) { + $lines1 = explode("\n", $lines1); + } + if (!is_array($lines2)) { + $lines2 = explode("\n", $lines2); + } + foreach ($lines1 as $i => $line) { + $lines1[$i] = rtrim($line, "\r\n"); + } + foreach ($lines2 as $i => $line) { + $lines2[$i] = rtrim($line, "\r\n"); + } + switch ($format) { + case 'inline': + $renderer = new \Diff_Renderer_Html_Inline(); + break; + case 'array': + $renderer = new \Diff_Renderer_Html_Array(); + break; + case 'side-by-side': + $renderer = new \Diff_Renderer_Html_SideBySide(); + break; + case 'context': + $renderer = new \Diff_Renderer_Text_Context(); + break; + case 'unified': + $renderer = new \Diff_Renderer_Text_Unified(); + break; + default: + throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); + } + $diff = new \Diff($lines1, $lines2); + return $diff->render($renderer); + } +} diff --git a/framework/yii/helpers/AbstractVarDumper.php b/framework/yii/helpers/AbstractVarDumper.php new file mode 100644 index 0000000..2c9f194 --- /dev/null +++ b/framework/yii/helpers/AbstractVarDumper.php @@ -0,0 +1,127 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\helpers; + +/** + * AbstractVarDumper provides concrete implementation for [[VarDumper]]. + * + * Do not use AbstractVarDumper. Use [[VarDumper]] instead. + * + * @author Qiang Xue + * @since 2.0 + */ +abstract class AbstractVarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo static::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = array(); + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + return self::$_output; + } + + /** + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= 'array(...)'; + } elseif (empty($var)) { + self::$_output .= 'array()'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "array\n" . $spaces . '('; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = array_push(self::$_objects, $var); + $className = get_class($var); + $members = (array)$var; + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ($members as $key => $value) { + $keyDisplay = strtr(trim($key), array("\0" => ':')); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } +} diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index 085104b..a63d3c2 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper extends ArrayHelperBase +class ArrayHelper extends AbstractArrayHelper { } diff --git a/framework/yii/helpers/ArrayHelperBase.php b/framework/yii/helpers/ArrayHelperBase.php deleted file mode 100644 index 59129de..0000000 --- a/framework/yii/helpers/ArrayHelperBase.php +++ /dev/null @@ -1,451 +0,0 @@ - - * @since 2.0 - */ -class ArrayHelperBase -{ - /** - * Converts an object or an array of objects into an array. - * @param object|array $object the object to be converted into an array - * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. - * The properties specified for each class is an array of the following format: - * - * ~~~ - * array( - * 'app\models\Post' => array( - * 'id', - * 'title', - * // the key name in array result => property name - * 'createTime' => 'create_time', - * // the key name in array result => anonymous function - * 'length' => function ($post) { - * return strlen($post->content); - * }, - * ), - * ) - * ~~~ - * - * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: - * - * ~~~ - * array( - * 'id' => 123, - * 'title' => 'test', - * 'createTime' => '2013-01-01 12:00AM', - * 'length' => 301, - * ) - * ~~~ - * - * @param boolean $recursive whether to recursively converts properties which are objects into arrays. - * @return array the array representation of the object - */ - public static function toArray($object, $properties = array(), $recursive = true) - { - if (!empty($properties) && is_object($object)) { - $className = get_class($object); - if (!empty($properties[$className])) { - $result = array(); - foreach ($properties[$className] as $key => $name) { - if (is_int($key)) { - $result[$name] = $object->$name; - } else { - $result[$key] = static::getValue($object, $name); - } - } - return $result; - } - } - if ($object instanceof Arrayable) { - $object = $object->toArray(); - if (!$recursive) { - return $object; - } - } - $result = array(); - foreach ($object as $key => $value) { - if ($recursive && (is_array($value) || is_object($value))) { - $result[$key] = static::toArray($value, true); - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge($a, $b) - { - $args = func_get_args(); - $res = array_shift($args); - while (!empty($args)) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_integer($k)) { - isset($res[$k]) ? $res[] = $v : $res[$k] = $v; - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Retrieves the value of an array element or object property with the given key or property name. - * If the key does not exist in the array, the default value will be returned instead. - * - * Below are some usage examples, - * - * ~~~ - * // working with array - * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); - * // working with object - * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); - * // working with anonymous function - * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; - * }); - * ~~~ - * - * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, - * or an anonymous function returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed the value of the element if found, default value otherwise - */ - public static function getValue($array, $key, $default = null) - { - if ($key instanceof \Closure) { - return $key($array, $default); - } elseif (is_array($array)) { - return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; - } else { - return $array->$key; - } - } - - /** - * Removes an item from an array and returns the value. If the key does not exist in the array, the default value - * will be returned instead. - * - * Usage examples, - * - * ~~~ - * // $array = array('type' => 'A', 'options' => array(1, 2)); - * // working with array - * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); - * // $array content - * // $array = array('options' => array(1, 2)); - * ~~~ - * - * @param array $array the array to extract value from - * @param string $key key name of the array element - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed|null the value of the element if found, default value otherwise - */ - public static function remove(&$array, $key, $default = null) - { - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { - $value = $array[$key]; - unset($array[$key]); - return $value; - } - return $default; - } - - /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. - * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. - * - * If a key value is null, the corresponding array element will be discarded and not put in the result. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) - * - * // using anonymous function - * $result = ArrayHelper::index($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array - */ - public static function index($array, $key) - { - $result = array(); - foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; - } - return $result; - } - - /** - * Returns the values of a specified column in an array. - * The input array should be multidimensional or an array of objects. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') - * - * // using anonymous function - * $result = ArrayHelper::getColumn($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array - * @param string|\Closure $name - * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array - * will be re-indexed with integers. - * @return array the list of column values - */ - public static function getColumn($array, $name, $keepKeys = true) - { - $result = array(); - if ($keepKeys) { - foreach ($array as $k => $element) { - $result[$k] = static::getValue($element, $name); - } - } else { - foreach ($array as $element) { - $result[] = static::getValue($element, $name); - } - } - - return $result; - } - - /** - * Builds a map (key-value pairs) from a multidimensional array or an array of objects. - * The `$from` and `$to` parameters specify the key names or property names to set up the map. - * Optionally, one can further group the map according to a grouping field `$group`. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - * ); - * - * $result = ArrayHelper::map($array, 'id', 'name'); - * // the result is: - * // array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // '345' => 'ccc', - * // ) - * - * $result = ArrayHelper::map($array, 'id', 'name', 'class'); - * // the result is: - * // array( - * // 'x' => array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // ), - * // 'y' => array( - * // '345' => 'ccc', - * // ), - * // ) - * ~~~ - * - * @param array $array - * @param string|\Closure $from - * @param string|\Closure $to - * @param string|\Closure $group - * @return array - */ - public static function map($array, $from, $to, $group = null) - { - $result = array(); - foreach ($array as $element) { - $key = static::getValue($element, $from); - $value = static::getValue($element, $to); - if ($group !== null) { - $result[static::getValue($element, $group)][$key] = $value; - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Sorts an array of objects or arrays (with the same structure) by one or several keys. - * @param array $array the array to be sorted. The array will be modified after calling this method. - * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array - * elements, a property name of the objects, or an anonymous function returning the values for comparison - * purpose. The anonymous function signature should be: `function($item)`. - * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $descending whether to sort in descending or ascending order. When - * sorting by multiple keys with different descending orders, use an array of descending flags. - * @param integer|array $sortFlag the PHP sort flag. Valid values include - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. - * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) - * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. - * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter - * is used only when `$sortFlag` is `SORT_STRING`. - * When sorting by multiple keys with different case sensitivities, use an array of boolean values. - * @throws InvalidParamException if the $descending or $sortFlag parameters do not have - * correct number of elements as that of $key. - */ - public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) - { - $keys = is_array($key) ? $key : array($key); - if (empty($keys) || empty($array)) { - return; - } - $n = count($keys); - if (is_scalar($descending)) { - $descending = array_fill(0, $n, $descending); - } elseif (count($descending) !== $n) { - throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); - } - if (is_scalar($sortFlag)) { - $sortFlag = array_fill(0, $n, $sortFlag); - } elseif (count($sortFlag) !== $n) { - throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); - } - if (is_scalar($caseSensitive)) { - $caseSensitive = array_fill(0, $n, $caseSensitive); - } elseif (count($caseSensitive) !== $n) { - throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); - } - $args = array(); - foreach ($keys as $i => $key) { - $flag = $sortFlag[$i]; - $cs = $caseSensitive[$i]; - if (!$cs && ($flag === SORT_STRING)) { - if (defined('SORT_FLAG_CASE')) { - $flag = $flag | SORT_FLAG_CASE; - $args[] = static::getColumn($array, $key); - } else { - $column = array(); - foreach (static::getColumn($array, $key) as $k => $value) { - $column[$k] = mb_strtolower($value); - } - $args[] = $column; - } - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; - $args[] = $flag; - } - $args[] = &$array; - call_user_func_array('array_multisort', $args); - } - - /** - * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded. - * If a value is an array, this method will also encode it recursively. - * @param array $data data to be encoded - * @param boolean $valuesOnly whether to encode array values only. If false, - * both the array keys and array values will be encoded. - * @param string $charset the charset that the data is using. If not set, - * [[\yii\base\Application::charset]] will be used. - * @return array the encoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function htmlEncode($data, $valuesOnly = true, $charset = null) - { - if ($charset === null) { - $charset = Yii::$app->charset; - } - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $d[$key] = static::htmlEncode($value, $charset); - } - } - return $d; - } - - /** - * Decodes HTML entities into the corresponding characters in an array of strings. - * Both the array keys and values will be decoded. - * If a value is an array, this method will also decode it recursively. - * @param array $data data to be decoded - * @param boolean $valuesOnly whether to decode array values only. If false, - * both the array keys and array values will be decoded. - * @return array the decoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function htmlDecode($data, $valuesOnly = true) - { - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars_decode($key, ENT_QUOTES); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); - } elseif (is_array($value)) { - $d[$key] = static::htmlDecode($value); - } - } - return $d; - } -} diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index c0b7b32..9b0656e 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Carsten Brandt * @since 2.0 */ -class Console extends ConsoleBase +class Console extends AbstractConsole { } diff --git a/framework/yii/helpers/ConsoleBase.php b/framework/yii/helpers/ConsoleBase.php deleted file mode 100644 index a985291..0000000 --- a/framework/yii/helpers/ConsoleBase.php +++ /dev/null @@ -1,835 +0,0 @@ - - * @since 2.0 - */ -class ConsoleBase -{ - const FG_BLACK = 30; - const FG_RED = 31; - const FG_GREEN = 32; - const FG_YELLOW = 33; - const FG_BLUE = 34; - const FG_PURPLE = 35; - const FG_CYAN = 36; - const FG_GREY = 37; - - const BG_BLACK = 40; - const BG_RED = 41; - const BG_GREEN = 42; - const BG_YELLOW = 43; - const BG_BLUE = 44; - const BG_PURPLE = 45; - const BG_CYAN = 46; - const BG_GREY = 47; - - const RESET = 0; - const NORMAL = 0; - const BOLD = 1; - const ITALIC = 3; - const UNDERLINE = 4; - const BLINK = 5; - const NEGATIVE = 7; - const CONCEALED = 8; - const CROSSED_OUT = 9; - const FRAMED = 51; - const ENCIRCLED = 52; - const OVERLINED = 53; - - /** - * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved up - */ - public static function moveCursorUp($rows = 1) - { - echo "\033[" . (int)$rows . 'A'; - } - - /** - * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved down - */ - public static function moveCursorDown($rows = 1) - { - echo "\033[" . (int)$rows . 'B'; - } - - /** - * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved forward - */ - public static function moveCursorForward($steps = 1) - { - echo "\033[" . (int)$steps . 'C'; - } - - /** - * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved backward - */ - public static function moveCursorBackward($steps = 1) - { - echo "\033[" . (int)$steps . 'D'; - } - - /** - * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. - * @param integer $lines number of lines the cursor should be moved down - */ - public static function moveCursorNextLine($lines = 1) - { - echo "\033[" . (int)$lines . 'E'; - } - - /** - * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. - * @param integer $lines number of lines the cursor should be moved up - */ - public static function moveCursorPrevLine($lines = 1) - { - echo "\033[" . (int)$lines . 'F'; - } - - /** - * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. - * @param integer $column 1-based column number, 1 is the left edge of the screen. - * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - */ - public static function moveCursorTo($column, $row = null) - { - if ($row === null) { - echo "\033[" . (int)$column . 'G'; - } else { - echo "\033[" . (int)$row . ';' . (int)$column . 'H'; - } - } - - /** - * Scrolls whole page up by sending ANSI control code SU to the terminal. - * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll up - */ - public static function scrollUp($lines = 1) - { - echo "\033[" . (int)$lines . "S"; - } - - /** - * Scrolls whole page down by sending ANSI control code SD to the terminal. - * New lines are added at the top. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll down - */ - public static function scrollDown($lines = 1) - { - echo "\033[" . (int)$lines . "T"; - } - - /** - * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. - */ - public static function saveCursorPosition() - { - echo "\033[s"; - } - - /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. - */ - public static function restoreCursorPosition() - { - echo "\033[u"; - } - - /** - * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. - * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. - */ - public static function hideCursor() - { - echo "\033[?25l"; - } - - /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. - */ - public static function showCursor() - { - echo "\033[?25h"; - } - - /** - * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. - * Cursor position will not be changed. - * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. - */ - public static function clearScreen() - { - echo "\033[2J"; - } - - /** - * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenBeforeCursor() - { - echo "\033[1J"; - } - - /** - * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenAfterCursor() - { - echo "\033[0J"; - } - - /** - * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLine() - { - echo "\033[2K"; - } - - /** - * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineBeforeCursor() - { - echo "\033[1K"; - } - - /** - * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineAfterCursor() - { - echo "\033[0K"; - } - - /** - * Returns the ANSI format code. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string The ANSI format code according to the given formatting constants. - */ - public static function ansiFormatCode($format) - { - return "\033[" . implode(';', $format) . 'm'; - } - - /** - * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @see ansiFormatCode() - * @see ansiFormatEnd() - */ - public static function beginAnsiFormat($format) - { - echo "\033[" . implode(';', $format) . 'm'; - } - - /** - * Resets any ANSI format set by previous method [[ansiFormatBegin()]] - * Any output after this will have default text format. - * This is equal to calling - * - * ```php - * echo Console::ansiFormatCode(array(Console::RESET)) - * ``` - */ - public static function endAnsiFormat() - { - echo "\033[0m"; - } - - /** - * Will return a string formatted with the given ANSI style - * - * @param string $string the string to be formatted - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string - */ - public static function ansiFormat($string, $format = array()) - { - $code = implode(';', $format); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; - } - - /** - * Returns the ansi format code for xterm foreground color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermFgColor($colorCode) - { - return '38;5;' . $colorCode; - } - - /** - * Returns the ansi format code for xterm background color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermBgColor($colorCode) - { - return '48;5;' . $colorCode; - } - - /** - * Strips ANSI control codes from a string - * - * @param string $string String to strip - * @return string - */ - public static function stripAnsiFormat($string) - { - return preg_replace('/\033\[[\d;?]*\w/', '', $string); - } - - /** - * Converts an ANSI formatted string to HTML - * @param $string - * @return mixed - */ - // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 - public static function ansiToHtml($string) - { - $tags = 0; - return preg_replace_callback( - '/\033\[[\d;]+m/', - function ($ansi) use (&$tags) { - $styleA = array(); - foreach (explode(';', $ansi) as $controlCode) { - switch ($controlCode) { - case self::FG_BLACK: - $style = array('color' => '#000000'); - break; - case self::FG_BLUE: - $style = array('color' => '#000078'); - break; - case self::FG_CYAN: - $style = array('color' => '#007878'); - break; - case self::FG_GREEN: - $style = array('color' => '#007800'); - break; - case self::FG_GREY: - $style = array('color' => '#787878'); - break; - case self::FG_PURPLE: - $style = array('color' => '#780078'); - break; - case self::FG_RED: - $style = array('color' => '#780000'); - break; - case self::FG_YELLOW: - $style = array('color' => '#787800'); - break; - case self::BG_BLACK: - $style = array('background-color' => '#000000'); - break; - case self::BG_BLUE: - $style = array('background-color' => '#000078'); - break; - case self::BG_CYAN: - $style = array('background-color' => '#007878'); - break; - case self::BG_GREEN: - $style = array('background-color' => '#007800'); - break; - case self::BG_GREY: - $style = array('background-color' => '#787878'); - break; - case self::BG_PURPLE: - $style = array('background-color' => '#780078'); - break; - case self::BG_RED: - $style = array('background-color' => '#780000'); - break; - case self::BG_YELLOW: - $style = array('background-color' => '#787800'); - break; - case self::BOLD: - $style = array('font-weight' => 'bold'); - break; - case self::ITALIC: - $style = array('font-style' => 'italic'); - break; - case self::UNDERLINE: - $style = array('text-decoration' => array('underline')); - break; - case self::OVERLINED: - $style = array('text-decoration' => array('overline')); - break; - case self::CROSSED_OUT: - $style = array('text-decoration' => array('line-through')); - break; - case self::BLINK: - $style = array('text-decoration' => array('blink')); - break; - case self::NEGATIVE: // ??? - case self::CONCEALED: - case self::ENCIRCLED: - case self::FRAMED: - // TODO allow resetting codes - break; - case 0: // ansi reset - $return = ''; - for ($n = $tags; $tags > 0; $tags--) { - $return .= ''; - } - return $return; - } - - $styleA = ArrayHelper::merge($styleA, $style); - } - $styleString[] = array(); - foreach ($styleA as $name => $content) { - if ($name === 'text-decoration') { - $content = implode(' ', $content); - } - $styleString[] = $name . ':' . $content; - } - $tags++; - return ' $value) { - echo " $key - $value\n"; - } - echo " ? - Show help\n"; - goto top; - } elseif (!in_array($input, array_keys($options))) { - goto top; - } - return $input; - } - - /** - * Displays and updates a simple progress bar on screen. - * - * @param integer $done the number of items that are completed - * @param integer $total the total value of items that are to be done - * @param integer $size the size of the status bar (optional) - * @see http://snipplr.com/view/29548/ - */ - public static function showProgress($done, $total, $size = 30) - { - static $start; - - // if we go over our bound, just ignore it - if ($done > $total) { - return; - } - - if (empty($start)) { - $start = time(); - } - - $now = time(); - - $percent = (double)($done / $total); - $bar = floor($percent * $size); - - $status = "\r["; - $status .= str_repeat("=", $bar); - if ($bar < $size) { - $status .= ">"; - $status .= str_repeat(" ", $size - $bar); - } else { - $status .= "="; - } - - $display = number_format($percent * 100, 0); - - $status .= "] $display% $done/$total"; - - $rate = ($now - $start) / $done; - $left = $total - $done; - $eta = round($rate * $left, 2); - - $elapsed = $now - $start; - - $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; - - static::stdout("$status "); - - flush(); - - // when done, send a newline - if ($done == $total) { - echo "\n"; - } - } -} diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 9241025..919dc09 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class FileHelper extends FileHelperBase +class FileHelper extends AbstractFileHelper { } diff --git a/framework/yii/helpers/FileHelperBase.php b/framework/yii/helpers/FileHelperBase.php deleted file mode 100644 index 0e480da..0000000 --- a/framework/yii/helpers/FileHelperBase.php +++ /dev/null @@ -1,329 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -class FileHelperBase -{ - /** - * Normalizes a file/directory path. - * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, - * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux - * will be normalized as '/home/demo'. - * @param string $path the file/directory path to be normalized - * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. - * @return string the normalized file/directory path - */ - public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) - { - return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); - } - - /** - * Returns the localized version of a specified file. - * - * The searching is based on the specified language code. In particular, - * a file with the same name will be looked for under the subdirectory - * whose name is the same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_CN", the localized file will be looked for as - * "path/to/zh_CN/view.php". If the file is not found, the original file - * will be returned. - * - * If the target and the source language codes are the same, - * the original file will be returned. - * - * @param string $file the original file - * @param string $language the target language that the file should be localized to. - * If not set, the value of [[\yii\base\Application::language]] will be used. - * @param string $sourceLanguage the language that the original file is in. - * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. - * @return string the matching localized file, or the original file if the localized version is not found. - * If the target and the source language codes are the same, the original file will be returned. - */ - public static function localize($file, $language = null, $sourceLanguage = null) - { - if ($language === null) { - $language = Yii::$app->language; - } - if ($sourceLanguage === null) { - $sourceLanguage = Yii::$app->sourceLanguage; - } - if ($language === $sourceLanguage) { - return $file; - } - $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); - return is_file($desiredFile) ? $desiredFile : $file; - } - - /** - * Determines the MIME type of the specified file. - * This method will first try to determine the MIME type based on - * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will - * fall back to [[getMimeTypeByExtension()]]. - * @param string $file the file name. - * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. - * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). - * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case - * `finfo_open()` cannot determine it. - * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. - */ - public static function getMimeType($file, $magicFile = null, $checkExtension = true) - { - if (function_exists('finfo_open')) { - $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info) { - $result = finfo_file($info, $file); - finfo_close($info); - if ($result !== false) { - return $result; - } - } - } - - return $checkExtension ? static::getMimeTypeByExtension($file) : null; - } - - /** - * Determines the MIME type based on the extension name of the specified file. - * This method will use a local map between extension names and MIME types. - * @param string $file the file name. - * @param string $magicFile the path of the file that contains all available MIME type information. - * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. - * @return string the MIME type. Null is returned if the MIME type cannot be determined. - */ - public static function getMimeTypeByExtension($file, $magicFile = null) - { - static $mimeTypes = array(); - if ($magicFile === null) { - $magicFile = __DIR__ . '/mimeTypes.php'; - } - if (!isset($mimeTypes[$magicFile])) { - $mimeTypes[$magicFile] = require($magicFile); - } - if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { - $ext = strtolower($ext); - if (isset($mimeTypes[$magicFile][$ext])) { - return $mimeTypes[$magicFile][$ext]; - } - } - return null; - } - - /** - * Copies a whole directory as another one. - * The files and sub-directories will also be copied over. - * @param string $src the source directory - * @param string $dst the destination directory - * @param array $options options for directory copy. Valid options are: - * - * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. - * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be copied (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be copied - * - * - only: array, list of patterns that the file paths should match if they want to be copied. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be copied. - * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. - * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. - * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or - * file copied from, while `$to` is the copy target. - */ - public static function copyDirectory($src, $dst, $options = array()) - { - if (!is_dir($dst)) { - static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); - } - - $handle = opendir($src); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $from = $src . DIRECTORY_SEPARATOR . $file; - $to = $dst . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($from, $options)) { - if (is_file($from)) { - copy($from, $to); - if (isset($options['fileMode'])) { - @chmod($to, $options['fileMode']); - } - } else { - static::copyDirectory($from, $to, $options); - } - if (isset($options['afterCopy'])) { - call_user_func($options['afterCopy'], $from, $to); - } - } - } - closedir($handle); - } - - /** - * Removes a directory (and all its content) recursively. - * @param string $dir the directory to be deleted recursively. - */ - public static function removeDirectory($dir) - { - if (!is_dir($dir) || !($handle = opendir($dir))) { - return; - } - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (is_file($path)) { - unlink($path); - } else { - static::removeDirectory($path); - } - } - closedir($handle); - rmdir($dir); - } - - /** - * Returns the files found under the specified directory and subdirectories. - * @param string $dir the directory under which the files will be looked for. - * @param array $options options for file searching. Valid options are: - * - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be returned (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be returned - * - * - only: array, list of patterns that the file paths should match if they want to be returned. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be returned. - * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. - * @return array files found under the directory. The file list is sorted. - */ - public static function findFiles($dir, $options = array()) - { - $list = array(); - $handle = opendir($dir); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($path, $options)) { - if (is_file($path)) { - $list[] = $path; - } elseif (!isset($options['recursive']) || $options['recursive']) { - $list = array_merge($list, static::findFiles($path, $options)); - } - } - } - closedir($handle); - return $list; - } - - /** - * Checks if the given file path satisfies the filtering options. - * @param string $path the path of the file or directory to be checked - * @param array $options the filtering options. See [[findFiles()]] for explanations of - * the supported options. - * @return boolean whether the file or directory satisfies the filtering options. - */ - public static function filterPath($path, $options) - { - if (isset($options['filter'])) { - $result = call_user_func($options['filter'], $path); - if (is_bool($result)) { - return $result; - } - } - $path = str_replace('\\', '/', $path); - if ($isDir = is_dir($path)) { - $path .= '/'; - } - $n = StringHelper::strlen($path); - - if (!empty($options['except'])) { - foreach ($options['except'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return false; - } - } - } - - if (!$isDir && !empty($options['only'])) { - foreach ($options['only'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return true; - } - } - return false; - } - return true; - } - - /** - * Creates a new directory. - * - * This method is similar to the PHP `mkdir()` function except that - * it uses `chmod()` to set the permission of the created directory - * in order to avoid the impact of the `umask` setting. - * - * @param string $path path of the directory to be created. - * @param integer $mode the permission to be set for the created directory. - * @param boolean $recursive whether to create parent directories if they do not exist. - * @return boolean whether the directory is created successfully - */ - public static function createDirectory($path, $mode = 0775, $recursive = true) - { - if (is_dir($path)) { - return true; - } - $parentDir = dirname($path); - if ($recursive && !is_dir($parentDir)) { - static::createDirectory($parentDir, $mode, true); - } - $result = mkdir($path, $mode); - chmod($path, $mode); - return $result; - } -} diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 8e4f1c9..0715c6c 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Html extends HtmlBase +class Html extends AbstractHtml { } diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/HtmlBase.php deleted file mode 100644 index a5786cb..0000000 --- a/framework/yii/helpers/HtmlBase.php +++ /dev/null @@ -1,1599 +0,0 @@ - - * @since 2.0 - */ -class HtmlBase -{ - /** - * @var array list of void elements (element name => 1) - * @see http://www.w3.org/TR/html-markup/syntax.html#void-element - */ - public static $voidElements = array( - 'area' => 1, - 'base' => 1, - 'br' => 1, - 'col' => 1, - 'command' => 1, - 'embed' => 1, - 'hr' => 1, - 'img' => 1, - 'input' => 1, - 'keygen' => 1, - 'link' => 1, - 'meta' => 1, - 'param' => 1, - 'source' => 1, - 'track' => 1, - 'wbr' => 1, - ); - /** - * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes - * that are rendered by [[renderTagAttributes()]]. - */ - public static $attributeOrder = array( - 'type', - 'id', - 'class', - 'name', - 'value', - - 'href', - 'src', - 'action', - 'method', - - 'selected', - 'checked', - 'readonly', - 'disabled', - 'multiple', - - 'size', - 'maxlength', - 'width', - 'height', - 'rows', - 'cols', - - 'alt', - 'title', - 'rel', - 'media', - ); - - /** - * Encodes special characters into HTML entities. - * The [[yii\base\Application::charset|application charset]] will be used for encoding. - * @param string $content the content to be encoded - * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, - * HTML entities in `$content` will not be further encoded. - * @return string the encoded content - * @see decode - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function encode($content, $doubleEncode = true) - { - return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); - } - - /** - * Decodes special HTML entities back to the corresponding characters. - * This is the opposite of [[encode()]]. - * @param string $content the content to be decoded - * @return string the decoded content - * @see encode - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function decode($content) - { - return htmlspecialchars_decode($content, ENT_QUOTES); - } - - /** - * Generates a complete HTML tag. - * @param string $name the tag name - * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. - * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated HTML tag - * @see beginTag - * @see endTag - */ - public static function tag($name, $content = '', $options = array()) - { - $html = "<$name" . static::renderTagAttributes($options) . '>'; - return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; - } - - /** - * Generates a start tag. - * @param string $name the tag name - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated start tag - * @see endTag - * @see tag - */ - public static function beginTag($name, $options = array()) - { - return "<$name" . static::renderTagAttributes($options) . '>'; - } - - /** - * Generates an end tag. - * @param string $name the tag name - * @return string the generated end tag - * @see beginTag - * @see tag - */ - public static function endTag($name) - { - return ""; - } - - /** - * Generates a style tag. - * @param string $content the style content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/css" will be used. - * @return string the generated style tag - */ - public static function style($content, $options = array()) - { - return static::tag('style', $content, $options); - } - - /** - * Generates a script tag. - * @param string $content the script content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. - * @return string the generated script tag - */ - public static function script($content, $options = array()) - { - return static::tag('script', $content, $options); - } - - /** - * Generates a link tag that refers to an external CSS file. - * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated link tag - * @see url - */ - public static function cssFile($url, $options = array()) - { - $options['rel'] = 'stylesheet'; - $options['href'] = static::url($url); - return static::tag('link', '', $options); - } - - /** - * Generates a script tag that refers to an external JavaScript file. - * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated script tag - * @see url - */ - public static function jsFile($url, $options = array()) - { - $options['src'] = static::url($url); - return static::tag('script', '', $options); - } - - /** - * Generates a form start tag. - * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. - * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). - * Since most browsers only support "post" and "get", if other methods are given, they will - * be simulated using "post", and a hidden input will be added which contains the actual method type. - * See [[\yii\web\Request::restVar]] for more details. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated form start tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $options = array()) - { - $action = static::url($action); - - $hiddenInputs = array(); - - $request = Yii::$app->getRequest(); - if ($request instanceof Request) { - if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { - // simulate PUT, DELETE, etc. via POST - $hiddenInputs[] = static::hiddenInput($request->restVar, $method); - $method = 'post'; - } - if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); - } - } - - if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { - // query parameters in the action are ignored for GET method - // we use hidden fields to add them back - foreach (explode('&', substr($action, $pos + 1)) as $pair) { - if (($pos1 = strpos($pair, '=')) !== false) { - $hiddenInputs[] = static::hiddenInput( - urldecode(substr($pair, 0, $pos1)), - urldecode(substr($pair, $pos1 + 1)) - ); - } else { - $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); - } - } - $action = substr($action, 0, $pos); - } - - $options['action'] = $action; - $options['method'] = $method; - $form = static::beginTag('form', $options); - if (!empty($hiddenInputs)) { - $form .= "\n" . implode("\n", $hiddenInputs); - } - - return $form; - } - - /** - * Generates a form end tag. - * @return string the generated tag - * @see beginForm - */ - public static function endForm() - { - return ''; - } - - /** - * 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 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 - * will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated hyperlink - * @see url - */ - public static function a($text, $url = null, $options = array()) - { - if ($url !== null) { - $options['href'] = static::url($url); - } - return static::tag('a', $text, $options); - } - - /** - * 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 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. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated mailto link - */ - public static function mailto($text, $email = null, $options = array()) - { - $options['href'] = 'mailto:' . ($email === null ? $text : $email); - return static::tag('a', $text, $options); - } - - /** - * Generates an image tag. - * @param string $src the image URL. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated image tag - */ - public static function img($src, $options = array()) - { - $options['src'] = static::url($src); - if (!isset($options['alt'])) { - $options['alt'] = ''; - } - return static::tag('img', '', $options); - } - - /** - * Generates a label tag. - * @param string $content label text. 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 [[encode()]] - * it to prevent XSS attacks. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is null, the "for" attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated label tag - */ - public static function label($content, $for = null, $options = array()) - { - $options['for'] = $for; - return static::tag('label', $content, $options); - } - - /** - * Generates a button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function button($content = 'Button', $options = array()) - { - return static::tag('button', $content, $options); - } - - /** - * Generates a submit button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated submit button tag - */ - public static function submitButton($content = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - return static::button($content, $options); - } - - /** - * Generates a reset button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated reset button tag - */ - public static function resetButton($content = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - return static::button($content, $options); - } - - /** - * Generates an input type of the given type. - * @param string $type the type attribute. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated input tag - */ - public static function input($type, $name = null, $value = null, $options = array()) - { - $options['type'] = $type; - $options['name'] = $name; - $options['value'] = $value === null ? null : (string)$value; - return static::tag('input', '', $options); - } - - /** - * Generates an input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function buttonInput($label = 'Button', $options = array()) - { - $options['type'] = 'button'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a submit input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function submitInput($label = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a reset input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the tag returned. - * @return string the generated button tag - */ - public static function resetInput($label = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a text input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function textInput($name, $value = null, $options = array()) - { - return static::input('text', $name, $value, $options); - } - - /** - * Generates a hidden input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function hiddenInput($name, $value = null, $options = array()) - { - return static::input('hidden', $name, $value, $options); - } - - /** - * Generates a password input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function passwordInput($name, $value = null, $options = array()) - { - return static::input('password', $name, $value, $options); - } - - /** - * Generates a file input field. - * To use a file input field, you should set the enclosing form's "enctype" attribute to - * be "multipart/form-data". After the form is submitted, the uploaded file information - * can be obtained via $_FILES[$name] (see PHP documentation). - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function fileInput($name, $value = null, $options = array()) - { - return static::input('file', $name, $value, $options); - } - - /** - * Generates a text area input. - * @param string $name the input name - * @param string $value the input value. Note that it will be encoded using [[encode()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated text area tag - */ - public static function textarea($name, $value = '', $options = array()) - { - $options['name'] = $name; - return static::tag('textarea', static::encode($value), $options); - } - - /** - * Generates a radio button input. - * @param string $name the name attribute. - * @param boolean $checked whether the radio button should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function radio($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'radio')); - } else { - return $hidden . static::input('radio', $name, $value, $options); - } - } - - /** - * Generates a checkbox input. - * @param string $name the name attribute. - * @param boolean $checked whether the checkbox should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute - * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function checkbox($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $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']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'checkbox')); - } else { - return $hidden . static::input('checkbox', $name, $value, $options); - } - } - - /** - * Generates a drop-down list. - * @param string $name the input name - * @param string $selection the selected value - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) - { - $options['name'] = $name; - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list box. - * @param string $name the input name - * @param string|array $selection the selected value(s) - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) - { - if (!isset($options['size'])) { - $options['size'] = 4; - } - if (!empty($options['multiple']) && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $options['name'] = $name; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); - } - $hidden = static::hiddenInput($name, $options['unselect']); - unset($options['unselect']); - } else { - $hidden = ''; - } - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * @param string $name the name attribute of each checkbox. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * @param array $options options (name => config) for the checkbox list container tag. - * The following options are specially handled: - * - * - tag: string, the tag name of the container element. - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input, respectively. - * @return string the generated checkbox list - */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) - { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } - - $formatter = isset($options['item']) ? $options['item'] : null; - $encode = !isset($options['encode']) || $options['encode']; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::checkbox($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; - $hidden = static::hiddenInput($name2, $options['unselect']); - } else { - $hidden = ''; - } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * @param string $name the name attribute of each radio button. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input, respectively. - * @return string the generated radio button list - */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) - { - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::radio($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; - } - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates an unordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated unordered list. An empty string is returned if `$items` is empty. - */ - public static function ul($items, $options = array()) - { - if (empty($items)) { - return ''; - } - $tag = isset($options['tag']) ? $options['tag'] : 'ul'; - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); - unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); - $results = array(); - foreach ($items as $index => $item) { - if ($formatter !== null) { - $results[] = call_user_func($formatter, $item, $index); - } else { - $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); - } - } - return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); - } - - /** - * Generates an ordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated ordered list. An empty string is returned if `$items` is empty. - */ - public static function ol($items, $options = array()) - { - $options['tag'] = 'ol'; - return static::ul($items, $options); - } - - /** - * Generates a label tag for the given model attribute. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * - * @return string the generated label tag - */ - public static function activeLabel($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); - $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); - unset($options['label'], $options['for']); - return static::label($label, $for, $options); - } - - /** - * Generates a tag that contains the first validation error of the specified model attribute. - * Note that even if there is no validation error, this method will still return an empty error tag. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded - * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - tag: this specifies the tag name. If not set, "div" will be used. - * - * @return string the generated label tag - */ - public static function error($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $error = $model->getFirstError($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - return Html::tag($tag, Html::encode($error), $options); - } - - /** - * Generates an input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param string $type the input type (e.g. 'text', 'password') - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeInput($type, $model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::input($type, $name, $value, $options); - } - - /** - * Generates a text input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeTextInput($model, $attribute, $options = array()) - { - return static::activeInput('text', $model, $attribute, $options); - } - - /** - * Generates a hidden input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeHiddenInput($model, $attribute, $options = array()) - { - return static::activeInput('hidden', $model, $attribute, $options); - } - - /** - * Generates a password input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activePasswordInput($model, $attribute, $options = array()) - { - return static::activeInput('password', $model, $attribute, $options); - } - - /** - * Generates a file input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeFileInput($model, $attribute, $options = array()) - { - // add a hidden field so that if a model only has a file field, we can - // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) - . static::activeInput('file', $model, $attribute, $options); - } - - /** - * Generates a textarea tag for the given model attribute. - * The model attribute value will be used as the content in the textarea. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated textarea tag - */ - public static function activeTextarea($model, $attribute, $options = array()) - { - $name = static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::textarea($name, $value, $options); - } - - /** - * Generates a radio button tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function activeRadio($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radio($name, $checked, $options); - } - - /** - * Generates a checkbox tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function activeCheckbox($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkbox($name, $checked, $options); - } - - /** - * Generates a drop-down list for the given model attribute. - * The selection of the drop-down list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function activeDropDownList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::dropDownList($name, $checked, $items, $options); - } - - /** - * Generates a list box. - * The selection of the list box is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function activeListBox($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::listBox($name, $checked, $items, $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * The selection of the checkbox list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input. - * @return string the generated checkbox list - */ - public static function activeCheckboxList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkboxList($name, $checked, $items, $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * The selection of the radio buttons is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input. - * @return string the generated radio button list - */ - public static function activeRadioList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radioList($name, $checked, $items, $options); - } - - /** - * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. - * @param string|array $selection the selected value(s). This can be either a string for single selection - * or an array for multiple selections. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. - * This method will take out these elements, if any: "prompt", "options" and "groups". See more details - * in [[dropDownList()]] for the explanation of these elements. - * - * @return string the generated list options - */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) - { - $lines = array(); - if (isset($tagOptions['prompt'])) { - $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); - } - - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); - unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); - - foreach ($items as $key => $value) { - if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); - $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); - $content = static::renderSelectOptions($selection, $value, $attrs); - $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); - } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); - $attrs['value'] = (string)$key; - $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); - $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); - } - } - - return implode("\n", $lines); - } - - /** - * Renders the HTML tag attributes. - * Attributes whose values are of boolean type will be treated as - * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). - * And attributes whose values are null will not be rendered. - * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. - * @return string the rendering result. If the attributes are not empty, they will be rendered - * into a string with a leading white space (so that it can be directly appended to the tag name - * in a tag. If there is no attribute, an empty string will be returned. - */ - public static function renderTagAttributes($attributes) - { - if (count($attributes) > 1) { - $sorted = array(); - foreach (static::$attributeOrder as $name) { - if (isset($attributes[$name])) { - $sorted[$name] = $attributes[$name]; - } - } - $attributes = array_merge($sorted, $attributes); - } - - $html = ''; - foreach ($attributes as $name => $value) { - if (is_bool($value)) { - if ($value) { - $html .= " $name"; - } - } elseif ($value !== null) { - $html .= " $name=\"" . static::encode($value) . '"'; - } - } - return $html; - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter - * - * - is an empty string: the currently requested URL will be returned; - * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result - * is an absolute URL, it will be returned without any change further; Otherwise, the result - * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. - * - is an array: the first array element is considered a route, while the rest of the name-value - * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. - * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. - * - * @param array|string $url the parameter to be used to generate a valid URL - * @return string the normalized URL - * @throws InvalidParamException if the parameter is invalid. - */ - public static function url($url) - { - if (is_array($url)) { - if (isset($url[0])) { - $route = $url[0]; - $params = array_splice($url, 1); - if (Yii::$app->controller instanceof \yii\web\Controller) { - return Yii::$app->controller->createUrl($route, $params); - } else { - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - } else { - throw new InvalidParamException('The array specifying a URL must contain at least one element.'); - } - } elseif ($url === '') { - return Yii::$app->getRequest()->getUrl(); - } else { - $url = Yii::getAlias($url); - if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { - return $url; - } else { - return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; - } - } - } - - /** - * Adds a CSS class to the specified options. - * If the CSS class is already in the options, it will not be added again. - * @param array $options the options to be modified. - * @param string $class the CSS class to be added - */ - public static function addCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = ' ' . $options['class'] . ' '; - if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { - $options['class'] .= ' ' . $class; - } - } else { - $options['class'] = $class; - } - } - - /** - * Removes a CSS class from the specified options. - * @param array $options the options to be modified. - * @param string $class the CSS class to be removed - */ - public static function removeCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); - if (($index = array_search($class, $classes)) !== false) { - unset($classes[$index]); - } - if (empty($classes)) { - unset($options['class']); - } else { - $options['class'] = implode(' ', $classes); - } - } - } - - /** - * Returns the real attribute name from the given attribute expression. - * - * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. - * It is mainly used in tabular data input and/or input of array type. Below are some examples: - * - * - `[0]content` is used in tabular data input to represent the "content" attribute - * for the first model in tabular input; - * - `dates[0]` represents the first array element of the "dates" attribute; - * - `[0]dates[0]` represents the first array element of the "dates" attribute - * for the first model in tabular input. - * - * If `$attribute` has neither prefix nor suffix, it will be returned back without change. - * @param string $attribute the attribute name or expression - * @return string the attribute name without prefix and suffix. - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeName($attribute) - { - if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - return $matches[2]; - } else { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - } - - /** - * Returns the value of the specified attribute name or expression. - * - * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. - * See [[getAttributeName()]] for more details about attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return mixed the corresponding attribute value - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeValue($model, $attribute) - { - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $attribute = $matches[2]; - $index = $matches[3]; - if ($index === '') { - return $model->$attribute; - } else { - $value = $model->$attribute; - foreach (explode('][', trim($index, '[]')) as $id) { - if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { - $value = $value[$id]; - } else { - return null; - } - } - return $value; - } - } - - /** - * Generates an appropriate input name for the specified attribute name or expression. - * - * This method generates a name that can be used as the input name to collect user input - * for the specified attribute. The name is generated according to the [[Model::formName|form name]] - * of the model and the given attribute name. For example, if the form name of the `Post` model - * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. - * - * See [[getAttributeName()]] for explanation of attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return string the generated input name - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputName($model, $attribute) - { - $formName = $model->formName(); - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $prefix = $matches[1]; - $attribute = $matches[2]; - $suffix = $matches[3]; - if ($formName === '' && $prefix === '') { - return $attribute . $suffix; - } elseif ($formName !== '') { - return $formName . $prefix . "[$attribute]" . $suffix; - } else { - throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); - } - } - - /** - * Generates an appropriate input ID for the specified attribute name or expression. - * - * This method converts the result [[getInputName()]] into a valid input ID. - * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. - * @return string the generated input ID - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputId($model, $attribute) - { - $name = strtolower(static::getInputName($model, $attribute)); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } -} diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index f7203e4..ca7e485 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifier extends HtmlPurifierBase +class HtmlPurifier extends AbstractHtmlPurifier { } diff --git a/framework/yii/helpers/HtmlPurifierBase.php b/framework/yii/helpers/HtmlPurifierBase.php deleted file mode 100644 index e89a589..0000000 --- a/framework/yii/helpers/HtmlPurifierBase.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @since 2.0 - */ -class HtmlPurifierBase -{ - /** - * Passes markup through HTMLPurifier making it safe to output to end user - * - * @param string $content - * @param array|null $config - * @return string - */ - public static function process($content, $config = null) - { - $configInstance = \HTMLPurifier_Config::create($config); - $configInstance->autoFinalize = false; - $purifier=\HTMLPurifier::instance($configInstance); - $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); - return $purifier->purify($content); - } -} diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index ba9c069..71e7f05 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez * @since 2.0 */ -class Inflector extends InflectorBase +class Inflector extends AbstractInflector { } diff --git a/framework/yii/helpers/InflectorBase.php b/framework/yii/helpers/InflectorBase.php deleted file mode 100644 index 87c1ff4..0000000 --- a/framework/yii/helpers/InflectorBase.php +++ /dev/null @@ -1,480 +0,0 @@ - - * @since 2.0 - */ -class InflectorBase -{ - /** - * @var array the rules for converting a word into its plural form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $plurals = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(m)ove$/i' => '\1oves', - '/(f)oot$/i' => '\1eet', - '/(h)uman$/i' => '\1umans', - '/(s)tatus$/i' => '\1tatuses', - '/(s)taff$/i' => '\1taff', - '/(t)ooth$/i' => '\1eeth', - '/(quiz)$/i' => '\1zes', - '/^(ox)$/i' => '\1\2en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(p)erson$/i' => '\1eople', - '/(m)an$/i' => '\1en', - '/(c)hild$/i' => '\1hildren', - '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', - '/us$/i' => 'uses', - '/(alias)$/i' => '\1es', - '/(ax|cris|test)is$/i' => '\1es', - '/s$/' => 's', - '/^$/' => '', - '/$/' => 's', - ); - /** - * @var array the rules for converting a word into its singular form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $singulars = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(s)tatuses$/i' => '\1tatus', - '/(f)eet$/i' => '\1oot', - '/(t)eeth$/i' => '\1ooth', - '/^(.*)(menu)s$/i' => '\1\2', - '/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\1ix', - '/(vert|ind)ices$/i' => '\1ex', - '/^(ox)en/i' => '\1', - '/(alias)(es)*$/i' => '\1', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', - '/([ftw]ax)es/i' => '\1', - '/(cris|ax|test)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', - '/(o)es$/i' => '\1', - '/ouses$/' => 'ouse', - '/([^a])uses$/' => '\1us', - '/([m|l])ice$/i' => '\1ouse', - '/(x|ch|ss|sh)es$/i' => '\1', - '/(m)ovies$/i' => '\1\2ovie', - '/(s)eries$/i' => '\1\2eries', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([lr])ves$/i' => '\1f', - '/(tive)s$/i' => '\1', - '/(hive)s$/i' => '\1', - '/(drive)s$/i' => '\1', - '/([^fo])ves$/i' => '\1fe', - '/(^analy)ses$/i' => '\1sis', - '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', - '/([ti])a$/i' => '\1um', - '/(p)eople$/i' => '\1\2erson', - '/(m)en$/i' => '\1an', - '/(c)hildren$/i' => '\1\2hild', - '/(n)ews$/i' => '\1\2ews', - '/eaus$/' => 'eau', - '/^(.*us)$/' => '\\1', - '/s$/i' => '', - ); - /** - * @var array the special rules for converting a word between its plural form and singular form. - * The keys are the special words in singular form, and the values are the corresponding plural form. - */ - public static $specials = array( - 'atlas' => 'atlases', - 'beef' => 'beefs', - 'brother' => 'brothers', - 'cafe' => 'cafes', - 'child' => 'children', - 'cookie' => 'cookies', - 'corpus' => 'corpuses', - 'cow' => 'cows', - 'curve' => 'curves', - 'foe' => 'foes', - 'ganglion' => 'ganglions', - 'genie' => 'genies', - 'genus' => 'genera', - 'graffito' => 'graffiti', - 'hoof' => 'hoofs', - 'loaf' => 'loaves', - 'man' => 'men', - 'money' => 'monies', - 'mongoose' => 'mongooses', - 'move' => 'moves', - 'mythos' => 'mythoi', - 'niche' => 'niches', - 'numen' => 'numina', - 'occiput' => 'occiputs', - 'octopus' => 'octopuses', - 'opus' => 'opuses', - 'ox' => 'oxen', - 'penis' => 'penises', - 'sex' => 'sexes', - 'soliloquy' => 'soliloquies', - 'testis' => 'testes', - 'trilby' => 'trilbys', - 'turf' => 'turfs', - 'wave' => 'waves', - 'Amoyese' => 'Amoyese', - 'bison' => 'bison', - 'Borghese' => 'Borghese', - 'bream' => 'bream', - 'breeches' => 'breeches', - 'britches' => 'britches', - 'buffalo' => 'buffalo', - 'cantus' => 'cantus', - 'carp' => 'carp', - 'chassis' => 'chassis', - 'clippers' => 'clippers', - 'cod' => 'cod', - 'coitus' => 'coitus', - 'Congoese' => 'Congoese', - 'contretemps' => 'contretemps', - 'corps' => 'corps', - 'debris' => 'debris', - 'diabetes' => 'diabetes', - 'djinn' => 'djinn', - 'eland' => 'eland', - 'elk' => 'elk', - 'equipment' => 'equipment', - 'Faroese' => 'Faroese', - 'flounder' => 'flounder', - 'Foochowese' => 'Foochowese', - 'gallows' => 'gallows', - 'Genevese' => 'Genevese', - 'Genoese' => 'Genoese', - 'Gilbertese' => 'Gilbertese', - 'graffiti' => 'graffiti', - 'headquarters' => 'headquarters', - 'herpes' => 'herpes', - 'hijinks' => 'hijinks', - 'Hottentotese' => 'Hottentotese', - 'information' => 'information', - 'innings' => 'innings', - 'jackanapes' => 'jackanapes', - 'Kiplingese' => 'Kiplingese', - 'Kongoese' => 'Kongoese', - 'Lucchese' => 'Lucchese', - 'mackerel' => 'mackerel', - 'Maltese' => 'Maltese', - 'mews' => 'mews', - 'moose' => 'moose', - 'mumps' => 'mumps', - 'Nankingese' => 'Nankingese', - 'news' => 'news', - 'nexus' => 'nexus', - 'Niasese' => 'Niasese', - 'Pekingese' => 'Pekingese', - 'Piedmontese' => 'Piedmontese', - 'pincers' => 'pincers', - 'Pistoiese' => 'Pistoiese', - 'pliers' => 'pliers', - 'Portuguese' => 'Portuguese', - 'proceedings' => 'proceedings', - 'rabies' => 'rabies', - 'rice' => 'rice', - 'rhinoceros' => 'rhinoceros', - 'salmon' => 'salmon', - 'Sarawakese' => 'Sarawakese', - 'scissors' => 'scissors', - 'series' => 'series', - 'Shavese' => 'Shavese', - 'shears' => 'shears', - 'siemens' => 'siemens', - 'species' => 'species', - 'swine' => 'swine', - 'testes' => 'testes', - 'trousers' => 'trousers', - 'trout' => 'trout', - 'tuna' => 'tuna', - 'Vermontese' => 'Vermontese', - 'Wenchowese' => 'Wenchowese', - 'whiting' => 'whiting', - 'wildebeest' => 'wildebeest', - 'Yengeese' => 'Yengeese', - ); - /** - * @var array map of special chars and its translation. This is used by [[slug()]]. - */ - public static $transliteration = array( - '/ä|æ|ǽ/' => 'ae', - '/ö|œ/' => 'oe', - '/ü/' => 'ue', - '/Ä/' => 'Ae', - '/Ü/' => 'Ue', - '/Ö/' => 'Oe', - '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', - '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', - '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', - '/ç|ć|ĉ|ċ|č/' => 'c', - '/Ð|Ď|Đ/' => 'D', - '/ð|ď|đ/' => 'd', - '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', - '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', - '/Ĝ|Ğ|Ġ|Ģ/' => 'G', - '/ĝ|ğ|ġ|ģ/' => 'g', - '/Ĥ|Ħ/' => 'H', - '/ĥ|ħ/' => 'h', - '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', - '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', - '/Ĵ/' => 'J', - '/ĵ/' => 'j', - '/Ķ/' => 'K', - '/ķ/' => 'k', - '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', - '/ĺ|ļ|ľ|ŀ|ł/' => 'l', - '/Ñ|Ń|Ņ|Ň/' => 'N', - '/ñ|ń|ņ|ň|ʼn/' => 'n', - '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', - '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', - '/Ŕ|Ŗ|Ř/' => 'R', - '/ŕ|ŗ|ř/' => 'r', - '/Ś|Ŝ|Ş|Ș|Š/' => 'S', - '/ś|ŝ|ş|ș|š|ſ/' => 's', - '/Ţ|Ț|Ť|Ŧ/' => 'T', - '/ţ|ț|ť|ŧ/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', - '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', - '/Ý|Ÿ|Ŷ/' => 'Y', - '/ý|ÿ|ŷ/' => 'y', - '/Ŵ/' => 'W', - '/ŵ/' => 'w', - '/Ź|Ż|Ž/' => 'Z', - '/ź|ż|ž/' => 'z', - '/Æ|Ǽ/' => 'AE', - '/ß/' => 'ss', - '/IJ/' => 'IJ', - '/ij/' => 'ij', - '/Œ/' => 'OE', - '/ƒ/' => 'f' - ); - - /** - * Converts a word to its plural form. - * Note that this is for English only! - * For example, 'apple' will become 'apples', and 'child' will become 'children'. - * @param string $word the word to be pluralized - * @return string the pluralized word - */ - public static function pluralize($word) - { - if (isset(self::$specials[$word])) { - return self::$specials[$word]; - } - foreach (static::$plurals as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Returns the singular of the $word - * @param string $word the english word to singularize - * @return string Singular noun. - */ - public static function singularize($word) - { - $result = array_search($word, self::$specials, true); - if ($result !== false) { - return $result; - } - foreach (static::$singulars as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Converts an underscored or CamelCase word into a English - * sentence. - * @param string $words - * @param bool $ucAll whether to set all words to uppercase - * @return string - */ - public static function titleize($words, $ucAll = false) - { - $words = static::humanize(static::underscore($words), $ucAll); - return $ucAll ? ucwords($words) : ucfirst($words); - } - - /** - * Returns given word as CamelCased - * Converts a word like "send_email" to "SendEmail". It - * will remove non alphanumeric character from the word, so - * "who's online" will be converted to "WhoSOnline" - * @see variablize - * @param string $word the word to CamelCase - * @return string - */ - public static function camelize($word) - { - return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); - } - - /** - * Converts a CamelCase name into space-separated words. - * For example, 'PostTag' will be converted to 'Post Tag'. - * @param string $name the string to be converted - * @param boolean $ucwords whether to capitalize the first letter in each word - * @return string the resulting words - */ - public static function camel2words($name, $ucwords = true) - { - $label = trim(strtolower(str_replace(array( - '-', - '_', - '.' - ), ' ', preg_replace('/(? ' ', - '/\\s+/' => $replacement, - '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', - str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' - ); - return preg_replace(array_keys($map), array_values($map), $string); - } - - /** - * Converts a table name to its class name. For example, converts "people" to "Person" - * @param string $tableName - * @return string - */ - public static function classify($tableName) - { - return static::camelize(static::singularize($tableName)); - } - - /** - * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... - * @param int $number the number to get its ordinal value - * @return string - */ - public static function ordinalize($number) - { - if (in_array(($number % 100), range(11, 13))) { - return $number . 'th'; - } - switch ($number % 10) { - case 1: return $number . 'st'; - case 2: return $number . 'nd'; - case 3: return $number . 'rd'; - default: return $number . 'th'; - } - } -} diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 424de1f..8544a61 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Json extends JsonBase +class Json extends AbstractJson { } diff --git a/framework/yii/helpers/JsonBase.php b/framework/yii/helpers/JsonBase.php deleted file mode 100644 index fa3fb01..0000000 --- a/framework/yii/helpers/JsonBase.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -class JsonBase -{ - /** - * Encodes the given value into a JSON string. - * The method enhances `json_encode()` by supporting JavaScript expressions. - * In particular, the method will not encode a JavaScript expression that is - * represented in terms of a [[JsExpression]] object. - * @param mixed $value the data to be encoded - * @param integer $options the encoding options. For more details please refer to - * [[http://www.php.net/manual/en/function.json-encode.php]] - * @return string the encoding result - */ - public static function encode($value, $options = 0) - { - $expressions = array(); - $value = static::processData($value, $expressions, uniqid()); - $json = json_encode($value, $options); - return empty($expressions) ? $json : strtr($json, $expressions); - } - - /** - * Decodes the given JSON string into a PHP data structure. - * @param string $json the JSON string to be decoded - * @param boolean $asArray whether to return objects in terms of associative arrays. - * @return mixed the PHP data - * @throws InvalidParamException if there is any decoding error - */ - public static function decode($json, $asArray = true) - { - if (is_array($json)) { - throw new InvalidParamException('Invalid JSON data.'); - } - $decode = json_decode((string)$json, $asArray); - switch (json_last_error()) { - case JSON_ERROR_NONE: - break; - case JSON_ERROR_DEPTH: - throw new InvalidParamException('The maximum stack depth has been exceeded.'); - case JSON_ERROR_CTRL_CHAR: - throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); - case JSON_ERROR_SYNTAX: - throw new InvalidParamException('Syntax error.'); - case JSON_ERROR_STATE_MISMATCH: - throw new InvalidParamException('Invalid or malformed JSON.'); - case JSON_ERROR_UTF8: - throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); - default: - throw new InvalidParamException('Unknown JSON decoding error.'); - } - - return $decode; - } - - /** - * Pre-processes the data before sending it to `json_encode()`. - * @param mixed $data the data to be processed - * @param array $expressions collection of JavaScript expressions - * @param string $expPrefix a prefix internally used to handle JS expressions - * @return mixed the processed data - */ - protected static function processData($data, &$expressions, $expPrefix) - { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $data[$key] = static::processData($value, $expressions, $expPrefix); - } - } - return $data; - } elseif (is_object($data)) { - if ($data instanceof JsExpression) { - $token = "!{[$expPrefix=" . count($expressions) . ']}!'; - $expressions['"' . $token . '"'] = $data->expression; - return $token; - } else { - $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions, $expPrefix); - } else { - $result[$key] = $value; - } - } - return $result; - } - } else { - return $data; - } - } -} diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 690df5d..89f8801 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class Markdown extends MarkdownBase +class Markdown extends AbstractMarkdown { } diff --git a/framework/yii/helpers/MarkdownBase.php b/framework/yii/helpers/MarkdownBase.php deleted file mode 100644 index 9db5b1e..0000000 --- a/framework/yii/helpers/MarkdownBase.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @since 2.0 - */ -class MarkdownBase -{ - /** - * @var MarkdownExtra - */ - protected static $markdown; - - /** - * Converts markdown into HTML - * - * @param string $content - * @param array $config - * @return string - */ - public static function process($content, $config = array()) - { - if (static::$markdown === null) { - static::$markdown = new MarkdownExtra(); - } - foreach ($config as $name => $value) { - static::$markdown->{$name} = $value; - } - return static::$markdown->transform($content); - } -} diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index d0ca2ed..e2c0314 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster * @since 2.0 */ -class Security extends SecurityBase +class Security extends AbstractSecurity { } diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php deleted file mode 100644 index 5b192de..0000000 --- a/framework/yii/helpers/SecurityBase.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @author Tom Worster - * @since 2.0 - */ -class SecurityBase -{ - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see decrypt() - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - srand(); - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); - mcrypt_generic_init($module, $key, $iv); - $encrypted = $iv . mcrypt_generic($module, $data); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; - } - - /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see encrypt() - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. - * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. - * @return string the data prefixed with the keyed hash - * @see validateData() - * @see getSecretKey() - */ - public static function hashData($data, $key, $algorithm = 'sha256') - { - return hash_hmac($algorithm, $data, $key) . $data; - } - - /** - * Validates if the given data is tampered. - * @param string $data the data to be validated. The data must be previously - * generated by [[hashData()]]. - * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. This must be the same - * as the value passed to [[hashData()]] when generating the hash for the data. - * @return string the real data with the hash stripped off. False if the data is tampered. - * @see hashData() - */ - public static function validateData($data, $key, $algorithm = 'sha256') - { - $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::strlen($data); - if ($n >= $hashSize) { - $hash = StringHelper::substr($data, 0, $hashSize); - $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); - return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory - * so that the same secret key can be returned in future requests. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @return string the secret key associated with the specified name - */ - public static function getSecretKey($name, $length = 32) - { - static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; - if ($keys === null) { - $keys = array(); - if (is_file($keyFile)) { - $keys = require($keyFile); - } - } - if (!isset($keys[$name])) { - $keys[$name] = static::generateRandomKey($length); - file_put_contents($keyFile, " 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 31) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } -} diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index ef75790..e367c59 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper extends StringHelperBase +class StringHelper extends AbstractStringHelper { } diff --git a/framework/yii/helpers/StringHelperBase.php b/framework/yii/helpers/StringHelperBase.php deleted file mode 100644 index cbb696e..0000000 --- a/framework/yii/helpers/StringHelperBase.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -class StringHelperBase -{ - /** - * Returns the number of bytes in the given string. - * This method ensures the string is treated as a byte array by using `mb_strlen()`. - * @param string $string the string being measured for length - * @return integer the number of bytes in the given string. - */ - public static function strlen($string) - { - return mb_strlen($string, '8bit'); - } - - /** - * Returns the portion of string specified by the start and length parameters. - * This method ensures the string is treated as a byte array by using `mb_substr()`. - * @param string $string the input string. Must be one character or longer. - * @param integer $start the starting position - * @param integer $length the desired portion length - * @return string the extracted part of string, or FALSE on failure or an empty string. - * @see http://www.php.net/manual/en/function.substr.php - */ - public static function substr($string, $start, $length) - { - return mb_substr($string, $start, $length, '8bit'); - } - - /** - * Returns the trailing name component of a path. - * This method is similar to the php function `basename()` except that it will - * treat both \ and / as directory separators, independent of the operating system. - * This method was mainly created to work on php namespaces. When working with real - * file paths, php's `basename()` should work fine for you. - * Note: this method is not aware of the actual filesystem, or path components such as "..". - * @param string $path A path string. - * @param string $suffix If the name component ends in suffix this will also be cut off. - * @return string the trailing name component of the given path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function basename($path, $suffix = '') - { - if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { - $path = mb_substr($path, 0, -$len); - } - $path = rtrim(str_replace('\\', '/', $path), '/\\'); - if (($pos = mb_strrpos($path, '/')) !== false) { - return mb_substr($path, $pos + 1); - } - return $path; - } - - /** - * Returns parent directory's path. - * This method is similar to `dirname()` except that it will treat - * both \ and / as directory separators, independent of the operating system. - * @param string $path A path string. - * @return string the parent directory's path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function dirname($path) - { - $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); - if ($pos !== false) { - return mb_substr($path, 0, $pos); - } else { - return $path; - } - } - - /** - * Compares two strings or string arrays, and return their differences. - * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. - * @param string|array $lines1 the first string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string|array $lines2 the second string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. - * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other - * formats, a string is returned. - * @throws InvalidParamException if the format is invalid. - */ - public static function diff($lines1, $lines2, $format = 'inline') - { - if (!is_array($lines1)) { - $lines1 = explode("\n", $lines1); - } - if (!is_array($lines2)) { - $lines2 = explode("\n", $lines2); - } - foreach ($lines1 as $i => $line) { - $lines1[$i] = rtrim($line, "\r\n"); - } - foreach ($lines2 as $i => $line) { - $lines2[$i] = rtrim($line, "\r\n"); - } - switch ($format) { - case 'inline': - $renderer = new \Diff_Renderer_Html_Inline(); - break; - case 'array': - $renderer = new \Diff_Renderer_Html_Array(); - break; - case 'side-by-side': - $renderer = new \Diff_Renderer_Html_SideBySide(); - break; - case 'context': - $renderer = new \Diff_Renderer_Text_Context(); - break; - case 'unified': - $renderer = new \Diff_Renderer_Text_Unified(); - break; - default: - throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); - } - $diff = new \Diff($lines1, $lines2); - return $diff->render($renderer); - } -} diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 50e543c..0aae16e 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class VarDumper extends VarDumperBase +class VarDumper extends AbstractVarDumper { } diff --git a/framework/yii/helpers/VarDumperBase.php b/framework/yii/helpers/VarDumperBase.php deleted file mode 100644 index c7da208..0000000 --- a/framework/yii/helpers/VarDumperBase.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\helpers; - -/** - * VarDumperBase provides concrete implementation for [[VarDumper]]. - * - * Do not use VarDumperBase. Use [[VarDumper]] instead. - * - * @author Qiang Xue - * @since 2.0 - */ -class VarDumperBase -{ - private static $_objects; - private static $_output; - private static $_depth; - - /** - * Displays a variable. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - */ - public static function dump($var, $depth = 10, $highlight = false) - { - echo static::dumpAsString($var, $depth, $highlight); - } - - /** - * Dumps a variable in terms of a string. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - * @return string the string representation of the variable - */ - public static function dumpAsString($var, $depth = 10, $highlight = false) - { - self::$_output = ''; - self::$_objects = array(); - self::$_depth = $depth; - self::dumpInternal($var, 0); - if ($highlight) { - $result = highlight_string("/', '', $result, 1); - } - return self::$_output; - } - - /** - * @param mixed $var variable to be dumped - * @param integer $level depth level - */ - private static function dumpInternal($var, $level) - { - switch (gettype($var)) { - case 'boolean': - self::$_output .= $var ? 'true' : 'false'; - break; - case 'integer': - self::$_output .= "$var"; - break; - case 'double': - self::$_output .= "$var"; - break; - case 'string': - self::$_output .= "'" . addslashes($var) . "'"; - break; - case 'resource': - self::$_output .= '{resource}'; - break; - case 'NULL': - self::$_output .= "null"; - break; - case 'unknown type': - self::$_output .= '{unknown}'; - break; - case 'array': - if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; - } elseif (empty($var)) { - self::$_output .= 'array()'; - } else { - $keys = array_keys($var); - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; - foreach ($keys as $key) { - self::$_output .= "\n" . $spaces . ' '; - self::dumpInternal($key, 0); - self::$_output .= ' => '; - self::dumpInternal($var[$key], $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - case 'object': - if (($id = array_search($var, self::$_objects, true)) !== false) { - self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; - } elseif (self::$_depth <= $level) { - self::$_output .= get_class($var) . '(...)'; - } else { - $id = array_push(self::$_objects, $var); - $className = get_class($var); - $members = (array)$var; - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); - self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; - self::dumpInternal($value, $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - } - } -} From b59812507acfac516b12dbc8364b67506406438f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Sep 2013 18:39:33 -0400 Subject: [PATCH 039/613] reverted doc change: the original one is correct. --- framework/yii/web/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index b353e7c..e6505fd 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -581,7 +581,7 @@ class Response extends \yii\base\Response * In a controller action you may use this method like this: * * ~~~ - * return Yii::$app->getResponse()->redirect($url)->send(); + * return Yii::$app->getResponse()->redirect($url); * ~~~ * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: From 558f499439fe9adfd9416dfde8944181ae35c3aa Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 02:48:54 +0400 Subject: [PATCH 040/613] Renamed YiiBase to AbstractYii --- build/build.xml | 2 +- build/controllers/ClassmapController.php | 2 +- build/controllers/PhpDocController.php | 2 +- docs/internals/autoloader.md | 2 +- framework/yii/AbstractYii.php | 574 +++++++++++++++++++++++++++++++ framework/yii/Yii.php | 6 +- framework/yii/YiiBase.php | 574 ------------------------------- framework/yii/log/Logger.php | 2 +- 8 files changed, 582 insertions(+), 582 deletions(-) create mode 100644 framework/yii/AbstractYii.php delete mode 100644 framework/yii/YiiBase.php diff --git a/build/build.xml b/build/build.xml index 9c18af0..85d5aa5 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and YiiBase.php + - sync : synchronize yiilite.php and AbstractYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 2a0483c..10eb134 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -45,7 +45,7 @@ class ClassmapController extends Controller 'only' => array('.php'), 'except' => array( 'Yii.php', - 'YiiBase.php', + 'AbstractYii.php', '/debug/', '/console/', '/test/', diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 5aac7e5..4fed3b2 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -56,7 +56,7 @@ class PhpDocController extends Controller }, 'only' => array('.php'), 'except' => array( - 'YiiBase.php', + 'AbstractYii.php', 'Yii.php', '/debug/views/', '/requirements/', diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index b7696d7..067773d 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- YiiBase::autoload \ No newline at end of file +- AbstractYii::autoload \ No newline at end of file diff --git a/framework/yii/AbstractYii.php b/framework/yii/AbstractYii.php new file mode 100644 index 0000000..c40aad9 --- /dev/null +++ b/framework/yii/AbstractYii.php @@ -0,0 +1,574 @@ + + * @since 2.0 + */ +abstract class AbstractYii +{ + /** + * @var array class map used by the Yii autoloading mechanism. + * The array keys are the class names (without leading backslashes), and the array values + * are the corresponding class file paths (or path aliases). This property mainly affects + * how [[autoload()]] works. + * @see import + * @see autoload + */ + public static $classMap = array(); + /** + * @var \yii\console\Application|\yii\web\Application the application instance + */ + public static $app; + /** + * @var array registered path aliases + * @see getAlias + * @see setAlias + */ + public static $aliases = array('@yii' => __DIR__); + /** + * @var array initial property values that will be applied to objects newly created via [[createObject]]. + * The array keys are class names without leading backslashes "\", and the array values are the corresponding + * name-value pairs for initializing the created class instances. For example, + * + * ~~~ + * array( + * 'Bar' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * 'mycompany\foo\Car' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * ) + * ~~~ + * + * @see createObject + */ + public static $objectConfig = array(); + + + /** + * @return string the version of Yii framework + */ + public static function getVersion() + { + return '2.0-dev'; + } + + /** + * Imports a set of namespaces. + * + * By importing a namespace, the method will create an alias for the directory corresponding + * to the namespace. For example, if "foo\bar" is a namespace associated with the directory + * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. + * + * This method is typically invoked in the bootstrap file to import the namespaces of + * the installed extensions. By default, Composer, when installing new extensions, will + * generate such a mapping file which can be loaded and passed to this method. + * + * @param array $namespaces the namespaces to be imported. The keys are the namespaces, + * and the values are the corresponding directories. + */ + public static function importNamespaces($namespaces) + { + foreach ($namespaces as $name => $path) { + if ($name !== '') { + $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); + if (is_array($path)) { + $path = reset($path); + } + static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); + } + } + } + + /** + * Translates a path alias into an actual path. + * + * The translation is done according to the following procedure: + * + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. + * + * @param string $alias the alias to be translated. + * @param boolean $throwException whether to throw an exception if the given alias is invalid. + * If this is false and an invalid alias is given, false will be returned by this method. + * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. + * @throws InvalidParamException if the alias is invalid while $throwException is true. + * @see setAlias + */ + public static function getAlias($alias, $throwException = true) + { + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } + } + } + } + + if ($throwException) { + throw new InvalidParamException("Invalid path alias: $alias"); + } else { + return false; + } + } + + /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $root; + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + return false; + } + + /** + * Registers a path alias. + * + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. + * + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. + * + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters + * will be trimmed. This can be + * + * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) + * - a URL (e.g. `http://www.yiiframework.com`) + * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the + * actual path first by calling [[getAlias()]]. + * + * @throws InvalidParamException if $path is an invalid alias. + * @see getAlias + */ + public static function setAlias($alias, $path) + { + if (strncmp($alias, '@', 1)) { + $alias = '@' . $alias; + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array($alias => $path); + } + } elseif (is_string(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array( + $alias => $path, + $root => self::$aliases[$root], + ); + } + } else { + self::$aliases[$root][$alias] = $path; + krsort(self::$aliases[$root]); + } + } elseif (isset(self::$aliases[$root])) { + if (is_array(self::$aliases[$root])) { + unset(self::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(self::$aliases[$root]); + } + } + } + + /** + * Class autoload loader. + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: + * + * 1. Search in [[classMap]]; + * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt + * to include the file associated with the corresponding path alias + * (e.g. `@yii/base/Component.php`); + * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), + * it will attempt to include the file associated with the corresponding path alias + * (e.g. `@PHPUnit/Framework/TestCase.php`); + * + * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). + * Therefor a path alias has to be defined for each top-level namespace. + * + * @param string $className the fully qualified class name without a leading backslash "\" + * @throws UnknownClassException if the class does not exist in the class file + */ + public static function autoload($className) + { + if (isset(self::$classMap[$className])) { + $classFile = self::$classMap[$className]; + if ($classFile[0] === '@') { + $classFile = static::getAlias($classFile); + } + } else { + // follow PSR-0 to determine the class file + if (($pos = strrpos($className, '\\')) !== false) { + // namespaced class, e.g. yii\base\Component + $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) + . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; + } else { + $path = str_replace('_', '/', $className) . '.php'; + } + + // try loading via path alias + if (strpos($path, '/') === false) { + return; + } else { + $classFile = static::getAlias('@' . $path, false); + if ($classFile === false || !is_file($classFile)) { + return; + } + } + } + + include($classFile); + + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && + (!function_exists('trait_exists') || !trait_exists($className, false))) { + throw new UnknownClassException("Unable to find '$className' in file: $classFile"); + } + } + + /** + * Creates a new object using the given configuration. + * + * The configuration can be either a string or an array. + * If a string, it is treated as the *object class*; if an array, + * it must contain a `class` element specifying the *object class*, and + * the rest of the name-value pairs in the array will be used to initialize + * the corresponding object properties. + * + * The object type can be either a class name or the [[getAlias()|alias]] of + * the class. For example, + * + * - `app\components\GoogleMap`: fully-qualified namespaced class. + * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. + * + * Below are some usage examples: + * + * ~~~ + * $object = \Yii::createObject('@app/components/GoogleMap'); + * $object = \Yii::createObject(array( + * 'class' => '\app\components\GoogleMap', + * 'apiKey' => 'xyz', + * )); + * ~~~ + * + * This method can be used to create any object as long as the object's constructor is + * defined like the following: + * + * ~~~ + * public function __construct(..., $config = array()) { + * } + * ~~~ + * + * The method will pass the given configuration as the last parameter of the constructor, + * and any additional parameters to this method will be passed as the rest of the constructor parameters. + * + * @param string|array $config the configuration. It can be either a string representing the class name + * or an array representing the object configuration. + * @return mixed the created object + * @throws InvalidConfigException if the configuration is invalid. + */ + public static function createObject($config) + { + static $reflections = array(); + + if (is_string($config)) { + $class = $config; + $config = array(); + } elseif (isset($config['class'])) { + $class = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + + $class = ltrim($class, '\\'); + + if (isset(self::$objectConfig[$class])) { + $config = array_merge(self::$objectConfig[$class], $config); + } + + if (($n = func_num_args()) > 1) { + /** @var $reflection \ReflectionClass */ + if (isset($reflections[$class])) { + $reflection = $reflections[$class]; + } else { + $reflection = $reflections[$class] = new \ReflectionClass($class); + } + $args = func_get_args(); + array_shift($args); // remove $config + if (!empty($config)) { + $args[] = $config; + } + return $reflection->newInstanceArgs($args); + } else { + return empty($config) ? new $class : new $class($config); + } + } + + /** + * Logs a trace message. + * Trace messages are logged mainly for development purpose to see + * the execution work flow of some code. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function trace($message, $category = 'application') + { + if (YII_DEBUG) { + self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); + } + } + + /** + * Logs an error message. + * An error message is typically logged when an unrecoverable error occurs + * during the execution of an application. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function error($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); + } + + /** + * Logs a warning message. + * A warning message is typically logged when an error occurs while the execution + * can still continue. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function warning($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); + } + + /** + * Logs an informative message. + * An informative message is typically logged by an application to keep record of + * something important (e.g. an administrator logs in). + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function info($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); + } + + /** + * Marks the beginning of a code block for profiling. + * This has to be matched with a call to [[endProfile]] with the same category name. + * The begin- and end- calls must also be properly nested. For example, + * + * ~~~ + * \Yii::beginProfile('block1'); + * // some code to be profiled + * \Yii::beginProfile('block2'); + * // some other code to be profiled + * \Yii::endProfile('block2'); + * \Yii::endProfile('block1'); + * ~~~ + * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile + */ + public static function beginProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to [[beginProfile]] with the same category name. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile + */ + public static function endProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); + } + + /** + * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. + * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information + */ + public static function powered() + { + return 'Powered by Yii Framework'; + } + + /** + * Translates a message to the specified language. + * + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. + * + * In case when a translated message has different plural forms (separated by "|"), this method + * will also attempt to choose an appropriate one according to a given numeric value which is + * specified as the first parameter (indexed by 0) in `$params`. + * + * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first + * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" + * will be replaced with the given number. + * + * For more details on how plural rules are applied, please refer to: + * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current + * [[\yii\base\Application::language|application language]] will be used. + * @return string the translated message. + */ + public static function t($category, $message, $params = array(), $language = null) + { + if (self::$app !== null) { + return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + } else { + return is_array($params) ? strtr($message, $params) : $message; + } + } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } +} diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index bde15cc..0406463 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -12,13 +12,13 @@ require(__DIR__ . '/YiiBase.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[YiiBase]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[YiiBase]]. + * It extends from [[AbstractYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[AbstractYii]]. * * @author Qiang Xue * @since 2.0 */ -class Yii extends \yii\YiiBase +class Yii extends \yii\AbstractYii { } diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php deleted file mode 100644 index f04903d..0000000 --- a/framework/yii/YiiBase.php +++ /dev/null @@ -1,574 +0,0 @@ - - * @since 2.0 - */ -class YiiBase -{ - /** - * @var array class map used by the Yii autoloading mechanism. - * The array keys are the class names (without leading backslashes), and the array values - * are the corresponding class file paths (or path aliases). This property mainly affects - * how [[autoload()]] works. - * @see import - * @see autoload - */ - public static $classMap = array(); - /** - * @var \yii\console\Application|\yii\web\Application the application instance - */ - public static $app; - /** - * @var array registered path aliases - * @see getAlias - * @see setAlias - */ - public static $aliases = array('@yii' => __DIR__); - /** - * @var array initial property values that will be applied to objects newly created via [[createObject]]. - * The array keys are class names without leading backslashes "\", and the array values are the corresponding - * name-value pairs for initializing the created class instances. For example, - * - * ~~~ - * array( - * 'Bar' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * 'mycompany\foo\Car' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * ) - * ~~~ - * - * @see createObject - */ - public static $objectConfig = array(); - - - /** - * @return string the version of Yii framework - */ - public static function getVersion() - { - return '2.0-dev'; - } - - /** - * Imports a set of namespaces. - * - * By importing a namespace, the method will create an alias for the directory corresponding - * to the namespace. For example, if "foo\bar" is a namespace associated with the directory - * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. - * - * This method is typically invoked in the bootstrap file to import the namespaces of - * the installed extensions. By default, Composer, when installing new extensions, will - * generate such a mapping file which can be loaded and passed to this method. - * - * @param array $namespaces the namespaces to be imported. The keys are the namespaces, - * and the values are the corresponding directories. - */ - public static function importNamespaces($namespaces) - { - foreach ($namespaces as $name => $path) { - if ($name !== '') { - $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); - if (is_array($path)) { - $path = reset($path); - } - static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); - } - } - } - - /** - * Translates a path alias into an actual path. - * - * The translation is done according to the following procedure: - * - * 1. If the given alias does not start with '@', it is returned back without change; - * 2. Otherwise, look for the longest registered alias that matches the beginning part - * of the given alias. If it exists, replace the matching part of the given alias with - * the corresponding registered path. - * 3. Throw an exception or return false, depending on the `$throwException` parameter. - * - * For example, by default '@yii' is registered as the alias to the Yii framework directory, - * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. - * - * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' - * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. - * This is because the longest alias takes precedence. - * - * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced - * instead of '@foo/bar', because '/' serves as the boundary character. - * - * Note, this method does not check if the returned path exists or not. - * - * @param string $alias the alias to be translated. - * @param boolean $throwException whether to throw an exception if the given alias is invalid. - * If this is false and an invalid alias is given, false will be returned by this method. - * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. - * @throws InvalidParamException if the alias is invalid while $throwException is true. - * @see setAlias - */ - public static function getAlias($alias, $throwException = true) - { - if (strncmp($alias, '@', 1)) { - // not an alias - return $alias; - } - - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $path . substr($alias, strlen($name)); - } - } - } - } - - if ($throwException) { - throw new InvalidParamException("Invalid path alias: $alias"); - } else { - return false; - } - } - - /** - * Returns the root alias part of a given alias. - * A root alias is an alias that has been registered via [[setAlias()]] previously. - * If a given alias matches multiple root aliases, the longest one will be returned. - * @param string $alias the alias - * @return string|boolean the root alias, or false if no root alias is found - */ - public static function getRootAlias($alias) - { - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $root; - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $name; - } - } - } - } - return false; - } - - /** - * Registers a path alias. - * - * A path alias is a short name representing a long path (a file path, a URL, etc.) - * For example, we use '@yii' as the alias of the path to the Yii framework directory. - * - * A path alias must start with the character '@' so that it can be easily differentiated - * from non-alias paths. - * - * Note that this method does not check if the given path exists or not. All it does is - * to associate the alias with the path. - * - * Any trailing '/' and '\' characters in the given path will be trimmed. - * - * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. - * It may contain the forward slash '/' which serves as boundary character when performing - * alias translation by [[getAlias()]]. - * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters - * will be trimmed. This can be - * - * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) - * - a URL (e.g. `http://www.yiiframework.com`) - * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the - * actual path first by calling [[getAlias()]]. - * - * @throws InvalidParamException if $path is an invalid alias. - * @see getAlias - */ - public static function setAlias($alias, $path) - { - if (strncmp($alias, '@', 1)) { - $alias = '@' . $alias; - } - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - if ($path !== null) { - $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); - if (!isset(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array($alias => $path); - } - } elseif (is_string(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array( - $alias => $path, - $root => self::$aliases[$root], - ); - } - } else { - self::$aliases[$root][$alias] = $path; - krsort(self::$aliases[$root]); - } - } elseif (isset(self::$aliases[$root])) { - if (is_array(self::$aliases[$root])) { - unset(self::$aliases[$root][$alias]); - } elseif ($pos === false) { - unset(self::$aliases[$root]); - } - } - } - - /** - * Class autoload loader. - * This method is invoked automatically when PHP sees an unknown class. - * The method will attempt to include the class file according to the following procedure: - * - * 1. Search in [[classMap]]; - * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt - * to include the file associated with the corresponding path alias - * (e.g. `@yii/base/Component.php`); - * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), - * it will attempt to include the file associated with the corresponding path alias - * (e.g. `@PHPUnit/Framework/TestCase.php`); - * - * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). - * Therefor a path alias has to be defined for each top-level namespace. - * - * @param string $className the fully qualified class name without a leading backslash "\" - * @throws UnknownClassException if the class does not exist in the class file - */ - public static function autoload($className) - { - if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; - if ($classFile[0] === '@') { - $classFile = static::getAlias($classFile); - } - } else { - // follow PSR-0 to determine the class file - if (($pos = strrpos($className, '\\')) !== false) { - // namespaced class, e.g. yii\base\Component - $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) - . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; - } else { - $path = str_replace('_', '/', $className) . '.php'; - } - - // try loading via path alias - if (strpos($path, '/') === false) { - return; - } else { - $classFile = static::getAlias('@' . $path, false); - if ($classFile === false || !is_file($classFile)) { - return; - } - } - } - - include($classFile); - - if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && - (!function_exists('trait_exists') || !trait_exists($className, false))) { - throw new UnknownClassException("Unable to find '$className' in file: $classFile"); - } - } - - /** - * Creates a new object using the given configuration. - * - * The configuration can be either a string or an array. - * If a string, it is treated as the *object class*; if an array, - * it must contain a `class` element specifying the *object class*, and - * the rest of the name-value pairs in the array will be used to initialize - * the corresponding object properties. - * - * The object type can be either a class name or the [[getAlias()|alias]] of - * the class. For example, - * - * - `app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. - * - * Below are some usage examples: - * - * ~~~ - * $object = \Yii::createObject('@app/components/GoogleMap'); - * $object = \Yii::createObject(array( - * 'class' => '\app\components\GoogleMap', - * 'apiKey' => 'xyz', - * )); - * ~~~ - * - * This method can be used to create any object as long as the object's constructor is - * defined like the following: - * - * ~~~ - * public function __construct(..., $config = array()) { - * } - * ~~~ - * - * The method will pass the given configuration as the last parameter of the constructor, - * and any additional parameters to this method will be passed as the rest of the constructor parameters. - * - * @param string|array $config the configuration. It can be either a string representing the class name - * or an array representing the object configuration. - * @return mixed the created object - * @throws InvalidConfigException if the configuration is invalid. - */ - public static function createObject($config) - { - static $reflections = array(); - - if (is_string($config)) { - $class = $config; - $config = array(); - } elseif (isset($config['class'])) { - $class = $config['class']; - unset($config['class']); - } else { - throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); - } - - $class = ltrim($class, '\\'); - - if (isset(self::$objectConfig[$class])) { - $config = array_merge(self::$objectConfig[$class], $config); - } - - if (($n = func_num_args()) > 1) { - /** @var $reflection \ReflectionClass */ - if (isset($reflections[$class])) { - $reflection = $reflections[$class]; - } else { - $reflection = $reflections[$class] = new \ReflectionClass($class); - } - $args = func_get_args(); - array_shift($args); // remove $config - if (!empty($config)) { - $args[] = $config; - } - return $reflection->newInstanceArgs($args); - } else { - return empty($config) ? new $class : new $class($config); - } - } - - /** - * Logs a trace message. - * Trace messages are logged mainly for development purpose to see - * the execution work flow of some code. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function trace($message, $category = 'application') - { - if (YII_DEBUG) { - self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); - } - } - - /** - * Logs an error message. - * An error message is typically logged when an unrecoverable error occurs - * during the execution of an application. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function error($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); - } - - /** - * Logs a warning message. - * A warning message is typically logged when an error occurs while the execution - * can still continue. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function warning($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); - } - - /** - * Logs an informative message. - * An informative message is typically logged by an application to keep record of - * something important (e.g. an administrator logs in). - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function info($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); - } - - /** - * Marks the beginning of a code block for profiling. - * This has to be matched with a call to [[endProfile]] with the same category name. - * The begin- and end- calls must also be properly nested. For example, - * - * ~~~ - * \Yii::beginProfile('block1'); - * // some code to be profiled - * \Yii::beginProfile('block2'); - * // some other code to be profiled - * \Yii::endProfile('block2'); - * \Yii::endProfile('block1'); - * ~~~ - * @param string $token token for the code block - * @param string $category the category of this log message - * @see endProfile - */ - public static function beginProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); - } - - /** - * Marks the end of a code block for profiling. - * This has to be matched with a previous call to [[beginProfile]] with the same category name. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see beginProfile - */ - public static function endProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); - } - - /** - * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. - * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information - */ - public static function powered() - { - return 'Powered by Yii Framework'; - } - - /** - * Translates a message to the specified language. - * - * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. - * - * The translation will be conducted according to the message category and the target language will be used. - * - * In case when a translated message has different plural forms (separated by "|"), this method - * will also attempt to choose an appropriate one according to a given numeric value which is - * specified as the first parameter (indexed by 0) in `$params`. - * - * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first - * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" - * will be replaced with the given number. - * - * For more details on how plural rules are applied, please refer to: - * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] - * - * @param string $category the message category. - * @param string $message the message to be translated. - * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current - * [[\yii\base\Application::language|application language]] will be used. - * @return string the translated message. - */ - public static function t($category, $message, $params = array(), $language = null) - { - if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); - } else { - return is_array($params) ? strtr($message, $params) : $message; - } - } - - /** - * Configures an object with the initial property values. - * @param object $object the object to be configured - * @param array $properties the property initial values given in terms of name-value pairs. - */ - public static function configure($object, $properties) - { - foreach ($properties as $name => $value) { - $object->$name = $value; - } - } - - /** - * Returns the public member variables of an object. - * This method is provided such that we can get the public member variables of an object. - * It is different from "get_object_vars()" because the latter will return private - * and protected variables if it is called within the object itself. - * @param object $object the object to be handled - * @return array the public member variables of the object - */ - public static function getObjectVars($object) - { - return get_object_vars($object); - } -} diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 54f3a49..76354d4 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -220,7 +220,7 @@ class Logger extends Component * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[YiiBase]] class file. + * of [[AbstractYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() From b873f9f242b3b927fcc9fe9b34ab1ec5e48f69d0 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 16:25:43 +0400 Subject: [PATCH 041/613] Fixes #915: helper classes renamed again --- build/build.xml | 2 +- build/controllers/ClassmapController.php | 2 +- build/controllers/PhpDocController.php | 2 +- docs/internals/autoloader.md | 2 +- framework/yii/AbstractYii.php | 574 --------- framework/yii/BaseYii.php | 574 +++++++++ framework/yii/Yii.php | 8 +- framework/yii/classes.php | 22 +- framework/yii/helpers/AbstractArrayHelper.php | 451 ------- framework/yii/helpers/AbstractConsole.php | 835 ------------- framework/yii/helpers/AbstractFileHelper.php | 329 ----- framework/yii/helpers/AbstractHtml.php | 1599 ------------------------ framework/yii/helpers/AbstractHtmlPurifier.php | 34 - framework/yii/helpers/AbstractInflector.php | 480 ------- framework/yii/helpers/AbstractJson.php | 112 -- framework/yii/helpers/AbstractMarkdown.php | 44 - framework/yii/helpers/AbstractSecurity.php | 285 ----- framework/yii/helpers/AbstractStringHelper.php | 138 -- framework/yii/helpers/AbstractVarDumper.php | 127 -- framework/yii/helpers/ArrayHelper.php | 2 +- framework/yii/helpers/BaseArrayHelper.php | 451 +++++++ framework/yii/helpers/BaseConsole.php | 835 +++++++++++++ framework/yii/helpers/BaseFileHelper.php | 329 +++++ framework/yii/helpers/BaseHtml.php | 1599 ++++++++++++++++++++++++ framework/yii/helpers/BaseHtmlPurifier.php | 34 + framework/yii/helpers/BaseInflector.php | 480 +++++++ framework/yii/helpers/BaseJson.php | 112 ++ framework/yii/helpers/BaseMarkdown.php | 44 + framework/yii/helpers/BaseSecurity.php | 285 +++++ framework/yii/helpers/BaseStringHelper.php | 138 ++ framework/yii/helpers/BaseVarDumper.php | 127 ++ framework/yii/helpers/Console.php | 2 +- framework/yii/helpers/FileHelper.php | 2 +- framework/yii/helpers/Html.php | 2 +- framework/yii/helpers/HtmlPurifier.php | 2 +- framework/yii/helpers/Inflector.php | 2 +- framework/yii/helpers/Json.php | 2 +- framework/yii/helpers/Markdown.php | 2 +- framework/yii/helpers/Security.php | 2 +- framework/yii/helpers/StringHelper.php | 2 +- framework/yii/helpers/VarDumper.php | 2 +- framework/yii/log/Logger.php | 2 +- 42 files changed, 5039 insertions(+), 5039 deletions(-) delete mode 100644 framework/yii/AbstractYii.php create mode 100644 framework/yii/BaseYii.php delete mode 100644 framework/yii/helpers/AbstractArrayHelper.php delete mode 100644 framework/yii/helpers/AbstractConsole.php delete mode 100644 framework/yii/helpers/AbstractFileHelper.php delete mode 100644 framework/yii/helpers/AbstractHtml.php delete mode 100644 framework/yii/helpers/AbstractHtmlPurifier.php delete mode 100644 framework/yii/helpers/AbstractInflector.php delete mode 100644 framework/yii/helpers/AbstractJson.php delete mode 100644 framework/yii/helpers/AbstractMarkdown.php delete mode 100644 framework/yii/helpers/AbstractSecurity.php delete mode 100644 framework/yii/helpers/AbstractStringHelper.php delete mode 100644 framework/yii/helpers/AbstractVarDumper.php create mode 100644 framework/yii/helpers/BaseArrayHelper.php create mode 100644 framework/yii/helpers/BaseConsole.php create mode 100644 framework/yii/helpers/BaseFileHelper.php create mode 100644 framework/yii/helpers/BaseHtml.php create mode 100644 framework/yii/helpers/BaseHtmlPurifier.php create mode 100644 framework/yii/helpers/BaseInflector.php create mode 100644 framework/yii/helpers/BaseJson.php create mode 100644 framework/yii/helpers/BaseMarkdown.php create mode 100644 framework/yii/helpers/BaseSecurity.php create mode 100644 framework/yii/helpers/BaseStringHelper.php create mode 100644 framework/yii/helpers/BaseVarDumper.php diff --git a/build/build.xml b/build/build.xml index 85d5aa5..b0975dc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and AbstractYii.php + - sync : synchronize yiilite.php and BaseYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 10eb134..6a5ac8c 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -45,7 +45,7 @@ class ClassmapController extends Controller 'only' => array('.php'), 'except' => array( 'Yii.php', - 'AbstractYii.php', + 'BaseYii.php', '/debug/', '/console/', '/test/', diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 4fed3b2..cb574ff 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -56,7 +56,7 @@ class PhpDocController extends Controller }, 'only' => array('.php'), 'except' => array( - 'AbstractYii.php', + 'BaseYii.php', 'Yii.php', '/debug/views/', '/requirements/', diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index 067773d..76a545b 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- AbstractYii::autoload \ No newline at end of file +- BaseYii::autoload \ No newline at end of file diff --git a/framework/yii/AbstractYii.php b/framework/yii/AbstractYii.php deleted file mode 100644 index c40aad9..0000000 --- a/framework/yii/AbstractYii.php +++ /dev/null @@ -1,574 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractYii -{ - /** - * @var array class map used by the Yii autoloading mechanism. - * The array keys are the class names (without leading backslashes), and the array values - * are the corresponding class file paths (or path aliases). This property mainly affects - * how [[autoload()]] works. - * @see import - * @see autoload - */ - public static $classMap = array(); - /** - * @var \yii\console\Application|\yii\web\Application the application instance - */ - public static $app; - /** - * @var array registered path aliases - * @see getAlias - * @see setAlias - */ - public static $aliases = array('@yii' => __DIR__); - /** - * @var array initial property values that will be applied to objects newly created via [[createObject]]. - * The array keys are class names without leading backslashes "\", and the array values are the corresponding - * name-value pairs for initializing the created class instances. For example, - * - * ~~~ - * array( - * 'Bar' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * 'mycompany\foo\Car' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * ) - * ~~~ - * - * @see createObject - */ - public static $objectConfig = array(); - - - /** - * @return string the version of Yii framework - */ - public static function getVersion() - { - return '2.0-dev'; - } - - /** - * Imports a set of namespaces. - * - * By importing a namespace, the method will create an alias for the directory corresponding - * to the namespace. For example, if "foo\bar" is a namespace associated with the directory - * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. - * - * This method is typically invoked in the bootstrap file to import the namespaces of - * the installed extensions. By default, Composer, when installing new extensions, will - * generate such a mapping file which can be loaded and passed to this method. - * - * @param array $namespaces the namespaces to be imported. The keys are the namespaces, - * and the values are the corresponding directories. - */ - public static function importNamespaces($namespaces) - { - foreach ($namespaces as $name => $path) { - if ($name !== '') { - $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); - if (is_array($path)) { - $path = reset($path); - } - static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); - } - } - } - - /** - * Translates a path alias into an actual path. - * - * The translation is done according to the following procedure: - * - * 1. If the given alias does not start with '@', it is returned back without change; - * 2. Otherwise, look for the longest registered alias that matches the beginning part - * of the given alias. If it exists, replace the matching part of the given alias with - * the corresponding registered path. - * 3. Throw an exception or return false, depending on the `$throwException` parameter. - * - * For example, by default '@yii' is registered as the alias to the Yii framework directory, - * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. - * - * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' - * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. - * This is because the longest alias takes precedence. - * - * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced - * instead of '@foo/bar', because '/' serves as the boundary character. - * - * Note, this method does not check if the returned path exists or not. - * - * @param string $alias the alias to be translated. - * @param boolean $throwException whether to throw an exception if the given alias is invalid. - * If this is false and an invalid alias is given, false will be returned by this method. - * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. - * @throws InvalidParamException if the alias is invalid while $throwException is true. - * @see setAlias - */ - public static function getAlias($alias, $throwException = true) - { - if (strncmp($alias, '@', 1)) { - // not an alias - return $alias; - } - - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $path . substr($alias, strlen($name)); - } - } - } - } - - if ($throwException) { - throw new InvalidParamException("Invalid path alias: $alias"); - } else { - return false; - } - } - - /** - * Returns the root alias part of a given alias. - * A root alias is an alias that has been registered via [[setAlias()]] previously. - * If a given alias matches multiple root aliases, the longest one will be returned. - * @param string $alias the alias - * @return string|boolean the root alias, or false if no root alias is found - */ - public static function getRootAlias($alias) - { - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $root; - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $name; - } - } - } - } - return false; - } - - /** - * Registers a path alias. - * - * A path alias is a short name representing a long path (a file path, a URL, etc.) - * For example, we use '@yii' as the alias of the path to the Yii framework directory. - * - * A path alias must start with the character '@' so that it can be easily differentiated - * from non-alias paths. - * - * Note that this method does not check if the given path exists or not. All it does is - * to associate the alias with the path. - * - * Any trailing '/' and '\' characters in the given path will be trimmed. - * - * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. - * It may contain the forward slash '/' which serves as boundary character when performing - * alias translation by [[getAlias()]]. - * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters - * will be trimmed. This can be - * - * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) - * - a URL (e.g. `http://www.yiiframework.com`) - * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the - * actual path first by calling [[getAlias()]]. - * - * @throws InvalidParamException if $path is an invalid alias. - * @see getAlias - */ - public static function setAlias($alias, $path) - { - if (strncmp($alias, '@', 1)) { - $alias = '@' . $alias; - } - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - if ($path !== null) { - $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); - if (!isset(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array($alias => $path); - } - } elseif (is_string(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array( - $alias => $path, - $root => self::$aliases[$root], - ); - } - } else { - self::$aliases[$root][$alias] = $path; - krsort(self::$aliases[$root]); - } - } elseif (isset(self::$aliases[$root])) { - if (is_array(self::$aliases[$root])) { - unset(self::$aliases[$root][$alias]); - } elseif ($pos === false) { - unset(self::$aliases[$root]); - } - } - } - - /** - * Class autoload loader. - * This method is invoked automatically when PHP sees an unknown class. - * The method will attempt to include the class file according to the following procedure: - * - * 1. Search in [[classMap]]; - * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt - * to include the file associated with the corresponding path alias - * (e.g. `@yii/base/Component.php`); - * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), - * it will attempt to include the file associated with the corresponding path alias - * (e.g. `@PHPUnit/Framework/TestCase.php`); - * - * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). - * Therefor a path alias has to be defined for each top-level namespace. - * - * @param string $className the fully qualified class name without a leading backslash "\" - * @throws UnknownClassException if the class does not exist in the class file - */ - public static function autoload($className) - { - if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; - if ($classFile[0] === '@') { - $classFile = static::getAlias($classFile); - } - } else { - // follow PSR-0 to determine the class file - if (($pos = strrpos($className, '\\')) !== false) { - // namespaced class, e.g. yii\base\Component - $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) - . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; - } else { - $path = str_replace('_', '/', $className) . '.php'; - } - - // try loading via path alias - if (strpos($path, '/') === false) { - return; - } else { - $classFile = static::getAlias('@' . $path, false); - if ($classFile === false || !is_file($classFile)) { - return; - } - } - } - - include($classFile); - - if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && - (!function_exists('trait_exists') || !trait_exists($className, false))) { - throw new UnknownClassException("Unable to find '$className' in file: $classFile"); - } - } - - /** - * Creates a new object using the given configuration. - * - * The configuration can be either a string or an array. - * If a string, it is treated as the *object class*; if an array, - * it must contain a `class` element specifying the *object class*, and - * the rest of the name-value pairs in the array will be used to initialize - * the corresponding object properties. - * - * The object type can be either a class name or the [[getAlias()|alias]] of - * the class. For example, - * - * - `app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. - * - * Below are some usage examples: - * - * ~~~ - * $object = \Yii::createObject('@app/components/GoogleMap'); - * $object = \Yii::createObject(array( - * 'class' => '\app\components\GoogleMap', - * 'apiKey' => 'xyz', - * )); - * ~~~ - * - * This method can be used to create any object as long as the object's constructor is - * defined like the following: - * - * ~~~ - * public function __construct(..., $config = array()) { - * } - * ~~~ - * - * The method will pass the given configuration as the last parameter of the constructor, - * and any additional parameters to this method will be passed as the rest of the constructor parameters. - * - * @param string|array $config the configuration. It can be either a string representing the class name - * or an array representing the object configuration. - * @return mixed the created object - * @throws InvalidConfigException if the configuration is invalid. - */ - public static function createObject($config) - { - static $reflections = array(); - - if (is_string($config)) { - $class = $config; - $config = array(); - } elseif (isset($config['class'])) { - $class = $config['class']; - unset($config['class']); - } else { - throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); - } - - $class = ltrim($class, '\\'); - - if (isset(self::$objectConfig[$class])) { - $config = array_merge(self::$objectConfig[$class], $config); - } - - if (($n = func_num_args()) > 1) { - /** @var $reflection \ReflectionClass */ - if (isset($reflections[$class])) { - $reflection = $reflections[$class]; - } else { - $reflection = $reflections[$class] = new \ReflectionClass($class); - } - $args = func_get_args(); - array_shift($args); // remove $config - if (!empty($config)) { - $args[] = $config; - } - return $reflection->newInstanceArgs($args); - } else { - return empty($config) ? new $class : new $class($config); - } - } - - /** - * Logs a trace message. - * Trace messages are logged mainly for development purpose to see - * the execution work flow of some code. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function trace($message, $category = 'application') - { - if (YII_DEBUG) { - self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); - } - } - - /** - * Logs an error message. - * An error message is typically logged when an unrecoverable error occurs - * during the execution of an application. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function error($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); - } - - /** - * Logs a warning message. - * A warning message is typically logged when an error occurs while the execution - * can still continue. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function warning($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); - } - - /** - * Logs an informative message. - * An informative message is typically logged by an application to keep record of - * something important (e.g. an administrator logs in). - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function info($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); - } - - /** - * Marks the beginning of a code block for profiling. - * This has to be matched with a call to [[endProfile]] with the same category name. - * The begin- and end- calls must also be properly nested. For example, - * - * ~~~ - * \Yii::beginProfile('block1'); - * // some code to be profiled - * \Yii::beginProfile('block2'); - * // some other code to be profiled - * \Yii::endProfile('block2'); - * \Yii::endProfile('block1'); - * ~~~ - * @param string $token token for the code block - * @param string $category the category of this log message - * @see endProfile - */ - public static function beginProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); - } - - /** - * Marks the end of a code block for profiling. - * This has to be matched with a previous call to [[beginProfile]] with the same category name. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see beginProfile - */ - public static function endProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); - } - - /** - * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. - * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information - */ - public static function powered() - { - return 'Powered by Yii Framework'; - } - - /** - * Translates a message to the specified language. - * - * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. - * - * The translation will be conducted according to the message category and the target language will be used. - * - * In case when a translated message has different plural forms (separated by "|"), this method - * will also attempt to choose an appropriate one according to a given numeric value which is - * specified as the first parameter (indexed by 0) in `$params`. - * - * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first - * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" - * will be replaced with the given number. - * - * For more details on how plural rules are applied, please refer to: - * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] - * - * @param string $category the message category. - * @param string $message the message to be translated. - * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current - * [[\yii\base\Application::language|application language]] will be used. - * @return string the translated message. - */ - public static function t($category, $message, $params = array(), $language = null) - { - if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); - } else { - return is_array($params) ? strtr($message, $params) : $message; - } - } - - /** - * Configures an object with the initial property values. - * @param object $object the object to be configured - * @param array $properties the property initial values given in terms of name-value pairs. - */ - public static function configure($object, $properties) - { - foreach ($properties as $name => $value) { - $object->$name = $value; - } - } - - /** - * Returns the public member variables of an object. - * This method is provided such that we can get the public member variables of an object. - * It is different from "get_object_vars()" because the latter will return private - * and protected variables if it is called within the object itself. - * @param object $object the object to be handled - * @return array the public member variables of the object - */ - public static function getObjectVars($object) - { - return get_object_vars($object); - } -} diff --git a/framework/yii/BaseYii.php b/framework/yii/BaseYii.php new file mode 100644 index 0000000..b586160 --- /dev/null +++ b/framework/yii/BaseYii.php @@ -0,0 +1,574 @@ + + * @since 2.0 + */ +class BaseYii +{ + /** + * @var array class map used by the Yii autoloading mechanism. + * The array keys are the class names (without leading backslashes), and the array values + * are the corresponding class file paths (or path aliases). This property mainly affects + * how [[autoload()]] works. + * @see import + * @see autoload + */ + public static $classMap = array(); + /** + * @var \yii\console\Application|\yii\web\Application the application instance + */ + public static $app; + /** + * @var array registered path aliases + * @see getAlias + * @see setAlias + */ + public static $aliases = array('@yii' => __DIR__); + /** + * @var array initial property values that will be applied to objects newly created via [[createObject]]. + * The array keys are class names without leading backslashes "\", and the array values are the corresponding + * name-value pairs for initializing the created class instances. For example, + * + * ~~~ + * array( + * 'Bar' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * 'mycompany\foo\Car' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * ) + * ~~~ + * + * @see createObject + */ + public static $objectConfig = array(); + + + /** + * @return string the version of Yii framework + */ + public static function getVersion() + { + return '2.0-dev'; + } + + /** + * Imports a set of namespaces. + * + * By importing a namespace, the method will create an alias for the directory corresponding + * to the namespace. For example, if "foo\bar" is a namespace associated with the directory + * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. + * + * This method is typically invoked in the bootstrap file to import the namespaces of + * the installed extensions. By default, Composer, when installing new extensions, will + * generate such a mapping file which can be loaded and passed to this method. + * + * @param array $namespaces the namespaces to be imported. The keys are the namespaces, + * and the values are the corresponding directories. + */ + public static function importNamespaces($namespaces) + { + foreach ($namespaces as $name => $path) { + if ($name !== '') { + $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); + if (is_array($path)) { + $path = reset($path); + } + static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); + } + } + } + + /** + * Translates a path alias into an actual path. + * + * The translation is done according to the following procedure: + * + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. + * + * @param string $alias the alias to be translated. + * @param boolean $throwException whether to throw an exception if the given alias is invalid. + * If this is false and an invalid alias is given, false will be returned by this method. + * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. + * @throws InvalidParamException if the alias is invalid while $throwException is true. + * @see setAlias + */ + public static function getAlias($alias, $throwException = true) + { + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } + } + } + } + + if ($throwException) { + throw new InvalidParamException("Invalid path alias: $alias"); + } else { + return false; + } + } + + /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $root; + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + return false; + } + + /** + * Registers a path alias. + * + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. + * + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. + * + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters + * will be trimmed. This can be + * + * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) + * - a URL (e.g. `http://www.yiiframework.com`) + * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the + * actual path first by calling [[getAlias()]]. + * + * @throws InvalidParamException if $path is an invalid alias. + * @see getAlias + */ + public static function setAlias($alias, $path) + { + if (strncmp($alias, '@', 1)) { + $alias = '@' . $alias; + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array($alias => $path); + } + } elseif (is_string(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array( + $alias => $path, + $root => self::$aliases[$root], + ); + } + } else { + self::$aliases[$root][$alias] = $path; + krsort(self::$aliases[$root]); + } + } elseif (isset(self::$aliases[$root])) { + if (is_array(self::$aliases[$root])) { + unset(self::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(self::$aliases[$root]); + } + } + } + + /** + * Class autoload loader. + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: + * + * 1. Search in [[classMap]]; + * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt + * to include the file associated with the corresponding path alias + * (e.g. `@yii/base/Component.php`); + * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), + * it will attempt to include the file associated with the corresponding path alias + * (e.g. `@PHPUnit/Framework/TestCase.php`); + * + * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). + * Therefor a path alias has to be defined for each top-level namespace. + * + * @param string $className the fully qualified class name without a leading backslash "\" + * @throws UnknownClassException if the class does not exist in the class file + */ + public static function autoload($className) + { + if (isset(self::$classMap[$className])) { + $classFile = self::$classMap[$className]; + if ($classFile[0] === '@') { + $classFile = static::getAlias($classFile); + } + } else { + // follow PSR-0 to determine the class file + if (($pos = strrpos($className, '\\')) !== false) { + // namespaced class, e.g. yii\base\Component + $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) + . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; + } else { + $path = str_replace('_', '/', $className) . '.php'; + } + + // try loading via path alias + if (strpos($path, '/') === false) { + return; + } else { + $classFile = static::getAlias('@' . $path, false); + if ($classFile === false || !is_file($classFile)) { + return; + } + } + } + + include($classFile); + + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && + (!function_exists('trait_exists') || !trait_exists($className, false))) { + throw new UnknownClassException("Unable to find '$className' in file: $classFile"); + } + } + + /** + * Creates a new object using the given configuration. + * + * The configuration can be either a string or an array. + * If a string, it is treated as the *object class*; if an array, + * it must contain a `class` element specifying the *object class*, and + * the rest of the name-value pairs in the array will be used to initialize + * the corresponding object properties. + * + * The object type can be either a class name or the [[getAlias()|alias]] of + * the class. For example, + * + * - `app\components\GoogleMap`: fully-qualified namespaced class. + * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. + * + * Below are some usage examples: + * + * ~~~ + * $object = \Yii::createObject('@app/components/GoogleMap'); + * $object = \Yii::createObject(array( + * 'class' => '\app\components\GoogleMap', + * 'apiKey' => 'xyz', + * )); + * ~~~ + * + * This method can be used to create any object as long as the object's constructor is + * defined like the following: + * + * ~~~ + * public function __construct(..., $config = array()) { + * } + * ~~~ + * + * The method will pass the given configuration as the last parameter of the constructor, + * and any additional parameters to this method will be passed as the rest of the constructor parameters. + * + * @param string|array $config the configuration. It can be either a string representing the class name + * or an array representing the object configuration. + * @return mixed the created object + * @throws InvalidConfigException if the configuration is invalid. + */ + public static function createObject($config) + { + static $reflections = array(); + + if (is_string($config)) { + $class = $config; + $config = array(); + } elseif (isset($config['class'])) { + $class = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + + $class = ltrim($class, '\\'); + + if (isset(self::$objectConfig[$class])) { + $config = array_merge(self::$objectConfig[$class], $config); + } + + if (($n = func_num_args()) > 1) { + /** @var $reflection \ReflectionClass */ + if (isset($reflections[$class])) { + $reflection = $reflections[$class]; + } else { + $reflection = $reflections[$class] = new \ReflectionClass($class); + } + $args = func_get_args(); + array_shift($args); // remove $config + if (!empty($config)) { + $args[] = $config; + } + return $reflection->newInstanceArgs($args); + } else { + return empty($config) ? new $class : new $class($config); + } + } + + /** + * Logs a trace message. + * Trace messages are logged mainly for development purpose to see + * the execution work flow of some code. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function trace($message, $category = 'application') + { + if (YII_DEBUG) { + self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); + } + } + + /** + * Logs an error message. + * An error message is typically logged when an unrecoverable error occurs + * during the execution of an application. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function error($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); + } + + /** + * Logs a warning message. + * A warning message is typically logged when an error occurs while the execution + * can still continue. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function warning($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); + } + + /** + * Logs an informative message. + * An informative message is typically logged by an application to keep record of + * something important (e.g. an administrator logs in). + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function info($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); + } + + /** + * Marks the beginning of a code block for profiling. + * This has to be matched with a call to [[endProfile]] with the same category name. + * The begin- and end- calls must also be properly nested. For example, + * + * ~~~ + * \Yii::beginProfile('block1'); + * // some code to be profiled + * \Yii::beginProfile('block2'); + * // some other code to be profiled + * \Yii::endProfile('block2'); + * \Yii::endProfile('block1'); + * ~~~ + * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile + */ + public static function beginProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to [[beginProfile]] with the same category name. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile + */ + public static function endProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); + } + + /** + * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. + * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information + */ + public static function powered() + { + return 'Powered by Yii Framework'; + } + + /** + * Translates a message to the specified language. + * + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. + * + * In case when a translated message has different plural forms (separated by "|"), this method + * will also attempt to choose an appropriate one according to a given numeric value which is + * specified as the first parameter (indexed by 0) in `$params`. + * + * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first + * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" + * will be replaced with the given number. + * + * For more details on how plural rules are applied, please refer to: + * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current + * [[\yii\base\Application::language|application language]] will be used. + * @return string the translated message. + */ + public static function t($category, $message, $params = array(), $language = null) + { + if (self::$app !== null) { + return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + } else { + return is_array($params) ? strtr($message, $params) : $message; + } + } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } +} diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index 0406463..232117f 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -7,18 +7,18 @@ * @license http://www.yiiframework.com/license/ */ -require(__DIR__ . '/YiiBase.php'); +require(__DIR__ . '/BaseYii.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[AbstractYii]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[AbstractYii]]. + * It extends from [[BaseYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[BaseYii]]. * * @author Qiang Xue * @since 2.0 */ -class Yii extends \yii\AbstractYii +class Yii extends \yii\BaseYii { } diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 22472d7..d4f304c 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -125,27 +125,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\AbstractArrayHelper' => YII_PATH . '/helpers/AbstractArrayHelper.php', + 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\AbstractConsole' => YII_PATH . '/helpers/AbstractConsole.php', + 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\AbstractFileHelper' => YII_PATH . '/helpers/AbstractFileHelper.php', + 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\AbstractHtml' => YII_PATH . '/helpers/AbstractHtml.php', + 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\AbstractHtmlPurifier' => YII_PATH . '/helpers/AbstractHtmlPurifier.php', + 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\AbstractInflector' => YII_PATH . '/helpers/AbstractInflector.php', + 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\AbstractJson' => YII_PATH . '/helpers/AbstractJson.php', + 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\AbstractMarkdown' => YII_PATH . '/helpers/AbstractMarkdown.php', + 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\AbstractSecurity' => YII_PATH . '/helpers/AbstractSecurity.php', + 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\AbstractStringHelper' => YII_PATH . '/helpers/AbstractStringHelper.php', + 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\AbstractVarDumper' => YII_PATH . '/helpers/AbstractVarDumper.php', + 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', diff --git a/framework/yii/helpers/AbstractArrayHelper.php b/framework/yii/helpers/AbstractArrayHelper.php deleted file mode 100644 index c26c1cd..0000000 --- a/framework/yii/helpers/AbstractArrayHelper.php +++ /dev/null @@ -1,451 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractArrayHelper -{ - /** - * Converts an object or an array of objects into an array. - * @param object|array $object the object to be converted into an array - * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. - * The properties specified for each class is an array of the following format: - * - * ~~~ - * array( - * 'app\models\Post' => array( - * 'id', - * 'title', - * // the key name in array result => property name - * 'createTime' => 'create_time', - * // the key name in array result => anonymous function - * 'length' => function ($post) { - * return strlen($post->content); - * }, - * ), - * ) - * ~~~ - * - * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: - * - * ~~~ - * array( - * 'id' => 123, - * 'title' => 'test', - * 'createTime' => '2013-01-01 12:00AM', - * 'length' => 301, - * ) - * ~~~ - * - * @param boolean $recursive whether to recursively converts properties which are objects into arrays. - * @return array the array representation of the object - */ - public static function toArray($object, $properties = array(), $recursive = true) - { - if (!empty($properties) && is_object($object)) { - $className = get_class($object); - if (!empty($properties[$className])) { - $result = array(); - foreach ($properties[$className] as $key => $name) { - if (is_int($key)) { - $result[$name] = $object->$name; - } else { - $result[$key] = static::getValue($object, $name); - } - } - return $result; - } - } - if ($object instanceof Arrayable) { - $object = $object->toArray(); - if (!$recursive) { - return $object; - } - } - $result = array(); - foreach ($object as $key => $value) { - if ($recursive && (is_array($value) || is_object($value))) { - $result[$key] = static::toArray($value, true); - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge($a, $b) - { - $args = func_get_args(); - $res = array_shift($args); - while (!empty($args)) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_integer($k)) { - isset($res[$k]) ? $res[] = $v : $res[$k] = $v; - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Retrieves the value of an array element or object property with the given key or property name. - * If the key does not exist in the array, the default value will be returned instead. - * - * Below are some usage examples, - * - * ~~~ - * // working with array - * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); - * // working with object - * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); - * // working with anonymous function - * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; - * }); - * ~~~ - * - * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, - * or an anonymous function returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed the value of the element if found, default value otherwise - */ - public static function getValue($array, $key, $default = null) - { - if ($key instanceof \Closure) { - return $key($array, $default); - } elseif (is_array($array)) { - return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; - } else { - return $array->$key; - } - } - - /** - * Removes an item from an array and returns the value. If the key does not exist in the array, the default value - * will be returned instead. - * - * Usage examples, - * - * ~~~ - * // $array = array('type' => 'A', 'options' => array(1, 2)); - * // working with array - * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); - * // $array content - * // $array = array('options' => array(1, 2)); - * ~~~ - * - * @param array $array the array to extract value from - * @param string $key key name of the array element - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed|null the value of the element if found, default value otherwise - */ - public static function remove(&$array, $key, $default = null) - { - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { - $value = $array[$key]; - unset($array[$key]); - return $value; - } - return $default; - } - - /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. - * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. - * - * If a key value is null, the corresponding array element will be discarded and not put in the result. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) - * - * // using anonymous function - * $result = ArrayHelper::index($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array - */ - public static function index($array, $key) - { - $result = array(); - foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; - } - return $result; - } - - /** - * Returns the values of a specified column in an array. - * The input array should be multidimensional or an array of objects. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') - * - * // using anonymous function - * $result = ArrayHelper::getColumn($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array - * @param string|\Closure $name - * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array - * will be re-indexed with integers. - * @return array the list of column values - */ - public static function getColumn($array, $name, $keepKeys = true) - { - $result = array(); - if ($keepKeys) { - foreach ($array as $k => $element) { - $result[$k] = static::getValue($element, $name); - } - } else { - foreach ($array as $element) { - $result[] = static::getValue($element, $name); - } - } - - return $result; - } - - /** - * Builds a map (key-value pairs) from a multidimensional array or an array of objects. - * The `$from` and `$to` parameters specify the key names or property names to set up the map. - * Optionally, one can further group the map according to a grouping field `$group`. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - * ); - * - * $result = ArrayHelper::map($array, 'id', 'name'); - * // the result is: - * // array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // '345' => 'ccc', - * // ) - * - * $result = ArrayHelper::map($array, 'id', 'name', 'class'); - * // the result is: - * // array( - * // 'x' => array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // ), - * // 'y' => array( - * // '345' => 'ccc', - * // ), - * // ) - * ~~~ - * - * @param array $array - * @param string|\Closure $from - * @param string|\Closure $to - * @param string|\Closure $group - * @return array - */ - public static function map($array, $from, $to, $group = null) - { - $result = array(); - foreach ($array as $element) { - $key = static::getValue($element, $from); - $value = static::getValue($element, $to); - if ($group !== null) { - $result[static::getValue($element, $group)][$key] = $value; - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Sorts an array of objects or arrays (with the same structure) by one or several keys. - * @param array $array the array to be sorted. The array will be modified after calling this method. - * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array - * elements, a property name of the objects, or an anonymous function returning the values for comparison - * purpose. The anonymous function signature should be: `function($item)`. - * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $descending whether to sort in descending or ascending order. When - * sorting by multiple keys with different descending orders, use an array of descending flags. - * @param integer|array $sortFlag the PHP sort flag. Valid values include - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. - * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) - * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. - * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter - * is used only when `$sortFlag` is `SORT_STRING`. - * When sorting by multiple keys with different case sensitivities, use an array of boolean values. - * @throws InvalidParamException if the $descending or $sortFlag parameters do not have - * correct number of elements as that of $key. - */ - public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) - { - $keys = is_array($key) ? $key : array($key); - if (empty($keys) || empty($array)) { - return; - } - $n = count($keys); - if (is_scalar($descending)) { - $descending = array_fill(0, $n, $descending); - } elseif (count($descending) !== $n) { - throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); - } - if (is_scalar($sortFlag)) { - $sortFlag = array_fill(0, $n, $sortFlag); - } elseif (count($sortFlag) !== $n) { - throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); - } - if (is_scalar($caseSensitive)) { - $caseSensitive = array_fill(0, $n, $caseSensitive); - } elseif (count($caseSensitive) !== $n) { - throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); - } - $args = array(); - foreach ($keys as $i => $key) { - $flag = $sortFlag[$i]; - $cs = $caseSensitive[$i]; - if (!$cs && ($flag === SORT_STRING)) { - if (defined('SORT_FLAG_CASE')) { - $flag = $flag | SORT_FLAG_CASE; - $args[] = static::getColumn($array, $key); - } else { - $column = array(); - foreach (static::getColumn($array, $key) as $k => $value) { - $column[$k] = mb_strtolower($value); - } - $args[] = $column; - } - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; - $args[] = $flag; - } - $args[] = &$array; - call_user_func_array('array_multisort', $args); - } - - /** - * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded. - * If a value is an array, this method will also encode it recursively. - * @param array $data data to be encoded - * @param boolean $valuesOnly whether to encode array values only. If false, - * both the array keys and array values will be encoded. - * @param string $charset the charset that the data is using. If not set, - * [[\yii\base\Application::charset]] will be used. - * @return array the encoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function htmlEncode($data, $valuesOnly = true, $charset = null) - { - if ($charset === null) { - $charset = Yii::$app->charset; - } - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $d[$key] = static::htmlEncode($value, $charset); - } - } - return $d; - } - - /** - * Decodes HTML entities into the corresponding characters in an array of strings. - * Both the array keys and values will be decoded. - * If a value is an array, this method will also decode it recursively. - * @param array $data data to be decoded - * @param boolean $valuesOnly whether to decode array values only. If false, - * both the array keys and array values will be decoded. - * @return array the decoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function htmlDecode($data, $valuesOnly = true) - { - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars_decode($key, ENT_QUOTES); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); - } elseif (is_array($value)) { - $d[$key] = static::htmlDecode($value); - } - } - return $d; - } -} diff --git a/framework/yii/helpers/AbstractConsole.php b/framework/yii/helpers/AbstractConsole.php deleted file mode 100644 index 8131aae..0000000 --- a/framework/yii/helpers/AbstractConsole.php +++ /dev/null @@ -1,835 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractConsole -{ - const FG_BLACK = 30; - const FG_RED = 31; - const FG_GREEN = 32; - const FG_YELLOW = 33; - const FG_BLUE = 34; - const FG_PURPLE = 35; - const FG_CYAN = 36; - const FG_GREY = 37; - - const BG_BLACK = 40; - const BG_RED = 41; - const BG_GREEN = 42; - const BG_YELLOW = 43; - const BG_BLUE = 44; - const BG_PURPLE = 45; - const BG_CYAN = 46; - const BG_GREY = 47; - - const RESET = 0; - const NORMAL = 0; - const BOLD = 1; - const ITALIC = 3; - const UNDERLINE = 4; - const BLINK = 5; - const NEGATIVE = 7; - const CONCEALED = 8; - const CROSSED_OUT = 9; - const FRAMED = 51; - const ENCIRCLED = 52; - const OVERLINED = 53; - - /** - * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved up - */ - public static function moveCursorUp($rows = 1) - { - echo "\033[" . (int)$rows . 'A'; - } - - /** - * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved down - */ - public static function moveCursorDown($rows = 1) - { - echo "\033[" . (int)$rows . 'B'; - } - - /** - * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved forward - */ - public static function moveCursorForward($steps = 1) - { - echo "\033[" . (int)$steps . 'C'; - } - - /** - * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved backward - */ - public static function moveCursorBackward($steps = 1) - { - echo "\033[" . (int)$steps . 'D'; - } - - /** - * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. - * @param integer $lines number of lines the cursor should be moved down - */ - public static function moveCursorNextLine($lines = 1) - { - echo "\033[" . (int)$lines . 'E'; - } - - /** - * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. - * @param integer $lines number of lines the cursor should be moved up - */ - public static function moveCursorPrevLine($lines = 1) - { - echo "\033[" . (int)$lines . 'F'; - } - - /** - * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. - * @param integer $column 1-based column number, 1 is the left edge of the screen. - * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - */ - public static function moveCursorTo($column, $row = null) - { - if ($row === null) { - echo "\033[" . (int)$column . 'G'; - } else { - echo "\033[" . (int)$row . ';' . (int)$column . 'H'; - } - } - - /** - * Scrolls whole page up by sending ANSI control code SU to the terminal. - * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll up - */ - public static function scrollUp($lines = 1) - { - echo "\033[" . (int)$lines . "S"; - } - - /** - * Scrolls whole page down by sending ANSI control code SD to the terminal. - * New lines are added at the top. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll down - */ - public static function scrollDown($lines = 1) - { - echo "\033[" . (int)$lines . "T"; - } - - /** - * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. - */ - public static function saveCursorPosition() - { - echo "\033[s"; - } - - /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. - */ - public static function restoreCursorPosition() - { - echo "\033[u"; - } - - /** - * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. - * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. - */ - public static function hideCursor() - { - echo "\033[?25l"; - } - - /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. - */ - public static function showCursor() - { - echo "\033[?25h"; - } - - /** - * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. - * Cursor position will not be changed. - * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. - */ - public static function clearScreen() - { - echo "\033[2J"; - } - - /** - * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenBeforeCursor() - { - echo "\033[1J"; - } - - /** - * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenAfterCursor() - { - echo "\033[0J"; - } - - /** - * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLine() - { - echo "\033[2K"; - } - - /** - * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineBeforeCursor() - { - echo "\033[1K"; - } - - /** - * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineAfterCursor() - { - echo "\033[0K"; - } - - /** - * Returns the ANSI format code. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string The ANSI format code according to the given formatting constants. - */ - public static function ansiFormatCode($format) - { - return "\033[" . implode(';', $format) . 'm'; - } - - /** - * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @see ansiFormatCode() - * @see ansiFormatEnd() - */ - public static function beginAnsiFormat($format) - { - echo "\033[" . implode(';', $format) . 'm'; - } - - /** - * Resets any ANSI format set by previous method [[ansiFormatBegin()]] - * Any output after this will have default text format. - * This is equal to calling - * - * ```php - * echo Console::ansiFormatCode(array(Console::RESET)) - * ``` - */ - public static function endAnsiFormat() - { - echo "\033[0m"; - } - - /** - * Will return a string formatted with the given ANSI style - * - * @param string $string the string to be formatted - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string - */ - public static function ansiFormat($string, $format = array()) - { - $code = implode(';', $format); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; - } - - /** - * Returns the ansi format code for xterm foreground color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermFgColor($colorCode) - { - return '38;5;' . $colorCode; - } - - /** - * Returns the ansi format code for xterm background color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermBgColor($colorCode) - { - return '48;5;' . $colorCode; - } - - /** - * Strips ANSI control codes from a string - * - * @param string $string String to strip - * @return string - */ - public static function stripAnsiFormat($string) - { - return preg_replace('/\033\[[\d;?]*\w/', '', $string); - } - - /** - * Converts an ANSI formatted string to HTML - * @param $string - * @return mixed - */ - // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 - public static function ansiToHtml($string) - { - $tags = 0; - return preg_replace_callback( - '/\033\[[\d;]+m/', - function ($ansi) use (&$tags) { - $styleA = array(); - foreach (explode(';', $ansi) as $controlCode) { - switch ($controlCode) { - case self::FG_BLACK: - $style = array('color' => '#000000'); - break; - case self::FG_BLUE: - $style = array('color' => '#000078'); - break; - case self::FG_CYAN: - $style = array('color' => '#007878'); - break; - case self::FG_GREEN: - $style = array('color' => '#007800'); - break; - case self::FG_GREY: - $style = array('color' => '#787878'); - break; - case self::FG_PURPLE: - $style = array('color' => '#780078'); - break; - case self::FG_RED: - $style = array('color' => '#780000'); - break; - case self::FG_YELLOW: - $style = array('color' => '#787800'); - break; - case self::BG_BLACK: - $style = array('background-color' => '#000000'); - break; - case self::BG_BLUE: - $style = array('background-color' => '#000078'); - break; - case self::BG_CYAN: - $style = array('background-color' => '#007878'); - break; - case self::BG_GREEN: - $style = array('background-color' => '#007800'); - break; - case self::BG_GREY: - $style = array('background-color' => '#787878'); - break; - case self::BG_PURPLE: - $style = array('background-color' => '#780078'); - break; - case self::BG_RED: - $style = array('background-color' => '#780000'); - break; - case self::BG_YELLOW: - $style = array('background-color' => '#787800'); - break; - case self::BOLD: - $style = array('font-weight' => 'bold'); - break; - case self::ITALIC: - $style = array('font-style' => 'italic'); - break; - case self::UNDERLINE: - $style = array('text-decoration' => array('underline')); - break; - case self::OVERLINED: - $style = array('text-decoration' => array('overline')); - break; - case self::CROSSED_OUT: - $style = array('text-decoration' => array('line-through')); - break; - case self::BLINK: - $style = array('text-decoration' => array('blink')); - break; - case self::NEGATIVE: // ??? - case self::CONCEALED: - case self::ENCIRCLED: - case self::FRAMED: - // TODO allow resetting codes - break; - case 0: // ansi reset - $return = ''; - for ($n = $tags; $tags > 0; $tags--) { - $return .= ''; - } - return $return; - } - - $styleA = ArrayHelper::merge($styleA, $style); - } - $styleString[] = array(); - foreach ($styleA as $name => $content) { - if ($name === 'text-decoration') { - $content = implode(' ', $content); - } - $styleString[] = $name . ':' . $content; - } - $tags++; - return ' $value) { - echo " $key - $value\n"; - } - echo " ? - Show help\n"; - goto top; - } elseif (!in_array($input, array_keys($options))) { - goto top; - } - return $input; - } - - /** - * Displays and updates a simple progress bar on screen. - * - * @param integer $done the number of items that are completed - * @param integer $total the total value of items that are to be done - * @param integer $size the size of the status bar (optional) - * @see http://snipplr.com/view/29548/ - */ - public static function showProgress($done, $total, $size = 30) - { - static $start; - - // if we go over our bound, just ignore it - if ($done > $total) { - return; - } - - if (empty($start)) { - $start = time(); - } - - $now = time(); - - $percent = (double)($done / $total); - $bar = floor($percent * $size); - - $status = "\r["; - $status .= str_repeat("=", $bar); - if ($bar < $size) { - $status .= ">"; - $status .= str_repeat(" ", $size - $bar); - } else { - $status .= "="; - } - - $display = number_format($percent * 100, 0); - - $status .= "] $display% $done/$total"; - - $rate = ($now - $start) / $done; - $left = $total - $done; - $eta = round($rate * $left, 2); - - $elapsed = $now - $start; - - $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; - - static::stdout("$status "); - - flush(); - - // when done, send a newline - if ($done == $total) { - echo "\n"; - } - } -} diff --git a/framework/yii/helpers/AbstractFileHelper.php b/framework/yii/helpers/AbstractFileHelper.php deleted file mode 100644 index 5eab927..0000000 --- a/framework/yii/helpers/AbstractFileHelper.php +++ /dev/null @@ -1,329 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -abstract class AbstractFileHelper -{ - /** - * Normalizes a file/directory path. - * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, - * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux - * will be normalized as '/home/demo'. - * @param string $path the file/directory path to be normalized - * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. - * @return string the normalized file/directory path - */ - public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) - { - return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); - } - - /** - * Returns the localized version of a specified file. - * - * The searching is based on the specified language code. In particular, - * a file with the same name will be looked for under the subdirectory - * whose name is the same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_CN", the localized file will be looked for as - * "path/to/zh_CN/view.php". If the file is not found, the original file - * will be returned. - * - * If the target and the source language codes are the same, - * the original file will be returned. - * - * @param string $file the original file - * @param string $language the target language that the file should be localized to. - * If not set, the value of [[\yii\base\Application::language]] will be used. - * @param string $sourceLanguage the language that the original file is in. - * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. - * @return string the matching localized file, or the original file if the localized version is not found. - * If the target and the source language codes are the same, the original file will be returned. - */ - public static function localize($file, $language = null, $sourceLanguage = null) - { - if ($language === null) { - $language = Yii::$app->language; - } - if ($sourceLanguage === null) { - $sourceLanguage = Yii::$app->sourceLanguage; - } - if ($language === $sourceLanguage) { - return $file; - } - $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); - return is_file($desiredFile) ? $desiredFile : $file; - } - - /** - * Determines the MIME type of the specified file. - * This method will first try to determine the MIME type based on - * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will - * fall back to [[getMimeTypeByExtension()]]. - * @param string $file the file name. - * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. - * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). - * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case - * `finfo_open()` cannot determine it. - * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. - */ - public static function getMimeType($file, $magicFile = null, $checkExtension = true) - { - if (function_exists('finfo_open')) { - $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info) { - $result = finfo_file($info, $file); - finfo_close($info); - if ($result !== false) { - return $result; - } - } - } - - return $checkExtension ? static::getMimeTypeByExtension($file) : null; - } - - /** - * Determines the MIME type based on the extension name of the specified file. - * This method will use a local map between extension names and MIME types. - * @param string $file the file name. - * @param string $magicFile the path of the file that contains all available MIME type information. - * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. - * @return string the MIME type. Null is returned if the MIME type cannot be determined. - */ - public static function getMimeTypeByExtension($file, $magicFile = null) - { - static $mimeTypes = array(); - if ($magicFile === null) { - $magicFile = __DIR__ . '/mimeTypes.php'; - } - if (!isset($mimeTypes[$magicFile])) { - $mimeTypes[$magicFile] = require($magicFile); - } - if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { - $ext = strtolower($ext); - if (isset($mimeTypes[$magicFile][$ext])) { - return $mimeTypes[$magicFile][$ext]; - } - } - return null; - } - - /** - * Copies a whole directory as another one. - * The files and sub-directories will also be copied over. - * @param string $src the source directory - * @param string $dst the destination directory - * @param array $options options for directory copy. Valid options are: - * - * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. - * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be copied (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be copied - * - * - only: array, list of patterns that the file paths should match if they want to be copied. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be copied. - * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. - * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. - * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or - * file copied from, while `$to` is the copy target. - */ - public static function copyDirectory($src, $dst, $options = array()) - { - if (!is_dir($dst)) { - static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); - } - - $handle = opendir($src); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $from = $src . DIRECTORY_SEPARATOR . $file; - $to = $dst . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($from, $options)) { - if (is_file($from)) { - copy($from, $to); - if (isset($options['fileMode'])) { - @chmod($to, $options['fileMode']); - } - } else { - static::copyDirectory($from, $to, $options); - } - if (isset($options['afterCopy'])) { - call_user_func($options['afterCopy'], $from, $to); - } - } - } - closedir($handle); - } - - /** - * Removes a directory (and all its content) recursively. - * @param string $dir the directory to be deleted recursively. - */ - public static function removeDirectory($dir) - { - if (!is_dir($dir) || !($handle = opendir($dir))) { - return; - } - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (is_file($path)) { - unlink($path); - } else { - static::removeDirectory($path); - } - } - closedir($handle); - rmdir($dir); - } - - /** - * Returns the files found under the specified directory and subdirectories. - * @param string $dir the directory under which the files will be looked for. - * @param array $options options for file searching. Valid options are: - * - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be returned (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be returned - * - * - only: array, list of patterns that the file paths should match if they want to be returned. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be returned. - * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. - * @return array files found under the directory. The file list is sorted. - */ - public static function findFiles($dir, $options = array()) - { - $list = array(); - $handle = opendir($dir); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($path, $options)) { - if (is_file($path)) { - $list[] = $path; - } elseif (!isset($options['recursive']) || $options['recursive']) { - $list = array_merge($list, static::findFiles($path, $options)); - } - } - } - closedir($handle); - return $list; - } - - /** - * Checks if the given file path satisfies the filtering options. - * @param string $path the path of the file or directory to be checked - * @param array $options the filtering options. See [[findFiles()]] for explanations of - * the supported options. - * @return boolean whether the file or directory satisfies the filtering options. - */ - public static function filterPath($path, $options) - { - if (isset($options['filter'])) { - $result = call_user_func($options['filter'], $path); - if (is_bool($result)) { - return $result; - } - } - $path = str_replace('\\', '/', $path); - if ($isDir = is_dir($path)) { - $path .= '/'; - } - $n = StringHelper::strlen($path); - - if (!empty($options['except'])) { - foreach ($options['except'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return false; - } - } - } - - if (!$isDir && !empty($options['only'])) { - foreach ($options['only'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return true; - } - } - return false; - } - return true; - } - - /** - * Creates a new directory. - * - * This method is similar to the PHP `mkdir()` function except that - * it uses `chmod()` to set the permission of the created directory - * in order to avoid the impact of the `umask` setting. - * - * @param string $path path of the directory to be created. - * @param integer $mode the permission to be set for the created directory. - * @param boolean $recursive whether to create parent directories if they do not exist. - * @return boolean whether the directory is created successfully - */ - public static function createDirectory($path, $mode = 0775, $recursive = true) - { - if (is_dir($path)) { - return true; - } - $parentDir = dirname($path); - if ($recursive && !is_dir($parentDir)) { - static::createDirectory($parentDir, $mode, true); - } - $result = mkdir($path, $mode); - chmod($path, $mode); - return $result; - } -} diff --git a/framework/yii/helpers/AbstractHtml.php b/framework/yii/helpers/AbstractHtml.php deleted file mode 100644 index 37e926c..0000000 --- a/framework/yii/helpers/AbstractHtml.php +++ /dev/null @@ -1,1599 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractHtml -{ - /** - * @var array list of void elements (element name => 1) - * @see http://www.w3.org/TR/html-markup/syntax.html#void-element - */ - public static $voidElements = array( - 'area' => 1, - 'base' => 1, - 'br' => 1, - 'col' => 1, - 'command' => 1, - 'embed' => 1, - 'hr' => 1, - 'img' => 1, - 'input' => 1, - 'keygen' => 1, - 'link' => 1, - 'meta' => 1, - 'param' => 1, - 'source' => 1, - 'track' => 1, - 'wbr' => 1, - ); - /** - * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes - * that are rendered by [[renderTagAttributes()]]. - */ - public static $attributeOrder = array( - 'type', - 'id', - 'class', - 'name', - 'value', - - 'href', - 'src', - 'action', - 'method', - - 'selected', - 'checked', - 'readonly', - 'disabled', - 'multiple', - - 'size', - 'maxlength', - 'width', - 'height', - 'rows', - 'cols', - - 'alt', - 'title', - 'rel', - 'media', - ); - - /** - * Encodes special characters into HTML entities. - * The [[yii\base\Application::charset|application charset]] will be used for encoding. - * @param string $content the content to be encoded - * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, - * HTML entities in `$content` will not be further encoded. - * @return string the encoded content - * @see decode - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function encode($content, $doubleEncode = true) - { - return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); - } - - /** - * Decodes special HTML entities back to the corresponding characters. - * This is the opposite of [[encode()]]. - * @param string $content the content to be decoded - * @return string the decoded content - * @see encode - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function decode($content) - { - return htmlspecialchars_decode($content, ENT_QUOTES); - } - - /** - * Generates a complete HTML tag. - * @param string $name the tag name - * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. - * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated HTML tag - * @see beginTag - * @see endTag - */ - public static function tag($name, $content = '', $options = array()) - { - $html = "<$name" . static::renderTagAttributes($options) . '>'; - return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; - } - - /** - * Generates a start tag. - * @param string $name the tag name - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated start tag - * @see endTag - * @see tag - */ - public static function beginTag($name, $options = array()) - { - return "<$name" . static::renderTagAttributes($options) . '>'; - } - - /** - * Generates an end tag. - * @param string $name the tag name - * @return string the generated end tag - * @see beginTag - * @see tag - */ - public static function endTag($name) - { - return ""; - } - - /** - * Generates a style tag. - * @param string $content the style content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/css" will be used. - * @return string the generated style tag - */ - public static function style($content, $options = array()) - { - return static::tag('style', $content, $options); - } - - /** - * Generates a script tag. - * @param string $content the script content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. - * @return string the generated script tag - */ - public static function script($content, $options = array()) - { - return static::tag('script', $content, $options); - } - - /** - * Generates a link tag that refers to an external CSS file. - * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated link tag - * @see url - */ - public static function cssFile($url, $options = array()) - { - $options['rel'] = 'stylesheet'; - $options['href'] = static::url($url); - return static::tag('link', '', $options); - } - - /** - * Generates a script tag that refers to an external JavaScript file. - * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated script tag - * @see url - */ - public static function jsFile($url, $options = array()) - { - $options['src'] = static::url($url); - return static::tag('script', '', $options); - } - - /** - * Generates a form start tag. - * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. - * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). - * Since most browsers only support "post" and "get", if other methods are given, they will - * be simulated using "post", and a hidden input will be added which contains the actual method type. - * See [[\yii\web\Request::restVar]] for more details. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated form start tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $options = array()) - { - $action = static::url($action); - - $hiddenInputs = array(); - - $request = Yii::$app->getRequest(); - if ($request instanceof Request) { - if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { - // simulate PUT, DELETE, etc. via POST - $hiddenInputs[] = static::hiddenInput($request->restVar, $method); - $method = 'post'; - } - if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); - } - } - - if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { - // query parameters in the action are ignored for GET method - // we use hidden fields to add them back - foreach (explode('&', substr($action, $pos + 1)) as $pair) { - if (($pos1 = strpos($pair, '=')) !== false) { - $hiddenInputs[] = static::hiddenInput( - urldecode(substr($pair, 0, $pos1)), - urldecode(substr($pair, $pos1 + 1)) - ); - } else { - $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); - } - } - $action = substr($action, 0, $pos); - } - - $options['action'] = $action; - $options['method'] = $method; - $form = static::beginTag('form', $options); - if (!empty($hiddenInputs)) { - $form .= "\n" . implode("\n", $hiddenInputs); - } - - return $form; - } - - /** - * Generates a form end tag. - * @return string the generated tag - * @see beginForm - */ - public static function endForm() - { - return ''; - } - - /** - * 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 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 - * will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated hyperlink - * @see url - */ - public static function a($text, $url = null, $options = array()) - { - if ($url !== null) { - $options['href'] = static::url($url); - } - return static::tag('a', $text, $options); - } - - /** - * 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 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. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated mailto link - */ - public static function mailto($text, $email = null, $options = array()) - { - $options['href'] = 'mailto:' . ($email === null ? $text : $email); - return static::tag('a', $text, $options); - } - - /** - * Generates an image tag. - * @param string $src the image URL. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated image tag - */ - public static function img($src, $options = array()) - { - $options['src'] = static::url($src); - if (!isset($options['alt'])) { - $options['alt'] = ''; - } - return static::tag('img', '', $options); - } - - /** - * Generates a label tag. - * @param string $content label text. 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 [[encode()]] - * it to prevent XSS attacks. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is null, the "for" attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated label tag - */ - public static function label($content, $for = null, $options = array()) - { - $options['for'] = $for; - return static::tag('label', $content, $options); - } - - /** - * Generates a button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function button($content = 'Button', $options = array()) - { - return static::tag('button', $content, $options); - } - - /** - * Generates a submit button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated submit button tag - */ - public static function submitButton($content = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - return static::button($content, $options); - } - - /** - * Generates a reset button tag. - * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated reset button tag - */ - public static function resetButton($content = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - return static::button($content, $options); - } - - /** - * Generates an input type of the given type. - * @param string $type the type attribute. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated input tag - */ - public static function input($type, $name = null, $value = null, $options = array()) - { - $options['type'] = $type; - $options['name'] = $name; - $options['value'] = $value === null ? null : (string)$value; - return static::tag('input', '', $options); - } - - /** - * Generates an input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function buttonInput($label = 'Button', $options = array()) - { - $options['type'] = 'button'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a submit input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function submitInput($label = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a reset input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the tag returned. - * @return string the generated button tag - */ - public static function resetInput($label = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a text input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function textInput($name, $value = null, $options = array()) - { - return static::input('text', $name, $value, $options); - } - - /** - * Generates a hidden input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function hiddenInput($name, $value = null, $options = array()) - { - return static::input('hidden', $name, $value, $options); - } - - /** - * Generates a password input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function passwordInput($name, $value = null, $options = array()) - { - return static::input('password', $name, $value, $options); - } - - /** - * Generates a file input field. - * To use a file input field, you should set the enclosing form's "enctype" attribute to - * be "multipart/form-data". After the form is submitted, the uploaded file information - * can be obtained via $_FILES[$name] (see PHP documentation). - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function fileInput($name, $value = null, $options = array()) - { - return static::input('file', $name, $value, $options); - } - - /** - * Generates a text area input. - * @param string $name the input name - * @param string $value the input value. Note that it will be encoded using [[encode()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated text area tag - */ - public static function textarea($name, $value = '', $options = array()) - { - $options['name'] = $name; - return static::tag('textarea', static::encode($value), $options); - } - - /** - * Generates a radio button input. - * @param string $name the name attribute. - * @param boolean $checked whether the radio button should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function radio($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'radio')); - } else { - return $hidden . static::input('radio', $name, $value, $options); - } - } - - /** - * Generates a checkbox input. - * @param string $name the name attribute. - * @param boolean $checked whether the checkbox should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute - * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function checkbox($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $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']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'checkbox')); - } else { - return $hidden . static::input('checkbox', $name, $value, $options); - } - } - - /** - * Generates a drop-down list. - * @param string $name the input name - * @param string $selection the selected value - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) - { - $options['name'] = $name; - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list box. - * @param string $name the input name - * @param string|array $selection the selected value(s) - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) - { - if (!isset($options['size'])) { - $options['size'] = 4; - } - if (!empty($options['multiple']) && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $options['name'] = $name; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); - } - $hidden = static::hiddenInput($name, $options['unselect']); - unset($options['unselect']); - } else { - $hidden = ''; - } - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * @param string $name the name attribute of each checkbox. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * @param array $options options (name => config) for the checkbox list container tag. - * The following options are specially handled: - * - * - tag: string, the tag name of the container element. - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input, respectively. - * @return string the generated checkbox list - */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) - { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } - - $formatter = isset($options['item']) ? $options['item'] : null; - $encode = !isset($options['encode']) || $options['encode']; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::checkbox($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; - $hidden = static::hiddenInput($name2, $options['unselect']); - } else { - $hidden = ''; - } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * @param string $name the name attribute of each radio button. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input, respectively. - * @return string the generated radio button list - */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) - { - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::radio($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; - } - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates an unordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated unordered list. An empty string is returned if `$items` is empty. - */ - public static function ul($items, $options = array()) - { - if (empty($items)) { - return ''; - } - $tag = isset($options['tag']) ? $options['tag'] : 'ul'; - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); - unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); - $results = array(); - foreach ($items as $index => $item) { - if ($formatter !== null) { - $results[] = call_user_func($formatter, $item, $index); - } else { - $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); - } - } - return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); - } - - /** - * Generates an ordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated ordered list. An empty string is returned if `$items` is empty. - */ - public static function ol($items, $options = array()) - { - $options['tag'] = 'ol'; - return static::ul($items, $options); - } - - /** - * Generates a label tag for the given model attribute. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * - * @return string the generated label tag - */ - public static function activeLabel($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); - $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); - unset($options['label'], $options['for']); - return static::label($label, $for, $options); - } - - /** - * Generates a tag that contains the first validation error of the specified model attribute. - * Note that even if there is no validation error, this method will still return an empty error tag. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded - * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - tag: this specifies the tag name. If not set, "div" will be used. - * - * @return string the generated label tag - */ - public static function error($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $error = $model->getFirstError($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - return Html::tag($tag, Html::encode($error), $options); - } - - /** - * Generates an input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param string $type the input type (e.g. 'text', 'password') - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeInput($type, $model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::input($type, $name, $value, $options); - } - - /** - * Generates a text input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeTextInput($model, $attribute, $options = array()) - { - return static::activeInput('text', $model, $attribute, $options); - } - - /** - * Generates a hidden input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeHiddenInput($model, $attribute, $options = array()) - { - return static::activeInput('hidden', $model, $attribute, $options); - } - - /** - * Generates a password input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activePasswordInput($model, $attribute, $options = array()) - { - return static::activeInput('password', $model, $attribute, $options); - } - - /** - * Generates a file input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeFileInput($model, $attribute, $options = array()) - { - // add a hidden field so that if a model only has a file field, we can - // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) - . static::activeInput('file', $model, $attribute, $options); - } - - /** - * Generates a textarea tag for the given model attribute. - * The model attribute value will be used as the content in the textarea. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated textarea tag - */ - public static function activeTextarea($model, $attribute, $options = array()) - { - $name = static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::textarea($name, $value, $options); - } - - /** - * Generates a radio button tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function activeRadio($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radio($name, $checked, $options); - } - - /** - * Generates a checkbox tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function activeCheckbox($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkbox($name, $checked, $options); - } - - /** - * Generates a drop-down list for the given model attribute. - * The selection of the drop-down list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function activeDropDownList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::dropDownList($name, $checked, $items, $options); - } - - /** - * Generates a list box. - * The selection of the list box is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function activeListBox($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::listBox($name, $checked, $items, $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * The selection of the checkbox list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input. - * @return string the generated checkbox list - */ - public static function activeCheckboxList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkboxList($name, $checked, $items, $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * The selection of the radio buttons is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input. - * @return string the generated radio button list - */ - public static function activeRadioList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radioList($name, $checked, $items, $options); - } - - /** - * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. - * @param string|array $selection the selected value(s). This can be either a string for single selection - * or an array for multiple selections. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. - * This method will take out these elements, if any: "prompt", "options" and "groups". See more details - * in [[dropDownList()]] for the explanation of these elements. - * - * @return string the generated list options - */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) - { - $lines = array(); - if (isset($tagOptions['prompt'])) { - $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); - } - - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); - unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); - - foreach ($items as $key => $value) { - if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); - $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); - $content = static::renderSelectOptions($selection, $value, $attrs); - $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); - } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); - $attrs['value'] = (string)$key; - $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); - $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); - } - } - - return implode("\n", $lines); - } - - /** - * Renders the HTML tag attributes. - * Attributes whose values are of boolean type will be treated as - * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). - * And attributes whose values are null will not be rendered. - * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. - * @return string the rendering result. If the attributes are not empty, they will be rendered - * into a string with a leading white space (so that it can be directly appended to the tag name - * in a tag. If there is no attribute, an empty string will be returned. - */ - public static function renderTagAttributes($attributes) - { - if (count($attributes) > 1) { - $sorted = array(); - foreach (static::$attributeOrder as $name) { - if (isset($attributes[$name])) { - $sorted[$name] = $attributes[$name]; - } - } - $attributes = array_merge($sorted, $attributes); - } - - $html = ''; - foreach ($attributes as $name => $value) { - if (is_bool($value)) { - if ($value) { - $html .= " $name"; - } - } elseif ($value !== null) { - $html .= " $name=\"" . static::encode($value) . '"'; - } - } - return $html; - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter - * - * - is an empty string: the currently requested URL will be returned; - * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result - * is an absolute URL, it will be returned without any change further; Otherwise, the result - * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. - * - is an array: the first array element is considered a route, while the rest of the name-value - * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. - * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. - * - * @param array|string $url the parameter to be used to generate a valid URL - * @return string the normalized URL - * @throws InvalidParamException if the parameter is invalid. - */ - public static function url($url) - { - if (is_array($url)) { - if (isset($url[0])) { - $route = $url[0]; - $params = array_splice($url, 1); - if (Yii::$app->controller instanceof \yii\web\Controller) { - return Yii::$app->controller->createUrl($route, $params); - } else { - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - } else { - throw new InvalidParamException('The array specifying a URL must contain at least one element.'); - } - } elseif ($url === '') { - return Yii::$app->getRequest()->getUrl(); - } else { - $url = Yii::getAlias($url); - if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { - return $url; - } else { - return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; - } - } - } - - /** - * Adds a CSS class to the specified options. - * If the CSS class is already in the options, it will not be added again. - * @param array $options the options to be modified. - * @param string $class the CSS class to be added - */ - public static function addCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = ' ' . $options['class'] . ' '; - if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { - $options['class'] .= ' ' . $class; - } - } else { - $options['class'] = $class; - } - } - - /** - * Removes a CSS class from the specified options. - * @param array $options the options to be modified. - * @param string $class the CSS class to be removed - */ - public static function removeCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); - if (($index = array_search($class, $classes)) !== false) { - unset($classes[$index]); - } - if (empty($classes)) { - unset($options['class']); - } else { - $options['class'] = implode(' ', $classes); - } - } - } - - /** - * Returns the real attribute name from the given attribute expression. - * - * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. - * It is mainly used in tabular data input and/or input of array type. Below are some examples: - * - * - `[0]content` is used in tabular data input to represent the "content" attribute - * for the first model in tabular input; - * - `dates[0]` represents the first array element of the "dates" attribute; - * - `[0]dates[0]` represents the first array element of the "dates" attribute - * for the first model in tabular input. - * - * If `$attribute` has neither prefix nor suffix, it will be returned back without change. - * @param string $attribute the attribute name or expression - * @return string the attribute name without prefix and suffix. - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeName($attribute) - { - if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - return $matches[2]; - } else { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - } - - /** - * Returns the value of the specified attribute name or expression. - * - * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. - * See [[getAttributeName()]] for more details about attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return mixed the corresponding attribute value - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeValue($model, $attribute) - { - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $attribute = $matches[2]; - $index = $matches[3]; - if ($index === '') { - return $model->$attribute; - } else { - $value = $model->$attribute; - foreach (explode('][', trim($index, '[]')) as $id) { - if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { - $value = $value[$id]; - } else { - return null; - } - } - return $value; - } - } - - /** - * Generates an appropriate input name for the specified attribute name or expression. - * - * This method generates a name that can be used as the input name to collect user input - * for the specified attribute. The name is generated according to the [[Model::formName|form name]] - * of the model and the given attribute name. For example, if the form name of the `Post` model - * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. - * - * See [[getAttributeName()]] for explanation of attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return string the generated input name - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputName($model, $attribute) - { - $formName = $model->formName(); - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $prefix = $matches[1]; - $attribute = $matches[2]; - $suffix = $matches[3]; - if ($formName === '' && $prefix === '') { - return $attribute . $suffix; - } elseif ($formName !== '') { - return $formName . $prefix . "[$attribute]" . $suffix; - } else { - throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); - } - } - - /** - * Generates an appropriate input ID for the specified attribute name or expression. - * - * This method converts the result [[getInputName()]] into a valid input ID. - * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. - * @return string the generated input ID - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputId($model, $attribute) - { - $name = strtolower(static::getInputName($model, $attribute)); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } -} diff --git a/framework/yii/helpers/AbstractHtmlPurifier.php b/framework/yii/helpers/AbstractHtmlPurifier.php deleted file mode 100644 index 221fc37..0000000 --- a/framework/yii/helpers/AbstractHtmlPurifier.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractHtmlPurifier -{ - /** - * Passes markup through HTMLPurifier making it safe to output to end user - * - * @param string $content - * @param array|null $config - * @return string - */ - public static function process($content, $config = null) - { - $configInstance = \HTMLPurifier_Config::create($config); - $configInstance->autoFinalize = false; - $purifier=\HTMLPurifier::instance($configInstance); - $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); - return $purifier->purify($content); - } -} diff --git a/framework/yii/helpers/AbstractInflector.php b/framework/yii/helpers/AbstractInflector.php deleted file mode 100644 index 27ee4f7..0000000 --- a/framework/yii/helpers/AbstractInflector.php +++ /dev/null @@ -1,480 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractInflector -{ - /** - * @var array the rules for converting a word into its plural form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $plurals = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(m)ove$/i' => '\1oves', - '/(f)oot$/i' => '\1eet', - '/(h)uman$/i' => '\1umans', - '/(s)tatus$/i' => '\1tatuses', - '/(s)taff$/i' => '\1taff', - '/(t)ooth$/i' => '\1eeth', - '/(quiz)$/i' => '\1zes', - '/^(ox)$/i' => '\1\2en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(p)erson$/i' => '\1eople', - '/(m)an$/i' => '\1en', - '/(c)hild$/i' => '\1hildren', - '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', - '/us$/i' => 'uses', - '/(alias)$/i' => '\1es', - '/(ax|cris|test)is$/i' => '\1es', - '/s$/' => 's', - '/^$/' => '', - '/$/' => 's', - ); - /** - * @var array the rules for converting a word into its singular form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $singulars = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(s)tatuses$/i' => '\1tatus', - '/(f)eet$/i' => '\1oot', - '/(t)eeth$/i' => '\1ooth', - '/^(.*)(menu)s$/i' => '\1\2', - '/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\1ix', - '/(vert|ind)ices$/i' => '\1ex', - '/^(ox)en/i' => '\1', - '/(alias)(es)*$/i' => '\1', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', - '/([ftw]ax)es/i' => '\1', - '/(cris|ax|test)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', - '/(o)es$/i' => '\1', - '/ouses$/' => 'ouse', - '/([^a])uses$/' => '\1us', - '/([m|l])ice$/i' => '\1ouse', - '/(x|ch|ss|sh)es$/i' => '\1', - '/(m)ovies$/i' => '\1\2ovie', - '/(s)eries$/i' => '\1\2eries', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([lr])ves$/i' => '\1f', - '/(tive)s$/i' => '\1', - '/(hive)s$/i' => '\1', - '/(drive)s$/i' => '\1', - '/([^fo])ves$/i' => '\1fe', - '/(^analy)ses$/i' => '\1sis', - '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', - '/([ti])a$/i' => '\1um', - '/(p)eople$/i' => '\1\2erson', - '/(m)en$/i' => '\1an', - '/(c)hildren$/i' => '\1\2hild', - '/(n)ews$/i' => '\1\2ews', - '/eaus$/' => 'eau', - '/^(.*us)$/' => '\\1', - '/s$/i' => '', - ); - /** - * @var array the special rules for converting a word between its plural form and singular form. - * The keys are the special words in singular form, and the values are the corresponding plural form. - */ - public static $specials = array( - 'atlas' => 'atlases', - 'beef' => 'beefs', - 'brother' => 'brothers', - 'cafe' => 'cafes', - 'child' => 'children', - 'cookie' => 'cookies', - 'corpus' => 'corpuses', - 'cow' => 'cows', - 'curve' => 'curves', - 'foe' => 'foes', - 'ganglion' => 'ganglions', - 'genie' => 'genies', - 'genus' => 'genera', - 'graffito' => 'graffiti', - 'hoof' => 'hoofs', - 'loaf' => 'loaves', - 'man' => 'men', - 'money' => 'monies', - 'mongoose' => 'mongooses', - 'move' => 'moves', - 'mythos' => 'mythoi', - 'niche' => 'niches', - 'numen' => 'numina', - 'occiput' => 'occiputs', - 'octopus' => 'octopuses', - 'opus' => 'opuses', - 'ox' => 'oxen', - 'penis' => 'penises', - 'sex' => 'sexes', - 'soliloquy' => 'soliloquies', - 'testis' => 'testes', - 'trilby' => 'trilbys', - 'turf' => 'turfs', - 'wave' => 'waves', - 'Amoyese' => 'Amoyese', - 'bison' => 'bison', - 'Borghese' => 'Borghese', - 'bream' => 'bream', - 'breeches' => 'breeches', - 'britches' => 'britches', - 'buffalo' => 'buffalo', - 'cantus' => 'cantus', - 'carp' => 'carp', - 'chassis' => 'chassis', - 'clippers' => 'clippers', - 'cod' => 'cod', - 'coitus' => 'coitus', - 'Congoese' => 'Congoese', - 'contretemps' => 'contretemps', - 'corps' => 'corps', - 'debris' => 'debris', - 'diabetes' => 'diabetes', - 'djinn' => 'djinn', - 'eland' => 'eland', - 'elk' => 'elk', - 'equipment' => 'equipment', - 'Faroese' => 'Faroese', - 'flounder' => 'flounder', - 'Foochowese' => 'Foochowese', - 'gallows' => 'gallows', - 'Genevese' => 'Genevese', - 'Genoese' => 'Genoese', - 'Gilbertese' => 'Gilbertese', - 'graffiti' => 'graffiti', - 'headquarters' => 'headquarters', - 'herpes' => 'herpes', - 'hijinks' => 'hijinks', - 'Hottentotese' => 'Hottentotese', - 'information' => 'information', - 'innings' => 'innings', - 'jackanapes' => 'jackanapes', - 'Kiplingese' => 'Kiplingese', - 'Kongoese' => 'Kongoese', - 'Lucchese' => 'Lucchese', - 'mackerel' => 'mackerel', - 'Maltese' => 'Maltese', - 'mews' => 'mews', - 'moose' => 'moose', - 'mumps' => 'mumps', - 'Nankingese' => 'Nankingese', - 'news' => 'news', - 'nexus' => 'nexus', - 'Niasese' => 'Niasese', - 'Pekingese' => 'Pekingese', - 'Piedmontese' => 'Piedmontese', - 'pincers' => 'pincers', - 'Pistoiese' => 'Pistoiese', - 'pliers' => 'pliers', - 'Portuguese' => 'Portuguese', - 'proceedings' => 'proceedings', - 'rabies' => 'rabies', - 'rice' => 'rice', - 'rhinoceros' => 'rhinoceros', - 'salmon' => 'salmon', - 'Sarawakese' => 'Sarawakese', - 'scissors' => 'scissors', - 'series' => 'series', - 'Shavese' => 'Shavese', - 'shears' => 'shears', - 'siemens' => 'siemens', - 'species' => 'species', - 'swine' => 'swine', - 'testes' => 'testes', - 'trousers' => 'trousers', - 'trout' => 'trout', - 'tuna' => 'tuna', - 'Vermontese' => 'Vermontese', - 'Wenchowese' => 'Wenchowese', - 'whiting' => 'whiting', - 'wildebeest' => 'wildebeest', - 'Yengeese' => 'Yengeese', - ); - /** - * @var array map of special chars and its translation. This is used by [[slug()]]. - */ - public static $transliteration = array( - '/ä|æ|ǽ/' => 'ae', - '/ö|œ/' => 'oe', - '/ü/' => 'ue', - '/Ä/' => 'Ae', - '/Ü/' => 'Ue', - '/Ö/' => 'Oe', - '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', - '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', - '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', - '/ç|ć|ĉ|ċ|č/' => 'c', - '/Ð|Ď|Đ/' => 'D', - '/ð|ď|đ/' => 'd', - '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', - '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', - '/Ĝ|Ğ|Ġ|Ģ/' => 'G', - '/ĝ|ğ|ġ|ģ/' => 'g', - '/Ĥ|Ħ/' => 'H', - '/ĥ|ħ/' => 'h', - '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', - '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', - '/Ĵ/' => 'J', - '/ĵ/' => 'j', - '/Ķ/' => 'K', - '/ķ/' => 'k', - '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', - '/ĺ|ļ|ľ|ŀ|ł/' => 'l', - '/Ñ|Ń|Ņ|Ň/' => 'N', - '/ñ|ń|ņ|ň|ʼn/' => 'n', - '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', - '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', - '/Ŕ|Ŗ|Ř/' => 'R', - '/ŕ|ŗ|ř/' => 'r', - '/Ś|Ŝ|Ş|Ș|Š/' => 'S', - '/ś|ŝ|ş|ș|š|ſ/' => 's', - '/Ţ|Ț|Ť|Ŧ/' => 'T', - '/ţ|ț|ť|ŧ/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', - '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', - '/Ý|Ÿ|Ŷ/' => 'Y', - '/ý|ÿ|ŷ/' => 'y', - '/Ŵ/' => 'W', - '/ŵ/' => 'w', - '/Ź|Ż|Ž/' => 'Z', - '/ź|ż|ž/' => 'z', - '/Æ|Ǽ/' => 'AE', - '/ß/' => 'ss', - '/IJ/' => 'IJ', - '/ij/' => 'ij', - '/Œ/' => 'OE', - '/ƒ/' => 'f' - ); - - /** - * Converts a word to its plural form. - * Note that this is for English only! - * For example, 'apple' will become 'apples', and 'child' will become 'children'. - * @param string $word the word to be pluralized - * @return string the pluralized word - */ - public static function pluralize($word) - { - if (isset(self::$specials[$word])) { - return self::$specials[$word]; - } - foreach (static::$plurals as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Returns the singular of the $word - * @param string $word the english word to singularize - * @return string Singular noun. - */ - public static function singularize($word) - { - $result = array_search($word, self::$specials, true); - if ($result !== false) { - return $result; - } - foreach (static::$singulars as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Converts an underscored or CamelCase word into a English - * sentence. - * @param string $words - * @param bool $ucAll whether to set all words to uppercase - * @return string - */ - public static function titleize($words, $ucAll = false) - { - $words = static::humanize(static::underscore($words), $ucAll); - return $ucAll ? ucwords($words) : ucfirst($words); - } - - /** - * Returns given word as CamelCased - * Converts a word like "send_email" to "SendEmail". It - * will remove non alphanumeric character from the word, so - * "who's online" will be converted to "WhoSOnline" - * @see variablize - * @param string $word the word to CamelCase - * @return string - */ - public static function camelize($word) - { - return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); - } - - /** - * Converts a CamelCase name into space-separated words. - * For example, 'PostTag' will be converted to 'Post Tag'. - * @param string $name the string to be converted - * @param boolean $ucwords whether to capitalize the first letter in each word - * @return string the resulting words - */ - public static function camel2words($name, $ucwords = true) - { - $label = trim(strtolower(str_replace(array( - '-', - '_', - '.' - ), ' ', preg_replace('/(? ' ', - '/\\s+/' => $replacement, - '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', - str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' - ); - return preg_replace(array_keys($map), array_values($map), $string); - } - - /** - * Converts a table name to its class name. For example, converts "people" to "Person" - * @param string $tableName - * @return string - */ - public static function classify($tableName) - { - return static::camelize(static::singularize($tableName)); - } - - /** - * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... - * @param int $number the number to get its ordinal value - * @return string - */ - public static function ordinalize($number) - { - if (in_array(($number % 100), range(11, 13))) { - return $number . 'th'; - } - switch ($number % 10) { - case 1: return $number . 'st'; - case 2: return $number . 'nd'; - case 3: return $number . 'rd'; - default: return $number . 'th'; - } - } -} diff --git a/framework/yii/helpers/AbstractJson.php b/framework/yii/helpers/AbstractJson.php deleted file mode 100644 index cda71a0..0000000 --- a/framework/yii/helpers/AbstractJson.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractJson -{ - /** - * Encodes the given value into a JSON string. - * The method enhances `json_encode()` by supporting JavaScript expressions. - * In particular, the method will not encode a JavaScript expression that is - * represented in terms of a [[JsExpression]] object. - * @param mixed $value the data to be encoded - * @param integer $options the encoding options. For more details please refer to - * [[http://www.php.net/manual/en/function.json-encode.php]] - * @return string the encoding result - */ - public static function encode($value, $options = 0) - { - $expressions = array(); - $value = static::processData($value, $expressions, uniqid()); - $json = json_encode($value, $options); - return empty($expressions) ? $json : strtr($json, $expressions); - } - - /** - * Decodes the given JSON string into a PHP data structure. - * @param string $json the JSON string to be decoded - * @param boolean $asArray whether to return objects in terms of associative arrays. - * @return mixed the PHP data - * @throws InvalidParamException if there is any decoding error - */ - public static function decode($json, $asArray = true) - { - if (is_array($json)) { - throw new InvalidParamException('Invalid JSON data.'); - } - $decode = json_decode((string)$json, $asArray); - switch (json_last_error()) { - case JSON_ERROR_NONE: - break; - case JSON_ERROR_DEPTH: - throw new InvalidParamException('The maximum stack depth has been exceeded.'); - case JSON_ERROR_CTRL_CHAR: - throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); - case JSON_ERROR_SYNTAX: - throw new InvalidParamException('Syntax error.'); - case JSON_ERROR_STATE_MISMATCH: - throw new InvalidParamException('Invalid or malformed JSON.'); - case JSON_ERROR_UTF8: - throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); - default: - throw new InvalidParamException('Unknown JSON decoding error.'); - } - - return $decode; - } - - /** - * Pre-processes the data before sending it to `json_encode()`. - * @param mixed $data the data to be processed - * @param array $expressions collection of JavaScript expressions - * @param string $expPrefix a prefix internally used to handle JS expressions - * @return mixed the processed data - */ - protected static function processData($data, &$expressions, $expPrefix) - { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $data[$key] = static::processData($value, $expressions, $expPrefix); - } - } - return $data; - } elseif (is_object($data)) { - if ($data instanceof JsExpression) { - $token = "!{[$expPrefix=" . count($expressions) . ']}!'; - $expressions['"' . $token . '"'] = $data->expression; - return $token; - } else { - $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions, $expPrefix); - } else { - $result[$key] = $value; - } - } - return $result; - } - } else { - return $data; - } - } -} diff --git a/framework/yii/helpers/AbstractMarkdown.php b/framework/yii/helpers/AbstractMarkdown.php deleted file mode 100644 index 6b76b44..0000000 --- a/framework/yii/helpers/AbstractMarkdown.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractMarkdown -{ - /** - * @var MarkdownExtra - */ - protected static $markdown; - - /** - * Converts markdown into HTML - * - * @param string $content - * @param array $config - * @return string - */ - public static function process($content, $config = array()) - { - if (static::$markdown === null) { - static::$markdown = new MarkdownExtra(); - } - foreach ($config as $name => $value) { - static::$markdown->{$name} = $value; - } - return static::$markdown->transform($content); - } -} diff --git a/framework/yii/helpers/AbstractSecurity.php b/framework/yii/helpers/AbstractSecurity.php deleted file mode 100644 index d308b7f..0000000 --- a/framework/yii/helpers/AbstractSecurity.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @author Tom Worster - * @since 2.0 - */ -abstract class AbstractSecurity -{ - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see decrypt() - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - srand(); - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); - mcrypt_generic_init($module, $key, $iv); - $encrypted = $iv . mcrypt_generic($module, $data); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; - } - - /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see encrypt() - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. - * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. - * @return string the data prefixed with the keyed hash - * @see validateData() - * @see getSecretKey() - */ - public static function hashData($data, $key, $algorithm = 'sha256') - { - return hash_hmac($algorithm, $data, $key) . $data; - } - - /** - * Validates if the given data is tampered. - * @param string $data the data to be validated. The data must be previously - * generated by [[hashData()]]. - * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. This must be the same - * as the value passed to [[hashData()]] when generating the hash for the data. - * @return string the real data with the hash stripped off. False if the data is tampered. - * @see hashData() - */ - public static function validateData($data, $key, $algorithm = 'sha256') - { - $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::strlen($data); - if ($n >= $hashSize) { - $hash = StringHelper::substr($data, 0, $hashSize); - $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); - return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory - * so that the same secret key can be returned in future requests. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @return string the secret key associated with the specified name - */ - public static function getSecretKey($name, $length = 32) - { - static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; - if ($keys === null) { - $keys = array(); - if (is_file($keyFile)) { - $keys = require($keyFile); - } - } - if (!isset($keys[$name])) { - $keys[$name] = static::generateRandomKey($length); - file_put_contents($keyFile, " 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 31) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } -} diff --git a/framework/yii/helpers/AbstractStringHelper.php b/framework/yii/helpers/AbstractStringHelper.php deleted file mode 100644 index 050481e..0000000 --- a/framework/yii/helpers/AbstractStringHelper.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -abstract class AbstractStringHelper -{ - /** - * Returns the number of bytes in the given string. - * This method ensures the string is treated as a byte array by using `mb_strlen()`. - * @param string $string the string being measured for length - * @return integer the number of bytes in the given string. - */ - public static function strlen($string) - { - return mb_strlen($string, '8bit'); - } - - /** - * Returns the portion of string specified by the start and length parameters. - * This method ensures the string is treated as a byte array by using `mb_substr()`. - * @param string $string the input string. Must be one character or longer. - * @param integer $start the starting position - * @param integer $length the desired portion length - * @return string the extracted part of string, or FALSE on failure or an empty string. - * @see http://www.php.net/manual/en/function.substr.php - */ - public static function substr($string, $start, $length) - { - return mb_substr($string, $start, $length, '8bit'); - } - - /** - * Returns the trailing name component of a path. - * This method is similar to the php function `basename()` except that it will - * treat both \ and / as directory separators, independent of the operating system. - * This method was mainly created to work on php namespaces. When working with real - * file paths, php's `basename()` should work fine for you. - * Note: this method is not aware of the actual filesystem, or path components such as "..". - * @param string $path A path string. - * @param string $suffix If the name component ends in suffix this will also be cut off. - * @return string the trailing name component of the given path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function basename($path, $suffix = '') - { - if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { - $path = mb_substr($path, 0, -$len); - } - $path = rtrim(str_replace('\\', '/', $path), '/\\'); - if (($pos = mb_strrpos($path, '/')) !== false) { - return mb_substr($path, $pos + 1); - } - return $path; - } - - /** - * Returns parent directory's path. - * This method is similar to `dirname()` except that it will treat - * both \ and / as directory separators, independent of the operating system. - * @param string $path A path string. - * @return string the parent directory's path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function dirname($path) - { - $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); - if ($pos !== false) { - return mb_substr($path, 0, $pos); - } else { - return $path; - } - } - - /** - * Compares two strings or string arrays, and return their differences. - * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. - * @param string|array $lines1 the first string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string|array $lines2 the second string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. - * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other - * formats, a string is returned. - * @throws InvalidParamException if the format is invalid. - */ - public static function diff($lines1, $lines2, $format = 'inline') - { - if (!is_array($lines1)) { - $lines1 = explode("\n", $lines1); - } - if (!is_array($lines2)) { - $lines2 = explode("\n", $lines2); - } - foreach ($lines1 as $i => $line) { - $lines1[$i] = rtrim($line, "\r\n"); - } - foreach ($lines2 as $i => $line) { - $lines2[$i] = rtrim($line, "\r\n"); - } - switch ($format) { - case 'inline': - $renderer = new \Diff_Renderer_Html_Inline(); - break; - case 'array': - $renderer = new \Diff_Renderer_Html_Array(); - break; - case 'side-by-side': - $renderer = new \Diff_Renderer_Html_SideBySide(); - break; - case 'context': - $renderer = new \Diff_Renderer_Text_Context(); - break; - case 'unified': - $renderer = new \Diff_Renderer_Text_Unified(); - break; - default: - throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); - } - $diff = new \Diff($lines1, $lines2); - return $diff->render($renderer); - } -} diff --git a/framework/yii/helpers/AbstractVarDumper.php b/framework/yii/helpers/AbstractVarDumper.php deleted file mode 100644 index 2c9f194..0000000 --- a/framework/yii/helpers/AbstractVarDumper.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\helpers; - -/** - * AbstractVarDumper provides concrete implementation for [[VarDumper]]. - * - * Do not use AbstractVarDumper. Use [[VarDumper]] instead. - * - * @author Qiang Xue - * @since 2.0 - */ -abstract class AbstractVarDumper -{ - private static $_objects; - private static $_output; - private static $_depth; - - /** - * Displays a variable. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - */ - public static function dump($var, $depth = 10, $highlight = false) - { - echo static::dumpAsString($var, $depth, $highlight); - } - - /** - * Dumps a variable in terms of a string. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - * @return string the string representation of the variable - */ - public static function dumpAsString($var, $depth = 10, $highlight = false) - { - self::$_output = ''; - self::$_objects = array(); - self::$_depth = $depth; - self::dumpInternal($var, 0); - if ($highlight) { - $result = highlight_string("/', '', $result, 1); - } - return self::$_output; - } - - /** - * @param mixed $var variable to be dumped - * @param integer $level depth level - */ - private static function dumpInternal($var, $level) - { - switch (gettype($var)) { - case 'boolean': - self::$_output .= $var ? 'true' : 'false'; - break; - case 'integer': - self::$_output .= "$var"; - break; - case 'double': - self::$_output .= "$var"; - break; - case 'string': - self::$_output .= "'" . addslashes($var) . "'"; - break; - case 'resource': - self::$_output .= '{resource}'; - break; - case 'NULL': - self::$_output .= "null"; - break; - case 'unknown type': - self::$_output .= '{unknown}'; - break; - case 'array': - if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; - } elseif (empty($var)) { - self::$_output .= 'array()'; - } else { - $keys = array_keys($var); - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; - foreach ($keys as $key) { - self::$_output .= "\n" . $spaces . ' '; - self::dumpInternal($key, 0); - self::$_output .= ' => '; - self::dumpInternal($var[$key], $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - case 'object': - if (($id = array_search($var, self::$_objects, true)) !== false) { - self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; - } elseif (self::$_depth <= $level) { - self::$_output .= get_class($var) . '(...)'; - } else { - $id = array_push(self::$_objects, $var); - $className = get_class($var); - $members = (array)$var; - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); - self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; - self::dumpInternal($value, $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - } - } -} diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index a63d3c2..9d428f5 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper extends AbstractArrayHelper +class ArrayHelper extends BaseArrayHelper { } diff --git a/framework/yii/helpers/BaseArrayHelper.php b/framework/yii/helpers/BaseArrayHelper.php new file mode 100644 index 0000000..0ed584f --- /dev/null +++ b/framework/yii/helpers/BaseArrayHelper.php @@ -0,0 +1,451 @@ + + * @since 2.0 + */ +class BaseArrayHelper +{ + /** + * Converts an object or an array of objects into an array. + * @param object|array $object the object to be converted into an array + * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. + * The properties specified for each class is an array of the following format: + * + * ~~~ + * array( + * 'app\models\Post' => array( + * 'id', + * 'title', + * // the key name in array result => property name + * 'createTime' => 'create_time', + * // the key name in array result => anonymous function + * 'length' => function ($post) { + * return strlen($post->content); + * }, + * ), + * ) + * ~~~ + * + * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: + * + * ~~~ + * array( + * 'id' => 123, + * 'title' => 'test', + * 'createTime' => '2013-01-01 12:00AM', + * 'length' => 301, + * ) + * ~~~ + * + * @param boolean $recursive whether to recursively converts properties which are objects into arrays. + * @return array the array representation of the object + */ + public static function toArray($object, $properties = array(), $recursive = true) + { + if (!empty($properties) && is_object($object)) { + $className = get_class($object); + if (!empty($properties[$className])) { + $result = array(); + foreach ($properties[$className] as $key => $name) { + if (is_int($key)) { + $result[$name] = $object->$name; + } else { + $result[$key] = static::getValue($object, $name); + } + } + return $result; + } + } + if ($object instanceof Arrayable) { + $object = $object->toArray(); + if (!$recursive) { + return $object; + } + } + $result = array(); + foreach ($object as $key => $value) { + if ($recursive && (is_array($value) || is_object($value))) { + $result[$key] = static::toArray($value, true); + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while (!empty($args)) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + isset($res[$k]) ? $res[] = $v : $res[$k] = $v; + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array, the default value will be returned instead. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed the value of the element if found, default value otherwise + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } elseif (is_array($array)) { + return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $array->$key; + } + } + + /** + * Removes an item from an array and returns the value. If the key does not exist in the array, the default value + * will be returned instead. + * + * Usage examples, + * + * ~~~ + * // $array = array('type' => 'A', 'options' => array(1, 2)); + * // working with array + * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); + * // $array content + * // $array = array('options' => array(1, 2)); + * ~~~ + * + * @param array $array the array to extract value from + * @param string $key key name of the array element + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed|null the value of the element if found, default value otherwise + */ + public static function remove(&$array, $key, $default = null) + { + if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + $value = $array[$key]; + unset($array[$key]); + return $value; + } + return $default; + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // array( + * // '123' => array('id' => '123', 'data' => 'abc'), + * // '345' => array('id' => '345', 'data' => 'def'), + * // ) + * + * // using anonymous function + * $result = ArrayHelper::index($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = array(); + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: array( '123', '345') + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = array(); + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), + * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), + * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ) + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // array( + * // 'x' => array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ), + * // 'y' => array( + * // '345' => 'ccc', + * // ), + * // ) + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = array(); + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param boolean|array $descending whether to sort in descending or ascending order. When + * sorting by multiple keys with different descending orders, use an array of descending flags. + * @param integer|array $sortFlag the PHP sort flag. Valid values include + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. + * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) + * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. + * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter + * is used only when `$sortFlag` is `SORT_STRING`. + * When sorting by multiple keys with different case sensitivities, use an array of boolean values. + * @throws InvalidParamException if the $descending or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) + { + $keys = is_array($key) ? $key : array($key); + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($descending)) { + $descending = array_fill(0, $n, $descending); + } elseif (count($descending) !== $n) { + throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); + } + if (is_scalar($caseSensitive)) { + $caseSensitive = array_fill(0, $n, $caseSensitive); + } elseif (count($caseSensitive) !== $n) { + throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); + } + $args = array(); + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + $cs = $caseSensitive[$i]; + if (!$cs && ($flag === SORT_STRING)) { + if (defined('SORT_FLAG_CASE')) { + $flag = $flag | SORT_FLAG_CASE; + $args[] = static::getColumn($array, $key); + } else { + $column = array(); + foreach (static::getColumn($array, $key) as $k => $value) { + $column[$k] = mb_strtolower($value); + } + $args[] = $column; + } + } else { + $args[] = static::getColumn($array, $key); + } + $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + return $d; + } +} diff --git a/framework/yii/helpers/BaseConsole.php b/framework/yii/helpers/BaseConsole.php new file mode 100644 index 0000000..6796283 --- /dev/null +++ b/framework/yii/helpers/BaseConsole.php @@ -0,0 +1,835 @@ + + * @since 2.0 + */ +class BaseConsole +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const RESET = 0; + const NORMAL = 0; + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows = 1) + { + echo "\033[" . (int)$rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows = 1) + { + echo "\033[" . (int)$rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps = 1) + { + echo "\033[" . (int)$steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps = 1) + { + echo "\033[" . (int)$steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines = 1) + { + echo "\033[" . (int)$lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines = 1) + { + echo "\033[" . (int)$lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row = null) + { + if ($row === null) { + echo "\033[" . (int)$column . 'G'; + } else { + echo "\033[" . (int)$row . ';' . (int)$column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll up + */ + public static function scrollUp($lines = 1) + { + echo "\033[" . (int)$lines . "S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll down + */ + public static function scrollDown($lines = 1) + { + echo "\033[" . (int)$lines . "T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with {@link restoreCursorPosition}. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use {@link showCursor} to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Returns the ANSI format code. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string The ANSI format code according to the given formatting constants. + */ + public static function ansiFormatCode($format) + { + return "\033[" . implode(';', $format) . 'm'; + } + + /** + * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @see ansiFormatCode() + * @see ansiFormatEnd() + */ + public static function beginAnsiFormat($format) + { + echo "\033[" . implode(';', $format) . 'm'; + } + + /** + * Resets any ANSI format set by previous method [[ansiFormatBegin()]] + * Any output after this will have default text format. + * This is equal to calling + * + * ```php + * echo Console::ansiFormatCode(array(Console::RESET)) + * ``` + */ + public static function endAnsiFormat() + { + echo "\033[0m"; + } + + /** + * Will return a string formatted with the given ANSI style + * + * @param string $string the string to be formatted + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string + */ + public static function ansiFormat($string, $format = array()) + { + $code = implode(';', $format); + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + } + + /** + * Returns the ansi format code for xterm foreground color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermFgColor($colorCode) + { + return '38;5;' . $colorCode; + } + + /** + * Returns the ansi format code for xterm background color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermBgColor($colorCode) + { + return '48;5;' . $colorCode; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function stripAnsiFormat($string) + { + return preg_replace('/\033\[[\d;?]*\w/', '', $string); + } + + /** + * Converts an ANSI formatted string to HTML + * @param $string + * @return mixed + */ + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + public static function ansiToHtml($string) + { + $tags = 0; + return preg_replace_callback( + '/\033\[[\d;]+m/', + function ($ansi) use (&$tags) { + $styleA = array(); + foreach (explode(';', $ansi) as $controlCode) { + switch ($controlCode) { + case self::FG_BLACK: + $style = array('color' => '#000000'); + break; + case self::FG_BLUE: + $style = array('color' => '#000078'); + break; + case self::FG_CYAN: + $style = array('color' => '#007878'); + break; + case self::FG_GREEN: + $style = array('color' => '#007800'); + break; + case self::FG_GREY: + $style = array('color' => '#787878'); + break; + case self::FG_PURPLE: + $style = array('color' => '#780078'); + break; + case self::FG_RED: + $style = array('color' => '#780000'); + break; + case self::FG_YELLOW: + $style = array('color' => '#787800'); + break; + case self::BG_BLACK: + $style = array('background-color' => '#000000'); + break; + case self::BG_BLUE: + $style = array('background-color' => '#000078'); + break; + case self::BG_CYAN: + $style = array('background-color' => '#007878'); + break; + case self::BG_GREEN: + $style = array('background-color' => '#007800'); + break; + case self::BG_GREY: + $style = array('background-color' => '#787878'); + break; + case self::BG_PURPLE: + $style = array('background-color' => '#780078'); + break; + case self::BG_RED: + $style = array('background-color' => '#780000'); + break; + case self::BG_YELLOW: + $style = array('background-color' => '#787800'); + break; + case self::BOLD: + $style = array('font-weight' => 'bold'); + break; + case self::ITALIC: + $style = array('font-style' => 'italic'); + break; + case self::UNDERLINE: + $style = array('text-decoration' => array('underline')); + break; + case self::OVERLINED: + $style = array('text-decoration' => array('overline')); + break; + case self::CROSSED_OUT: + $style = array('text-decoration' => array('line-through')); + break; + case self::BLINK: + $style = array('text-decoration' => array('blink')); + break; + case self::NEGATIVE: // ??? + case self::CONCEALED: + case self::ENCIRCLED: + case self::FRAMED: + // TODO allow resetting codes + break; + case 0: // ansi reset + $return = ''; + for ($n = $tags; $tags > 0; $tags--) { + $return .= ''; + } + return $return; + } + + $styleA = ArrayHelper::merge($styleA, $style); + } + $styleString[] = array(); + foreach ($styleA as $name => $content) { + if ($name === 'text-decoration') { + $content = implode(' ', $content); + } + $styleString[] = $name . ':' . $content; + } + $tags++; + return ' $value) { + echo " $key - $value\n"; + } + echo " ? - Show help\n"; + goto top; + } elseif (!in_array($input, array_keys($options))) { + goto top; + } + return $input; + } + + /** + * Displays and updates a simple progress bar on screen. + * + * @param integer $done the number of items that are completed + * @param integer $total the total value of items that are to be done + * @param integer $size the size of the status bar (optional) + * @see http://snipplr.com/view/29548/ + */ + public static function showProgress($done, $total, $size = 30) + { + static $start; + + // if we go over our bound, just ignore it + if ($done > $total) { + return; + } + + if (empty($start)) { + $start = time(); + } + + $now = time(); + + $percent = (double)($done / $total); + $bar = floor($percent * $size); + + $status = "\r["; + $status .= str_repeat("=", $bar); + if ($bar < $size) { + $status .= ">"; + $status .= str_repeat(" ", $size - $bar); + } else { + $status .= "="; + } + + $display = number_format($percent * 100, 0); + + $status .= "] $display% $done/$total"; + + $rate = ($now - $start) / $done; + $left = $total - $done; + $eta = round($rate * $left, 2); + + $elapsed = $now - $start; + + $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; + + static::stdout("$status "); + + flush(); + + // when done, send a newline + if ($done == $total) { + echo "\n"; + } + } +} diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php new file mode 100644 index 0000000..2c911b0 --- /dev/null +++ b/framework/yii/helpers/BaseFileHelper.php @@ -0,0 +1,329 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseFileHelper +{ + /** + * Normalizes a file/directory path. + * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, + * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux + * will be normalized as '/home/demo'. + * @param string $path the file/directory path to be normalized + * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. + * @return string the normalized file/directory path + */ + public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) + { + return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_CN", the localized file will be looked for as + * "path/to/zh_CN/view.php". If the file is not found, the original file + * will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); + return is_file($desiredFile) ? $desiredFile : $file; + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will + * fall back to [[getMimeTypeByExtension()]]. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if (function_exists('finfo_open')) { + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + if ($info) { + $result = finfo_file($info, $file); + finfo_close($info); + if ($result !== false) { + return $result; + } + } + } + + return $checkExtension ? static::getMimeTypeByExtension($file) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path of the file that contains all available MIME type information. + * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + static $mimeTypes = array(); + if ($magicFile === null) { + $magicFile = __DIR__ . '/mimeTypes.php'; + } + if (!isset($mimeTypes[$magicFile])) { + $mimeTypes[$magicFile] = require($magicFile); + } + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$magicFile][$ext])) { + return $mimeTypes[$magicFile][$ext]; + } + } + return null; + } + + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be copied (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be copied + * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file copied from, while `$to` is the copy target. + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($from, $options)) { + if (is_file($from)) { + copy($from, $to); + if (isset($options['fileMode'])) { + @chmod($to, $options['fileMode']); + } + } else { + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); + } + } + } + closedir($handle); + } + + /** + * Removes a directory (and all its content) recursively. + * @param string $dir the directory to be deleted recursively. + */ + public static function removeDirectory($dir) + { + if (!is_dir($dir) || !($handle = opendir($dir))) { + return; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_file($path)) { + unlink($path); + } else { + static::removeDirectory($path); + } + } + closedir($handle); + rmdir($dir); + } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be returned (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be returned + * + * - only: array, list of patterns that the file paths should match if they want to be returned. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be returned. + * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. + * @return array files found under the directory. The file list is sorted. + */ + public static function findFiles($dir, $options = array()) + { + $list = array(); + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($path, $options)) { + if (is_file($path)) { + $list[] = $path; + } elseif (!isset($options['recursive']) || $options['recursive']) { + $list = array_merge($list, static::findFiles($path, $options)); + } + } + } + closedir($handle); + return $list; + } + + /** + * Checks if the given file path satisfies the filtering options. + * @param string $path the path of the file or directory to be checked + * @param array $options the filtering options. See [[findFiles()]] for explanations of + * the supported options. + * @return boolean whether the file or directory satisfies the filtering options. + */ + public static function filterPath($path, $options) + { + if (isset($options['filter'])) { + $result = call_user_func($options['filter'], $path); + if (is_bool($result)) { + return $result; + } + } + $path = str_replace('\\', '/', $path); + if ($isDir = is_dir($path)) { + $path .= '/'; + } + $n = StringHelper::strlen($path); + + if (!empty($options['except'])) { + foreach ($options['except'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return false; + } + } + } + + if (!$isDir && !empty($options['only'])) { + foreach ($options['only'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return true; + } + } + return false; + } + return true; + } + + /** + * Creates a new directory. + * + * This method is similar to the PHP `mkdir()` function except that + * it uses `chmod()` to set the permission of the created directory + * in order to avoid the impact of the `umask` setting. + * + * @param string $path path of the directory to be created. + * @param integer $mode the permission to be set for the created directory. + * @param boolean $recursive whether to create parent directories if they do not exist. + * @return boolean whether the directory is created successfully + */ + public static function createDirectory($path, $mode = 0775, $recursive = true) + { + if (is_dir($path)) { + return true; + } + $parentDir = dirname($path); + if ($recursive && !is_dir($parentDir)) { + static::createDirectory($parentDir, $mode, true); + } + $result = mkdir($path, $mode); + chmod($path, $mode); + return $result; + } +} diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php new file mode 100644 index 0000000..2baa679 --- /dev/null +++ b/framework/yii/helpers/BaseHtml.php @@ -0,0 +1,1599 @@ + + * @since 2.0 + */ +class BaseHtml +{ + /** + * @var array list of void elements (element name => 1) + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = array( + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderTagAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); + + /** + * Encodes special characters into HTML entities. + * The [[yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, + * HTML entities in `$content` will not be further encoded. + * @return string the encoded content + * @see decode + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content, $doubleEncode = true) + { + return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated HTML tag + * @see beginTag + * @see endTag + */ + public static function tag($name, $content = '', $options = array()) + { + $html = "<$name" . static::renderTagAttributes($options) . '>'; + return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated start tag + * @see endTag + * @see tag + */ + public static function beginTag($name, $options = array()) + { + return "<$name" . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag + * @see tag + */ + public static function endTag($name) + { + return ""; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * @return string the generated style tag + */ + public static function style($content, $options = array()) + { + return static::tag('style', $content, $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * @return string the generated script tag + */ + public static function script($content, $options = array()) + { + return static::tag('script', $content, $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated link tag + * @see url + */ + public static function cssFile($url, $options = array()) + { + $options['rel'] = 'stylesheet'; + $options['href'] = static::url($url); + return static::tag('link', '', $options); + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated script tag + * @see url + */ + public static function jsFile($url, $options = array()) + { + $options['src'] = static::url($url); + return static::tag('script', '', $options); + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). + * Since most browsers only support "post" and "get", if other methods are given, they will + * be simulated using "post", and a hidden input will be added which contains the actual method type. + * See [[\yii\web\Request::restVar]] for more details. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated form start tag. + * @see endForm + */ + public static function beginForm($action = '', $method = 'post', $options = array()) + { + $action = static::url($action); + + $hiddenInputs = array(); + + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { + // simulate PUT, DELETE, etc. via POST + $hiddenInputs[] = static::hiddenInput($request->restVar, $method); + $method = 'post'; + } + if ($request->enableCsrfValidation) { + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); + } + } + + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddenInputs[] = static::hiddenInput( + urldecode(substr($pair, 0, $pos1)), + urldecode(substr($pair, $pos1 + 1)) + ); + } else { + $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if (!empty($hiddenInputs)) { + $form .= "\n" . implode("\n", $hiddenInputs); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return ''; + } + + /** + * 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 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 + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated hyperlink + * @see url + */ + public static function a($text, $url = null, $options = array()) + { + if ($url !== null) { + $options['href'] = static::url($url); + } + return static::tag('a', $text, $options); + } + + /** + * 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 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. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = array()) + { + $options['href'] = 'mailto:' . ($email === null ? $text : $email); + return static::tag('a', $text, $options); + } + + /** + * Generates an image tag. + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated image tag + */ + public static function img($src, $options = array()) + { + $options['src'] = static::url($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', '', $options); + } + + /** + * Generates a label tag. + * @param string $content label text. 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 [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = array()) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function button($content = 'Button', $options = array()) + { + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated submit button tag + */ + public static function submitButton($content = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + return static::button($content, $options); + } + + /** + * Generates a reset button tag. + * @param string $content the content enclosed within the button tag. 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()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated reset button tag + */ + public static function resetButton($content = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + return static::button($content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = array()) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value === null ? null : (string)$value; + return static::tag('input', '', $options); + } + + /** + * Generates an input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function buttonInput($label = 'Button', $options = array()) + { + $options['type'] = 'button'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a submit input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function submitInput($label = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a reset input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag + */ + public static function resetInput($label = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = array()) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = array()) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = array()) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = array()) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = array()) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'radio')); + } else { + return $hidden . static::input('radio', $name, $value, $options); + } + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $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']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'checkbox')); + } else { + return $hidden . static::input('checkbox', $name, $value, $options); + } + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + { + $options['name'] = $name; + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = array(), $options = array()) + { + if (!isset($options['size'])) { + $options['size'] = 4; + } + if (!empty($options['multiple']) && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * @param array $options options (name => config) for the checkbox list container tag. + * The following options are specially handled: + * + * - tag: string, the tag name of the container element. + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input, respectively. + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $encode = !isset($options['encode']) || $options['encode']; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::checkbox($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input, respectively. + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = array(), $options = array()) + { + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::radio($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates an unordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated unordered list. An empty string is returned if `$items` is empty. + */ + public static function ul($items, $options = array()) + { + if (empty($items)) { + return ''; + } + $tag = isset($options['tag']) ? $options['tag'] : 'ul'; + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); + unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + $results = array(); + foreach ($items as $index => $item) { + if ($formatter !== null) { + $results[] = call_user_func($formatter, $item, $index); + } else { + $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); + } + } + return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + } + + /** + * Generates an ordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated ordered list. An empty string is returned if `$items` is empty. + */ + public static function ol($items, $options = array()) + { + $options['tag'] = 'ol'; + return static::ul($items, $options); + } + + /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. + * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display + * (after encoding). + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + unset($options['label'], $options['for']); + return static::label($label, $for, $options); + } + + /** + * Generates a tag that contains the first validation error of the specified model attribute. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded + * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * @return string the generated label tag + */ + public static function error($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $error = $model->getFirstError($attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag']); + return Html::tag($tag, Html::encode($error), $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = array()) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = array()) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = array()) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = array()) + { + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = array()) + { + $name = static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radio($name, $checked, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. 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 [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkbox($name, $checked, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::dropDownList($name, $checked, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::listBox($name, $checked, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkboxList($name, $checked, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radioList($name, $checked, $items, $options); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + { + $lines = array(); + if (isset($tagOptions['prompt'])) { + $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); + $lines[] = static::tag('option', $prompt, array('value' => '')); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = (string)$key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * Attributes whose values are of boolean type will be treated as + * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). + * And attributes whose values are null will not be rendered. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (so that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (is_bool($value)) { + if ($value) { + $html .= " $name"; + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + return $html; + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result + * is an absolute URL, it will be returned without any change further; Otherwise, the result + * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. + * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + $route = $url[0]; + $params = array_splice($url, 1); + if (Yii::$app->controller instanceof \yii\web\Controller) { + return Yii::$app->controller->createUrl($route, $params); + } else { + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + $url = Yii::getAlias($url); + if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { + return $url; + } else { + return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; + } + } + } + + /** + * Adds a CSS class to the specified options. + * If the CSS class is already in the options, it will not be added again. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added + */ + public static function addCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = ' ' . $options['class'] . ' '; + if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { + $options['class'] .= ' ' . $class; + } + } else { + $options['class'] = $class; + } + } + + /** + * Removes a CSS class from the specified options. + * @param array $options the options to be modified. + * @param string $class the CSS class to be removed + */ + public static function removeCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); + if (($index = array_search($class, $classes)) !== false) { + unset($classes[$index]); + } + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } + } + } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return mixed the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $index = $matches[3]; + if ($index === '') { + return $model->$attribute; + } else { + $value = $model->$attribute; + foreach (explode('][', trim($index, '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + return $value; + } + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputName($model, $attribute) + { + $formName = $model->formName(); + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $prefix = $matches[1]; + $attribute = $matches[2]; + $suffix = $matches[3]; + if ($formName === '' && $prefix === '') { + return $attribute . $suffix; + } elseif ($formName !== '') { + return $formName . $prefix . "[$attribute]" . $suffix; + } else { + throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = strtolower(static::getInputName($model, $attribute)); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } +} diff --git a/framework/yii/helpers/BaseHtmlPurifier.php b/framework/yii/helpers/BaseHtmlPurifier.php new file mode 100644 index 0000000..17d2122 --- /dev/null +++ b/framework/yii/helpers/BaseHtmlPurifier.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class BaseHtmlPurifier +{ + /** + * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content + * @param array|null $config + * @return string + */ + public static function process($content, $config = null) + { + $configInstance = \HTMLPurifier_Config::create($config); + $configInstance->autoFinalize = false; + $purifier=\HTMLPurifier::instance($configInstance); + $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + return $purifier->purify($content); + } +} diff --git a/framework/yii/helpers/BaseInflector.php b/framework/yii/helpers/BaseInflector.php new file mode 100644 index 0000000..affd3dd --- /dev/null +++ b/framework/yii/helpers/BaseInflector.php @@ -0,0 +1,480 @@ + + * @since 2.0 + */ +class BaseInflector +{ + /** + * @var array the rules for converting a word into its plural form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $plurals = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ); + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $singulars = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ); + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + public static $specials = array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ); + /** + * @var array map of special chars and its translation. This is used by [[slug()]]. + */ + public static $transliteration = array( + '/ä|æ|ǽ/' => 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Ð|Ď|Đ/' => 'D', + '/ð|ď|đ/' => 'd', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', + '/Ĝ|Ğ|Ġ|Ģ/' => 'G', + '/ĝ|ğ|ġ|ģ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/ķ/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł/' => 'l', + '/Ñ|Ń|Ņ|Ň/' => 'N', + '/ñ|ń|ņ|ň|ʼn/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', + '/Ŕ|Ŗ|Ř/' => 'R', + '/ŕ|ŗ|ř/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š/' => 'S', + '/ś|ŝ|ş|ș|š|ſ/' => 's', + '/Ţ|Ț|Ť|Ŧ/' => 'T', + '/ţ|ț|ť|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', + '/Ý|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|ŷ/' => 'y', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f' + ); + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($word) + { + if (isset(self::$specials[$word])) { + return self::$specials[$word]; + } + foreach (static::$plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Returns the singular of the $word + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public static function singularize($word) + { + $result = array_search($word, self::$specials, true); + if ($result !== false) { + return $result; + } + foreach (static::$singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * @param string $words + * @param bool $ucAll whether to set all words to uppercase + * @return string + */ + public static function titleize($words, $ucAll = false) + { + $words = static::humanize(static::underscore($words), $ucAll); + return $ucAll ? ucwords($words) : ucfirst($words); + } + + /** + * Returns given word as CamelCased + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * @see variablize + * @param string $word the word to CamelCase + * @return string + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace(array( + '-', + '_', + '.' + ), ' ', preg_replace('/(? ' ', + '/\\s+/' => $replacement, + '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', + str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' + ); + return preg_replace(array_keys($map), array_values($map), $string); + } + + /** + * Converts a table name to its class name. For example, converts "people" to "Person" + * @param string $tableName + * @return string + */ + public static function classify($tableName) + { + return static::camelize(static::singularize($tableName)); + } + + /** + * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... + * @param int $number the number to get its ordinal value + * @return string + */ + public static function ordinalize($number) + { + if (in_array(($number % 100), range(11, 13))) { + return $number . 'th'; + } + switch ($number % 10) { + case 1: return $number . 'st'; + case 2: return $number . 'nd'; + case 3: return $number . 'rd'; + default: return $number . 'th'; + } + } +} diff --git a/framework/yii/helpers/BaseJson.php b/framework/yii/helpers/BaseJson.php new file mode 100644 index 0000000..bd6ede5 --- /dev/null +++ b/framework/yii/helpers/BaseJson.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +class BaseJson +{ + /** + * Encodes the given value into a JSON string. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * @param mixed $value the data to be encoded + * @param integer $options the encoding options. For more details please refer to + * [[http://www.php.net/manual/en/function.json-encode.php]] + * @return string the encoding result + */ + public static function encode($value, $options = 0) + { + $expressions = array(); + $value = static::processData($value, $expressions, uniqid()); + $json = json_encode($value, $options); + return empty($expressions) ? $json : strtr($json, $expressions); + } + + /** + * Decodes the given JSON string into a PHP data structure. + * @param string $json the JSON string to be decoded + * @param boolean $asArray whether to return objects in terms of associative arrays. + * @return mixed the PHP data + * @throws InvalidParamException if there is any decoding error + */ + public static function decode($json, $asArray = true) + { + if (is_array($json)) { + throw new InvalidParamException('Invalid JSON data.'); + } + $decode = json_decode((string)$json, $asArray); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + case JSON_ERROR_DEPTH: + throw new InvalidParamException('The maximum stack depth has been exceeded.'); + case JSON_ERROR_CTRL_CHAR: + throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); + case JSON_ERROR_SYNTAX: + throw new InvalidParamException('Syntax error.'); + case JSON_ERROR_STATE_MISMATCH: + throw new InvalidParamException('Invalid or malformed JSON.'); + case JSON_ERROR_UTF8: + throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); + default: + throw new InvalidParamException('Unknown JSON decoding error.'); + } + + return $decode; + } + + /** + * Pre-processes the data before sending it to `json_encode()`. + * @param mixed $data the data to be processed + * @param array $expressions collection of JavaScript expressions + * @param string $expPrefix a prefix internally used to handle JS expressions + * @return mixed the processed data + */ + protected static function processData($data, &$expressions, $expPrefix) + { + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); + } + } + return $data; + } elseif (is_object($data)) { + if ($data instanceof JsExpression) { + $token = "!{[$expPrefix=" . count($expressions) . ']}!'; + $expressions['"' . $token . '"'] = $data->expression; + return $token; + } else { + $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); + $result = array(); + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $result[$key] = static::processData($value, $expressions, $expPrefix); + } else { + $result[$key] = $value; + } + } + return $result; + } + } else { + return $data; + } + } +} diff --git a/framework/yii/helpers/BaseMarkdown.php b/framework/yii/helpers/BaseMarkdown.php new file mode 100644 index 0000000..40a1dc4 --- /dev/null +++ b/framework/yii/helpers/BaseMarkdown.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +class BaseMarkdown +{ + /** + * @var MarkdownExtra + */ + protected static $markdown; + + /** + * Converts markdown into HTML + * + * @param string $content + * @param array $config + * @return string + */ + public static function process($content, $config = array()) + { + if (static::$markdown === null) { + static::$markdown = new MarkdownExtra(); + } + foreach ($config as $name => $value) { + static::$markdown->{$name} = $value; + } + return static::$markdown->transform($content); + } +} diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php new file mode 100644 index 0000000..3be07b4 --- /dev/null +++ b/framework/yii/helpers/BaseSecurity.php @@ -0,0 +1,285 @@ + + * @author Tom Worster + * @since 2.0 + */ +class BaseSecurity +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + srand(); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + mcrypt_generic_init($module, $key, $iv); + $encrypted = $iv . mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return $encrypted; + } + + /** + * Decrypts data + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $keys = array(); + if (is_file($keyFile)) { + $keys = require($keyFile); + } + } + if (!isset($keys[$name])) { + $keys[$name] = static::generateRandomKey($length); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 31) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} diff --git a/framework/yii/helpers/BaseStringHelper.php b/framework/yii/helpers/BaseStringHelper.php new file mode 100644 index 0000000..e1622b9 --- /dev/null +++ b/framework/yii/helpers/BaseStringHelper.php @@ -0,0 +1,138 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseStringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array by using `mb_strlen()`. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function strlen($string) + { + return mb_strlen($string, '8bit'); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array by using `mb_substr()`. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function substr($string, $start, $length) + { + return mb_substr($string, $start, $length, '8bit'); + } + + /** + * Returns the trailing name component of a path. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. + * This method was mainly created to work on php namespaces. When working with real + * file paths, php's `basename()` should work fine for you. + * Note: this method is not aware of the actual filesystem, or path components such as "..". + * @param string $path A path string. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string the trailing name component of the given path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function basename($path, $suffix = '') + { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + $path = mb_substr($path, 0, -$len); + } + $path = rtrim(str_replace('\\', '/', $path), '/\\'); + if (($pos = mb_strrpos($path, '/')) !== false) { + return mb_substr($path, $pos + 1); + } + return $path; + } + + /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** + * Compares two strings or string arrays, and return their differences. + * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. + * @param string|array $lines1 the first string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string|array $lines2 the second string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. + * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other + * formats, a string is returned. + * @throws InvalidParamException if the format is invalid. + */ + public static function diff($lines1, $lines2, $format = 'inline') + { + if (!is_array($lines1)) { + $lines1 = explode("\n", $lines1); + } + if (!is_array($lines2)) { + $lines2 = explode("\n", $lines2); + } + foreach ($lines1 as $i => $line) { + $lines1[$i] = rtrim($line, "\r\n"); + } + foreach ($lines2 as $i => $line) { + $lines2[$i] = rtrim($line, "\r\n"); + } + switch ($format) { + case 'inline': + $renderer = new \Diff_Renderer_Html_Inline(); + break; + case 'array': + $renderer = new \Diff_Renderer_Html_Array(); + break; + case 'side-by-side': + $renderer = new \Diff_Renderer_Html_SideBySide(); + break; + case 'context': + $renderer = new \Diff_Renderer_Text_Context(); + break; + case 'unified': + $renderer = new \Diff_Renderer_Text_Unified(); + break; + default: + throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); + } + $diff = new \Diff($lines1, $lines2); + return $diff->render($renderer); + } +} diff --git a/framework/yii/helpers/BaseVarDumper.php b/framework/yii/helpers/BaseVarDumper.php new file mode 100644 index 0000000..f109125 --- /dev/null +++ b/framework/yii/helpers/BaseVarDumper.php @@ -0,0 +1,127 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\helpers; + +/** + * BaseVarDumper provides concrete implementation for [[VarDumper]]. + * + * Do not use BaseVarDumper. Use [[VarDumper]] instead. + * + * @author Qiang Xue + * @since 2.0 + */ +class BaseVarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo static::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = array(); + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + return self::$_output; + } + + /** + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= 'array(...)'; + } elseif (empty($var)) { + self::$_output .= 'array()'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "array\n" . $spaces . '('; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = array_push(self::$_objects, $var); + $className = get_class($var); + $members = (array)$var; + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ($members as $key => $value) { + $keyDisplay = strtr(trim($key), array("\0" => ':')); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } +} diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index 9b0656e..a34dc96 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Carsten Brandt * @since 2.0 */ -class Console extends AbstractConsole +class Console extends BaseConsole { } diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 919dc09..63954a4 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class FileHelper extends AbstractFileHelper +class FileHelper extends BaseFileHelper { } diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 0715c6c..f4fbbba 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Html extends AbstractHtml +class Html extends BaseHtml { } diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index ca7e485..e1511e4 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifier extends AbstractHtmlPurifier +class HtmlPurifier extends BaseHtmlPurifier { } diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index 71e7f05..ab4713e 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez * @since 2.0 */ -class Inflector extends AbstractInflector +class Inflector extends BaseInflector { } diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 8544a61..8ca436a 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Json extends AbstractJson +class Json extends BaseJson { } diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 89f8801..3dcc750 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class Markdown extends AbstractMarkdown +class Markdown extends BaseMarkdown { } diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index e2c0314..0e3ee38 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster * @since 2.0 */ -class Security extends AbstractSecurity +class Security extends BaseSecurity { } diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index e367c59..5ecd390 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper extends AbstractStringHelper +class StringHelper extends BaseStringHelper { } diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 0aae16e..1ac5aa7 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class VarDumper extends AbstractVarDumper +class VarDumper extends BaseVarDumper { } diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 76354d4..2046ecc 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -220,7 +220,7 @@ class Logger extends Component * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[AbstractYii]] class file. + * of [[BaseYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() From bf722c0423c69a0606abd539bfce4f3988325ca8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 02:50:25 +0400 Subject: [PATCH 042/613] Used intl ICU for message translation --- build/controllers/LocaleController.php | 114 ---- docs/guide/i18n.md | 48 ++ framework/yii/i18n/I18N.php | 95 +--- framework/yii/i18n/MessageFormatter.php | 74 +++ framework/yii/i18n/data/plurals.php | 627 --------------------- framework/yii/i18n/data/plurals.xml | 109 ---- framework/yii/requirements/requirements.php | 4 +- tests/unit/framework/i18n/MessageFormatterTest.php | 47 ++ 8 files changed, 187 insertions(+), 931 deletions(-) delete mode 100644 build/controllers/LocaleController.php create mode 100644 framework/yii/i18n/MessageFormatter.php delete mode 100644 framework/yii/i18n/data/plurals.php delete mode 100644 framework/yii/i18n/data/plurals.xml create mode 100644 tests/unit/framework/i18n/MessageFormatterTest.php diff --git a/build/controllers/LocaleController.php b/build/controllers/LocaleController.php deleted file mode 100644 index 7b2a5df..0000000 --- a/build/controllers/LocaleController.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @since 2.0 - */ -class LocaleController extends Controller -{ - public $defaultAction = 'plural'; - - /** - * Generates the plural rules data. - * - * This command will parse the plural rule XML file from CLDR and convert them - * into appropriate PHP representation to support Yii message translation feature. - * @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in - * http://www.unicode.org/Public/cldr/latest/core.zip - * Extract the zip file and locate the file "common/supplemental/plurals.xml". - * @throws Exception - */ - public function actionPlural($xmlFile) - { - if (!is_file($xmlFile)) { - throw new Exception("The source plural rule file does not exist: $xmlFile"); - } - - $xml = simplexml_load_file($xmlFile); - - $allRules = array(); - - $patterns = array( - '/n in 0..1/' => '(n==0||n==1)', - '/\s+is\s+not\s+/i' => '!=', //is not - '/\s+is\s+/i' => '==', //is - '/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%") - '/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in - '/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in - '/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within - '/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within - ); - foreach ($xml->plurals->pluralRules as $node) { - $attributes = $node->attributes(); - $locales = explode(' ', $attributes['locales']); - $rules = array(); - - if (!empty($node->pluralRule)) { - foreach ($node->pluralRule as $rule) { - $expr_or = preg_split('/\s+or\s+/i', $rule); - foreach ($expr_or as $key_or => $val_or) { - $expr_and = preg_split('/\s+and\s+/i', $val_or); - $expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and); - $expr_or[$key_or] = implode('&&', $expr_and); - } - $expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or)); - $rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) { - if ($matches[2] - $matches[1] <= 5) { - return 'array(' . implode(',', range($matches[1], $matches[2])) . ')'; - } else { - return $matches[0]; - } - }, $expr); - - } - foreach ($locales as $locale) { - $allRules[$locale] = $rules; - } - } - } - // hard fix for "br": the rule is too complex - $allRules['br'] = array( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', - 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', - 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', - 3 => 'fmod($n,1000000)==0&&$n!=0', - ); - if (preg_match('/\d+/', $xml->version['number'], $matches)) { - $revision = $matches[0]; - } else { - $revision = -1; - } - - echo "getMessageSource($category)->translate($category, $message, $language); + $params = (array)$params; - if (!is_array($params)) { - $params = array($params); - } - - if (isset($params[0])) { - $message = $this->applyPluralRules($message, $params[0], $language); - if (!isset($params['{n}'])) { - $params['{n}'] = $params[0]; + if (class_exists('MessageFormatter', false) && preg_match('~{\s*[\d\w]+\s*,~u', $message)) { + $formatter = new MessageFormatter($language, $message); + if ($formatter === null) { + \Yii::$app->getLog()->log("$language message from category $category failed. Message is: $message.", Logger::LEVEL_WARNING, 'application'); + } + $result = $formatter->format($params); + if ($result === false) { + $errorMessage = $formatter->getErrorMessage(); + \Yii::$app->getLog()->log("$language message from category $category failed with error: $errorMessage. Message is: $message.", Logger::LEVEL_WARNING, 'application'); + } + else { + return $result; } - unset($params[0]); } return empty($params) ? $message : strtr($message, $params); @@ -125,62 +118,4 @@ class I18N extends Component throw new InvalidConfigException("Unable to locate message source for category '$category'."); } } - - /** - * Applies appropriate plural rules to the given message. - * @param string $message the message to be applied with plural rules - * @param mixed $number the number by which plural rules will be applied - * @param string $language the language code that determines which set of plural rules to be applied. - * @return string the message that has applied plural rules - */ - protected function applyPluralRules($message, $number, $language) - { - if (strpos($message, '|') === false) { - return $message; - } - $chunks = explode('|', $message); - - $rules = $this->getPluralRules($language); - foreach ($rules as $i => $rule) { - if (isset($chunks[$i]) && $this->evaluate($rule, $number)) { - return $chunks[$i]; - } - } - $n = count($rules); - return isset($chunks[$n]) ? $chunks[$n] : $chunks[0]; - } - - private $_pluralRules = array(); // language => rule set - - /** - * Returns the plural rules for the given language code. - * @param string $language the language code (e.g. `en_US`, `en`). - * @return array the plural rules - * @throws InvalidParamException if the language code is invalid. - */ - protected function getPluralRules($language) - { - if (isset($this->_pluralRules[$language])) { - return $this->_pluralRules[$language]; - } - $allRules = require(Yii::getAlias($this->pluralRuleFile)); - if (isset($allRules[$language])) { - return $this->_pluralRules[$language] = $allRules[$language]; - } elseif (preg_match('/^[a-z]+/', strtolower($language), $matches)) { - return $this->_pluralRules[$language] = isset($allRules[$matches[0]]) ? $allRules[$matches[0]] : array(); - } else { - throw new InvalidParamException("Invalid language code: $language"); - } - } - - /** - * Evaluates a PHP expression with the given number value. - * @param string $expression the PHP expression - * @param mixed $n the number value - * @return boolean the expression result - */ - protected function evaluate($expression, $n) - { - return eval("return $expression;"); - } } diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php new file mode 100644 index 0000000..e7163c0 --- /dev/null +++ b/framework/yii/i18n/MessageFormatter.php @@ -0,0 +1,74 @@ + + * @since 2.0 + */ +class MessageFormatter extends \MessageFormatter +{ + /** + * Format the message. + * + * @link http://php.net/manual/en/messageformatter.format.php + * @param array $args Arguments to insert into the format string. + * @return string|boolean The formatted string, or false if an error occurred. + */ + public function format($args) + { + $pattern = self::replaceNamedArguments($this->getPattern(), $args); + $this->setPattern($pattern); + return parent::format(array_values($args)); + } + + /** + * Quick format message. + * + * @link http://php.net/manual/en/messageformatter.formatmessage.php + * @param string $locale The locale to use for formatting locale-dependent parts. + * @param string $pattern The pattern string to insert things into. + * @param array $args The array of values to insert into the format string. + * @return string|boolean The formatted pattern string or false if an error occurred. + */ + public static function formatMessage($locale, $pattern, $args) + { + $pattern = self::replaceNamedArguments($pattern, $args); + return parent::formatMessage($locale, $pattern, array_values($args)); + } + + /** + * Replace named placeholders with numeric placeholders. + * + * @param string $pattern The pattern string to relace things into. + * @param array $args The array of values to insert into the format string. + * @return string The pattern string with placeholders replaced. + */ + private static function replaceNamedArguments($pattern, $args) + { + $map = array_flip(array_keys($args)); + return preg_replace_callback('~({\s*)([\d\w]+)(\s*[,}])~u', function ($input) use ($map) { + $name = $input[2]; + if (isset($map[$name])) { + return $input[1] . $map[$name] . $input[3]; + } + else { + return "'" . $input[1] . $name . $input[3] . "'"; + } + }, $pattern); + } +} + \ No newline at end of file diff --git a/framework/yii/i18n/data/plurals.php b/framework/yii/i18n/data/plurals.php deleted file mode 100644 index cb51307..0000000 --- a/framework/yii/i18n/data/plurals.php +++ /dev/null @@ -1,627 +0,0 @@ - - array ( - 0 => '$n==0', - 1 => '$n==1', - 2 => '$n==2', - 3 => 'in_array(fmod($n,100),range(3,10))', - 4 => 'in_array(fmod($n,100),range(11,99))', - ), - 'asa' => - array ( - 0 => '$n==1', - ), - 'af' => - array ( - 0 => '$n==1', - ), - 'bem' => - array ( - 0 => '$n==1', - ), - 'bez' => - array ( - 0 => '$n==1', - ), - 'bg' => - array ( - 0 => '$n==1', - ), - 'bn' => - array ( - 0 => '$n==1', - ), - 'brx' => - array ( - 0 => '$n==1', - ), - 'ca' => - array ( - 0 => '$n==1', - ), - 'cgg' => - array ( - 0 => '$n==1', - ), - 'chr' => - array ( - 0 => '$n==1', - ), - 'da' => - array ( - 0 => '$n==1', - ), - 'de' => - array ( - 0 => '$n==1', - ), - 'dv' => - array ( - 0 => '$n==1', - ), - 'ee' => - array ( - 0 => '$n==1', - ), - 'el' => - array ( - 0 => '$n==1', - ), - 'en' => - array ( - 0 => '$n==1', - ), - 'eo' => - array ( - 0 => '$n==1', - ), - 'es' => - array ( - 0 => '$n==1', - ), - 'et' => - array ( - 0 => '$n==1', - ), - 'eu' => - array ( - 0 => '$n==1', - ), - 'fi' => - array ( - 0 => '$n==1', - ), - 'fo' => - array ( - 0 => '$n==1', - ), - 'fur' => - array ( - 0 => '$n==1', - ), - 'fy' => - array ( - 0 => '$n==1', - ), - 'gl' => - array ( - 0 => '$n==1', - ), - 'gsw' => - array ( - 0 => '$n==1', - ), - 'gu' => - array ( - 0 => '$n==1', - ), - 'ha' => - array ( - 0 => '$n==1', - ), - 'haw' => - array ( - 0 => '$n==1', - ), - 'he' => - array ( - 0 => '$n==1', - ), - 'is' => - array ( - 0 => '$n==1', - ), - 'it' => - array ( - 0 => '$n==1', - ), - 'jmc' => - array ( - 0 => '$n==1', - ), - 'kaj' => - array ( - 0 => '$n==1', - ), - 'kcg' => - array ( - 0 => '$n==1', - ), - 'kk' => - array ( - 0 => '$n==1', - ), - 'kl' => - array ( - 0 => '$n==1', - ), - 'ksb' => - array ( - 0 => '$n==1', - ), - 'ku' => - array ( - 0 => '$n==1', - ), - 'lb' => - array ( - 0 => '$n==1', - ), - 'lg' => - array ( - 0 => '$n==1', - ), - 'mas' => - array ( - 0 => '$n==1', - ), - 'ml' => - array ( - 0 => '$n==1', - ), - 'mn' => - array ( - 0 => '$n==1', - ), - 'mr' => - array ( - 0 => '$n==1', - ), - 'nah' => - array ( - 0 => '$n==1', - ), - 'nb' => - array ( - 0 => '$n==1', - ), - 'nd' => - array ( - 0 => '$n==1', - ), - 'ne' => - array ( - 0 => '$n==1', - ), - 'nl' => - array ( - 0 => '$n==1', - ), - 'nn' => - array ( - 0 => '$n==1', - ), - 'no' => - array ( - 0 => '$n==1', - ), - 'nr' => - array ( - 0 => '$n==1', - ), - 'ny' => - array ( - 0 => '$n==1', - ), - 'nyn' => - array ( - 0 => '$n==1', - ), - 'om' => - array ( - 0 => '$n==1', - ), - 'or' => - array ( - 0 => '$n==1', - ), - 'pa' => - array ( - 0 => '$n==1', - ), - 'pap' => - array ( - 0 => '$n==1', - ), - 'ps' => - array ( - 0 => '$n==1', - ), - 'pt' => - array ( - 0 => '$n==1', - ), - 'rof' => - array ( - 0 => '$n==1', - ), - 'rm' => - array ( - 0 => '$n==1', - ), - 'rwk' => - array ( - 0 => '$n==1', - ), - 'saq' => - array ( - 0 => '$n==1', - ), - 'seh' => - array ( - 0 => '$n==1', - ), - 'sn' => - array ( - 0 => '$n==1', - ), - 'so' => - array ( - 0 => '$n==1', - ), - 'sq' => - array ( - 0 => '$n==1', - ), - 'ss' => - array ( - 0 => '$n==1', - ), - 'ssy' => - array ( - 0 => '$n==1', - ), - 'st' => - array ( - 0 => '$n==1', - ), - 'sv' => - array ( - 0 => '$n==1', - ), - 'sw' => - array ( - 0 => '$n==1', - ), - 'syr' => - array ( - 0 => '$n==1', - ), - 'ta' => - array ( - 0 => '$n==1', - ), - 'te' => - array ( - 0 => '$n==1', - ), - 'teo' => - array ( - 0 => '$n==1', - ), - 'tig' => - array ( - 0 => '$n==1', - ), - 'tk' => - array ( - 0 => '$n==1', - ), - 'tn' => - array ( - 0 => '$n==1', - ), - 'ts' => - array ( - 0 => '$n==1', - ), - 'ur' => - array ( - 0 => '$n==1', - ), - 'wae' => - array ( - 0 => '$n==1', - ), - 've' => - array ( - 0 => '$n==1', - ), - 'vun' => - array ( - 0 => '$n==1', - ), - 'xh' => - array ( - 0 => '$n==1', - ), - 'xog' => - array ( - 0 => '$n==1', - ), - 'zu' => - array ( - 0 => '$n==1', - ), - 'ak' => - array ( - 0 => '($n==0||$n==1)', - ), - 'am' => - array ( - 0 => '($n==0||$n==1)', - ), - 'bh' => - array ( - 0 => '($n==0||$n==1)', - ), - 'fil' => - array ( - 0 => '($n==0||$n==1)', - ), - 'tl' => - array ( - 0 => '($n==0||$n==1)', - ), - 'guw' => - array ( - 0 => '($n==0||$n==1)', - ), - 'hi' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ln' => - array ( - 0 => '($n==0||$n==1)', - ), - 'mg' => - array ( - 0 => '($n==0||$n==1)', - ), - 'nso' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ti' => - array ( - 0 => '($n==0||$n==1)', - ), - 'wa' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ff' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'fr' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'kab' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'lv' => - array ( - 0 => '$n==0', - 1 => 'fmod($n,10)==1&&fmod($n,100)!=11', - ), - 'iu' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'kw' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'naq' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'se' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'sma' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smi' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smj' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smn' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'sms' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'ga' => - array ( - 0 => '$n==1', - 1 => '$n==2', - 2 => 'in_array($n,array(3,4,5,6))', - 3 => 'in_array($n,array(7,8,9,10))', - ), - 'ro' => - array ( - 0 => '$n==1', - 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', - ), - 'mo' => - array ( - 0 => '$n==1', - 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', - ), - 'lt' => - array ( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),range(11,19))', - 1 => 'in_array(fmod($n,10),range(2,9))&&!in_array(fmod($n,100),range(11,19))', - ), - 'be' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'bs' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'hr' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'ru' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'sh' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'sr' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'uk' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'cs' => - array ( - 0 => '$n==1', - 1 => 'in_array($n,array(2,3,4))', - ), - 'sk' => - array ( - 0 => '$n==1', - 1 => 'in_array($n,array(2,3,4))', - ), - 'pl' => - array ( - 0 => '$n==1', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => '$n!=1&&in_array(fmod($n,10),array(0,1))||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(12,13,14))', - ), - 'sl' => - array ( - 0 => 'fmod($n,100)==1', - 1 => 'fmod($n,100)==2', - 2 => 'in_array(fmod($n,100),array(3,4))', - ), - 'mt' => - array ( - 0 => '$n==1', - 1 => '$n==0||in_array(fmod($n,100),range(2,10))', - 2 => 'in_array(fmod($n,100),range(11,19))', - ), - 'mk' => - array ( - 0 => 'fmod($n,10)==1&&$n!=11', - ), - 'cy' => - array ( - 0 => '$n==0', - 1 => '$n==1', - 2 => '$n==2', - 3 => '$n==3', - 4 => '$n==6', - ), - 'lag' => - array ( - 0 => '$n==0', - 1 => '($n>=0&&$n<=2)&&$n!=0&&$n!=2', - ), - 'shi' => - array ( - 0 => '($n>=0&&$n<=1)', - 1 => 'in_array($n,range(2,10))', - ), - 'br' => - array ( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', - 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', - 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', - 3 => 'fmod($n,1000000)==0&&$n!=0', - ), - 'ksh' => - array ( - 0 => '$n==0', - 1 => '$n==1', - ), - 'tzm' => - array ( - 0 => '($n==0||$n==1)||in_array($n,range(11,99))', - ), - 'gv' => - array ( - 0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0', - ), -); diff --git a/framework/yii/i18n/data/plurals.xml b/framework/yii/i18n/data/plurals.xml deleted file mode 100644 index 9227dc6..0000000 --- a/framework/yii/i18n/data/plurals.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - n is 0 - n is 1 - n is 2 - n mod 100 in 3..10 - n mod 100 in 11..99 - - - n is 1 - - - n in 0..1 - - - n within 0..2 and n is not 2 - - - n is 0 - n mod 10 is 1 and n mod 100 is not 11 - - - n is 1 - n is 2 - - - n is 1 - n is 2 - n in 3..6 - n in 7..10 - - - n is 1 - n is 0 OR n is not 1 AND n mod 100 in 1..19 - - - n mod 10 is 1 and n mod 100 not in 11..19 - n mod 10 in 2..9 and n mod 100 not in 11..19 - - - n mod 10 is 1 and n mod 100 is not 11 - n mod 10 in 2..4 and n mod 100 not in 12..14 - n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14 - - - - n is 1 - n in 2..4 - - - n is 1 - n mod 10 in 2..4 and n mod 100 not in 12..14 - n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14 - - - - - n mod 100 is 1 - n mod 100 is 2 - n mod 100 in 3..4 - - - n is 1 - n is 0 or n mod 100 in 2..10 - n mod 100 in 11..19 - - - n mod 10 is 1 and n is not 11 - - - n is 0 - n is 1 - n is 2 - n is 3 - n is 6 - - - n is 0 - n within 0..2 and n is not 0 and n is not 2 - - - n within 0..1 - n in 2..10 - - - n mod 10 is 1 and n mod 100 not in 11,71,91 - n mod 10 is 2 and n mod 100 not in 12,72,92 - n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99 - n mod 1000000 is 0 and n is not 0 - - - n is 0 - n is 1 - - - n in 0..1 or n in 11..99 - - - n mod 10 in 1..2 or n mod 20 is 0 - - - diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 005a205..571aa57 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -43,6 +43,8 @@ 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 or the yii\i18n\Formatter class.' + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting + in \Yii::t(), IDN-feature of + EmailValidator or UrlValidator or the yii\i18n\Formatter class.' ), ); diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/unit/framework/i18n/MessageFormatterTest.php new file mode 100644 index 0000000..7595fe0 --- /dev/null +++ b/tests/unit/framework/i18n/MessageFormatterTest.php @@ -0,0 +1,47 @@ + + * @since 2.0 + * @group i18n + */ +class MessageFormatterTest extends TestCase +{ + const N = 'n'; + const N_VALUE = 42; + const SUBJECT = 'сабж'; + const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything'; + + public function testNamedArguments() + { + $expected = self::SUBJECT_VALUE.' is '.self::N_VALUE; + + $result = MessageFormatter::formatMessage('en_US', '{'.self::SUBJECT.'} is {'.self::N.', number}', array( + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + )); + + $this->assertEquals($expected, $result); + } + + public function testInsufficientArguments() + { + $expected = '{'.self::SUBJECT.'} is '.self::N_VALUE; + + $result = MessageFormatter::formatMessage('en_US', '{'.self::SUBJECT.'} is {'.self::N.', number}', array( + self::N => self::N_VALUE, + )); + + $this->assertEquals($expected, $result); + } +} \ No newline at end of file From dafbeda301bb1eb85eec55a468fbd043efadd348 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 22:30:16 +0400 Subject: [PATCH 043/613] More i18n tests, docs, added check to skip fixes where possible --- docs/guide/i18n.md | 245 +++++++++++++++++---- framework/yii/i18n/MessageFormatter.php | 34 ++- tests/unit/framework/i18n/MessageFormatterTest.php | 33 +++ 3 files changed, 268 insertions(+), 44 deletions(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 214b7d2..a1c6314 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -5,44 +5,215 @@ Internationalization (I18N) refers to the process of designing a software applic various languages and regions without engineering changes. For Web applications, this is of particular importance because the potential users may be worldwide. -When developing an application it's assumed that we're relying on -[PHP internationalization extension](http://www.php.net/manual/en/intro.intl.php). While extension covers a lot of aspects -Yii adds a bit more: +Locale and Language +------------------- -- It handles message translation. +There are two languages defined in Yii application: [[\yii\base\Application::$sourceLanguage|source language]] and +[[\yii\base\Application::$language|target language]]. +Source language is the language original application messages are written in such as: -Locale and Language -------------------- +```php +echo \Yii::t('app', 'I am a message!'); +``` + +> **Tip**: Default is English and it's not recommended to change it. The reason is that it's easier to find people translating from +> English to any language than from non-English to non-English. + +Target language is what's currently used. It's defined in application configuration like the following: + +```php +// ... +return array( + 'id' => 'applicationID', + 'basePath' => dirname(__DIR__), + 'language' => 'ru_RU' // ← here! +``` + +Later you can easily change it in runtime: + +```php +\Yii::$app->language = 'zh_CN'; +``` + +Basic message translation +------------------------- + +### Strings translation + +Yii basic message translation that works without additional PHP extension and + + +### Named placeholders + +```php +$username = 'Alexander'; +echo \Yii::t('app', 'Hello, {username}!', array( + 'username' => $username, +)); +``` + +### Positional placeholders + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0}', $sum); +``` + +> **Tip**: When messages are extracted and passed to translator, he sees strings only. For the code above extracted message will be +> "Balance: {0}". It's not recommended to use positional placeholders except when there's only one and message context is +> clear as above. + +Advanced placeholder formatting +------------------------------- + +In order to use advanced features you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP +extension. After installing and enabling it you will be able to use extended syntax for placeholders. Either short form +`{placeholderName, argumentType}` that means default setting or full form `{placeholderName, argumentType, argumentStyle}` +that allows you to specify formatting style. + +Full reference is [available at ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but since it's +a bit crypric we have our own reference below. + +### Numbers + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number}', $sum); +``` + +You can specify one of the built-in styles (`integer`, `currency`, `percent`): + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number, currency}', $sum); +``` + +Or specify custom pattern: + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html). + +### Dates + +```php +echo \Yii::t('app', 'Today is {0, date}', time()); +``` + +Built in formats (`short`, `medium`, `long`, `full`): + +```php +echo \Yii::t('app', 'Today is {0, date, short}', time()); +``` + +Custom pattern: + +```php +echo \Yii::t('app', 'Today is {0, date, YYYY-MM-dd}', time()); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). + +### Time + +```php +echo \Yii::t('app', 'It is {0, time}', time()); +``` + +Built in formats (`short`, `medium`, `long`, `full`): + +```php +echo \Yii::t('app', 'It is {0, time, short}', time()); +``` + +Custom pattern: + +```php +echo \Yii::t('app', 'It is {0, date, HH:mm}', time()); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). + + +### Spellout + +```php +echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', array( + 'n' => 42, +)); +``` + +### Ordinal + +```php +echo \Yii::t('app', 'You are {n, ordinal} visitor here!', array( + 'n' => 42, +)); +``` + +Will produce "You are 42nd visitor here!". + +### Duration + + +```php +echo \Yii::t('app', 'You are here for {n, duration} already!', array( + 'n' => 42, +)); +``` + +Will produce "You are here for 47 sec. already!". + +### Plurals + +Different languages have different ways to inflect plurals. Some rules are very complex so it's very handy that this +functionality is provided without the need to specify inflection rule. Instead it only requires your input of inflected +word in certain situations. + +```php +echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', array( + 'n' => 0, +)); +``` + +Will give us "There are no cats!". + +In the plural rule arguments above `=0` means exactly zero, `=1` stands for exactly one `other` is for any other number. +`#` is replaced with the `n` argument value. It's not that simple for languages other than English. Here's an example +for Russian: + +``` +Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}! +``` + +In the above it worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`. + +To learn which inflection forms you should specify for your language you can referer to +[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). + +### Selections + +You can select phrases based on keywords. The pattern in this case specifies how to map keywords to phrases and +provides a default phrase. + +```php +echo \Yii::t('app', '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', array( + 'name' => 'Snoopy', + 'gender' => 'dog', +)); +``` + +Will produce "Snoopy is dog and it loves Yii!". + +In the expression `female` and `male` are possible values. `other` handler values that do not match. Strings inside +brackets are sub-expressions so could be just a string or a string with more placeholders. + +Formatters +---------- -Translation ------------ - -/* - -numeric arg \{\s*\d+\s*\} -named arg \{\s*(\w|(\w|\d){2,})\s*\} - -named placeholder can be unicode!!! - - -argName [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ - -message = messageText (argument messageText)* - argument = noneArg | simpleArg | complexArg - complexArg = choiceArg | pluralArg | selectArg | selectordinalArg - noneArg = '{' argNameOrNumber '}' - simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}' - choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}' - pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}' - selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}' - selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}' - choiceStyle: see ChoiceFormat - pluralStyle: see PluralFormat - selectStyle: see SelectFormat - argNameOrNumber = argName | argNumber - argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ - argNumber = '0' | ('1'..'9' ('0'..'9')*) - argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration" - argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText - */ \ No newline at end of file +In order to use formatters you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP +extension. diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php index e7163c0..de717dc 100644 --- a/framework/yii/i18n/MessageFormatter.php +++ b/framework/yii/i18n/MessageFormatter.php @@ -14,8 +14,6 @@ namespace yii\i18n; * - Issues no error when an insufficient number of arguments have been provided. Instead, the placeholders will not be * substituted. * - * @see http://php.net/manual/en/migration55.changed-functions.php - * * @author Alexander Makarov * @since 2.0 */ @@ -30,9 +28,12 @@ class MessageFormatter extends \MessageFormatter */ public function format($args) { - $pattern = self::replaceNamedArguments($this->getPattern(), $args); - $this->setPattern($pattern); - return parent::format(array_values($args)); + if (self::needFix()) { + $pattern = self::replaceNamedArguments($this->getPattern(), $args); + $this->setPattern($pattern); + $args = array_values($args); + } + return parent::format($args); } /** @@ -46,8 +47,11 @@ class MessageFormatter extends \MessageFormatter */ public static function formatMessage($locale, $pattern, $args) { - $pattern = self::replaceNamedArguments($pattern, $args); - return parent::formatMessage($locale, $pattern, array_values($args)); + if (self::needFix()) { + $pattern = self::replaceNamedArguments($pattern, $args); + $args = array_values($args); + } + return parent::formatMessage($locale, $pattern, $args); } /** @@ -66,9 +70,25 @@ class MessageFormatter extends \MessageFormatter return $input[1] . $map[$name] . $input[3]; } else { + //return $input[1] . $name . $input[3]; return "'" . $input[1] . $name . $input[3] . "'"; } }, $pattern); } + + /** + * Checks if fix should be applied + * + * @see http://php.net/manual/en/migration55.changed-functions.php + * @return boolean if fix should be applied + */ + private static function needFix() + { + return ( + !defined('INTL_ICU_VERSION') || + version_compare(INTL_ICU_VERSION, '48.0.0', '<') || + version_compare(PHP_VERSION, '5.5.0', '<') + ); + } } \ No newline at end of file diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/unit/framework/i18n/MessageFormatterTest.php index 7595fe0..51e512c 100644 --- a/tests/unit/framework/i18n/MessageFormatterTest.php +++ b/tests/unit/framework/i18n/MessageFormatterTest.php @@ -32,6 +32,39 @@ class MessageFormatterTest extends TestCase )); $this->assertEquals($expected, $result); + + $pattern = <<<_MSG_ +{gender_of_host, select, + female {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to her party.} + =2 {{host} invites {guest} and one other person to her party.} + other {{host} invites {guest} and # other people to her party.}}} + male {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to his party.} + =2 {{host} invites {guest} and one other person to his party.} + other {{host} invites {guest} and # other people to his party.}}} + other {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to their party.} + =2 {{host} invites {guest} and one other person to their party.} + other {{host} invites {guest} and # other people to their party.}}}} +_MSG_; + $result = MessageFormatter::formatMessage('en_US', $pattern, array( + 'gender_of_host' => 'male', + 'num_guests' => 4, + 'host' => 'ralph', + 'guest' => 'beep' + )); + $this->assertEquals('ralph invites beep and 3 other people to his party.', $result); + + $pattern = '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!'; + $result = MessageFormatter::formatMessage('en_US', $pattern, array( + 'name' => 'Alexander', + 'gender' => 'male', + )); + $this->assertEquals('Alexander is male and he loves Yii!', $result); } public function testInsufficientArguments() From fc75ab87be5008807c529e0cb28a7911a71261d7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 22:42:42 +0400 Subject: [PATCH 044/613] =?UTF-8?q?Renamed=20ListViewBase=20=E2=86=92=20Ba?= =?UTF-8?q?seListView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/yii/classes.php | 2 +- framework/yii/grid/GridView.php | 4 +- framework/yii/widgets/BaseListView.php | 191 +++++++++++++++++++++++++++++++++ framework/yii/widgets/ListView.php | 2 +- framework/yii/widgets/ListViewBase.php | 191 --------------------------------- 5 files changed, 195 insertions(+), 195 deletions(-) create mode 100644 framework/yii/widgets/BaseListView.php delete mode 100644 framework/yii/widgets/ListViewBase.php diff --git a/framework/yii/classes.php b/framework/yii/classes.php index d4f304c..1469910 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -235,7 +235,7 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index a783a75..f4433bc 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -14,13 +14,13 @@ use yii\base\InvalidConfigException; use yii\base\Widget; use yii\db\ActiveRecord; use yii\helpers\Html; -use yii\widgets\ListViewBase; +use yii\widgets\BaseListView; /** * @author Qiang Xue * @since 2.0 */ -class GridView extends ListViewBase +class GridView extends BaseListView { const FILTER_POS_HEADER = 'header'; const FILTER_POS_FOOTER = 'footer'; diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php new file mode 100644 index 0000000..7268fbc --- /dev/null +++ b/framework/yii/widgets/BaseListView.php @@ -0,0 +1,191 @@ + + * @since 2.0 + */ +abstract class BaseListView extends Widget +{ + /** + * @var array the HTML attributes for the container tag of the list view. + * The "tag" element specifies the tag name of the container element and defaults to "div". + */ + public $options = array(); + /** + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. + */ + public $dataProvider; + /** + * @var array the configuration for the pager widget. By default, [[LinkPager]] will be + * used to render the pager. You can use a different widget class by configuring the "class" element. + */ + public $pager = array(); + /** + * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be + * used to render the sorter. You can use a different widget class by configuring the "class" element. + */ + public $sorter = array(); + /** + * @var string the HTML content to be displayed as the summary of the list view. + * If you do not want to show the summary, you may set it with an empty string. + * + * The following tokens will be replaced with the corresponding values: + * + * - `{begin}`: the starting row number (1-based) currently being displayed + * - `{end}`: the ending row number (1-based) currently being displayed + * - `{count}`: the number of rows currently being displayed + * - `{totalCount}`: the total number of rows available + * - `{page}`: the page number (1-based) current being displayed + * - `{pageCount}`: the number of pages available + */ + public $summary; + /** + * @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. + * If false, the list view will still be displayed (without body content though). + */ + public $empty; + /** + * @var string the layout that determines how different sections of the list view should be organized. + * The following tokens will be replaced with the corresponding section contents: + * + * - `{summary}`: the summary section. See [[renderSummary()]]. + * - `{items}`: the list items. See [[renderItems()]]. + * - `{sorter}`: the sorter. See [[renderSorter()]]. + * - `{pager}`: the pager. See [[renderPager()]]. + */ + public $layout = "{summary}\n{items}\n{pager}"; + + + /** + * Renders the data models. + * @return string the rendering result. + */ + abstract public function renderItems(); + + /** + * Initializes the view. + */ + public function init() + { + if ($this->dataProvider === null) { + throw new InvalidConfigException('The "dataProvider" property must be set.'); + } + } + + /** + * Runs the widget. + */ + public function run() + { + if ($this->dataProvider->getCount() > 0 || $this->empty === false) { + $widget = $this; + $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { + $content = $widget->renderSection($matches[0]); + return $content === false ? $matches[0] : $content; + }, $this->layout); + } else { + $content = '
' . ($this->empty === null ? Yii::t('yii', 'No results found.') : $this->empty) . '
'; + } + $tag = ArrayHelper::remove($this->options, 'tag', 'div'); + echo Html::tag($tag, $content, $this->options); + } + + /** + * Renders a section of the specified name. + * If the named section is not supported, false will be returned. + * @param string $name the section name, e.g., `{summary}`, `{items}`. + * @return string|boolean the rendering result of the section, or false if the named section is not supported. + */ + public function renderSection($name) + { + switch ($name) { + case '{summary}': + return $this->renderSummary(); + case '{items}': + return $this->renderItems(); + case '{pager}': + return $this->renderPager(); + case '{sorter}': + return $this->renderSorter(); + default: + return false; + } + } + + /** + * Renders the summary text. + */ + public function renderSummary() + { + $count = $this->dataProvider->getCount(); + if (($pagination = $this->dataProvider->getPagination()) !== false) { + $totalCount = $this->dataProvider->getTotalCount(); + $begin = $pagination->getPage() * $pagination->pageSize + 1; + $end = $begin + $count - 1; + $page = $pagination->getPage() + 1; + $pageCount = $pagination->pageCount; + if (($summaryContent = $this->summary) === null) { + $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Showing {begin}-{end} of {totalCount} items.', $totalCount) . '
'; + } + } else { + $begin = $page = $pageCount = 1; + $end = $totalCount = $count; + if (($summaryContent = $this->summary) === null) { + $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Total {count} items.', $count) . '
'; + } + } + return strtr($summaryContent, array( + '{begin}' => $begin, + '{end}' => $end, + '{count}' => $count, + '{totalCount}' => $totalCount, + '{page}' => $page, + '{pageCount}' => $pageCount, + )); + } + + /** + * Renders the pager. + * @return string the rendering result + */ + public function renderPager() + { + $pagination = $this->dataProvider->getPagination(); + if ($pagination === false || $this->dataProvider->getCount() <= 0) { + return ''; + } + /** @var LinkPager $class */ + $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className()); + $this->pager['pagination'] = $pagination; + return $class::widget($this->pager); + } + + /** + * Renders the sorter. + * @return string the rendering result + */ + public function renderSorter() + { + $sort = $this->dataProvider->getSort(); + if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) { + return ''; + } + /** @var LinkSorter $class */ + $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className()); + $this->sorter['sort'] = $sort; + return $class::widget($this->sorter); + } +} diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index c191389..1d8745d 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -16,7 +16,7 @@ use yii\helpers\Html; * @author Qiang Xue * @since 2.0 */ -class ListView extends ListViewBase +class ListView extends BaseListView { /** * @var array the HTML attributes for the container of the rendering result of each data model. diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php deleted file mode 100644 index 33186ae..0000000 --- a/framework/yii/widgets/ListViewBase.php +++ /dev/null @@ -1,191 +0,0 @@ - - * @since 2.0 - */ -abstract class ListViewBase extends Widget -{ - /** - * @var array the HTML attributes for the container tag of the list view. - * The "tag" element specifies the tag name of the container element and defaults to "div". - */ - public $options = array(); - /** - * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. - */ - public $dataProvider; - /** - * @var array the configuration for the pager widget. By default, [[LinkPager]] will be - * used to render the pager. You can use a different widget class by configuring the "class" element. - */ - public $pager = array(); - /** - * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be - * used to render the sorter. You can use a different widget class by configuring the "class" element. - */ - public $sorter = array(); - /** - * @var string the HTML content to be displayed as the summary of the list view. - * If you do not want to show the summary, you may set it with an empty string. - * - * The following tokens will be replaced with the corresponding values: - * - * - `{begin}`: the starting row number (1-based) currently being displayed - * - `{end}`: the ending row number (1-based) currently being displayed - * - `{count}`: the number of rows currently being displayed - * - `{totalCount}`: the total number of rows available - * - `{page}`: the page number (1-based) current being displayed - * - `{pageCount}`: the number of pages available - */ - public $summary; - /** - * @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. - * If false, the list view will still be displayed (without body content though). - */ - public $empty; - /** - * @var string the layout that determines how different sections of the list view should be organized. - * The following tokens will be replaced with the corresponding section contents: - * - * - `{summary}`: the summary section. See [[renderSummary()]]. - * - `{items}`: the list items. See [[renderItems()]]. - * - `{sorter}`: the sorter. See [[renderSorter()]]. - * - `{pager}`: the pager. See [[renderPager()]]. - */ - public $layout = "{summary}\n{items}\n{pager}"; - - - /** - * Renders the data models. - * @return string the rendering result. - */ - abstract public function renderItems(); - - /** - * Initializes the view. - */ - public function init() - { - if ($this->dataProvider === null) { - throw new InvalidConfigException('The "dataProvider" property must be set.'); - } - } - - /** - * Runs the widget. - */ - public function run() - { - if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $widget = $this; - $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { - $content = $widget->renderSection($matches[0]); - return $content === false ? $matches[0] : $content; - }, $this->layout); - } else { - $content = '
' . ($this->empty === null ? Yii::t('yii', 'No results found.') : $this->empty) . '
'; - } - $tag = ArrayHelper::remove($this->options, 'tag', 'div'); - echo Html::tag($tag, $content, $this->options); - } - - /** - * Renders a section of the specified name. - * If the named section is not supported, false will be returned. - * @param string $name the section name, e.g., `{summary}`, `{items}`. - * @return string|boolean the rendering result of the section, or false if the named section is not supported. - */ - public function renderSection($name) - { - switch ($name) { - case '{summary}': - return $this->renderSummary(); - case '{items}': - return $this->renderItems(); - case '{pager}': - return $this->renderPager(); - case '{sorter}': - return $this->renderSorter(); - default: - return false; - } - } - - /** - * Renders the summary text. - */ - public function renderSummary() - { - $count = $this->dataProvider->getCount(); - if (($pagination = $this->dataProvider->getPagination()) !== false) { - $totalCount = $this->dataProvider->getTotalCount(); - $begin = $pagination->getPage() * $pagination->pageSize + 1; - $end = $begin + $count - 1; - $page = $pagination->getPage() + 1; - $pageCount = $pagination->pageCount; - if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Showing {begin}-{end} of {totalCount} items.', $totalCount) . '
'; - } - } else { - $begin = $page = $pageCount = 1; - $end = $totalCount = $count; - if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Total {count} items.', $count) . '
'; - } - } - return strtr($summaryContent, array( - '{begin}' => $begin, - '{end}' => $end, - '{count}' => $count, - '{totalCount}' => $totalCount, - '{page}' => $page, - '{pageCount}' => $pageCount, - )); - } - - /** - * Renders the pager. - * @return string the rendering result - */ - public function renderPager() - { - $pagination = $this->dataProvider->getPagination(); - if ($pagination === false || $this->dataProvider->getCount() <= 0) { - return ''; - } - /** @var LinkPager $class */ - $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className()); - $this->pager['pagination'] = $pagination; - return $class::widget($this->pager); - } - - /** - * Renders the sorter. - * @return string the rendering result - */ - public function renderSorter() - { - $sort = $this->dataProvider->getSort(); - if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) { - return ''; - } - /** @var LinkSorter $class */ - $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className()); - $this->sorter['sort'] = $sort; - return $class::widget($this->sorter); - } -} From 40629ca49bef1a864b2d94593335eac7c927b084 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 23:30:54 +0400 Subject: [PATCH 045/613] Added missing beforeCopy option to FileHelper::copyDirectory It was mentioned in AssetManager::publish phpdoc. --- framework/yii/helpers/BaseFileHelper.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index 2c911b0..c71a1a9 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -155,6 +155,11 @@ class BaseFileHelper * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * This option is used only when publishing a directory. If the callback returns false, the copy + * operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file copied from, while `$to` is the copy target. @@ -173,6 +178,9 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { + if (isset($options['beforeCopy'])) { + call_user_func($options['beforeCopy'], $from, $to); + } if (is_file($from)) { copy($from, $to); if (isset($options['fileMode'])) { From ba1496cd508ca003bff8ffbae5e1d055a652e5b0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 28 Sep 2013 20:11:24 -0400 Subject: [PATCH 046/613] doc fix. --- framework/yii/web/Response.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index e6505fd..f0d506b 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -559,7 +559,20 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. * - * This method will send out a "Location" header to achieve the redirection. + * This method adds a "Location" header to the current response. Note that it does not send out + * the header until [[send()]] is called. In a controller action you may use this method as follows: + * + * ~~~ + * return Yii::$app->getResponse()->redirect($url); + * ~~~ + * + * In other places, if you want to send out the "Location" header immediately, you should use + * the following code: + * + * ~~~ + * Yii::$app->getResponse()->redirect($url)->send(); + * return; + * ~~~ * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, @@ -578,12 +591,6 @@ class Response extends \yii\base\Response * }); * ~~~ * - * In a controller action you may use this method like this: - * - * ~~~ - * return Yii::$app->getResponse()->redirect($url); - * ~~~ - * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: * * - a string representing a URL (e.g. "http://example.com") From efef0e52ca41ed16f52eefae8c24a1103ee020f6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 28 Sep 2013 20:13:48 -0400 Subject: [PATCH 047/613] Fixed beforeCopy option. --- framework/yii/helpers/BaseFileHelper.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index c71a1a9..caed59f 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -156,8 +156,7 @@ class BaseFileHelper * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. - * This option is used only when publishing a directory. If the callback returns false, the copy - * operation for the sub-directory or file will be cancelled. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. @@ -178,8 +177,8 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { - if (isset($options['beforeCopy'])) { - call_user_func($options['beforeCopy'], $from, $to); + if (!isset($options['beforeCopy']) || !call_user_func($options['beforeCopy'], $from, $to)) { + continue; } if (is_file($from)) { copy($from, $to); From b45b16d84502b6d557c72839d17b4b8b229806c1 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 29 Sep 2013 05:05:57 +0400 Subject: [PATCH 048/613] Renamed test to match class name --- tests/unit/framework/BaseYiiTest.php | 63 ++++++++++++++++++++++++++++++++++++ tests/unit/framework/YiiBaseTest.php | 63 ------------------------------------ 2 files changed, 63 insertions(+), 63 deletions(-) create mode 100644 tests/unit/framework/BaseYiiTest.php delete mode 100644 tests/unit/framework/YiiBaseTest.php diff --git a/tests/unit/framework/BaseYiiTest.php b/tests/unit/framework/BaseYiiTest.php new file mode 100644 index 0000000..a04de99 --- /dev/null +++ b/tests/unit/framework/BaseYiiTest.php @@ -0,0 +1,63 @@ +aliases = Yii::$aliases; + } + + protected function tearDown() + { + parent::tearDown(); + Yii::$aliases = $this->aliases; + } + + public function testAlias() + { + $this->assertEquals(YII_PATH, Yii::getAlias('@yii')); + + Yii::$aliases = array(); + $this->assertFalse(Yii::getAlias('@yii', false)); + + Yii::setAlias('@yii', '/yii/framework'); + $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); + $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); + Yii::setAlias('@yii/gii', '/yii/gii'); + $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); + $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); + $this->assertEquals('/yii/gii', Yii::getAlias('@yii/gii')); + $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); + + Yii::setAlias('@tii', '@yii/test'); + $this->assertEquals('/yii/framework/test', Yii::getAlias('@tii')); + + Yii::setAlias('@yii', null); + $this->assertFalse(Yii::getAlias('@yii', false)); + $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); + + Yii::setAlias('@some/alias', '/www'); + $this->assertEquals('/www', Yii::getAlias('@some/alias')); + } + + public function testGetVersion() + { + $this->assertTrue((boolean)preg_match('~\d+\.\d+(?:\.\d+)?(?:-\w+)?~', \Yii::getVersion())); + } + + public function testPowered() + { + $this->assertTrue(is_string(Yii::powered())); + } +} diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php deleted file mode 100644 index 72b5f24..0000000 --- a/tests/unit/framework/YiiBaseTest.php +++ /dev/null @@ -1,63 +0,0 @@ -aliases = Yii::$aliases; - } - - protected function tearDown() - { - parent::tearDown(); - Yii::$aliases = $this->aliases; - } - - public function testAlias() - { - $this->assertEquals(YII_PATH, Yii::getAlias('@yii')); - - Yii::$aliases = array(); - $this->assertFalse(Yii::getAlias('@yii', false)); - - Yii::setAlias('@yii', '/yii/framework'); - $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); - $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); - Yii::setAlias('@yii/gii', '/yii/gii'); - $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); - $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); - $this->assertEquals('/yii/gii', Yii::getAlias('@yii/gii')); - $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); - - Yii::setAlias('@tii', '@yii/test'); - $this->assertEquals('/yii/framework/test', Yii::getAlias('@tii')); - - Yii::setAlias('@yii', null); - $this->assertFalse(Yii::getAlias('@yii', false)); - $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); - - Yii::setAlias('@some/alias', '/www'); - $this->assertEquals('/www', Yii::getAlias('@some/alias')); - } - - public function testGetVersion() - { - $this->assertTrue((boolean)preg_match('~\d+\.\d+(?:\.\d+)?(?:-\w+)?~', \Yii::getVersion())); - } - - public function testPowered() - { - $this->assertTrue(is_string(Yii::powered())); - } -} From fb684774db5870abb3ecb0bf91d2924704d6bba6 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 29 Sep 2013 05:11:46 +0400 Subject: [PATCH 049/613] better wording --- docs/guide/i18n.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index a1c6314..6532717 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -41,7 +41,11 @@ Basic message translation ### Strings translation -Yii basic message translation that works without additional PHP extension and +Yii basic message translation that works without additional PHP extension. + +```php +echo \Yii::t('app', 'This is a string to translate!'); +``` ### Named placeholders @@ -60,9 +64,8 @@ $sum = 42; echo \Yii::t('app', 'Balance: {0}', $sum); ``` -> **Tip**: When messages are extracted and passed to translator, he sees strings only. For the code above extracted message will be -> "Balance: {0}". It's not recommended to use positional placeholders except when there's only one and message context is -> clear as above. +> **Tip**: Try keep message strings meaningful and avoid using too many positional parameters. Remember that +> translator has source string only so it should be obvious about what will replace each placeholder. Advanced placeholder formatting ------------------------------- From abc0df6145bda4e4531d5b9482d39aa1a9ba09be Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 29 Sep 2013 05:12:36 +0400 Subject: [PATCH 050/613] typo --- framework/yii/i18n/MessageFormatter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php index de717dc..76197d1 100644 --- a/framework/yii/i18n/MessageFormatter.php +++ b/framework/yii/i18n/MessageFormatter.php @@ -57,7 +57,7 @@ class MessageFormatter extends \MessageFormatter /** * Replace named placeholders with numeric placeholders. * - * @param string $pattern The pattern string to relace things into. + * @param string $pattern The pattern string to replace things into. * @param array $args The array of values to insert into the format string. * @return string The pattern string with placeholders replaced. */ From 6dc69e68b55e1eece655c4716f0969693b4aba0e Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 17:21:41 +0200 Subject: [PATCH 051/613] Add data padding and key derivation. --- framework/yii/helpers/BaseSecurity.php | 85 ++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index 3be07b4..41caade 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -24,20 +24,40 @@ use yii\base\InvalidParamException; class BaseSecurity { /** + * Uses AES, block size is 128-bit (16 bytes). + */ + const CRYPT_BLOCK_SIZE = 16; + + /** + * Uses AES-192, key size is 192-bit (24 bytes). + */ + const CRYPT_KEY_SIZE = 24; + + /** + * Uses SHA-256. + */ + const DERIVATION_HASH = 'sha256'; + + /** + * Uses 1000 iterations. + */ + const DERIVATION_ITERATIONS = 1000; + + /** * Encrypts data. * @param string $data data to be encrypted. - * @param string $key the encryption secret key + * @param string $password the encryption password * @return string the encrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see decrypt() */ - public static function encrypt($data, $key) + public static function encrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); + $data = static::addPadding($data); srand(); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $encrypted = $iv . mcrypt_generic($module, $data); mcrypt_generic_deinit($module); @@ -48,23 +68,70 @@ class BaseSecurity /** * Decrypts data * @param string $data data to be decrypted. - * @param string $key the decryption secret key + * @param string $password the decryption password * @return string the decrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see encrypt() */ - public static function decrypt($data, $key) + public static function decrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); $ivSize = mcrypt_enc_get_iv_size($module); $iv = StringHelper::substr($data, 0, $ivSize); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); mcrypt_generic_deinit($module); mcrypt_module_close($module); - return rtrim($decrypted, "\0"); + return static::stripPadding($decrypted); + } + + /** + * Adds a padding to the given data (PKCS #7). + * @param string $data the data to pad + * @return string the padded data + */ + protected static function addPadding($data) + { + $pad = self::CRYPT_BLOCK_SIZE - (StringHelper::strlen($data) % self::CRYPT_BLOCK_SIZE); + return $data . str_repeat(chr($pad), $pad); + } + + /** + * Strips the padding from the given data. + * @param string $data the data to trim + * @return string the trimmed data + */ + protected static function stripPadding($data) + { + $end = StringHelper::substr($data, -1); + $last = ord($end); + $n = StringHelper::strlen($data) - $last; + if (StringHelper::substr($data, $n) == str_repeat($end, $last)) { + return StringHelper::substr($data, 0, $n); + } + return false; + } + + /** + * Derives a key from the given password (PBKDF2). + * @param string $password the source password + * @param string $salt the random salt + * @param int $iterations the number of iterations + * @return string the derived key + */ + protected static function deriveKey($password, $salt) + { + if (function_exists('hash_pbkdf2')) { + return hash_pbkdf2(self::DERIVATION_HASH, $password, $salt, self::DERIVATION_ITERATIONS, self::CRYPT_KEY_SIZE, true); + } + $hmac = hash_hmac(self::DERIVATION_HASH, $salt . pack('N', 1), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < self::DERIVATION_ITERATIONS; $i++) { + $hmac = hash_hmac(self::DERIVATION_HASH, $hmac, $password, true); + $xorsum ^= $hmac; + } + return substr($xorsum, 0, self::CRYPT_KEY_SIZE); } /** From 5d7c37bd7ea2d0e467fabeff312bd184c6216d7d Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Sun, 29 Sep 2013 23:27:14 +0800 Subject: [PATCH 052/613] Incorrect array representation when has previous --- framework/yii/base/Exception.php | 8 ++++---- tests/unit/framework/base/ExceptionTest.php | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/unit/framework/base/ExceptionTest.php diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 4f66e53..b771490 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -41,10 +41,10 @@ class Exception extends \Exception implements Arrayable { if ($exception instanceof self) { $array = array( - 'type' => get_class($this), - 'name' => $this->getName(), - 'message' => $this->getMessage(), - 'code' => $this->getCode(), + 'type' => get_class($exception), + 'name' => $exception->getName(), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), ); } else { $array = array( diff --git a/tests/unit/framework/base/ExceptionTest.php b/tests/unit/framework/base/ExceptionTest.php new file mode 100644 index 0000000..af4293a --- /dev/null +++ b/tests/unit/framework/base/ExceptionTest.php @@ -0,0 +1,23 @@ +toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + + $e = new InvalidCallException('bar', 0 ,new UserException('foo')); + $array = $e->toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + } +} From cb7921b8a5ecd391076f869fd344b78f789adf70 Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 17:59:31 +0200 Subject: [PATCH 053/613] Fix StringHelper::substr() call. --- framework/yii/helpers/BaseSecurity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index 41caade..ca42d37 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -104,10 +104,10 @@ class BaseSecurity */ protected static function stripPadding($data) { - $end = StringHelper::substr($data, -1); + $end = StringHelper::substr($data, -1, NULL); $last = ord($end); $n = StringHelper::strlen($data) - $last; - if (StringHelper::substr($data, $n) == str_repeat($end, $last)) { + if (StringHelper::substr($data, $n, NULL) == str_repeat($end, $last)) { return StringHelper::substr($data, 0, $n); } return false; From 9e9b3548db5544c2b3df47985601c95fe3de6011 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 29 Sep 2013 12:06:00 -0400 Subject: [PATCH 054/613] refactor Exception::toArrayRecursive(). --- framework/yii/base/Exception.php | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index b771490..64f1d1b 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -39,21 +39,12 @@ class Exception extends \Exception implements Arrayable */ protected function toArrayRecursive($exception) { - if ($exception instanceof self) { - $array = array( - 'type' => get_class($exception), - 'name' => $exception->getName(), - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } else { - $array = array( - 'type' => get_class($exception), - 'name' => 'Exception', - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } + $array = array( + 'type' => get_class($exception), + 'name' => $exception instanceof self ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ); if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->toArrayRecursive($prev); } From c4f4e52a5aaadd3369865ac2151dc965c675ecbf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 29 Sep 2013 12:16:20 -0400 Subject: [PATCH 055/613] fixed test break. --- framework/yii/helpers/BaseFileHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index caed59f..7540168 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -177,7 +177,7 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { - if (!isset($options['beforeCopy']) || !call_user_func($options['beforeCopy'], $from, $to)) { + if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) { continue; } if (is_file($from)) { From 64a33e78498197796cbda9150ec0cf3d846801f8 Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 18:18:59 +0200 Subject: [PATCH 056/613] Fix phpdoc. --- framework/yii/helpers/BaseSecurity.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index ca42d37..a1b0ec4 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -117,7 +117,6 @@ class BaseSecurity * Derives a key from the given password (PBKDF2). * @param string $password the source password * @param string $salt the random salt - * @param int $iterations the number of iterations * @return string the derived key */ protected static function deriveKey($password, $salt) From 8d4d0ee0bcbd5986e5bc21e48b9c967665ee2e21 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 30 Sep 2013 12:53:04 +0200 Subject: [PATCH 057/613] Update RedisCache.php added version information --- framework/yii/caching/RedisCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index 5c778fc..7cb6451 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -10,7 +10,7 @@ namespace yii\caching; use yii\redis\Connection; /** - * RedisCache implements a cache application component based on [redis](http://redis.io/). + * RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6 or higher. * * RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server * to connect to. By default RedisCache assumes there is a redis server running on localhost at From b4b9ad483a8d33985a852af23993b9360b482ab0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 30 Sep 2013 14:42:05 +0200 Subject: [PATCH 058/613] Added version information to redis --- docs/guide/caching.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 0624d78..64c7e8d 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -60,7 +60,8 @@ is a summary of the available cache components: the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load balancers, etc.) -* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) NoSQL database. +* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) key-value store + (redis version 2.6 or higher is required). * [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) ([see also](http://php.net/manual/en/book.wincache.php)) extension. From d03d681583b1bdc8ea0246ca610bbb1bb3a6fd73 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 30 Sep 2013 10:14:08 -0400 Subject: [PATCH 059/613] Fixes #928: client validation should be applied to active attributes only. --- framework/yii/widgets/ActiveField.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index ea8aa1b..7df67fa 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -589,9 +589,13 @@ class ActiveField extends Component */ protected function getClientOptions() { + $attribute = Html::getAttributeName($this->attribute); + if (!in_array($attribute, $this->model->activeAttributes(), true)) { + return array(); + } + $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; if ($enableClientValidation) { - $attribute = Html::getAttributeName($this->attribute); $validators = array(); foreach ($this->model->getActiveValidators($attribute) as $validator) { /** @var \yii\validators\Validator $validator */ From 2c30ddfcb0621e74552e7f6628cb092f666c5709 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 1 Oct 2013 20:01:14 +0200 Subject: [PATCH 060/613] added web\Controller::goBack() as shortcut goBack() will redirect user to his returnUrl fixes #925 --- apps/basic/controllers/SiteController.php | 2 +- framework/yii/web/Controller.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index 1196280..e243223 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -61,7 +61,7 @@ class SiteController extends Controller { $model = new LoginForm(); if ($model->load($_POST) && $model->login()) { - return $this->goHome(); + return $this->goBack(); } else { return $this->render('login', array( 'model' => $model, diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 6b8afa4..7509186 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -146,6 +146,19 @@ class Controller extends \yii\base\Controller } /** + * Redirects the browser to the last visited page. + * @param string|array $defaultUrl the default return URL in case it was not set previously. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[User::setReturnUrl()]] on accepted format of the URL. + * @return Response the current response object + * @see User::getReturnUrl() + */ + public function goBack($defaultUrl = null) + { + return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl)); + } + + /** * Refreshes the current page. * This method is a shortcut to [[Response::refresh()]]. * @param string $anchor the anchor that should be appended to the redirection URL. From 0035f23781750f2e598077e11fa8deb43b10bd21 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 1 Oct 2013 21:35:35 -0400 Subject: [PATCH 061/613] refactored data providers. --- framework/yii/classes.php | 27 +-- framework/yii/data/ActiveDataProvider.php | 158 ++++--------- framework/yii/data/ArrayDataProvider.php | 104 +++------ framework/yii/data/BaseDataProvider.php | 253 +++++++++++++++++++++ framework/yii/data/DataProvider.php | 133 ----------- framework/yii/data/DataProviderInterface.php | 12 + framework/yii/widgets/BaseListView.php | 1 + .../unit/framework/data/ActiveDataProviderTest.php | 5 +- 8 files changed, 357 insertions(+), 336 deletions(-) create mode 100644 framework/yii/data/BaseDataProvider.php delete mode 100644 framework/yii/data/DataProvider.php diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 1469910..78cce95 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -85,7 +85,7 @@ return array( 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php', 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', - 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', + 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php', 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', @@ -118,6 +118,7 @@ return array( 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php', 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php', 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php', + 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php', 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php', 'yii\grid\Column' => YII_PATH . '/grid/Column.php', 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php', @@ -126,26 +127,26 @@ return array( 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', - 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', - 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', - 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', - 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', - 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', - 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', - 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', - 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', - 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', - 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', + 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', + 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', + 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', + 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', + 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', + 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', + 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', + 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', + 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', + 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', @@ -194,6 +195,7 @@ return array( 'yii\web\Application' => YII_PATH . '/web/Application.php', 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php', 'yii\web\Controller' => YII_PATH . '/web/Controller.php', @@ -204,7 +206,6 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', @@ -226,6 +227,7 @@ return array( 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php', 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php', 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php', 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php', 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php', @@ -235,7 +237,6 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index 2fe0efb..c5f1bcd 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -48,16 +48,10 @@ use yii\db\Connection; * $posts = $provider->getModels(); * ~~~ * - * @property integer $count The number of data models in the current page. This property is read-only. - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. This property is read-only. - * @property array $models The list of data models in the current page. This property is read-only. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue * @since 2.0 */ -class ActiveDataProvider extends DataProvider +class ActiveDataProvider extends BaseDataProvider { /** * @var Query the query that is used to fetch data models and [[totalCount]] @@ -82,10 +76,6 @@ class ActiveDataProvider extends DataProvider */ public $db; - private $_models; - private $_keys; - private $_totalCount; - /** * Initializes the DbCache component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. @@ -103,122 +93,72 @@ class ActiveDataProvider extends DataProvider } /** - * Returns the number of data models in the current page. - * This is equivalent to `count($provider->models)`. - * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data models in the current page. - */ - public function getCount() - { - return count($this->getModels()); - } - - /** - * Returns the total number of data models. - * When [[pagination]] is false, this returns the same value as [[count]]. - * If [[totalCount]] is not explicitly set, it will be calculated - * using [[query]] with a COUNT query. - * @return integer total number of possible data models. - * @throws InvalidConfigException + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - $query = clone $this->query; - $this->_totalCount = $query->limit(-1)->offset(-1)->count('*', $this->db); + if (!$this->query instanceof Query) { + throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - * @throws InvalidConfigException if [[query]] is not set or invalid. - */ - public function getModels() - { - if ($this->_models === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - if (($pagination = $this->getPagination()) !== false) { - $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); - } - if (($sort = $this->getSort()) !== false) { - $this->query->addOrderBy($sort->getOrders()); - } - $this->_models = $this->query->all($this->db); + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); + } + if (($sort = $this->getSort()) !== false) { + $this->query->addOrderBy($sort->getOrders()); } - return $this->_models; + return $this->query->all($this->db); } /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { + $keys = array(); + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + return $keys; + } elseif ($this->query instanceof ActiveQuery) { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->query->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + $pk = $pks[0]; foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + $keys[] = $model[$pk]; } - } elseif ($this->query instanceof ActiveQuery) { - /** @var \yii\db\ActiveRecord $class */ - $class = $this->query->modelClass; - $pks = $class::primaryKey(); - if (count($pks) === 1) { - $pk = $pks[0]; - foreach ($models as $model) { - $this->_keys[] = $model[$pk]; - } - } else { - foreach ($models as $model) { - $keys = array(); - foreach ($pks as $pk) { - $keys[] = $model[$pk]; - } - $this->_keys[] = json_encode($keys); + } else { + foreach ($models as $model) { + $kk = array(); + foreach ($pks as $pk) { + $kk[] = $model[$pk]; } + $keys[] = json_encode($kk); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Refreshes the data provider. - * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, - * they will re-execute the query and return the latest data available. + * @inheritdoc */ - public function refresh() + protected function prepareTotalCount() { - $this->_models = null; - $this->_totalCount = null; - $this->_keys = null; + if (!$this->query instanceof Query) { + throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); + } + $query = clone $this->query; + return $query->limit(-1)->offset(-1)->count('*', $this->db); } /** @@ -227,9 +167,7 @@ class ActiveDataProvider extends DataProvider public function setSort($value) { parent::setSort($value); - if (($sort = $this->getSort()) !== false && empty($sort->attributes) && - $this->query instanceof ActiveQuery) { - + if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQuery) { /** @var Model $model */ $model = new $this->query->modelClass; foreach($model->attributes() as $attribute) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index 9534803..925c18e 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -47,15 +47,10 @@ use yii\helpers\ArrayHelper; * Note: if you want to use the sorting feature, you must configure the [[sort]] property * so that the provider knows which columns can be sorted. * - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. - * @property array $models The list of data models in the current page. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue * @since 2.0 */ -class ArrayDataProvider extends DataProvider +class ArrayDataProvider extends BaseDataProvider { /** * @var string|callable the column that is used as the key of the data models. @@ -71,100 +66,53 @@ class ArrayDataProvider extends DataProvider */ public $allModels; - private $_totalCount; /** - * Returns the total number of data models. - * @return integer total number of possible data models. + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - $this->_totalCount = count($this->allModels); + if (($models = $this->allModels) === null) { + return array(); } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - private $_models; - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - */ - public function getModels() - { - if ($this->_models === null) { - if (($models = $this->allModels) === null) { - return array(); - } - - if (($sort = $this->getSort()) !== false) { - $models = $this->sortModels($models, $sort); - } - - if (($pagination = $this->getPagination()) !== false) { - $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); - } + if (($sort = $this->getSort()) !== false) { + $models = $this->sortModels($models, $sort); + } - $this->_models = $models; + if (($pagination = $this->getPagination()) !== false) { + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } - return $this->_models; - } - /** - * Sets the data models in the current page. - * @param array $models the models in the current page - */ - public function setModels($models) - { - $this->_models = $models; + return $models; } - private $_keys; - /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { - foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + if ($this->key !== null) { + $keys = array(); + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Sets the key values associated with the data models. - * @param array $keys the list of key values corresponding to [[models]]. + * @inheritdoc */ - public function setKeys($keys) + protected function prepareTotalCount() { - $this->_keys = $keys; + return count($this->allModels); } /** diff --git a/framework/yii/data/BaseDataProvider.php b/framework/yii/data/BaseDataProvider.php new file mode 100644 index 0000000..15705b7 --- /dev/null +++ b/framework/yii/data/BaseDataProvider.php @@ -0,0 +1,253 @@ + + * @since 2.0 + */ +abstract class BaseDataProvider extends Component implements DataProviderInterface +{ + /** + * @var string an ID that uniquely identifies the data provider among all data providers. + * You should set this property if the same page contains two or more different data providers. + * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. + */ + public $id; + + private $_sort; + private $_pagination; + private $_keys; + private $_models; + private $_totalCount; + + + /** + * Prepares the data models that will be made available in the current page. + * @return array the available data models + */ + abstract protected function prepareModels(); + + /** + * Prepares the keys associated with the currently available data models. + * @param array $models the available data models + * @return array the keys + */ + abstract protected function prepareKeys($models); + + /** + * Returns a value indicating the total number of data models in this data provider. + * @return integer total number of data models in this data provider. + */ + abstract protected function prepareTotalCount(); + + /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false) + { + if ($forcePrepare || $this->_models === null) { + $this->_models = $this->prepareModels(); + } + if ($forcePrepare || $this->_keys === null) { + $this->_keys = $this->prepareKeys($this->_models); + } + } + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels() + { + $this->prepare(); + return $this->_models; + } + + /** + * Sets the data models in the current page. + * @param array $models the models in the current page + */ + public function setModels($models) + { + $this->_models = $models; + } + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys() + { + $this->prepare(); + return $this->_keys; + } + + /** + * Sets the key values associated with the data models. + * @param array $keys the list of key values corresponding to [[models]]. + */ + public function setKeys($keys) + { + $this->_keys = $keys; + } + + /** + * Returns the number of data models in the current page. + * @return integer the number of data models in the current page. + */ + public function getCount() + { + return count($this->getModels()); + } + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this returns the same value as [[count]]. + * Otherwise, it will call [[prepareTotalCount()]] to get the count. + * @return integer total number of possible data models. + */ + public function getTotalCount() + { + if ($this->getPagination() === false) { + return $this->getCount(); + } elseif ($this->_totalCount === null) { + $this->_totalCount = $this->prepareTotalCount(); + } + return $this->_totalCount; + } + + /** + * Sets the total number of data models. + * @param integer $value the total number of data models. + */ + public function setTotalCount($value) + { + $this->_totalCount = $value; + } + + /** + * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination() + { + if ($this->_pagination === null) { + $this->_pagination = new Pagination; + if ($this->id !== null) { + $this->_pagination->pageVar = $this->id . '-page'; + } + } + return $this->_pagination; + } + + /** + * Sets the pagination for this data provider. + * @param array|Pagination|boolean $value the pagination to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the pagination object. The "class" element defaults + * to 'yii\data\Pagination' + * - an instance of [[Pagination]] or its subclass + * - false, if pagination needs to be disabled. + * + * @throws InvalidParamException + */ + public function setPagination($value) + { + if (is_array($value)) { + $config = array( + 'class' => Pagination::className(), + ); + if ($this->id !== null) { + $config['pageVar'] = $this->id . '-page'; + } + $this->_pagination = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Pagination || $value === false) { + $this->_pagination = $value; + } else { + throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.'); + } + } + + /** + * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort() + { + if ($this->_sort === null) { + $this->setSort(array()); + } + return $this->_sort; + } + + /** + * Sets the sort definition for this data provider. + * @param array|Sort|boolean $value the sort definition to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the sort definition object. The "class" element defaults + * to 'yii\data\Sort' + * - an instance of [[Sort]] or its subclass + * - false, if sorting needs to be disabled. + * + * @throws InvalidParamException + */ + public function setSort($value) + { + if (is_array($value)) { + $config = array( + 'class' => Sort::className(), + ); + if ($this->id !== null) { + $config['sortVar'] = $this->id . '-sort'; + } + $this->_sort = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Sort || $value === false) { + $this->_sort = $value; + } else { + throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.'); + } + } + + /** + * Refreshes the data provider. + * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, + * they will re-execute the query and return the latest data available. + */ + public function refresh() + { + $this->_totalCount = null; + $this->_models = null; + $this->_keys = null; + } +} diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php deleted file mode 100644 index d75be6f..0000000 --- a/framework/yii/data/DataProvider.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @since 2.0 - */ -abstract class DataProvider extends Component implements DataProviderInterface -{ - /** - * @var string an ID that uniquely identifies the data provider among all data providers. - * You should set this property if the same page contains two or more different data providers. - * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. - */ - public $id; - - private $_sort; - private $_pagination; - - /** - * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. - */ - public function getPagination() - { - if ($this->_pagination === null) { - $this->_pagination = new Pagination; - if ($this->id !== null) { - $this->_pagination->pageVar = $this->id . '-page'; - } - $this->_pagination->totalCount = $this->getTotalCount(); - } - return $this->_pagination; - } - - /** - * Sets the pagination for this data provider. - * @param array|Pagination|boolean $value the pagination to be used by this data provider. - * This can be one of the following: - * - * - a configuration array for creating the pagination object. The "class" element defaults - * to 'yii\data\Pagination' - * - an instance of [[Pagination]] or its subclass - * - false, if pagination needs to be disabled. - * - * @throws InvalidParamException - */ - public function setPagination($value) - { - if (is_array($value)) { - $config = array( - 'class' => Pagination::className(), - ); - if ($this->id !== null) { - $config['pageVar'] = $this->id . '-page'; - } - $this->_pagination = Yii::createObject(array_merge($config, $value)); - } elseif ($value instanceof Pagination || $value === false) { - $this->_pagination = $value; - } else { - throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.'); - } - } - - /** - * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled. - */ - public function getSort() - { - if ($this->_sort === null) { - $this->setSort(array()); - } - return $this->_sort; - } - - /** - * Sets the sort definition for this data provider. - * @param array|Sort|boolean $value the sort definition to be used by this data provider. - * This can be one of the following: - * - * - a configuration array for creating the sort definition object. The "class" element defaults - * to 'yii\data\Sort' - * - an instance of [[Sort]] or its subclass - * - false, if sorting needs to be disabled. - * - * @throws InvalidParamException - */ - public function setSort($value) - { - if (is_array($value)) { - $config = array( - 'class' => Sort::className(), - ); - if ($this->id !== null) { - $config['sortVar'] = $this->id . '-sort'; - } - $this->_sort = Yii::createObject(array_merge($config, $value)); - } elseif ($value instanceof Sort || $value === false) { - $this->_sort = $value; - } else { - throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.'); - } - } - - /** - * Returns the number of data models in the current page. - * @return integer the number of data models in the current page. - */ - public function getCount() - { - return count($this->getModels()); - } -} diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php index f0bc39d..1dea1e6 100644 --- a/framework/yii/data/DataProviderInterface.php +++ b/framework/yii/data/DataProviderInterface.php @@ -19,6 +19,18 @@ namespace yii\data; interface DataProviderInterface { /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false); + + /** * Returns the number of data models in the current page. * This is equivalent to `count($provider->getModels())`. * When [[pagination]] is false, this is the same as [[totalCount]]. diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index 7268fbc..d4647ff 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -83,6 +83,7 @@ abstract class BaseListView extends Widget if ($this->dataProvider === null) { throw new InvalidConfigException('The "dataProvider" property must be set.'); } + $this->dataProvider->prepare(); } /** diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 79c0a39..276c525 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -94,9 +94,10 @@ class ActiveDataProviderTest extends DatabaseTestCase 'query' => $query->from('tbl_order')->orderBy('id'), )); $pagination = $provider->getPagination(); - $this->assertEquals(1, $pagination->getPageCount()); + $this->assertEquals(0, $pagination->getPageCount()); $this->assertCount(3, $provider->getModels()); - + $this->assertEquals(1, $pagination->getPageCount()); + $provider->getPagination()->pageSize = 2; $this->assertEquals(3, count($provider->getModels())); $provider->refresh(); From fe58cbd56a02af1dcd349356de5585ec78287bd3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 1 Oct 2013 21:35:56 -0400 Subject: [PATCH 062/613] doc update. --- framework/yii/web/AssetManager.php | 4 ++-- framework/yii/web/Request.php | 2 ++ framework/yii/web/User.php | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/framework/yii/web/AssetManager.php b/framework/yii/web/AssetManager.php index 500848b..35e555f 100644 --- a/framework/yii/web/AssetManager.php +++ b/framework/yii/web/AssetManager.php @@ -16,8 +16,8 @@ use yii\helpers\FileHelper; /** * AssetManager manages asset bundles and asset publishing. * - * @property AssetConverterInterface $converter The asset converter. Note that the type of this property differs in - * getter and setter. See [[getConverter()]] and [[setConverter()]] for details. + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property + * differs in getter and setter. See [[getConverter()]] and [[setConverter()]] for details. * * @author Qiang Xue * @since 2.0 diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 4fb6257..e5d9477 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -29,6 +29,8 @@ use yii\helpers\Security; * previously, a random key will be generated and used. * @property CookieCollection $cookies The cookie collection. This property is read-only. * @property string $csrfToken The random token for CSRF validation. This property is read-only. + * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned + * if no such header is sent. This property is read-only. * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. * `http://www.yiiframework.com`). * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index f6a9bc8..0dd16d0 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -23,8 +23,8 @@ use yii\base\InvalidParamException; * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. - * @property IdentityInterface $identity The identity object associated with the currently logged user. Null is - * returned if the user is not logged in (not authenticated). + * @property IdentityInterface $identity The identity object associated with the currently logged user. Null + * is returned if the user is not logged in (not authenticated). * @property boolean $isGuest Whether the current user is a guest. This property is read-only. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details. From d798cc791b4c1195a042848485279e19fdebe589 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 1 Oct 2013 21:40:35 -0400 Subject: [PATCH 063/613] Set totalCount. --- framework/yii/data/ArrayDataProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index 925c18e..77f31cd 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -81,6 +81,7 @@ class ArrayDataProvider extends BaseDataProvider } if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } From 09a91910866ef97bf78733ce70e995d666e69fee Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 1 Oct 2013 21:56:08 -0400 Subject: [PATCH 064/613] Fixes #927: doc typo. --- framework/yii/log/FileTarget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/log/FileTarget.php b/framework/yii/log/FileTarget.php index 970c71b..5aa4c12 100644 --- a/framework/yii/log/FileTarget.php +++ b/framework/yii/log/FileTarget.php @@ -78,7 +78,7 @@ class FileTarget extends Target } /** - * Sends log messages to specified email addresses. + * Writes log messages to a file. * @throws InvalidConfigException if unable to open the log file for writing */ public function export() From c1f977cd459eb9997484d577de307c741305f0d4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 1 Oct 2013 22:57:08 -0400 Subject: [PATCH 065/613] Simplified the default file map for PhpMessageSource. --- framework/yii/i18n/PhpMessageSource.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/i18n/PhpMessageSource.php b/framework/yii/i18n/PhpMessageSource.php index f62939f..1cd2103 100644 --- a/framework/yii/i18n/PhpMessageSource.php +++ b/framework/yii/i18n/PhpMessageSource.php @@ -26,6 +26,8 @@ use Yii; * ); * ~~~ * + * You may use [[fileMap]] to customize the association between category names and the file names. + * * @author Qiang Xue * @since 2.0 */ @@ -60,10 +62,8 @@ class PhpMessageSource extends MessageSource $messageFile = Yii::getAlias($this->basePath) . "/$language/"; if (isset($this->fileMap[$category])) { $messageFile .= $this->fileMap[$category]; - } elseif (($pos = strrpos($category, '\\')) !== false) { - $messageFile .= (substr($category, $pos) . '.php'); } else { - $messageFile .= "$category.php"; + $messageFile .= str_replace('\\', '/', $category) . '.php'; } if (is_file($messageFile)) { $messages = include($messageFile); From 3f88320595023696fdbd30af625f325dac444a57 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 2 Oct 2013 12:27:04 +0200 Subject: [PATCH 066/613] second try to fix random memcache failure on travis issue #877 --- tests/unit/framework/caching/CacheTestCase.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 480941f..542999a 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -145,14 +145,9 @@ abstract class CacheTestCase extends TestCase $cache = $this->getCacheInstance(); $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - sleep(1); + usleep(500000); $this->assertEquals('expire_test', $cache->get('expire_test')); - // wait a bit more than 2 sec to avoid random test failure - if (isset($_ENV['TRAVIS']) && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { - sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache - } else { - usleep(2500000); - } + usleep(2500000); $this->assertFalse($cache->get('expire_test')); } From bcc0cbeba8648eb8eb41c4c5becc654bb476bc3d Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 2 Oct 2013 08:29:52 -0400 Subject: [PATCH 067/613] Added doc about BaseDataProvider::getPagination(). --- framework/yii/data/BaseDataProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/yii/data/BaseDataProvider.php b/framework/yii/data/BaseDataProvider.php index 15705b7..3b0669d 100644 --- a/framework/yii/data/BaseDataProvider.php +++ b/framework/yii/data/BaseDataProvider.php @@ -157,6 +157,9 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa } /** + * Returns the pagination object used by this data provider. + * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values + * of [[Pagination::totalCount]] and [[Pagination::pageCount]]. * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. */ public function getPagination() From 618f9811123e680cba38616a18305bdd27136778 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 3 Oct 2013 01:41:38 +0400 Subject: [PATCH 068/613] Renamed i18n to be all lowecase when accessed as a property --- framework/yii/BaseYii.php | 2 +- framework/yii/base/Application.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/BaseYii.php b/framework/yii/BaseYii.php index b586160..cf94166 100644 --- a/framework/yii/BaseYii.php +++ b/framework/yii/BaseYii.php @@ -541,7 +541,7 @@ class BaseYii public static function t($category, $message, $params = array(), $language = null) { if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + return self::$app->getI18n()->translate($category, $message, $params, $language ?: self::$app->language); } else { return is_array($params) ? strtr($message, $params) : $message; } diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index e8c496c..3cc90d9 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -383,7 +383,7 @@ abstract class Application extends Module * Returns the internationalization (i18n) component * @return \yii\i18n\I18N the internationalization component */ - public function getI18N() + public function getI18n() { return $this->getComponent('i18n'); } From 6fc5f0a4caceaddbc10daa775d24fb0d399817ae Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 3 Oct 2013 01:54:58 +0400 Subject: [PATCH 069/613] Minor additions to i18n docs --- docs/guide/i18n.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 6532717..2e17ce0 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -39,14 +39,17 @@ Later you can easily change it in runtime: Basic message translation ------------------------- -### Strings translation - -Yii basic message translation that works without additional PHP extension. +Yii basic message translation in its basic variant works without additional PHP extension. What it does is finding a +translation of the message from source language into targer language. Message itself is specified as the first +`\Yii::t` method parameter: ```php echo \Yii::t('app', 'This is a string to translate!'); ``` +Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration. + +TBD: https://github.com/yiisoft/yii2/issues/930 ### Named placeholders From 8c9dadf36212bff6fef815a930dbcda4a4263bf5 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 2 Oct 2013 21:22:25 -0400 Subject: [PATCH 070/613] Fixed DB validation handling for model generator. --- framework/yii/gii/generators/model/Generator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index b9c8f23..7fba83f 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -464,6 +464,9 @@ class Generator extends \yii\gii\Generator return $this->_tableNames; } $db = $this->getDbConnection(); + if ($db === null) { + return array(); + } $tableNames = array(); if (strpos($this->tableName, '*') !== false) { if (($pos = strrpos($this->tableName, '.')) !== false) { From 38c0b197f9a10c9caec82dd4de194002f8219fa2 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 3 Oct 2013 11:01:52 -0400 Subject: [PATCH 071/613] Fixes #916: generation of class name from table name has problem. --- framework/yii/gii/generators/model/Generator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index 7fba83f..69edb5f 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -514,7 +514,8 @@ class Generator extends \yii\gii\Generator $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; } if (!empty($db->tablePrefix)) { - $patterns[] = "/^{$db->tablePrefix}(.*?)|(.*?){$db->tablePrefix}$/"; + $patterns[] = "/^{$db->tablePrefix}(.*?)$/"; + $patterns[] = "/^(.*?){$db->tablePrefix}$/"; } else { $patterns[] = "/^tbl_(.*?)$/"; } @@ -523,6 +524,7 @@ class Generator extends \yii\gii\Generator foreach ($patterns as $pattern) { if (preg_match($pattern, $tableName, $matches)) { $className = $matches[1]; + break; } } return $this->_classNames[$tableName] = Inflector::id2camel($className, '_'); From c5a34c534b987a5767db7757699453cb14991c14 Mon Sep 17 00:00:00 2001 From: Aaron Francis Date: Thu, 3 Oct 2013 16:33:28 -0500 Subject: [PATCH 072/613] Fix Issue #934 Fixes issue #934. Command is created for the selected database, not the default database. --- framework/yii/console/controllers/MigrateController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php index e2c771c..e9e3973 100644 --- a/framework/yii/console/controllers/MigrateController.php +++ b/framework/yii/console/controllers/MigrateController.php @@ -584,7 +584,7 @@ class MigrateController extends Controller ->from($this->migrationTable) ->orderBy('version DESC') ->limit($limit) - ->createCommand() + ->createCommand($this->db) ->queryAll(); $history = ArrayHelper::map($rows, 'version', 'apply_time'); unset($history[self::BASE_MIGRATION]); From 536075c0408e745fb285f86867062338d744128d Mon Sep 17 00:00:00 2001 From: egorpromo Date: Fri, 4 Oct 2013 21:17:13 +0700 Subject: [PATCH 073/613] Just simple correction of unintelligible description --- framework/yii/web/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index 5fd2310..b0638a7 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -45,7 +45,7 @@ class Application extends \yii\base\Application * ) * ~~~ * - * Defaults to null, meaning catch-all is not effective. + * Defaults to null, meaning catch-all is not used. */ public $catchAll; /** From 293cb9d86f0f62f71a6943075d9a2985e09af655 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 4 Oct 2013 20:35:58 -0400 Subject: [PATCH 074/613] Fixed search form. --- framework/yii/gii/generators/crud/templates/views/_search.php | 5 ++++- framework/yii/grid/DataColumn.php | 2 +- framework/yii/helpers/BaseHtml.php | 2 +- framework/yii/web/Request.php | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php index a649589..13e2b82 100644 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -23,7 +23,10 @@ use yii\widgets\ActiveForm; From 6f9785a2937144744ae5f3466b0d1167b6bb995e Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 17 Oct 2013 12:30:41 +0400 Subject: [PATCH 148/613] fixes #995 --- framework/yii/base/Model.php | 6 +++--- framework/yii/data/ActiveDataProvider.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index c8c253a..7fb8c35 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -117,7 +117,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * ~~~ * * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of - * validator configuration options such as `max` in case of `length` validator. Currently validate attribute value + * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value * can be accessed as `$this->[$attribute]`. * * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. @@ -129,8 +129,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * array( * // built-in "required" validator * array('username', 'required'), - * // built-in "length" validator customized with "min" and "max" properties - * array('username', 'length', 'min' => 3, 'max' => 12), + * // built-in "string" validator customized with "min" and "max" properties + * array('username', 'string', 'min' => 3, 'max' => 12), * // built-in "compare" validator that is used in "register" scenario only * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'), * // an inline validator defined via the "authenticate()" method in the model class diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index c5f1bcd..bd822f9 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -77,7 +77,7 @@ class ActiveDataProvider extends BaseDataProvider public $db; /** - * Initializes the DbCache component. + * Initializes the DB connection component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. * @throws InvalidConfigException if [[db]] is invalid. */ From 22055868581d8df27e61023a1f72875b15496322 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 17 Oct 2013 12:51:58 +0400 Subject: [PATCH 149/613] Fixed typos --- docs/guide/i18n.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 9d12f65..2ec822d 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -40,7 +40,7 @@ Basic message translation ------------------------- Yii basic message translation in its basic variant works without additional PHP extension. What it does is finding a -translation of the message from source language into targer language. Message itself is specified as the first +translation of the message from source language into target language. Message itself is specified as the second `\Yii::t` method parameter: ```php From 0ddd52c09a3e5ab38277376913764f5f6c94476c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 17 Oct 2013 12:56:14 +0200 Subject: [PATCH 150/613] commented private property fixes #997 --- framework/yii/data/Sort.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php index 78fe2e0..5eb031e 100644 --- a/framework/yii/data/Sort.php +++ b/framework/yii/data/Sort.php @@ -234,6 +234,9 @@ class Sort extends Object return $orders; } + /** + * @var array the currently requested sort order as computed by [[getAttributeOrders]]. + */ private $_attributeOrders; /** From 38ccc9e793d728ca777d8ef73a9600dff3bd1046 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 17 Oct 2013 15:10:00 +0400 Subject: [PATCH 151/613] Fixes #930: Added i18n docs about configuring message source --- docs/guide/i18n.md | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 2ec822d..0196434 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -47,9 +47,46 @@ translation of the message from source language into target language. Message it echo \Yii::t('app', 'This is a string to translate!'); ``` -Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration. +Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration: -TBD: https://github.com/yiisoft/yii2/issues/930 +```php +'components' => array( + // ... + 'i18n' => array( + 'translations' => array( + 'app*' => array( + 'class' => 'yii\i18n\PhpMessageSource', + //'basePath' => '@app/messages', + //'sourceLanguage' => 'en_US', + 'fileMap' => array( + 'app' => 'app.php', + 'app/error' => 'error.php', + ), + ), + ), + ), +), +``` + +In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're +handling everything that begins with `app`. + +`class` defines which message source is used. There following message sources are available: + +- PhpMessageSource that uses PHP files. +- GettextMessageSource that uses GNU Gettext MO or PO files. +- DbMessageSource that uses database. + +`basePath` defines where to store messages for the currently used message source. In this case it's `messages` directory + in your application directory. In case of using database this option should be skipped. + +`sourceLanguage` defines which language is used in `\Yii::t` second argument. If not specified, application's source +language is used. + +`fileMap` specifies how message categories specified in the first argument of `\Yii::t()` are mapped to files when +`PhpMessageSource` is used. In the example we're defining two categories `app` and `app/error`. + +Instead of configuring `fileMap` you can rely on convention which is `messages/BasePath/LanguageID/CategoryName.php`. ### Named placeholders From f03c689b0209e35b98fe8fb5c71af2c95912d64e Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 17 Oct 2013 15:37:56 +0400 Subject: [PATCH 152/613] typo fix --- docs/guide/i18n.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 0196434..57f08f7 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -71,7 +71,7 @@ Yii tries to load approprite translation from one of the message sources defined In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're handling everything that begins with `app`. -`class` defines which message source is used. There following message sources are available: +`class` defines which message source is used. The following message sources are available: - PhpMessageSource that uses PHP files. - GettextMessageSource that uses GNU Gettext MO or PO files. From b9de474ccec42d3d04f7bb5ad80040f083596ae5 Mon Sep 17 00:00:00 2001 From: Eugene Kuzminov Date: Thu, 17 Oct 2013 15:24:52 +0300 Subject: [PATCH 153/613] debug panel : change json_encode/decode to serialise/unserialise to prevent non-UTF characters error --- framework/yii/debug/LogTarget.php | 6 +++--- framework/yii/debug/controllers/DefaultController.php | 4 ++-- framework/yii/debug/panels/RequestPanel.php | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php index b415b50..33c81eb 100644 --- a/framework/yii/debug/LogTarget.php +++ b/framework/yii/debug/LogTarget.php @@ -49,7 +49,7 @@ class LogTarget extends Target if (!is_file($indexFile)) { $manifest = array(); } else { - $manifest = json_decode(file_get_contents($indexFile), true); + $manifest = unserialize(file_get_contents($indexFile)); } $request = Yii::$app->getRequest(); $manifest[$this->tag] = $summary = array( @@ -68,8 +68,8 @@ class LogTarget extends Target $data[$id] = $panel->save(); } $data['summary'] = $summary; - file_put_contents($dataFile, json_encode($data)); - file_put_contents($indexFile, json_encode($manifest)); + file_put_contents($dataFile, serialize($data)); + file_put_contents($indexFile, serialize($manifest)); } /** diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php index dd01412..b00881b 100644 --- a/framework/yii/debug/controllers/DefaultController.php +++ b/framework/yii/debug/controllers/DefaultController.php @@ -76,7 +76,7 @@ class DefaultController extends Controller if ($this->_manifest === null) { $indexFile = $this->module->dataPath . '/index.json'; if (is_file($indexFile)) { - $this->_manifest = array_reverse(json_decode(file_get_contents($indexFile), true), true); + $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true); } else { $this->_manifest = array(); } @@ -89,7 +89,7 @@ class DefaultController extends Controller $manifest = $this->getManifest(); if (isset($manifest[$tag])) { $dataFile = $this->module->dataPath . "/$tag.json"; - $data = json_decode(file_get_contents($dataFile), true); + $data = unserialize(file_get_contents($dataFile)); foreach ($this->module->panels as $id => $panel) { if (isset($data[$id])) { $panel->tag = $tag; diff --git a/framework/yii/debug/panels/RequestPanel.php b/framework/yii/debug/panels/RequestPanel.php index 6aa4bf8..58256e4 100644 --- a/framework/yii/debug/panels/RequestPanel.php +++ b/framework/yii/debug/panels/RequestPanel.php @@ -151,7 +151,7 @@ EOD; } $rows = array(); foreach ($values as $name => $value) { - $rows[] = '' . Html::encode($name) . '' . Html::encode(var_export($value, true)) . ''; + $rows[] = '' . Html::encode($name) . '' . htmlspecialchars(var_export($value, true), ENT_QUOTES|ENT_SUBSTITUTE, \Yii::$app->charset, TRUE) . ''; } $rows = implode("\n", $rows); return << Date: Thu, 17 Oct 2013 16:37:31 +0300 Subject: [PATCH 154/613] debug panel : changed .json to .php --- framework/yii/debug/LogTarget.php | 6 +++--- framework/yii/debug/controllers/DefaultController.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php index 33c81eb..31bca6e 100644 --- a/framework/yii/debug/LogTarget.php +++ b/framework/yii/debug/LogTarget.php @@ -45,7 +45,7 @@ class LogTarget extends Target if (!is_dir($path)) { mkdir($path); } - $indexFile = "$path/index.json"; + $indexFile = "$path/index.php"; if (!is_file($indexFile)) { $manifest = array(); } else { @@ -62,7 +62,7 @@ class LogTarget extends Target ); $this->gc($manifest); - $dataFile = "$path/{$this->tag}.json"; + $dataFile = "$path/{$this->tag}.php"; $data = array(); foreach ($this->module->panels as $id => $panel) { $data[$id] = $panel->save(); @@ -93,7 +93,7 @@ class LogTarget extends Target if (count($manifest) > $this->module->historySize + 10) { $n = count($manifest) - $this->module->historySize; foreach (array_keys($manifest) as $tag) { - $file = $this->module->dataPath . "/$tag.json"; + $file = $this->module->dataPath . "/$tag.php"; @unlink($file); unset($manifest[$tag]); if (--$n <= 0) { diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php index b00881b..2026dc7 100644 --- a/framework/yii/debug/controllers/DefaultController.php +++ b/framework/yii/debug/controllers/DefaultController.php @@ -74,7 +74,7 @@ class DefaultController extends Controller protected function getManifest() { if ($this->_manifest === null) { - $indexFile = $this->module->dataPath . '/index.json'; + $indexFile = $this->module->dataPath . '/index.php'; if (is_file($indexFile)) { $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true); } else { @@ -88,7 +88,7 @@ class DefaultController extends Controller { $manifest = $this->getManifest(); if (isset($manifest[$tag])) { - $dataFile = $this->module->dataPath . "/$tag.json"; + $dataFile = $this->module->dataPath . "/$tag.php"; $data = unserialize(file_get_contents($dataFile)); foreach ($this->module->panels as $id => $panel) { if (isset($data[$id])) { From 8f8c7053091269045558467d36838b900cc42ad7 Mon Sep 17 00:00:00 2001 From: chelishchev Date: Thu, 17 Oct 2013 22:53:27 +0300 Subject: [PATCH 155/613] Just simple support PostgreSQL in QueryBuilder --- framework/yii/db/pgsql/QueryBuilder.php | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 9701fd6..c86ec94 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -37,4 +37,43 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BOOLEAN => 'boolean', Schema::TYPE_MONEY => 'numeric(19,4)', ); + + /** + * Builds a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name); + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $oldName the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($oldName, $newName) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName); + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract + * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept + * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null' + * will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' + . $this->db->quoteColumnName($column) . ' TYPE ' + . $this->getColumnType($type); + } } From 89394db017216dc5afab53a00820defb3506aea5 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 17 Oct 2013 17:12:29 -0400 Subject: [PATCH 156/613] Fixes #999. --- framework/yii/debug/LogTarget.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php index 31bca6e..30392f0 100644 --- a/framework/yii/debug/LogTarget.php +++ b/framework/yii/debug/LogTarget.php @@ -45,7 +45,7 @@ class LogTarget extends Target if (!is_dir($path)) { mkdir($path); } - $indexFile = "$path/index.php"; + $indexFile = "$path/index.data"; if (!is_file($indexFile)) { $manifest = array(); } else { @@ -62,7 +62,7 @@ class LogTarget extends Target ); $this->gc($manifest); - $dataFile = "$path/{$this->tag}.php"; + $dataFile = "$path/{$this->tag}.data"; $data = array(); foreach ($this->module->panels as $id => $panel) { $data[$id] = $panel->save(); @@ -93,7 +93,7 @@ class LogTarget extends Target if (count($manifest) > $this->module->historySize + 10) { $n = count($manifest) - $this->module->historySize; foreach (array_keys($manifest) as $tag) { - $file = $this->module->dataPath . "/$tag.php"; + $file = $this->module->dataPath . "/$tag.data"; @unlink($file); unset($manifest[$tag]); if (--$n <= 0) { From 9e1b498fb513b3c51c6f943180a317ffef6f05e2 Mon Sep 17 00:00:00 2001 From: resurtm Date: Fri, 18 Oct 2013 09:31:37 +0600 Subject: [PATCH 157/613] PHP 5.4 supports $this with closures. --- extensions/mutex/yii/mutex/Mutex.php | 5 ++--- framework/yii/base/ErrorHandler.php | 7 +++---- framework/yii/db/Connection.php | 7 +++---- framework/yii/debug/Module.php | 5 ++--- framework/yii/grid/ActionColumn.php | 7 +++---- framework/yii/widgets/BaseListView.php | 5 ++--- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/extensions/mutex/yii/mutex/Mutex.php b/extensions/mutex/yii/mutex/Mutex.php index 8f0a560..db14ce8 100644 --- a/extensions/mutex/yii/mutex/Mutex.php +++ b/extensions/mutex/yii/mutex/Mutex.php @@ -34,11 +34,10 @@ abstract class Mutex extends Component public function init() { if ($this->autoRelease) { - $mutex = $this; $locks = &$this->_locks; - register_shutdown_function(function () use ($mutex, &$locks) { + register_shutdown_function(function () use ($this, &$locks) { foreach ($locks as $lock) { - $mutex->release($lock); + $this->release($lock); } }); } diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 40f5c37..37e4a85 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -175,10 +175,9 @@ class ErrorHandler extends Component $html = rtrim($html, '\\'); } elseif (strpos($code, '()') !== false) { // method/function call - $self = $this; - $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) use ($self) { - return '' . - $self->htmlEncode($matches[1]) . '()'; + $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) use ($this) { + return '' . + $this->htmlEncode($matches[1]) . '()'; }, $code); } return $html; diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 69bf6a5..d3058cf 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -508,13 +508,12 @@ class Connection extends Component */ public function quoteSql($sql) { - $db = $this; return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/', - function ($matches) use ($db) { + function ($matches) use ($this) { if (isset($matches[3])) { - return $db->quoteColumnName($matches[3]); + return $this->quoteColumnName($matches[3]); } else { - return str_replace('%', $db->tablePrefix, $db->quoteTableName($matches[2])); + return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2])); } }, $sql); } diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php index 9550b57..a9929c4 100644 --- a/framework/yii/debug/Module.php +++ b/framework/yii/debug/Module.php @@ -57,9 +57,8 @@ class Module extends \yii\base\Module $this->dataPath = Yii::getAlias($this->dataPath); $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this); // do not initialize view component before application is ready (needed when debug in preload) - $module = $this; - Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() use ($module) { - Yii::$app->getView()->on(View::EVENT_END_BODY, array($module, 'renderToolbar')); + Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() use ($this) { + Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); }); foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php index 6a4ed9f..aae59ae 100644 --- a/framework/yii/grid/ActionColumn.php +++ b/framework/yii/grid/ActionColumn.php @@ -88,11 +88,10 @@ class ActionColumn extends Column */ protected function renderDataCellContent($model, $index) { - $column = $this; - return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $column) { + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($this, $model) { $name = $matches[1]; - if (isset($column->buttons[$name])) { - return call_user_func($column->buttons[$name], $model, $column); + if (isset($this->buttons[$name])) { + return call_user_func($this->buttons[$name], $model, $this); } else { return ''; } diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index d90ac89..e292f38 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -92,9 +92,8 @@ abstract class BaseListView extends Widget public function run() { if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $widget = $this; - $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { - $content = $widget->renderSection($matches[0]); + $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($this) { + $content = $this->renderSection($matches[0]); return $content === false ? $matches[0] : $content; }, $this->layout); } else { From b3b5e36d42a1187c14acfa371cf237529a8cddc0 Mon Sep 17 00:00:00 2001 From: resurtm Date: Fri, 18 Oct 2013 09:46:52 +0600 Subject: [PATCH 158/613] Remove use ($this). Related to 9e1b498fb513b3c51c6f943180a317ffef6f05e2. --- framework/yii/base/ErrorHandler.php | 2 +- framework/yii/db/Connection.php | 2 +- framework/yii/debug/Module.php | 2 +- framework/yii/widgets/BaseListView.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 37e4a85..dec5779 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -175,7 +175,7 @@ class ErrorHandler extends Component $html = rtrim($html, '\\'); } elseif (strpos($code, '()') !== false) { // method/function call - $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) use ($this) { + $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) { return '' . $this->htmlEncode($matches[1]) . '()'; }, $code); diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index d3058cf..b79146d 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -509,7 +509,7 @@ class Connection extends Component public function quoteSql($sql) { return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/', - function ($matches) use ($this) { + function ($matches) { if (isset($matches[3])) { return $this->quoteColumnName($matches[3]); } else { diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php index a9929c4..260a981 100644 --- a/framework/yii/debug/Module.php +++ b/framework/yii/debug/Module.php @@ -57,7 +57,7 @@ class Module extends \yii\base\Module $this->dataPath = Yii::getAlias($this->dataPath); $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this); // do not initialize view component before application is ready (needed when debug in preload) - Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() use ($this) { + Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() { Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); }); diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index e292f38..d59c197 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -92,7 +92,7 @@ abstract class BaseListView extends Widget public function run() { if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($this) { + $content = preg_replace_callback("/{\\w+}/", function ($matches) { $content = $this->renderSection($matches[0]); return $content === false ? $matches[0] : $content; }, $this->layout); From 153f697117bd6d21495efc6a8005860b8ae375df Mon Sep 17 00:00:00 2001 From: resurtm Date: Fri, 18 Oct 2013 09:53:26 +0600 Subject: [PATCH 159/613] Remove PHP 5.3 Travis build rule. Related to #35. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebc42ad..a905b36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.3 - 5.4 - 5.5 From 249493985d440de71d77194052f980817e7f85fa Mon Sep 17 00:00:00 2001 From: sensorario Date: Fri, 18 Oct 2013 07:30:41 +0200 Subject: [PATCH 160/613] Convert to short syntax --- apps/basic/config/AppAsset.php | 12 ++++++------ apps/basic/config/console.php | 30 +++++++++++++++--------------- apps/basic/config/params.php | 4 ++-- apps/basic/config/web.php | 38 +++++++++++++++++++------------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/basic/config/AppAsset.php b/apps/basic/config/AppAsset.php index 3e22b9b..87f6f8b 100644 --- a/apps/basic/config/AppAsset.php +++ b/apps/basic/config/AppAsset.php @@ -17,13 +17,13 @@ class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; - public $css = array( + public $css = [ 'css/site.css', - ); - public $js = array( - ); - public $depends = array( + ]; + public $js = [ + ]; + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/apps/basic/config/console.php b/apps/basic/config/console.php index 12f13cd..8d50bd2 100644 --- a/apps/basic/config/console.php +++ b/apps/basic/config/console.php @@ -1,25 +1,25 @@ 'bootstrap-console', 'basePath' => dirname(__DIR__), 'preload' => array('log'), 'controllerPath' => dirname(__DIR__) . '/commands', 'controllerNamespace' => 'app\commands', - 'modules' => array( - ), - 'components' => array( - 'cache' => array( + 'modules' => [ + ], + 'components' => [ + 'cache' => [ 'class' => 'yii\caching\FileCache', - ), - 'log' => array( - 'targets' => array( - array( + ], + 'log' => [ + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; diff --git a/apps/basic/config/params.php b/apps/basic/config/params.php index 398a1ce..93cb368 100644 --- a/apps/basic/config/params.php +++ b/apps/basic/config/params.php @@ -1,5 +1,5 @@ 'admin@example.com', -); +]; diff --git a/apps/basic/config/web.php b/apps/basic/config/web.php index e7d9420..6e4d2a1 100644 --- a/apps/basic/config/web.php +++ b/apps/basic/config/web.php @@ -1,33 +1,33 @@ 'bootstrap', 'basePath' => dirname(__DIR__), - 'components' => array( - 'request' => array( + 'components' => [ + 'request' => [ 'enableCsrfValidation' => true, - ), - 'cache' => array( + ], + 'cache' => [ 'class' => 'yii\caching\FileCache', - ), - 'user' => array( + ], + 'user' => [ 'identityClass' => 'app\models\User', - ), - 'errorHandler' => array( + ], + 'errorHandler' => [ 'errorAction' => 'site/error', - ), - 'log' => array( + ], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; if (YII_ENV_DEV) { $config['preload'][] = 'debug'; From dfb38954570a57a54ef85614b97de20b4a9dbe44 Mon Sep 17 00:00:00 2001 From: Yakir Sitbon Date: Fri, 18 Oct 2013 09:48:27 +0000 Subject: [PATCH 161/613] Convert to short syntax (array) --- apps/basic/controllers/SiteController.php | 60 +++++++++++++++---------------- apps/basic/models/ContactForm.php | 14 ++++---- apps/basic/models/LoginForm.php | 10 +++--- apps/basic/models/User.php | 12 +++---- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index e243223..f369e3b 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -13,43 +13,43 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => AccessControl::className(), - 'only' => array('login', 'logout'), - 'rules' => array( - array( - 'actions' => array('login'), + 'only' => ['login', 'logout'], + 'rules' => [ + [ + 'actions' => ['login'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout'), + 'roles' => ['?'], + ], + [ + 'actions' => ['logout'], 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - 'verbs' => array( + 'roles' => ['@'], + ], + ], + ], + 'verbs' => [ 'class' => VerbFilter::className(), - 'actions' => array( - 'logout' => array('post'), - ), - ), - ); + 'actions' => [ + 'logout' => ['post'], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - 'captcha' => array( + ], + 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ), - ); + ], + ]; } public function actionIndex() @@ -63,9 +63,9 @@ class SiteController extends Controller if ($model->load($_POST) && $model->login()) { return $this->goBack(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } @@ -82,9 +82,9 @@ class SiteController extends Controller Yii::$app->session->setFlash('contactFormSubmitted'); return $this->refresh(); } else { - return $this->render('contact', array( + return $this->render('contact', [ 'model' => $model, - )); + ]); } } diff --git a/apps/basic/models/ContactForm.php b/apps/basic/models/ContactForm.php index 7b713a1..f42cd53 100644 --- a/apps/basic/models/ContactForm.php +++ b/apps/basic/models/ContactForm.php @@ -20,14 +20,14 @@ class ContactForm extends Model */ public function rules() { - return array( + return [ // name, email, subject and body are required - array('name, email, subject, body', 'required'), + ['name, email, subject, body', 'required'], // email has to be a valid email address - array('email', 'email'), + ['email', 'email'], // verifyCode needs to be entered correctly - array('verifyCode', 'captcha'), - ); + ['verifyCode', 'captcha'], + ]; } /** @@ -35,9 +35,9 @@ class ContactForm extends Model */ public function attributeLabels() { - return array( + return [ 'verifyCode' => 'Verification Code', - ); + ]; } /** diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php index 5ba1dc6..339cf31 100644 --- a/apps/basic/models/LoginForm.php +++ b/apps/basic/models/LoginForm.php @@ -19,14 +19,14 @@ class LoginForm extends Model */ public function rules() { - return array( + return [ // username and password are both required - array('username, password', 'required'), + ['username, password', 'required'], // password is validated by validatePassword() - array('password', 'validatePassword'), + ['password', 'validatePassword'], // rememberMe must be a boolean value - array('rememberMe', 'boolean'), - ); + ['rememberMe', 'boolean'], + ]; } /** diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index e1088a0..085cda2 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -9,20 +9,20 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface public $password; public $authKey; - private static $users = array( - '100' => array( + private static $users = [ + '100' => [ 'id' => '100', 'username' => 'admin', 'password' => 'admin', 'authKey' => 'test100key', - ), - '101' => array( + ], + '101' => [ 'id' => '101', 'username' => 'demo', 'password' => 'demo', 'authKey' => 'test101key', - ), - ); + ], + ]; public static function findIdentity($id) { From e115b0c8604a498adde9604ab250e24487045ec5 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 18 Oct 2013 13:01:13 +0400 Subject: [PATCH 162/613] Updated docs, composer.json and other things to mention and use PHP 5.4 --- README.md | 2 +- apps/advanced/README.md | 2 +- apps/advanced/composer.json | 2 +- apps/basic/README.md | 2 +- apps/basic/composer.json | 2 +- apps/benchmark/README.md | 2 +- apps/benchmark/composer.json | 2 +- docs/guide/apps-advanced.md | 2 +- docs/guide/apps-basic.md | 2 +- docs/guide/installation.md | 4 ++-- docs/guide/overview.md | 2 +- framework/README.md | 2 +- framework/composer.json | 2 +- framework/yii/db/Connection.php | 3 +-- framework/yii/requirements/requirements.php | 4 ++-- 15 files changed, 17 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index dc03ac7..3bc2d67 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4. DOCUMENTATION diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 3903532..1afef67 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -52,7 +52,7 @@ environments/ contains environment-based overrides REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. In order for captcha to work you need either GD2 extension or ImageMagick PHP extension. diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 4c0fced..88522f2 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -14,7 +14,7 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", "yiisoft/yii2-composer": "dev-master" }, diff --git a/apps/basic/README.md b/apps/basic/README.md index aaa7fba..60c3270 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -31,7 +31,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. In order for captcha to work you need either GD2 extension or ImageMagick PHP extension. diff --git a/apps/basic/composer.json b/apps/basic/composer.json index dd50b69..e9af8b7 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -14,7 +14,7 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", "yiisoft/yii2-composer": "dev-master" }, diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md index 2d5871a..7eddae8 100644 --- a/apps/benchmark/README.md +++ b/apps/benchmark/README.md @@ -27,7 +27,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. INSTALLATION diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json index 2b077e0..c074233 100644 --- a/apps/benchmark/composer.json +++ b/apps/benchmark/composer.json @@ -17,7 +17,7 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master" } } diff --git a/docs/guide/apps-advanced.md b/docs/guide/apps-advanced.md index 61e2489..9c6532a 100644 --- a/docs/guide/apps-advanced.md +++ b/docs/guide/apps-advanced.md @@ -138,7 +138,7 @@ directory: }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", "yiisoft/yii2-composer": "dev-master" }, diff --git a/docs/guide/apps-basic.md b/docs/guide/apps-basic.md index e61ab55..0f366af 100644 --- a/docs/guide/apps-basic.md +++ b/docs/guide/apps-basic.md @@ -128,7 +128,7 @@ directory: }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", "yiisoft/yii2-composer": "dev-master" }, diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 16a496c..8ec3619 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -48,10 +48,10 @@ script via the following URL in a Web browser: http://hostname/path/to/yii/requirements/index.php ~~~ -Yii requires PHP 5.3.7, so the server must have PHP 5.3.7 or above installed and +Yii requires PHP 5.4.0, so the server must have PHP 5.4.0 or above installed and available to the web server. Yii has been tested with [Apache HTTP server](http://httpd.apache.org/) on Windows and Linux. It may also run on other Web servers and platforms, -provided PHP 5.3 is supported. +provided PHP 5.4 is supported. Recommended Apache Configuration diff --git a/docs/guide/overview.md b/docs/guide/overview.md index 835c511..48e6e0d 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -11,7 +11,7 @@ Requirements ------------ To run a Yii-powered Web application, you need a Web server that supports -PHP 5.3.? or greater. +PHP 5.4.0 or greater. For developers who want to use Yii, understanding object-oriented programming (OOP) is very helpful, because Yii is a pure OOP framework. diff --git a/framework/README.md b/framework/README.md index 1cbfdf8..b5c754f 100644 --- a/framework/README.md +++ b/framework/README.md @@ -16,6 +16,6 @@ without prior notices. **Yii 2.0 is not ready for production use yet.** REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. diff --git a/framework/composer.json b/framework/composer.json index c5ff072..0a9c630 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -64,7 +64,7 @@ "source": "https://github.com/yiisoft/yii2" }, "require": { - "php": ">=5.3.7", + "php": ">=5.4.0", "ext-mbstring": "*", "lib-pcre": "*", "phpspec/php-diff": "1.0.*", diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index b79146d..b080836 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -205,8 +205,7 @@ class Connection extends Component * as specified by the database. * * Note that if you're using GBK or BIG5 then it's highly recommended to - * update to PHP 5.3.6+ and to specify charset via DSN like - * 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. + * specify charset via DSN like 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. */ public $charset; /** diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 571aa57..f70f414 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -9,9 +9,9 @@ return array( array( 'name' => 'PHP version', 'mandatory' => true, - 'condition' => version_compare(PHP_VERSION, '5.3.7', '>='), + 'condition' => version_compare(PHP_VERSION, '5.4.0', '>='), 'by' => 'Yii Framework', - 'memo' => 'PHP 5.3.7 or higher is required.', + 'memo' => 'PHP 5.4.0 or higher is required.', ), array( 'name' => 'Reflection extension', From dfb6e1f404bcca2e6c0128e45ab41f78181bcf71 Mon Sep 17 00:00:00 2001 From: Yakir Sitbon Date: Fri, 18 Oct 2013 12:03:36 +0000 Subject: [PATCH 163/613] Convert to short syntax [2]. --- .../backend/controllers/SiteController.php | 40 ++++++------ apps/advanced/backend/views/layouts/main.php | 30 ++++----- apps/advanced/backend/views/site/login.php | 4 +- apps/advanced/common/config/params.php | 12 ++-- apps/advanced/common/models/LoginForm.php | 10 +-- apps/advanced/common/models/User.php | 54 ++++++++-------- apps/advanced/console/config/main.php | 26 ++++---- apps/advanced/console/config/params.php | 4 +- .../environments/dev/backend/config/main-local.php | 12 ++-- .../dev/backend/config/params-local.php | 4 +- .../dev/common/config/params-local.php | 4 +- .../environments/dev/console/config/main-local.php | 4 +- .../dev/console/config/params-local.php | 4 +- .../dev/frontend/config/main-local.php | 12 ++-- .../dev/frontend/config/params-local.php | 4 +- apps/advanced/environments/index.php | 28 ++++---- .../prod/backend/config/main-local.php | 4 +- .../prod/backend/config/params-local.php | 4 +- .../prod/common/config/params-local.php | 4 +- .../prod/console/config/main-local.php | 4 +- .../prod/console/config/params-local.php | 4 +- .../prod/frontend/config/main-local.php | 4 +- .../prod/frontend/config/params-local.php | 4 +- apps/advanced/frontend/config/AppAsset.php | 12 ++-- apps/advanced/frontend/config/main.php | 38 +++++------ apps/advanced/frontend/config/params.php | 4 +- .../frontend/controllers/SiteController.php | 74 +++++++++++----------- apps/advanced/frontend/models/ContactForm.php | 14 ++-- .../frontend/views/emails/passwordResetToken.php | 2 +- apps/advanced/frontend/views/layouts/main.php | 36 +++++------ apps/advanced/frontend/views/site/contact.php | 12 ++-- apps/advanced/frontend/views/site/login.php | 6 +- .../views/site/requestPasswordResetToken.php | 4 +- .../advanced/frontend/views/site/resetPassword.php | 4 +- apps/advanced/frontend/views/site/signup.php | 4 +- apps/advanced/init | 6 +- apps/advanced/requirements.php | 44 ++++++------- apps/basic/config/console.php | 2 +- apps/basic/requirements.php | 44 ++++++------- apps/basic/tests/acceptance/ContactCept.php | 10 +-- apps/basic/tests/acceptance/LoginCept.php | 10 +-- apps/basic/tests/functional/ContactCept.php | 10 +-- apps/basic/tests/functional/LoginCept.php | 10 +-- apps/basic/views/layouts/main.php | 38 +++++------ apps/basic/views/site/contact.php | 12 ++-- apps/basic/views/site/login.php | 18 +++--- apps/basic/web/index-test.php | 2 +- 47 files changed, 348 insertions(+), 348 deletions(-) diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php index 28f2310..ebfb484 100644 --- a/apps/advanced/backend/controllers/SiteController.php +++ b/apps/advanced/backend/controllers/SiteController.php @@ -10,32 +10,32 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => \yii\web\AccessControl::className(), - 'rules' => array( - array( - 'actions' => array('login'), + 'rules' => [ + [ + 'actions' => ['login'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout', 'index'), + 'roles' => ['?'], + ], + [ + 'actions' => ['logout', 'index'], 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - ); + 'roles' => ['@'], + ], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - ); + ], + ]; } public function actionIndex() @@ -49,9 +49,9 @@ class SiteController extends Controller if ($model->load($_POST) && $model->login()) { return $this->goHome(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php index 928f990..6d958cb 100644 --- a/apps/advanced/backend/views/layouts/main.php +++ b/apps/advanced/backend/views/layouts/main.php @@ -22,32 +22,32 @@ AppAsset::register($this); beginBody(); ?> 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - $menuItems = array( - array('label' => 'Home', 'url' => array('/site/index')), - ); + ], + ]); + $menuItems = [ + ['label' => 'Home', 'url' => ['/site/index']], + ]; if (Yii::$app->user->isGuest) { - $menuItems[] = array('label' => 'Login', 'url' => array('/site/login')); + $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; } else { - $menuItems[] = array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')); + $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; } - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav pull-right'], 'items' => $menuItems, - )); + ]); NavBar::end(); ?>
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> + isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]); ?>
diff --git a/apps/advanced/backend/views/site/login.php b/apps/advanced/backend/views/site/login.php index 0c16570..bcb673d 100644 --- a/apps/advanced/backend/views/site/login.php +++ b/apps/advanced/backend/views/site/login.php @@ -17,12 +17,12 @@ $this->params['breadcrumbs'][] = $this->title;
- 'login-form')); ?> + 'login-form']); ?> field($model, 'username'); ?> field($model, 'password')->passwordInput(); ?> field($model, 'rememberMe')->checkbox(); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/common/config/params.php b/apps/advanced/common/config/params.php index 2dff87b..7dab548 100644 --- a/apps/advanced/common/config/params.php +++ b/apps/advanced/common/config/params.php @@ -4,19 +4,19 @@ Yii::setAlias('common', __DIR__ . '/../'); Yii::setAlias('frontend', __DIR__ . '/../../frontend'); Yii::setAlias('backend', __DIR__ . '/../../backend'); -return array( +return [ 'adminEmail' => 'admin@example.com', 'supportEmail' => 'support@example.com', - 'components.cache' => array( + 'components.cache' => [ 'class' => 'yii\caching\FileCache', - ), + ], - 'components.db' => array( + 'components.db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', 'username' => 'root', 'password' => '', 'charset' => 'utf8', - ), -); + ], +]; diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php index 4631dbd..339005b 100644 --- a/apps/advanced/common/models/LoginForm.php +++ b/apps/advanced/common/models/LoginForm.php @@ -19,14 +19,14 @@ class LoginForm extends Model */ public function rules() { - return array( + return [ // username and password are both required - array('username, password', 'required'), + ['username, password', 'required'], // password is validated by validatePassword() - array('password', 'validatePassword'), + ['password', 'validatePassword'], // rememberMe must be a boolean value - array('rememberMe', 'boolean'), - ); + ['rememberMe', 'boolean'], + ]; } /** diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index 62baf48..17bd630 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -34,15 +34,15 @@ class User extends ActiveRecord implements IdentityInterface public function behaviors() { - return array( - 'timestamp' => array( + return [ + 'timestamp' => [ 'class' => 'yii\behaviors\AutoTimestamp', - 'attributes' => array( - ActiveRecord::EVENT_BEFORE_INSERT => array('create_time', 'update_time'), + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'], ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', - ), - ), - ); + ], + ], + ]; } /** @@ -64,7 +64,7 @@ class User extends ActiveRecord implements IdentityInterface */ public static function findByUsername($username) { - return static::find(array('username' => $username, 'status' => static::STATUS_ACTIVE)); + return static::find(['username' => $username, 'status' => static::STATUS_ACTIVE]); } /** @@ -103,29 +103,29 @@ class User extends ActiveRecord implements IdentityInterface public function rules() { - return array( - array('username', 'filter', 'filter' => 'trim'), - array('username', 'required'), - array('username', 'string', 'min' => 2, 'max' => 255), - - array('email', 'filter', 'filter' => 'trim'), - array('email', 'required'), - array('email', 'email'), - array('email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'), - array('email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'), - - array('password', 'required'), - array('password', 'string', 'min' => 6), - ); + return [ + ['username', 'filter', 'filter' => 'trim'], + ['username', 'required'], + ['username', 'string', 'min' => 2, 'max' => 255], + + ['email', 'filter', 'filter' => 'trim'], + ['email', 'required'], + ['email', 'email'], + ['email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'], + ['email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'], + + ['password', 'required'], + ['password', 'string', 'min' => 6], + ]; } public function scenarios() { - return array( - 'signup' => array('username', 'email', 'password'), - 'resetPassword' => array('password'), - 'requestPasswordResetToken' => array('email'), - ); + return [ + 'signup' => ['username', 'email', 'password'], + 'resetPassword' => ['password'], + 'requestPasswordResetToken' => ['email'], + ]; } public function beforeSave($insert) diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php index 7a223c3..fbf452a 100644 --- a/apps/advanced/console/config/main.php +++ b/apps/advanced/console/config/main.php @@ -8,24 +8,24 @@ $params = array_merge( require(__DIR__ . '/params-local.php') ); -return array( +return [ 'id' => 'app-console', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'controllerNamespace' => 'console\controllers', - 'modules' => array( - ), - 'components' => array( + 'modules' => [ + ], + 'components' => [ 'db' => $params['components.db'], 'cache' => $params['components.cache'], - 'log' => array( - 'targets' => array( - array( + 'log' => [ + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; diff --git a/apps/advanced/console/config/params.php b/apps/advanced/console/config/params.php index 1643a70..0e625dc 100644 --- a/apps/advanced/console/config/params.php +++ b/apps/advanced/console/config/params.php @@ -1,4 +1,4 @@ 'admin@example.com', -); +]; diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php index 2689ed1..0c1f40e 100644 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ b/apps/advanced/environments/dev/backend/config/main-local.php @@ -1,11 +1,11 @@ array( +return [ + 'preload' => [ //'debug', - ), - 'modules' => array( + ], + 'modules' => [ // 'debug' => array( // 'class' => 'yii\debug\Module', // ), - ), -); + ], +]; diff --git a/apps/advanced/environments/dev/backend/config/params-local.php b/apps/advanced/environments/dev/backend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/backend/config/params-local.php +++ b/apps/advanced/environments/dev/backend/config/params-local.php @@ -1,3 +1,3 @@ array( +return [ + 'preload' => [ //'debug', - ), - 'modules' => array( + ], + 'modules' => [ // 'debug' => array( // 'class' => 'yii\debug\Module', // ), - ), -); + ], +]; diff --git a/apps/advanced/environments/dev/frontend/config/params-local.php b/apps/advanced/environments/dev/frontend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/frontend/config/params-local.php +++ b/apps/advanced/environments/dev/frontend/config/params-local.php @@ -1,3 +1,3 @@ array( +return [ + 'Development' => [ 'path' => 'dev', - 'writable' => array( + 'writable' => [ // handled by composer.json already - ), - 'executable' => array( + ], + 'executable' => [ 'yii', - ), - ), - 'Production' => array( + ], + ], + 'Production' => [ 'path' => 'prod', - 'writable' => array( + 'writable' => [ // handled by composer.json already - ), - 'executable' => array( + ], + 'executable' => [ 'yii', - ), - ), -); + ], + ], +]; diff --git a/apps/advanced/environments/prod/backend/config/main-local.php b/apps/advanced/environments/prod/backend/config/main-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/backend/config/main-local.php +++ b/apps/advanced/environments/prod/backend/config/main-local.php @@ -1,3 +1,3 @@ 'app-frontend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'controllerNamespace' => 'frontend\controllers', - 'modules' => array( + 'modules' => [ 'gii' => 'yii\gii\Module' - ), - 'components' => array( - 'request' => array( + ], + 'components' => [ + 'request' => [ 'enableCsrfValidation' => true, - ), + ], 'db' => $params['components.db'], 'cache' => $params['components.cache'], - 'user' => array( + 'user' => [ 'identityClass' => 'common\models\User', - ), - 'log' => array( + ], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - 'errorHandler' => array( + 'levels' => ['error', 'warning'], + ], + ], + ], + 'errorHandler' => [ 'errorAction' => 'site/error', - ), - ), + ], + ], 'params' => $params, -); +]; diff --git a/apps/advanced/frontend/config/params.php b/apps/advanced/frontend/config/params.php index 1643a70..0e625dc 100644 --- a/apps/advanced/frontend/config/params.php +++ b/apps/advanced/frontend/config/params.php @@ -1,4 +1,4 @@ 'admin@example.com', -); +]; diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index a9413de..26aa300 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -14,37 +14,37 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => \yii\web\AccessControl::className(), - 'only' => array('login', 'logout', 'signup'), - 'rules' => array( - array( - 'actions' => array('login', 'signup'), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'actions' => ['login', 'signup'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout'), + 'roles' => ['?'], + ], + [ + 'actions' => ['logout'], 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - ); + 'roles' => ['@'], + ], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - 'captcha' => array( + ], + 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ), - ); + ], + ]; } public function actionIndex() @@ -58,9 +58,9 @@ class SiteController extends Controller if ($model->load($_POST) && $model->login()) { return $this->goHome(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } @@ -77,9 +77,9 @@ class SiteController extends Controller Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.'); return $this->refresh(); } else { - return $this->render('contact', array( + return $this->render('contact', [ 'model' => $model, - )); + ]); } } @@ -98,9 +98,9 @@ class SiteController extends Controller } } - return $this->render('signup', array( + return $this->render('signup', [ 'model' => $model, - )); + ]); } public function actionRequestPasswordReset() @@ -115,17 +115,17 @@ class SiteController extends Controller Yii::$app->getSession()->setFlash('error', 'There was an error sending email.'); } } - return $this->render('requestPasswordResetToken', array( + return $this->render('requestPasswordResetToken', [ 'model' => $model, - )); + ]); } public function actionResetPassword($token) { - $model = User::find(array( + $model = User::find([ 'password_reset_token' => $token, 'status' => User::STATUS_ACTIVE, - )); + ]); if (!$model) { throw new HttpException(400, 'Wrong password reset token.'); @@ -137,17 +137,17 @@ class SiteController extends Controller return $this->goHome(); } - return $this->render('resetPassword', array( + return $this->render('resetPassword', [ 'model' => $model, - )); + ]); } private function sendPasswordResetEmail($email) { - $user = User::find(array( + $user = User::find([ 'status' => User::STATUS_ACTIVE, 'email' => $email, - )); + ]); if (!$user) { return false; @@ -158,9 +158,9 @@ class SiteController extends Controller $fromEmail = \Yii::$app->params['supportEmail']; $name = '=?UTF-8?B?' . base64_encode(\Yii::$app->name . ' robot') . '?='; $subject = '=?UTF-8?B?' . base64_encode('Password reset for ' . \Yii::$app->name) . '?='; - $body = $this->renderPartial('/emails/passwordResetToken', array( + $body = $this->renderPartial('/emails/passwordResetToken', [ 'user' => $user, - )); + ]); $headers = "From: $name <{$fromEmail}>\r\n" . "MIME-Version: 1.0\r\n" . "Content-type: text/plain; charset=UTF-8"; diff --git a/apps/advanced/frontend/models/ContactForm.php b/apps/advanced/frontend/models/ContactForm.php index b3d8682..ddb622b 100644 --- a/apps/advanced/frontend/models/ContactForm.php +++ b/apps/advanced/frontend/models/ContactForm.php @@ -20,14 +20,14 @@ class ContactForm extends Model */ public function rules() { - return array( + return [ // name, email, subject and body are required - array('name, email, subject, body', 'required'), + ['name, email, subject, body', 'required'], // email has to be a valid email address - array('email', 'email'), + ['email', 'email'], // verifyCode needs to be entered correctly - array('verifyCode', 'captcha'), - ); + ['verifyCode', 'captcha'], + ]; } /** @@ -35,9 +35,9 @@ class ContactForm extends Model */ public function attributeLabels() { - return array( + return [ 'verifyCode' => 'Verification Code', - ); + ]; } /** diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 1e7a855..134dc08 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -6,7 +6,7 @@ use yii\helpers\Html; * @var common\models\User $user; */ -$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', array('token' => $user->password_reset_token)); +$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', ['token' => $user->password_reset_token]); ?> Hello username)?>, diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php index 0165ba0..d3e05a7 100644 --- a/apps/advanced/frontend/views/layouts/main.php +++ b/apps/advanced/frontend/views/layouts/main.php @@ -23,35 +23,35 @@ AppAsset::register($this); beginBody(); ?> 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - $menuItems = array( - array('label' => 'Home', 'url' => array('/site/index')), - array('label' => 'About', 'url' => array('/site/about')), - array('label' => 'Contact', 'url' => array('/site/contact')), - ); + ], + ]); + $menuItems = [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], + ]; if (Yii::$app->user->isGuest) { - $menuItems[] = array('label' => 'Signup', 'url' => array('/site/signup')); - $menuItems[] = array('label' => 'Login', 'url' => array('/site/login')); + $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; + $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; } else { - $menuItems[] = array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')); + $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; } - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav pull-right'], 'items' => $menuItems, - )); + ]); NavBar::end(); ?>
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> + isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]); ?>
diff --git a/apps/advanced/frontend/views/site/contact.php b/apps/advanced/frontend/views/site/contact.php index 851deda..9b1b392 100644 --- a/apps/advanced/frontend/views/site/contact.php +++ b/apps/advanced/frontend/views/site/contact.php @@ -20,17 +20,17 @@ $this->params['breadcrumbs'][] = $this->title;
- 'contact-form')); ?> + 'contact-form']); ?> field($model, 'name'); ?> field($model, 'email'); ?> field($model, 'subject'); ?> - field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode')->widget(Captcha::className(), array( - 'options' => array('class' => 'form-control'), + field($model, 'body')->textArea(['rows' => 6]); ?> + field($model, 'verifyCode')->widget(Captcha::className(), [ + 'options' => ['class' => 'form-control'], 'template' => '
{image}
{input}
', - )); ?> + ]); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/frontend/views/site/login.php b/apps/advanced/frontend/views/site/login.php index 5e7f6f6..22b5db5 100644 --- a/apps/advanced/frontend/views/site/login.php +++ b/apps/advanced/frontend/views/site/login.php @@ -17,15 +17,15 @@ $this->params['breadcrumbs'][] = $this->title;
- 'login-form')); ?> + 'login-form']); ?> field($model, 'username'); ?> field($model, 'password')->passwordInput(); ?> field($model, 'rememberMe')->checkbox(); ?>
- If you forgot your password you can . + If you forgot your password you can .
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/frontend/views/site/requestPasswordResetToken.php b/apps/advanced/frontend/views/site/requestPasswordResetToken.php index c754948..0b0ace4 100644 --- a/apps/advanced/frontend/views/site/requestPasswordResetToken.php +++ b/apps/advanced/frontend/views/site/requestPasswordResetToken.php @@ -17,10 +17,10 @@ $this->params['breadcrumbs'][] = $this->title;
- 'request-password-reset-form')); ?> + 'request-password-reset-form']); ?> field($model, 'email'); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/frontend/views/site/resetPassword.php b/apps/advanced/frontend/views/site/resetPassword.php index 2c38028..92a1363 100644 --- a/apps/advanced/frontend/views/site/resetPassword.php +++ b/apps/advanced/frontend/views/site/resetPassword.php @@ -17,10 +17,10 @@ $this->params['breadcrumbs'][] = $this->title;
- 'reset-password-form')); ?> + 'reset-password-form']); ?> field($model, 'password')->passwordInput(); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/frontend/views/site/signup.php b/apps/advanced/frontend/views/site/signup.php index 92525bf..7f6c2ad 100644 --- a/apps/advanced/frontend/views/site/signup.php +++ b/apps/advanced/frontend/views/site/signup.php @@ -17,12 +17,12 @@ $this->params['breadcrumbs'][] = $this->title;
- 'form-signup')); ?> + 'form-signup']); ?> field($model, 'username'); ?> field($model, 'email'); ?> field($model, 'password')->passwordInput(); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/advanced/init b/apps/advanced/init index 3a8f6a6..f5d2691 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -73,7 +73,7 @@ echo "\n ... initialization completed.\n\n"; function getFileList($root, $basePath = '') { - $files = array(); + $files = []; $handle = opendir($root); while (($path = readdir($handle)) !== false) { if ($path === '.svn' || $path === '.' || $path === '..') { @@ -135,13 +135,13 @@ function copyFile($root, $source, $target, &$all) function getParams() { - $rawParams = array(); + $rawParams = []; if (isset($_SERVER['argv'])) { $rawParams = $_SERVER['argv']; array_shift($rawParams); } - $params = array(); + $params = []; foreach ($rawParams as $param) { if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { $name = $matches[1]; diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php index c9e6493..139284f 100644 --- a/apps/advanced/requirements.php +++ b/apps/advanced/requirements.php @@ -26,78 +26,78 @@ $requirementsChecker = new YiiRequirementChecker(); /** * Adjust requirements according to your application specifics. */ -$requirements = array( +$requirements = [ // Database : - array( + [ 'name' => 'PDO extension', 'mandatory' => true, 'condition' => extension_loaded('pdo'), 'by' => 'All DB-related classes', - ), - array( + ], + [ 'name' => 'PDO SQLite extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_sqlite'), 'by' => 'All DB-related classes', 'memo' => 'Required for SQLite database.', - ), - array( + ], + [ 'name' => 'PDO MySQL extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_mysql'), 'by' => 'All DB-related classes', 'memo' => 'Required for MySQL database.', - ), + ], // Cache : - array( + [ 'name' => 'Memcache extension', 'mandatory' => false, 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 'by' => 'CMemCache', 'memo' => extension_loaded('memcached') ? 'To use memcached set CMemCache::useMemcached to true.' : '' - ), - array( + ], + [ 'name' => 'APC extension', 'mandatory' => false, 'condition' => extension_loaded('apc') || extension_loaded('apc'), 'by' => 'CApcCache', - ), + ], // Additional PHP extensions : - array( + [ 'name' => 'Mcrypt extension', 'mandatory' => false, 'condition' => extension_loaded('mcrypt'), 'by' => 'CSecurityManager', 'memo' => 'Required by encrypt and decrypt methods.' - ), + ], // PHP ini : - 'phpSafeMode' => array( + 'phpSafeMode' => [ 'name' => 'PHP safe mode', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 'by' => 'File uploading and console command execution', 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( + ], + 'phpExposePhp' => [ 'name' => 'Expose PHP', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 'by' => 'Security reasons', 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( + ], + 'phpAllowUrlInclude' => [ 'name' => 'PHP allow url include', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 'by' => 'Security reasons', 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( + ], + 'phpSmtp' => [ 'name' => 'PHP mail SMTP', 'mandatory' => false, 'condition' => strlen(ini_get('SMTP'))>0, 'by' => 'Email sending', 'memo' => 'PHP mail SMTP server required', - ), -); + ], +]; $requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/config/console.php b/apps/basic/config/console.php index 8d50bd2..cc1cdb8 100644 --- a/apps/basic/config/console.php +++ b/apps/basic/config/console.php @@ -3,7 +3,7 @@ $params = require(__DIR__ . '/params.php'); return [ 'id' => 'bootstrap-console', 'basePath' => dirname(__DIR__), - 'preload' => array('log'), + 'preload' => ['log'], 'controllerPath' => dirname(__DIR__) . '/commands', 'controllerNamespace' => 'app\commands', 'modules' => [ diff --git a/apps/basic/requirements.php b/apps/basic/requirements.php index c9e6493..139284f 100644 --- a/apps/basic/requirements.php +++ b/apps/basic/requirements.php @@ -26,78 +26,78 @@ $requirementsChecker = new YiiRequirementChecker(); /** * Adjust requirements according to your application specifics. */ -$requirements = array( +$requirements = [ // Database : - array( + [ 'name' => 'PDO extension', 'mandatory' => true, 'condition' => extension_loaded('pdo'), 'by' => 'All DB-related classes', - ), - array( + ], + [ 'name' => 'PDO SQLite extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_sqlite'), 'by' => 'All DB-related classes', 'memo' => 'Required for SQLite database.', - ), - array( + ], + [ 'name' => 'PDO MySQL extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_mysql'), 'by' => 'All DB-related classes', 'memo' => 'Required for MySQL database.', - ), + ], // Cache : - array( + [ 'name' => 'Memcache extension', 'mandatory' => false, 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 'by' => 'CMemCache', 'memo' => extension_loaded('memcached') ? 'To use memcached set CMemCache::useMemcached to true.' : '' - ), - array( + ], + [ 'name' => 'APC extension', 'mandatory' => false, 'condition' => extension_loaded('apc') || extension_loaded('apc'), 'by' => 'CApcCache', - ), + ], // Additional PHP extensions : - array( + [ 'name' => 'Mcrypt extension', 'mandatory' => false, 'condition' => extension_loaded('mcrypt'), 'by' => 'CSecurityManager', 'memo' => 'Required by encrypt and decrypt methods.' - ), + ], // PHP ini : - 'phpSafeMode' => array( + 'phpSafeMode' => [ 'name' => 'PHP safe mode', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 'by' => 'File uploading and console command execution', 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( + ], + 'phpExposePhp' => [ 'name' => 'Expose PHP', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 'by' => 'Security reasons', 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( + ], + 'phpAllowUrlInclude' => [ 'name' => 'PHP allow url include', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 'by' => 'Security reasons', 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( + ], + 'phpSmtp' => [ 'name' => 'PHP mail SMTP', 'mandatory' => false, 'condition' => strlen(ini_get('SMTP'))>0, 'by' => 'Email sending', 'memo' => 'PHP mail SMTP server required', - ), -); + ], +]; $requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/tests/acceptance/ContactCept.php b/apps/basic/tests/acceptance/ContactCept.php index 73527ab..5ec5641 100644 --- a/apps/basic/tests/acceptance/ContactCept.php +++ b/apps/basic/tests/acceptance/ContactCept.php @@ -4,7 +4,7 @@ $I->wantTo('ensure that contact works'); $I->amOnPage('?r=site/contact'); $I->see('Contact', 'h1'); -$I->submitForm('#contact-form', array()); +$I->submitForm('#contact-form', []); $I->see('Contact', 'h1'); $I->see('Name cannot be blank'); $I->see('Email cannot be blank'); @@ -12,25 +12,25 @@ $I->see('Subject cannot be blank'); $I->see('Body cannot be blank'); $I->see('The verification code is incorrect'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester.email', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSee('Name cannot be blank', '.help-inline'); $I->see('Email is not a valid email address.'); $I->dontSee('Subject cannot be blank', '.help-inline'); $I->dontSee('Body cannot be blank', '.help-inline'); $I->dontSee('The verification code is incorrect', '.help-inline'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester@example.com', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSeeElement('#contact-form'); $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/acceptance/LoginCept.php b/apps/basic/tests/acceptance/LoginCept.php index 77c4a07..5621b15 100644 --- a/apps/basic/tests/acceptance/LoginCept.php +++ b/apps/basic/tests/acceptance/LoginCept.php @@ -4,20 +4,20 @@ $I->wantTo('ensure that login works'); $I->amOnPage('?r=site/login'); $I->see('Login', 'h1'); -$I->submitForm('#login-form', array()); +$I->submitForm('#login-form', []); $I->dontSee('Logout (admin)'); $I->see('Username cannot be blank'); $I->see('Password cannot be blank'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'wrong', -)); +]); $I->dontSee('Logout (admin)'); $I->see('Incorrect username or password'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'admin', -)); +]); $I->see('Logout (admin)'); diff --git a/apps/basic/tests/functional/ContactCept.php b/apps/basic/tests/functional/ContactCept.php index 6feafd9..b58361a 100644 --- a/apps/basic/tests/functional/ContactCept.php +++ b/apps/basic/tests/functional/ContactCept.php @@ -4,7 +4,7 @@ $I->wantTo('ensure that contact works'); $I->amOnPage('?r=site/contact'); $I->see('Contact', 'h1'); -$I->submitForm('#contact-form', array()); +$I->submitForm('#contact-form', []); $I->see('Contact', 'h1'); $I->see('Name cannot be blank'); $I->see('Email cannot be blank'); @@ -12,25 +12,25 @@ $I->see('Subject cannot be blank'); $I->see('Body cannot be blank'); $I->see('The verification code is incorrect'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester.email', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSee('Name cannot be blank', '.help-inline'); $I->see('Email is not a valid email address.'); $I->dontSee('Subject cannot be blank', '.help-inline'); $I->dontSee('Body cannot be blank', '.help-inline'); $I->dontSee('The verification code is incorrect', '.help-inline'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester@example.com', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSeeElement('#contact-form'); $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/functional/LoginCept.php b/apps/basic/tests/functional/LoginCept.php index 11f8f6b..9d71378 100644 --- a/apps/basic/tests/functional/LoginCept.php +++ b/apps/basic/tests/functional/LoginCept.php @@ -4,20 +4,20 @@ $I->wantTo('ensure that login works'); $I->amOnPage('?r=site/login'); $I->see('Login', 'h1'); -$I->submitForm('#login-form', array()); +$I->submitForm('#login-form', []); $I->dontSee('Logout (admin)'); $I->see('Username cannot be blank'); $I->see('Password cannot be blank'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'wrong', -)); +]); $I->dontSee('Logout (admin)'); $I->see('Incorrect username or password'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'admin', -)); +]); $I->see('Logout (admin)'); diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 1b7083d..30246b1 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -21,33 +21,33 @@ app\config\AppAsset::register($this); beginBody(); ?> 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), - 'items' => array( - array('label' => 'Home', 'url' => array('/site/index')), - array('label' => 'About', 'url' => array('/site/about')), - array('label' => 'Contact', 'url' => array('/site/contact')), + ], + ]); + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav pull-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], Yii::$app->user->isGuest ? - array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , - 'url' => array('/site/logout'), - 'linkOptions' => array('data-method' => 'post')), - ), - )); + ['label' => 'Login', 'url' => ['/site/login']] : + ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post']], + ], + ]); NavBar::end(); ?>
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> + isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]); ?>
diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php index d1411c0..55113cc 100644 --- a/apps/basic/views/site/contact.php +++ b/apps/basic/views/site/contact.php @@ -28,17 +28,17 @@ $this->params['breadcrumbs'][] = $this->title;
- 'contact-form')); ?> + 'contact-form']); ?> field($model, 'name'); ?> field($model, 'email'); ?> field($model, 'subject'); ?> - field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode')->widget(Captcha::className(), array( - 'options' => array('class' => 'form-control'), + field($model, 'body')->textArea(['rows' => 6]); ?> + field($model, 'verifyCode')->widget(Captcha::className(), [ + 'options' => ['class' => 'form-control'], 'template' => '
{image}
{input}
', - )); ?> + ]); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php index f61d9d7..7efb8b7 100644 --- a/apps/basic/views/site/login.php +++ b/apps/basic/views/site/login.php @@ -15,26 +15,26 @@ $this->params['breadcrumbs'][] = $this->title;

Please fill out the following fields to login:

- 'login-form', - 'options' => array('class' => 'form-horizontal'), - 'fieldConfig' => array( + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => [ 'template' => "{label}\n
{input}
\n
{error}
", - 'labelOptions' => array('class' => 'col-lg-1 control-label'), - ), - )); ?> + 'labelOptions' => ['class' => 'col-lg-1 control-label'], + ], + ]); ?> field($model, 'username'); ?> field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe', array( + field($model, 'rememberMe', [ 'template' => "
{input}
\n
{error}
", - ))->checkbox(); ?> + ])->checkbox(); ?>
- 'btn btn-primary')); ?> + 'btn btn-primary']); ?>
diff --git a/apps/basic/web/index-test.php b/apps/basic/web/index-test.php index 79273ae..c9bd338 100644 --- a/apps/basic/web/index-test.php +++ b/apps/basic/web/index-test.php @@ -1,6 +1,6 @@ Date: Fri, 18 Oct 2013 14:04:15 +0400 Subject: [PATCH 164/613] Replaced " - - <?php echo Html::encode($this->title); ?> + + <?=Html::encode($this->title); ?> head(); ?> @@ -45,16 +45,16 @@ AppAsset::register($this); ?>
- isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], ]); ?> - +
-

© My Company

-

+

© My Company

+

diff --git a/apps/advanced/backend/views/site/error.php b/apps/advanced/backend/views/site/error.php index 024e27d..eef87f6 100644 --- a/apps/advanced/backend/views/site/error.php +++ b/apps/advanced/backend/views/site/error.php @@ -13,10 +13,10 @@ $this->title = $name; ?>
-

title); ?>

+

title); ?>

- +

diff --git a/apps/advanced/backend/views/site/login.php b/apps/advanced/backend/views/site/login.php index bcb673d..d52eaa1 100644 --- a/apps/advanced/backend/views/site/login.php +++ b/apps/advanced/backend/views/site/login.php @@ -11,18 +11,18 @@ $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; ?>