Browse Source

Merge branch 'master' into redis

* master: (27 commits)
  form wip
  form wip
  form WIP
  script WIP
  Minor change about twig alias.
  Added Controller::populate().
  Added bootstrap. form WIP
  form wip
  form wip
  Refactored UrlManager.
  refactored AccessControl
  activeform WIP
  Removed the $value parameter from radio and checkbox.
  Support alternative URL rule syntax.
  Support default standard component class.
  updated view renderers docs
  activeform wip
  Refactored view renderers.
  updated view renderers docs
  refactoring activeform.
  ...
tags/2.0.0-beta
Carsten Brandt 12 years ago
parent
commit
9634b72820
  1. 1109
      app/css/bootstrap-responsive.css
  2. 9
      app/css/bootstrap-responsive.min.css
  3. 6158
      app/css/bootstrap.css
  4. 9
      app/css/bootstrap.min.css
  5. BIN
      app/img/glyphicons-halflings-white.png
  6. BIN
      app/img/glyphicons-halflings.png
  7. 2276
      app/js/bootstrap.js
  8. 6
      app/js/bootstrap.min.js
  9. 14
      app/protected/controllers/SiteController.php
  10. 51
      app/protected/models/LoginForm.php
  11. 24
      app/protected/models/User.php
  12. 18
      app/protected/views/layouts/main.php
  13. 2
      app/protected/views/site/index.php
  14. 22
      app/protected/views/site/login.php
  15. 51
      docs/view_renderers.md
  16. 28
      framework/assets.php
  17. 5
      framework/assets/jquery.min.js
  18. 421
      framework/assets/yii.activeForm.js
  19. 30
      framework/assets/yii.js
  20. 17
      framework/assets/yii.validation.js
  21. 2
      framework/base/Application.php
  22. 44
      framework/base/Controller.php
  23. 36
      framework/base/Module.php
  24. 36
      framework/base/View.php
  25. 568
      framework/helpers/base/Html.php
  26. 72
      framework/renderers/SmartyViewRenderer.php
  27. 74
      framework/renderers/TwigViewRenderer.php
  28. 4
      framework/validators/BooleanValidator.php
  29. 2
      framework/validators/RequiredValidator.php
  30. 18
      framework/web/AccessControl.php
  31. 98
      framework/web/Request.php
  32. 33
      framework/web/UrlManager.php
  33. 4
      framework/web/User.php
  34. 407
      framework/widgets/ActiveField.php
  35. 317
      framework/widgets/ActiveForm.php
  36. 20
      tests/unit/framework/helpers/HtmlTest.php

1109
app/css/bootstrap-responsive.css vendored

File diff suppressed because it is too large Load Diff

9
app/css/bootstrap-responsive.min.css vendored

File diff suppressed because one or more lines are too long

6158
app/css/bootstrap.css vendored

File diff suppressed because it is too large Load Diff

9
app/css/bootstrap.min.css vendored

File diff suppressed because one or more lines are too long

BIN
app/img/glyphicons-halflings-white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
app/img/glyphicons-halflings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

2276
app/js/bootstrap.js vendored

File diff suppressed because it is too large Load Diff

6
app/js/bootstrap.min.js vendored

File diff suppressed because one or more lines are too long

14
app/protected/controllers/SiteController.php

@ -1,6 +1,9 @@
<?php <?php
class SiteController extends \yii\web\Controller use yii\web\Controller;
use app\models\LoginForm;
class SiteController extends Controller
{ {
public function actionIndex() public function actionIndex()
{ {
@ -9,9 +12,14 @@ class SiteController extends \yii\web\Controller
public function actionLogin() public function actionLogin()
{ {
$user = app\models\User::findIdentity(100); $model = new LoginForm();
Yii::$app->getUser()->login($user); if ($this->populate($_POST, $model) && $model->login()) {
Yii::$app->getResponse()->redirect(array('site/index')); Yii::$app->getResponse()->redirect(array('site/index'));
} else {
echo $this->render('login', array(
'model' => $model,
));
}
} }
public function actionLogout() public function actionLogout()

51
app/protected/models/LoginForm.php

@ -0,0 +1,51 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace app\models;
use Yii;
use yii\base\Model;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
public function rules()
{
return array(
array('username', 'required'),
array('password', 'required'),
array('password', 'validatePassword'),
array('rememberMe', 'boolean'),
);
}
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError('password', 'Incorrect username or password.');
}
}
public function login()
{
if ($this->validate()) {
$user = User::findByUsername($this->username);
Yii::$app->getUser()->login($user, $this->rememberMe ? 3600*24*30 : 0);
return true;
} else {
return false;
}
}
}

24
app/protected/models/User.php

@ -5,19 +5,22 @@ namespace app\models;
class User extends \yii\base\Object implements \yii\web\Identity class User extends \yii\base\Object implements \yii\web\Identity
{ {
public $id; public $id;
public $name; public $username;
public $password;
public $authKey; public $authKey;
private static $users = array( private static $users = array(
'100' => array( '100' => array(
'id' => '100', 'id' => '100',
'username' => 'admin',
'password' => 'admin',
'authKey' => 'test100key', 'authKey' => 'test100key',
'name' => 'admin',
), ),
'101' => array( '101' => array(
'id' => '101', 'id' => '101',
'username' => 'demo',
'password' => 'demo',
'authKey' => 'test101key', 'authKey' => 'test101key',
'name' => 'demo',
), ),
); );
@ -26,6 +29,16 @@ class User extends \yii\base\Object implements \yii\web\Identity
return isset(self::$users[$id]) ? new self(self::$users[$id]) : null; return isset(self::$users[$id]) ? new self(self::$users[$id]) : null;
} }
public static function findByUsername($username)
{
foreach (self::$users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return new self($user);
}
}
return null;
}
public function getId() public function getId()
{ {
return $this->id; return $this->id;
@ -40,4 +53,9 @@ class User extends \yii\base\Object implements \yii\web\Identity
{ {
return $this->authKey === $authKey; return $this->authKey === $authKey;
} }
public function validatePassword($password)
{
return $this->password === $password;
}
} }

18
app/protected/views/layouts/main.php

@ -5,18 +5,22 @@
*/ */
use yii\helpers\Html; use yii\helpers\Html;
?> ?>
<!DOCTYPE html>
<html>
<?php $this->beginPage(); ?> <?php $this->beginPage(); ?>
<!DOCTYPE html>
<html lang="en">
<head> <head>
<meta charset="utf-8" />
<title><?php echo Html::encode($this->title); ?></title> <title><?php echo Html::encode($this->title); ?></title>
<?php echo Html::cssFile("css/bootstrap.min.css", array('media' => 'screen')); ?>
<?php $this->head(); ?> <?php $this->head(); ?>
</head> </head>
<body> <body>
<h1>Welcome</h1> <div class="container">
<?php $this->beginBody(); ?> <h1>Welcome</h1>
<?php echo $content; ?> <?php $this->beginBody(); ?>
<?php $this->endBody(); ?> <?php echo $content; ?>
<?php $this->endBody(); ?>
</div>
</body> </body>
<?php $this->endPage(); ?>
</html> </html>
<?php $this->endPage(); ?>

2
app/protected/views/site/index.php

@ -9,7 +9,7 @@ $user = Yii::$app->getUser();
if ($user->isGuest) { if ($user->isGuest) {
echo Html::a('login', array('login')); echo Html::a('login', array('login'));
} else { } else {
echo "You are logged in as " . $user->identity->name . "<br/>"; echo "You are logged in as " . $user->identity->username . "<br/>";
echo Html::a('logout', array('logout')); echo Html::a('logout', array('logout'));
} }
?> ?>

22
app/protected/views/site/login.php

@ -0,0 +1,22 @@
<?php
use yii\helpers\Html;
/**
* @var yii\base\View $this
* @var yii\widgets\ActiveForm $form
* @var app\models\LoginForm $model
*/
?>
<h1>Login</h1>
<p>Please fill out the following fields to login:</p>
<?php $form = $this->beginWidget('yii\widgets\ActiveForm', array('options' => array('class' => 'form-horizontal'))); ?>
<?php echo $form->field($model, 'username')->textInput(); ?>
<?php echo $form->field($model, 'password')->passwordInput(); ?>
<?php echo $form->field($model, 'rememberMe')->checkbox(); ?>
<div class="control-group">
<div class="controls">
<?php echo Html::submitButton('Login', null, null, array('class' => 'btn btn-primary')); ?>
</div>
</div>
<?php $this->endWidget(); ?>

51
docs/view_renderers.md

@ -0,0 +1,51 @@
Yii2 view renderers
===================
By default Yii uses PHP as template language but you can configure it to be able
to render templates with special engines such as Twig or Smarty.
The component responsible for rendering a view is called `view`. You can add
a custom template engines as follows:
```php
array(
'components' => array(
'view' => array(
'class' => 'yii\base\View',
'renderers' => array(
'tpl' => array(
'class' => 'yii\renderers\SmartyViewRenderer',
),
'twig' => array(
'class' => 'yii\renderers\TwigViewRenderer',
),
// ...
),
),
),
)
```
Twig
----
In order to use Twig you need to put you templates in files with extension `.twig`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.twig', array('username' => 'Alex'));
```
Smarty
------
In order to use Smarty you need to put you templates in files with extension `.tpl`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.tpl', array('username' => 'Alex'));
```

28
framework/assets.php

@ -1,5 +1,31 @@
<?php <?php
return array( return array(
'basePath' => __DIR__ . '/web/assets', 'jquery' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'jquery.min.js',
),
),
'yii' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'yii.js',
),
'depends' => array('jquery'),
),
'yii/validation' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'yii.validation.js',
),
'depends' => array('yii'),
),
'yii/form' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'yii.activeForm.js',
),
'depends' => array('yii', 'yii/validation'),
),
); );

5
framework/assets/jquery.min.js vendored

File diff suppressed because one or more lines are too long

421
framework/assets/yii.activeForm.js

@ -0,0 +1,421 @@
/**
* Yii form widget.
*
* This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
(function ($) {
$.fn.yiiActiveForm = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm');
return false;
}
};
var defaults = {
// whether it is waiting for ajax submission result
submitting: false
};
var methods = {
/**
* Initializes the plugin.
* @param attributes array attribute configurations. Each attribute may contain the following options:
*
* - id: 'ModelClass_attribute', // the unique attribute ID
* - model: 'ModelClass', // the model class name
* - name: 'name', // attribute name
* - inputID: 'input-tag-id',
* - errorID: 'error-tag-id',
* - value: undefined,
* - status: 0, // 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
* - validationDelay: 200,
* - validateOnChange: true,
* - validateOnType: false,
* - hideErrorMessage: false,
* - inputContainer: undefined,
* - errorCssClass: 'error',
* - successCssClass: 'success',
* - validatingCssClass: 'validating',
* - enableAjaxValidation: true,
* - enableClientValidation: true,
* - clientValidation: undefined, // function (value, messages, attribute) | client-side validation
* - beforeValidateAttribute: undefined, // function (form, attribute) | boolean
* - afterValidateAttribute: undefined, // function (form, attribute, data, hasError)
*
* @param options object the configuration for the plugin. The following options can be set:
*/
init: function (attributes, options) {
return this.each(function () {
var $form = $(this);
if ($form.data('yiiActiveForm')) {
return;
}
var settings = $.extend(defaults, options || {});
if (settings.validationUrl === undefined) {
settings.validationUrl = $form.attr('action');
}
$.each(attributes, function (i) {
this.value = getInputValue($form.find('#' + this.inputID));
attributes[i] = $.extend(settings, this);
});
$form.data('yiiActiveForm', {
settings: settings,
attributes: attributes
});
bindAttributes(attributes);
$form.bind('reset', resetForm);
if (settings.validateOnSubmit) {
$form.on('mouseup keyup', ':submit', function () {
$form.data('submitObject', $(this));
});
var validated = false;
$form.submit(function () {
if (validated) {
validated = false;
return true;
}
if (settings.timer !== undefined) {
clearTimeout(settings.timer);
}
settings.submitting = true;
if (settings.beforeValidate === undefined || settings.beforeValidate($form)) {
$.fn.yiiactiveform.validate($form, function (data) {
var hasError = false;
$.each(settings.attributes, function () {
hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
});
$.fn.yiiactiveform.updateSummary($form, data);
if (settings.afterValidate === undefined || settings.afterValidate($form, data, hasError)) {
if (!hasError) {
validated = true;
var $button = $form.data('submitObject') || $form.find(':submit:first');
// TODO: if the submission is caused by "change" event, it will not work
if ($button.length) {
$button.click();
} else { // no submit button in the form
$form.submit();
}
return;
}
}
settings.submitting = false;
});
} else {
settings.submitting = false;
}
return false;
});
}
});
},
destroy: function () {
return this.each(function () {
$(window).unbind('.yiiActiveForm');
$(this).removeData('yiiActiveForm');
})
}
};
/**
* Returns the value of the specified input element.
* This method will perform additional checks to get proper values
* for checkbox, radio, checkbox list and radio list.
* @param $e jQuery the jQuery object of the input element
* @return string the input value
*/
var getInputValue = function ($e) {
var type,
c = [];
if (!$e.length) {
return undefined;
}
if ($e[0].tagName.toLowerCase() === 'div') {
$e.find(':checked').each(function () {
c.push(this.value);
});
return c.join(',');
}
type = $e.attr('type');
if (type === 'checkbox' || type === 'radio') {
return $e.filter(':checked').val();
} else {
return $e.val();
}
};
var bindAttributes = function (attributes) {
$.each(attributes, function (i, attribute) {
if (this.validateOnChange) {
$form.find('#' + this.inputID).change(function () {
validateAttribute(attribute, false);
}).blur(function () {
if (attribute.status !== 2 && attribute.status !== 3) {
validateAttribute(attribute, !attribute.status);
}
});
}
if (this.validateOnType) {
$form.find('#' + this.inputID).keyup(function () {
if (attribute.value !== getAFValue($(this))) {
validateAttribute(attribute, false);
}
});
}
});
};
/**
* Performs the ajax validation request.
* This method is invoked internally to trigger the ajax validation.
* @param form jquery the jquery representation of the form
* @param successCallback function the function to be invoked if the ajax request succeeds
* @param errorCallback function the function to be invoked if the ajax request fails
*/
var validateForm = function (form, successCallback, errorCallback) {
var $form = $(form),
settings = $form.data('settings'),
needAjaxValidation = false,
messages = {};
$.each(settings.attributes, function () {
var value,
msg = [];
if (this.clientValidation !== undefined && (settings.submitting || this.status === 2 || this.status === 3)) {
value = getInputValue($form.find('#' + this.inputID));
this.clientValidation(value, msg, this);
if (msg.length) {
messages[this.id] = msg;
}
}
if (this.enableAjaxValidation && !msg.length && (settings.submitting || this.status === 2 || this.status === 3)) {
needAjaxValidation = true;
}
});
if (!needAjaxValidation || settings.submitting && !$.isEmptyObject(messages)) {
if (settings.submitting) {
// delay callback so that the form can be submitted without problem
setTimeout(function () {
successCallback(messages);
}, 200);
} else {
successCallback(messages);
}
return;
}
var $button = $form.data('submitObject'),
extData = '&' + settings.ajaxVar + '=' + $form.attr('id');
if ($button && $button.length) {
extData += '&' + $button.attr('name') + '=' + $button.attr('value');
}
$.ajax({
url: settings.validationUrl,
type: $form.attr('method'),
data: $form.serialize() + extData,
dataType: 'json',
success: function (data) {
if (data !== null && typeof data === 'object') {
$.each(settings.attributes, function () {
if (!this.enableAjaxValidation) {
delete data[this.id];
}
});
successCallback($.extend({}, messages, data));
} else {
successCallback(messages);
}
},
error: function () {
if (errorCallback !== undefined) {
errorCallback();
}
}
});
};
var validateAttribute = function (attribute, forceValidate) {
if (forceValidate) {
attribute.status = 2;
}
$.each(attributes, function () {
if (this.value !== getInputValue($form.find('#' + this.inputID))) {
this.status = 2;
forceValidate = true;
}
});
if (!forceValidate) {
return;
}
if (settings.timer !== undefined) {
clearTimeout(settings.timer);
}
settings.timer = setTimeout(function () {
if (settings.submitting || $form.is(':hidden')) {
return;
}
if (attribute.beforeValidateAttribute === undefined || attribute.beforeValidateAttribute($form, attribute)) {
$.each(settings.attributes, function () {
if (this.status === 2) {
this.status = 3;
$.fn.yiiactiveform.getInputContainer(this, $form).addClass(this.validatingCssClass);
}
});
$.fn.yiiactiveform.validate($form, function (data) {
var hasError = false;
$.each(settings.attributes, function () {
if (this.status === 2 || this.status === 3) {
hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
}
});
if (attribute.afterValidateAttribute !== undefined) {
attribute.afterValidateAttribute($form, attribute, data, hasError);
}
});
}
}, attribute.validationDelay);
};
var resetForm = function () {
/*
* In case of resetting the form we need to reset error messages
* NOTE1: $form.reset - does not exist
* NOTE2: $form.on('reset', ...) does not work
*/
/*
* because we bind directly to a form reset event, not to a reset button (that could or could not exist),
* when this function is executed form elements values have not been reset yet,
* because of that we use the setTimeout
*/
setTimeout(function () {
$.each(settings.attributes, function () {
this.status = 0;
var $error = $form.find('#' + this.errorID),
$container = $.fn.yiiactiveform.getInputContainer(this, $form);
$container.removeClass(
this.validatingCssClass + ' ' +
this.errorCssClass + ' ' +
this.successCssClass
);
$error.html('').hide();
/*
* without the setTimeout() we would get here the current entered value before the reset instead of the reseted value
*/
this.value = getAFValue($form.find('#' + this.inputID));
});
/*
* If the form is submited (non ajax) with errors, labels and input gets the class 'error'
*/
$form.find('label, input').each(function () {
$(this).removeClass(settings.errorCss);
});
$('#' + settings.summaryID).hide().find('ul').html('');
//.. set to initial focus on reset
if (settings.focus !== undefined && !window.location.hash) {
$form.find(settings.focus).focus();
}
}, 1);
};
/**
* Returns the container element of the specified attribute.
* @param attribute object the configuration for a particular attribute.
* @param form the form jQuery object
* @return jQuery the jQuery representation of the container
*/
var getInputContainer = function (attribute, form) {
if (attribute.inputContainer === undefined) {
return form.find('#' + attribute.inputID).closest('div');
} else {
return form.find(attribute.inputContainer).filter(':has("#' + attribute.inputID + '")');
}
};
/**
* updates the error message and the input container for a particular attribute.
* @param attribute object the configuration for a particular attribute.
* @param messages array the json data obtained from the ajax validation request
* @param form the form jQuery object
* @return boolean whether there is a validation error for the specified attribute
*/
var updateInput = function (attribute, messages, form) {
attribute.status = 1;
var $error, $container,
hasError = false,
$el = form.find('#' + attribute.inputID),
errorCss = form.data('settings').errorCss;
if ($el.length) {
hasError = messages !== null && $.isArray(messages[attribute.id]) && messages[attribute.id].length > 0;
$error = form.find('#' + attribute.errorID);
$container = $.fn.yiiactiveform.getInputContainer(attribute, form);
$container.removeClass(
attribute.validatingCssClass + ' ' +
attribute.errorCssClass + ' ' +
attribute.successCssClass
);
$container.find('label, input').each(function () {
$(this).removeClass(errorCss);
});
if (hasError) {
$error.html(messages[attribute.id][0]);
$container.addClass(attribute.errorCssClass);
} else if (attribute.enableAjaxValidation || attribute.clientValidation) {
$container.addClass(attribute.successCssClass);
}
if (!attribute.hideErrorMessage) {
$error.toggle(hasError);
}
attribute.value = getAFValue($el);
}
return hasError;
};
/**
* updates the error summary, if any.
* @param form jquery the jquery representation of the form
* @param messages array the json data obtained from the ajax validation request
*/
var updateSummary = function (form, messages) {
var settings = $(form).data('settings'),
content = '';
if (settings.summaryID === undefined) {
return;
}
if (messages) {
$.each(settings.attributes, function () {
if ($.isArray(messages[this.id])) {
$.each(messages[this.id], function (j, message) {
content = content + '<li>' + message + '</li>';
});
}
});
}
$('#' + settings.summaryID).toggle(content !== '').find('ul').html(content);
};
})(window.jQuery);

30
framework/assets/yii.js

@ -0,0 +1,30 @@
/**
* Yii JavaScript module.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
yii = (function ($) {
var pub = {
version: '2.0'
};
return pub;
})(jQuery);
jQuery(document).ready(function ($) {
// call the init() method of every module
var init = function (module) {
if ($.isFunction(module.init) && (module.trigger == undefined || $(module.trigger).length)) {
module.init();
}
$.each(module, function () {
if ($.isPlainObject(this)) {
init(this);
}
});
};
init(yii);
});

17
framework/assets/yii.validation.js

@ -0,0 +1,17 @@
/**
* Yii validation module.
*
* This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
yii.validation = (function ($) {
var pub = {
};
return pub;
})(jQuery);

2
framework/base/Application.php

@ -176,7 +176,7 @@ class Application extends Module
*/ */
public function getRuntimePath() public function getRuntimePath()
{ {
if ($this->_runtimePath !== null) { if ($this->_runtimePath === null) {
$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
} }
return $this->_runtimePath; return $this->_runtimePath;

44
framework/base/Controller.php

@ -19,7 +19,14 @@ use yii\helpers\StringHelper;
*/ */
class Controller extends Component class Controller extends Component
{ {
/**
* @event ActionEvent an event raised right before executing a controller action.
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction'; const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised right after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction'; const EVENT_AFTER_ACTION = 'afterAction';
/** /**
@ -105,16 +112,15 @@ class Controller extends Component
if ($action !== null) { if ($action !== null) {
$oldAction = $this->action; $oldAction = $this->action;
$this->action = $action; $this->action = $action;
$status = 1;
if ($this->module->beforeAction($action)) {
if ($this->beforeAction($action)) { if ($this->beforeAction($action)) {
$status = $action->runWithParams($params); $status = $action->runWithParams($params);
$this->afterAction($action); $this->afterAction($action);
} else {
$status = 1;
} }
$this->module->afterAction($action);
}
$this->action = $oldAction; $this->action = $oldAction;
return $status; return $status;
} else { } else {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id); throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
@ -297,6 +303,34 @@ class Controller extends Component
} }
/** /**
* Populates one or multiple models from the given data array.
* @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array.
* @param Model $model the model to be populated. If there are more than one model to be populated,
* you may supply them as additional parameters.
* @return boolean whether at least one model is successfully populated with the data.
*/
public function populate($data, $model)
{
$success = false;
if (!empty($data) && is_array($data)) {
$models = func_get_args();
array_shift($models);
foreach ($models as $model) {
/** @var Model $model */
$scope = $model->formName();
if ($scope == '') {
$model->attributes = $data;
$success = true;
} elseif (isset($data[$scope])) {
$model->attributes = $data[$scope];
$success = true;
}
}
}
return $success;
}
/**
* Renders a view and applies layout if available. * Renders a view and applies layout if available.
* *
* The view to be rendered can be specified in one of the following formats: * The view to be rendered can be specified in one of the following formats:

36
framework/base/Module.php

@ -9,7 +9,6 @@ namespace yii\base;
use Yii; use Yii;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
use yii\helpers\FileHelper;
/** /**
* Module is the base class for module and application classes. * Module is the base class for module and application classes.
@ -39,6 +38,15 @@ use yii\helpers\FileHelper;
abstract class Module extends Component abstract class Module extends Component
{ {
/** /**
* @event ActionEvent an event raised before executing a controller action.
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var array custom module parameters (name => value). * @var array custom module parameters (name => value).
*/ */
public $params = array(); public $params = array();
@ -523,6 +531,9 @@ abstract class Module extends Component
public function setComponents($components) public function setComponents($components)
{ {
foreach ($components as $id => $component) { foreach ($components as $id => $component) {
if (isset($this->_components[$id]['class']) && !isset($component['class'])) {
$component['class'] = $this->_components[$id]['class'];
}
$this->_components[$id] = $component; $this->_components[$id] = $component;
} }
} }
@ -613,4 +624,27 @@ abstract class Module extends Component
return isset($controller) ? array($controller, $route) : false; return isset($controller) ? array($controller, $route) : false;
} }
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
}
/**
* This method is invoked right after an action is executed.
* You may override this method to do some postprocessing for the action.
* @param Action $action the action just executed.
*/
public function afterAction($action)
{
$this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action));
}
} }

36
framework/base/View.php

@ -70,10 +70,25 @@ class View extends Component
*/ */
public $params; public $params;
/** /**
* @var ViewRenderer|array the view renderer object or the configuration array for * @var array a list of available renderers indexed by their corresponding supported file extensions.
* creating the view renderer. If not set, view files will be treated as normal PHP files. * Each renderer may be a view renderer object or the configuration for creating the renderer object.
* For example,
*
* ~~~
* array(
* 'tpl' => array(
* 'class' => 'yii\renderers\SmartyRenderer',
* ),
* 'twig' => array(
* 'class' => 'yii\renderers\TwigRenderer',
* ),
* )
* ~~~
*
* If no renderer is available for the given view file, the view file will be treated as a normal PHP
* and rendered via [[renderPhpFile()]].
*/ */
public $renderer; public $renderers = array();
/** /**
* @var Theme|array the theme object or the configuration array for creating the theme object. * @var Theme|array the theme object or the configuration array for creating the theme object.
* If not set, it means theming is not enabled. * If not set, it means theming is not enabled.
@ -152,9 +167,6 @@ class View extends Component
public function init() public function init()
{ {
parent::init(); parent::init();
if (is_array($this->renderer)) {
$this->renderer = Yii::createObject($this->renderer);
}
if (is_array($this->theme)) { if (is_array($this->theme)) {
$this->theme = Yii::createObject($this->theme); $this->theme = Yii::createObject($this->theme);
} }
@ -226,8 +238,14 @@ class View extends Component
$output = ''; $output = '';
if ($this->beforeRender($viewFile)) { if ($this->beforeRender($viewFile)) {
if ($this->renderer !== null) { $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
$output = $this->renderer->render($this, $viewFile, $params); if (isset($this->renderers[$ext])) {
if (is_array($this->renderers[$ext])) {
$this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
}
/** @var ViewRenderer $renderer */
$renderer = $this->renderers[$ext];
$output = $renderer->render($this, $viewFile, $params);
} else { } else {
$output = $this->renderPhpFile($viewFile, $params); $output = $this->renderPhpFile($viewFile, $params);
} }
@ -352,7 +370,7 @@ class View extends Component
if (!isset($properties['view'])) { if (!isset($properties['view'])) {
$properties['view'] = $this; $properties['view'] = $this;
} }
return Yii::createObject($properties, $this); return Yii::createObject($properties);
} }
/** /**

568
framework/helpers/base/Html.php

@ -9,6 +9,8 @@ namespace yii\helpers\base;
use Yii; use Yii;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
use yii\web\Request;
use yii\base\Model;
/** /**
* Html provides a set of static methods for generating commonly used HTML tags. * Html provides a set of static methods for generating commonly used HTML tags.
@ -276,7 +278,10 @@ class Html
/** /**
* Generates a form start tag. * Generates a form start tag.
* @param array|string $action the form action URL. This parameter will be processed by [[url()]]. * @param array|string $action the form action URL. This parameter will be processed by [[url()]].
* @param string $method the form submission method, either "post" or "get" (case-insensitive) * @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 * @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()]]. * 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 a value is null, the corresponding attribute will not be rendered.
@ -287,15 +292,28 @@ class Html
{ {
$action = static::url($action); $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->csrfTokenName, $request->getCsrfToken());
}
}
if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) {
// query parameters in the action are ignored for GET method // query parameters in the action are ignored for GET method
// we use hidden fields to add them back // we use hidden fields to add them back
$hiddens = array();
if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) {
foreach (explode('&', substr($action, $pos + 1)) as $pair) { foreach (explode('&', substr($action, $pos + 1)) as $pair) {
if (($pos1 = strpos($pair, '=')) !== false) { if (($pos1 = strpos($pair, '=')) !== false) {
$hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1))); $hiddenInputs[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1)));
} else { } else {
$hiddens[] = static::hiddenInput(urldecode($pair), ''); $hiddenInputs[] = static::hiddenInput(urldecode($pair), '');
} }
} }
$action = substr($action, 0, $pos); $action = substr($action, 0, $pos);
@ -304,8 +322,8 @@ class Html
$options['action'] = $action; $options['action'] = $action;
$options['method'] = $method; $options['method'] = $method;
$form = static::beginTag('form', $options); $form = static::beginTag('form', $options);
if ($hiddens !== array()) { if ($hiddenInputs !== array()) {
$form .= "\n" . implode("\n", $hiddens); $form .= "\n" . implode("\n", $hiddenInputs);
} }
return $form; return $form;
@ -380,7 +398,7 @@ class Html
/** /**
* Generates a label tag. * Generates a label tag.
* @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code * @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 consider [[encode()]] * such as an image tag. If this is is coming from end users, you should [[encode()]]
* it to prevent XSS attacks. * it to prevent XSS attacks.
* @param string $for the ID of the HTML element that this label is associated with. * @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. * If this is null, the "for" attribute will not be generated.
@ -397,18 +415,18 @@ class Html
/** /**
* Generates a button tag. * Generates a button tag.
* @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 string $content the content enclosed within the button tag. It will NOT be HTML-encoded. * @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, * 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. * you should consider [[encode()]] it to prevent XSS attacks.
* @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 * @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()]]. * 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 a value is null, the corresponding attribute will not be rendered.
* If the options does not contain "type", a "type" attribute with value "button" will be rendered. * If the options does not contain "type", a "type" attribute with value "button" will be rendered.
* @return string the generated button tag * @return string the generated button tag
*/ */
public static function button($name = null, $value = null, $content = 'Button', $options = array()) public static function button($content = 'Button', $name = null, $value = null, $options = array())
{ {
$options['name'] = $name; $options['name'] = $name;
$options['value'] = $value; $options['value'] = $value;
@ -420,38 +438,38 @@ class Html
/** /**
* Generates a submit button tag. * Generates a submit button tag.
* @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 string $content the content enclosed within the button tag. It will NOT be HTML-encoded. * @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, * 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. * you should consider [[encode()]] it to prevent XSS attacks.
* @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 * @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()]]. * 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 a value is null, the corresponding attribute will not be rendered.
* @return string the generated submit button tag * @return string the generated submit button tag
*/ */
public static function submitButton($name = null, $value = null, $content = 'Submit', $options = array()) public static function submitButton($content = 'Submit', $name = null, $value = null, $options = array())
{ {
$options['type'] = 'submit'; $options['type'] = 'submit';
return static::button($name, $value, $content, $options); return static::button($content, $name, $value, $options);
} }
/** /**
* Generates a reset button tag. * Generates a reset button tag.
* @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 string $content the content enclosed within the button tag. It will NOT be HTML-encoded. * @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, * 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. * you should consider [[encode()]] it to prevent XSS attacks.
* @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 * @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()]]. * 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 a value is null, the corresponding attribute will not be rendered.
* @return string the generated reset button tag * @return string the generated reset button tag
*/ */
public static function resetButton($name = null, $value = null, $content = 'Reset', $options = array()) public static function resetButton($content = 'Reset', $name = null, $value = null, $options = array())
{ {
$options['type'] = 'reset'; $options['type'] = 'reset';
return static::button($name, $value, $content, $options); return static::button($content, $name, $value, $options);
} }
/** /**
@ -591,8 +609,7 @@ class Html
* Generates a radio button input. * Generates a radio button input.
* @param string $name the name attribute. * @param string $name the name attribute.
* @param boolean $checked whether the radio button should be checked. * @param boolean $checked whether the radio button should be checked.
* @param string $value the value attribute. If it is null, the value attribute will not be rendered. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
* @param array $options the tag options in terms of name-value pairs. The following options are supported:
* *
* - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute * - 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, * is present, a hidden input will be generated so that if the radio button is not checked and is submitted,
@ -603,10 +620,10 @@ class Html
* *
* @return string the generated radio button tag * @return string the generated radio button tag
*/ */
public static function radio($name, $checked = false, $value = '1', $options = array()) public static function radio($name, $checked = false, $options = array())
{ {
$options['checked'] = $checked; $options['checked'] = $checked;
$options['value'] = $value; $value = array_key_exists('value', $options) ? $options['value'] : '1';
if (isset($options['uncheck'])) { if (isset($options['uncheck'])) {
// add a hidden field so that if the radio button is not selected, it still submits a value // add a hidden field so that if the radio button is not selected, it still submits a value
$hidden = static::hiddenInput($name, $options['uncheck']); $hidden = static::hiddenInput($name, $options['uncheck']);
@ -621,8 +638,7 @@ class Html
* Generates a checkbox input. * Generates a checkbox input.
* @param string $name the name attribute. * @param string $name the name attribute.
* @param boolean $checked whether the checkbox should be checked. * @param boolean $checked whether the checkbox should be checked.
* @param string $value the value attribute. If it is null, the value attribute will not be rendered. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
* @param array $options the tag options in terms of name-value pairs. The following options are supported:
* *
* - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute * - 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, * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted,
@ -633,10 +649,10 @@ class Html
* *
* @return string the generated checkbox tag * @return string the generated checkbox tag
*/ */
public static function checkbox($name, $checked = false, $value = '1', $options = array()) public static function checkbox($name, $checked = false, $options = array())
{ {
$options['checked'] = $checked; $options['checked'] = $checked;
$options['value'] = $value; $value = array_key_exists('value', $options) ? $options['value'] : '1';
if (isset($options['uncheck'])) { if (isset($options['uncheck'])) {
// add a hidden field so that if the checkbox is not selected, it still submits a value // add a hidden field so that if the checkbox is not selected, it still submits a value
$hidden = static::hiddenInput($name, $options['uncheck']); $hidden = static::hiddenInput($name, $options['uncheck']);
@ -659,7 +675,7 @@ class Html
* *
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in * 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. * the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are supported: * @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; * - 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, * - options: array, the attributes for the select option tags. The array keys must be valid option values,
@ -699,7 +715,7 @@ class Html
* *
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in * 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. * the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are supported: * @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; * - 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, * - options: array, the attributes for the select option tags. The array keys must be valid option values,
@ -769,7 +785,7 @@ class Html
* *
* where $index is the zero-based index of the checkbox in the whole list; $label * 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, * is the label for the checkbox; and $name, $value and $checked represent the name,
* value and the checked status of the checkbox input. * value and the checked status of the checkbox input, respectively.
* @return string the generated checkbox list * @return string the generated checkbox list
*/ */
public static function checkboxList($name, $selection = null, $items = array(), $options = array()) public static function checkboxList($name, $selection = null, $items = array(), $options = array())
@ -788,7 +804,7 @@ class Html
if ($formatter !== null) { if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else { } else {
$lines[] = static::label(static::checkbox($name, $checked, $value) . ' ' . $label); $lines[] = static::label(static::checkbox($name, $checked, array('value' => $value)) . ' ' . $label);
} }
$index++; $index++;
} }
@ -827,7 +843,7 @@ class Html
* *
* where $index is the zero-based index of the radio button in the whole list; $label * 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, * 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. * value and the checked status of the radio button input, respectively.
* @return string the generated radio button list * @return string the generated radio button list
*/ */
public static function radioList($name, $selection = null, $items = array(), $options = array()) public static function radioList($name, $selection = null, $items = array(), $options = array())
@ -842,7 +858,7 @@ class Html
if ($formatter !== null) { if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else { } else {
$lines[] = static::label(static::radio($name, $checked, $value) . ' ' . $label); $lines[] = static::label(static::radio($name, $checked, array('value' => $value)) . ' ' . $label);
} }
$index++; $index++;
} }
@ -859,6 +875,372 @@ class Html
} }
/** /**
* 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);
return static::label($label, $for, $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())
{
return 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 "name" tag attribute automatically unless it is explicitly specified in `$options`.
* 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.
*
* 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 "name" tag attribute automatically unless it is explicitly specified in `$options`.
* 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.
*
* 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';
}
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';
}
return static::radioList($name, $checked, $items, $options);
}
/**
* Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. * 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 * @param string|array $selection the selected value(s). This can be either a string for single selection
* or an array for multiple selections. * or an array for multiple selections.
@ -949,7 +1331,9 @@ class Html
* If the input parameter * If the input parameter
* *
* - is an empty string: the currently requested URL will be returned; * - is an empty string: the currently requested URL will be returned;
* - is a non-empty string: it will be processed by [[Yii::getAlias()]] and 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 with 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 * - 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()]]. * 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')`. * For example: `array('post/index', 'page' => 2)`, `array('index')`.
@ -975,7 +1359,121 @@ class Html
} elseif ($url === '') { } elseif ($url === '') {
return Yii::$app->getRequest()->getUrl(); return Yii::$app->getRequest()->getUrl();
} else { } else {
return Yii::getAlias($url); $url = Yii::getAlias($url);
if ($url[0] === '/' || strpos($url, '://')) {
return $url;
} else {
return Yii::$app->getRequest()->getBaseUrl() . '/' . $url;
}
} }
} }
/**
* 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, [[getInputName()]] returns `Post[content]`, this method will return `Post-method`.
* @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);
}
} }

72
framework/renderers/SmartyViewRenderer.php

@ -0,0 +1,72 @@
<?php
/**
* Smarty view renderer class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\renderers;
use Yii;
use Smarty;
use yii\base\View;
use yii\base\ViewRenderer;
/**
* SmartyViewRenderer allows you to use Smarty templates in views.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class SmartyViewRenderer extends ViewRenderer
{
/**
* @var string the directory or path alias pointing to where Smarty code is located.
*/
public $smartyPath = '@app/vendors/Smarty';
/**
* @var string the directory or path alias pointing to where Smarty cache will be stored.
*/
public $cachePath = '@app/runtime/Smarty/cache';
/**
* @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
*/
public $compilePath = '@app/runtime/Smarty/compile';
/**
* @var Smarty
*/
public $smarty;
public function init()
{
require_once(Yii::getAlias($this->smartyPath) . '/Smarty.class.php');
$this->smarty = new Smarty();
$this->smarty->setCompileDir(Yii::getAlias($this->compilePath));
$this->smarty->setCacheDir(Yii::getAlias($this->cachePath));
}
/**
* Renders a view file.
*
* This method is invoked by [[View]] whenever it tries to render a view.
* Child classes must implement this method to render the given view file.
*
* @param View $view the view object used for rendering the file.
* @param string $file the view file.
* @param array $params the parameters to be passed to the view file.
*
* @return string the rendering result
*/
public function render($view, $file, $params)
{
$ext = pathinfo($file, PATHINFO_EXTENSION);
/** @var \Smarty_Internal_Template $template */
$template = $this->smarty->createTemplate($file, null, null, $params, true);
return $template->fetch();
}
}

74
framework/renderers/TwigViewRenderer.php

@ -0,0 +1,74 @@
<?php
/**
* Twig view renderer class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\renderers;
use Yii;
use yii\base\View;
use yii\base\ViewRenderer;
/**
* TwigViewRenderer allows you to use Twig templates in views.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class TwigViewRenderer extends ViewRenderer
{
/**
* @var string the directory or path alias pointing to where Twig code is located.
*/
public $twigPath = '@Twig';
/**
* @var string the directory or path alias pointing to where Twig cache will be stored.
*/
public $cachePath = '@app/runtime/Twig/cache';
/**
* @var array Twig options
* @see http://twig.sensiolabs.org/doc/api.html#environment-options
*/
public $options = array();
/**
* @var \Twig_Environment
*/
public $twig;
public function init()
{
if (!isset(Yii::$aliases['@Twig'])) {
Yii::setAlias('@Twig', $this->twigPath);
}
$loader = new \Twig_Loader_String();
$this->twig = new \Twig_Environment($loader, array_merge(array(
'cache' => Yii::getAlias($this->cachePath),
), $this->options));
}
/**
* Renders a view file.
*
* This method is invoked by [[View]] whenever it tries to render a view.
* Child classes must implement this method to render the given view file.
*
* @param View $view the view object used for rendering the file.
* @param string $file the view file.
* @param array $params the parameters to be passed to the view file.
*
* @return string the rendering result
*/
public function render($view, $file, $params)
{
return $this->twig->render(file_get_contents($file), $params);
}
}

4
framework/validators/BooleanValidator.php

@ -70,8 +70,8 @@ class BooleanValidator extends Validator
*/ */
public function validateValue($value) public function validateValue($value)
{ {
return $this->strict && ($value == $this->trueValue || $value == $this->falseValue) return !$this->strict && ($value == $this->trueValue || $value == $this->falseValue)
|| !$this->strict && ($value === $this->trueValue || $value === $this->falseValue); || $this->strict && ($value === $this->trueValue || $value === $this->falseValue);
} }
/** /**

2
framework/validators/RequiredValidator.php

@ -47,7 +47,7 @@ class RequiredValidator extends Validator
{ {
parent::init(); parent::init();
if ($this->message === null) { if ($this->message === null) {
$this->message = $this->requiredValue === null ? Yii::t('yii|{attribute} is invalid.') $this->message = $this->requiredValue === null ? Yii::t('yii|{attribute} cannot be blank.')
: Yii::t('yii|{attribute} must be "{requiredValue}".'); : Yii::t('yii|{attribute} must be "{requiredValue}".');
} }
} }

18
framework/web/AccessControl.php

@ -33,12 +33,17 @@ class AccessControl extends ActionFilter
*/ */
public $denyCallback; public $denyCallback;
/** /**
* @var string the default class of the access rules. This is used when * @var array the default configuration of access rules. Individual rule configurations
* a rule is configured without specifying a class in [[rules]]. * specified via [[rules]] will take precedence when the same property of the rule is configured.
*/ */
public $defaultRuleClass = 'yii\web\AccessRule'; public $ruleConfig = array(
'class' => 'yii\web\AccessRule',
);
/** /**
* @var array a list of access rule objects or configurations for creating the rule objects. * @var array a list of access rule objects or configuration arrays for creating the rule objects.
* If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first
* before it is used for creating the rule object.
* @see ruleConfig
*/ */
public $rules = array(); public $rules = array();
@ -50,10 +55,7 @@ class AccessControl extends ActionFilter
parent::init(); parent::init();
foreach ($this->rules as $i => $rule) { foreach ($this->rules as $i => $rule) {
if (is_array($rule)) { if (is_array($rule)) {
if (!isset($rule['class'])) { $this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $rule));
$rule['class'] = $this->defaultRuleClass;
}
$this->rules[$i] = Yii::createObject($rule);
} }
} }
} }

98
framework/web/Request.php

@ -18,6 +18,27 @@ use yii\base\InvalidConfigException;
class Request extends \yii\base\Request class Request extends \yii\base\Request
{ {
/** /**
* @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
* By setting this property to true, forms submitted to an Yii Web application must be originated
* from the same application. If not, a 400 HTTP exception will be raised.
*
* Note, this feature requires that the user client accepts cookie. Also, to use this feature,
* forms submitted via POST method must contain a hidden input whose name is specified by [[csrfTokenName]].
* You may use [[\yii\web\Html::beginForm()]] to generate his hidden input.
* @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
*/
public $enableCsrfValidation = false;
/**
* @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'.
* This property is effectively only when {@link enableCsrfValidation} is true.
*/
public $csrfTokenName = '_csrf';
/**
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
* @see Cookie
*/
public $csrfCookie = array('httponly' => true);
/**
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true. * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
*/ */
public $enableCookieValidation = true; public $enableCookieValidation = true;
@ -27,8 +48,7 @@ class Request extends \yii\base\Request
public $cookieValidationKey; public $cookieValidationKey;
/** /**
* @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
* request tunneled through POST. If false, it means disabling REST request tunneled through POST. * request tunneled through POST. Default to '_method'.
* Default to '_method'.
* @see getRequestMethod * @see getRequestMethod
* @see getRestParams * @see getRestParams
*/ */
@ -36,6 +56,7 @@ class Request extends \yii\base\Request
private $_cookies; private $_cookies;
/** /**
* Resolves the current request into a route and the associated parameters. * Resolves the current request into a route and the associated parameters.
* @return array the first element is the route, and the second is the associated parameters. * @return array the first element is the route, and the second is the associated parameters.
@ -43,6 +64,8 @@ class Request extends \yii\base\Request
*/ */
public function resolve() public function resolve()
{ {
$this->validateCsrfToken();
$result = Yii::$app->getUrlManager()->parseRequest($this); $result = Yii::$app->getUrlManager()->parseRequest($this);
if ($result !== false) { if ($result !== false) {
list ($route, $params) = $result; list ($route, $params) = $result;
@ -60,7 +83,7 @@ class Request extends \yii\base\Request
*/ */
public function getRequestMethod() public function getRequestMethod()
{ {
if ($this->restVar !== false && isset($_POST[$this->restVar])) { if (isset($_POST[$this->restVar])) {
return strtoupper($_POST[$this->restVar]); return strtoupper($_POST[$this->restVar]);
} else { } else {
return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
@ -123,7 +146,7 @@ class Request extends \yii\base\Request
public function getRestParams() public function getRestParams()
{ {
if ($this->_restParams === null) { if ($this->_restParams === null) {
if ($this->restVar !== false && isset($_POST[$this->restVar])) { if (isset($_POST[$this->restVar])) {
$this->_restParams = $_POST; $this->_restParams = $_POST;
} else { } else {
$this->_restParams = array(); $this->_restParams = array();
@ -700,5 +723,72 @@ class Request extends \yii\base\Request
} }
return $this->_cookies; return $this->_cookies;
} }
private $_csrfToken;
/**
* Returns the random token used to perform CSRF validation.
* The token will be read from cookie first. If not found, a new token will be generated.
* @return string the random token for CSRF validation.
* @see enableCsrfValidation
*/
public function getCsrfToken()
{
if ($this->_csrfToken === null) {
$cookies = $this->getCookies();
if (($this->_csrfToken = $cookies->getValue($this->csrfTokenName)) === null) {
$cookie = $this->createCsrfCookie();
$this->_csrfToken = $cookie->value;
$cookies->add($cookie);
}
}
return $this->_csrfToken;
}
/**
* Creates a cookie with a randomly generated CSRF token.
* Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
* @return Cookie the generated cookie
* @see enableCsrfValidation
*/
protected function createCsrfCookie()
{
$options = $this->csrfCookie;
$options['name'] = $this->csrfTokenName;
$options['value'] = sha1(uniqid(mt_rand(), true));
return new Cookie($options);
}
/**
* Performs the CSRF validation.
* The method will compare the CSRF token obtained from a cookie and from a POST field.
* If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised.
* @throws HttpException if the validation fails
*/
public function validateCsrfToken()
{
if (!$this->enableCsrfValidation) {
return;
}
$method = $this->getRequestMethod();
if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE') {
$cookies = $this->getCookies();
switch ($method) {
case 'POST':
$token = $this->getPost($this->csrfTokenName);
break;
case 'PUT':
$token = $this->getPut($this->csrfTokenName);
break;
case 'DELETE':
$token = $this->getDelete($this->csrfTokenName);
}
if (empty($token) || $cookies->getValue($this->csrfTokenName) !== $token) {
throw new HttpException(400, Yii::t('yii|Unable to verify your data submission.'));
}
}
}
} }

33
framework/web/UrlManager.php

@ -29,8 +29,15 @@ class UrlManager extends Component
/** /**
* @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true. * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true.
* This property is used only if [[enablePrettyUrl]] is true. Each element in the array * This property is used only if [[enablePrettyUrl]] is true. Each element in the array
* is the configuration of creating a single URL rule whose class by default is [[defaultRuleClass]]. * is the configuration array for creating a single URL rule. The configuration will
* If you modify this property after the UrlManager object is created, make sure * be merged with [[ruleConfig]] first before it is used for creating the rule object.
*
* A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
* and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
* array, one can use the key to represent the pattern and the value the corresponding route.
* For example, `'post/<id:\d+>' => 'post/view'`.
*
* Note that if you modify this property after the UrlManager object is created, make sure
* you populate the array with rule objects instead of rule configurations. * you populate the array with rule objects instead of rule configurations.
*/ */
public $rules = array(); public $rules = array();
@ -59,15 +66,16 @@ class UrlManager extends Component
*/ */
public $cache = 'cache'; public $cache = 'cache';
/** /**
* @var string the default class name for creating URL rule instances * @var array the default configuration of URL rules. Individual rule configurations
* when it is not specified in [[rules]]. * specified via [[rules]] will take precedence when the same property of the rule is configured.
*/ */
public $defaultRuleClass = 'yii\web\UrlRule'; public $ruleConfig = array(
'class' => 'yii\web\UrlRule',
);
private $_baseUrl; private $_baseUrl;
private $_hostInfo; private $_hostInfo;
/** /**
* Initializes UrlManager. * Initializes UrlManager.
*/ */
@ -97,12 +105,17 @@ class UrlManager extends Component
} }
} }
foreach ($this->rules as $i => $rule) { $rules = array();
if (!isset($rule['class'])) { foreach ($this->rules as $key => $rule) {
$rule['class'] = $this->defaultRuleClass; if (!is_array($rule)) {
$rule = array(
'pattern' => $key,
'route' => $rule,
);
} }
$this->rules[$i] = Yii::createObject($rule); $rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule));
} }
$this->rules = $rules;
if (isset($key, $hash)) { if (isset($key, $hash)) {
$this->cache->set($key, array($this->rules, $hash)); $this->cache->set($key, array($this->rules, $hash));

4
framework/web/User.php

@ -32,7 +32,7 @@ class User extends Component
const EVENT_AFTER_LOGOUT = 'afterLogout'; const EVENT_AFTER_LOGOUT = 'afterLogout';
/** /**
* @var string the class name or alias of the [[identity]] object. * @var string the class name of the [[identity]] object.
*/ */
public $identityClass; public $identityClass;
/** /**
@ -56,7 +56,7 @@ class User extends Component
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true. * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
* @see Cookie * @see Cookie
*/ */
public $identityCookie = array('name' => '__identity', 'httponly' => true); public $identityCookie = array('name' => '_identity', 'httponly' => true);
/** /**
* @var integer the number of seconds in which the user will be logged out automatically if he * @var integer the number of seconds in which the user will be logged out automatically if he
* remains inactive. If this property is not set, the user will be logged out after * remains inactive. If this property is not set, the user will be logged out after

407
framework/widgets/ActiveField.php

@ -0,0 +1,407 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\Component;
use yii\helpers\Html;
use yii\base\Model;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveField extends Component
{
/**
* @var ActiveForm the form that this field is associated with.
*/
public $form;
/**
* @var Model the data model that this field is associated with
*/
public $model;
/**
* @var string the model attribute that this field is associated with
*/
public $attribute;
/**
* @var string the tag name for the field container.
*/
public $tag = 'div';
/**
* @var array the HTML attributes (name-value pairs) for the field container tag.
* The values will be HTML-encoded using [[Html::encode()]].
* If a value is null, the corresponding attribute will not be rendered.
*/
public $options = array(
'class' => 'control-group',
);
/**
* @var string the template that is used to arrange the label, the input and the error message.
* The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}` and `{error}`.
*/
public $template = "{label}\n<div class=\"controls\">\n{input}\n{error}\n</div>";
/**
* @var array the default options for the error message. This property is used when calling [[error()]]
* without the `$options` parameter.
*/
public $errorOptions = array('tag' => 'span', 'class' => 'help-inline');
/**
* @var array the default options for the label. This property is used when calling [[label()]]
* without the `$options` parameter.
*/
public $labelOptions = array('class' => 'control-label');
public function begin()
{
$options = $this->options;
$class = isset($options['class']) ? array($options['class']) : array();
$class[] = 'field-' . Html::getInputId($this->model, $this->attribute);
if ($this->model->isAttributeRequired($this->attribute)) {
$class[] = $this->form->requiredCssClass;
}
if ($this->model->hasErrors($this->attribute)) {
$class[] = $this->form->errorCssClass;
}
$options['class'] = implode(' ', $class);
return Html::beginTag($this->tag, $options);
}
public function end()
{
return Html::endTag($this->tag);
}
/**
* Generates a label tag for [[attribute]].
* The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]].
* @param array $options the tag options in terms of name-value pairs. If this is null, [[labelOptions]] will be used.
* 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.
*
* 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 function label($options = null)
{
if ($options === null) {
$options = $this->labelOptions;
}
return Html::activeLabel($this->model, $this->attribute, $options);
}
/**
* Generates a tag that contains the first validation error of [[attribute]].
* If there is no validation, the tag will be returned and styled as hidden.
* @param array $options the tag options in terms of name-value pairs. If this is null, [[errorOptions]] will be used.
* 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.
*
* The following options are specially handled:
*
* - tag: this specifies the tag name. If not set, "span" will be used.
*
* @return string the generated label tag
*/
public function error($options = null)
{
if ($options === null) {
$options = $this->errorOptions;
}
$attribute = Html::getAttributeName($this->attribute);
$error = $this->model->getFirstError($attribute);
if ($error === null) {
$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
}
$tag = isset($options['tag']) ? $options['tag'] : 'span';
unset($options['tag']);
return Html::tag($tag, Html::encode($error), $options);
}
/**
* Renders the field with the given input HTML.
* This method will generate the label and error tags, and return them together with the given
* input HTML according to [[template]].
* @param string $input the input HTML
* @return string the rendering result
*/
public function render($input)
{
return $this->begin() . "\n" . strtr($this->template, array(
'{input}' => $input,
'{label}' => $this->label(),
'{error}' => $this->error(),
)) . "\n" . $this->end();
}
/**
* Generates an input tag for the given model attribute.
* @param string $type the input type (e.g. 'text', 'password')
* @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 function input($type, $options = array())
{
return $this->render(Html::activeInput($type, $this->model, $this->attribute, $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 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 function textInput($options = array())
{
return $this->render(Html::activeTextInput($this->model, $this->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 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 function hiddenInput($options = array())
{
return $this->render(Html::activeHiddenInput($this->model, $this->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 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 function passwordInput($options = array())
{
return $this->render(Html::activePasswordInput($this->model, $this->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 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 function fileInput($options = array())
{
return $this->render(Html::activeFileInput($this->model, $this->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 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 function textarea($options = array())
{
return $this->render(Html::activeTextarea($this->model, $this->attribute, $options));
}
/**
* Generates a radio button tag for the given model attribute.
* This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`.
* This method will generate the "checked" tag attribute according to the model attribute value.
* @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.
*
* 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 function radio($options = array())
{
return $this->render(Html::activeRadio($this->model, $this->attribute, $options));
}
/**
* Generates a checkbox tag for the given model attribute.
* This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`.
* This method will generate the "checked" tag attribute according to the model attribute value.
* @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.
*
* 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 function checkbox($options = array())
{
return $this->render(Html::activeCheckbox($this->model, $this->attribute, $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 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 function dropDownList($items, $options = array())
{
return $this->render(Html::activeDropDownList($this->model, $this->attribute, $items, $options));
}
/**
* Generates a list box.
* The selection of the list box is taken from the value of the model attribute.
* @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 function listBox($items, $options = array())
{
return $this->render(Html::activeListBox($this->model, $this->attribute, $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 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 function checkboxList($items, $options = array())
{
return $this->render(
'<div id="' . Html::getInputId($this->model, $this->attribute) . '">'
. Html::activeCheckboxList($this->model, $this->attribute, $items, $options)
. '</div>'
);
}
/**
* 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 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 function radioList($items, $options = array())
{
return $this->render(
'<div id="' . Html::getInputId($this->model, $this->attribute) . '">'
. Html::activeRadioList($this->model, $this->attribute, $items, $options)
. '</div>'
);
}
}

317
framework/widgets/ActiveForm.php

@ -8,11 +8,9 @@
namespace yii\widgets; namespace yii\widgets;
use Yii; use Yii;
use yii\base\InvalidParamException;
use yii\base\Widget; use yii\base\Widget;
use yii\base\Model; use yii\base\Model;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\ArrayHelper;
/** /**
* ActiveForm ... * ActiveForm ...
@ -32,282 +30,131 @@ class ActiveForm extends Widget
*/ */
public $method = 'post'; public $method = 'post';
/** /**
* @var array the HTML attributes (name-value pairs) for the form tag.
* The values will be HTML-encoded using [[Html::encode()]].
* If a value is null, the corresponding attribute will not be rendered.
*/
public $options = array();
/**
* @var string the default CSS class for the error summary container. * @var string the default CSS class for the error summary container.
* @see errorSummary() * @see errorSummary()
*/ */
public $errorSummaryClass = 'yii-error-summary'; public $errorSummaryCssClass = 'yii-error-summary';
public $errorMessageClass = 'yii-error-message';
/** /**
* @var string the default CSS class that indicates an input has error. * @var boolean whether to enable client-side data validation.
* Client-side validation will be performed by validators that support it
* (see [[\yii\validators\Validator::enableClientValidation]] and [[\yii\validators\Validator::clientValidateAttribute()]]).
*/ */
public $errorClass = 'yii-error'; public $enableClientValidation = true;
/** /**
* @var string the default CSS class that indicates an input validated successfully. * @var array the default configuration used by [[field()]] when creating a new field object.
*/ */
public $successClass = 'yii-success'; public $fieldConfig = array(
'class' => 'yii\widgets\ActiveField',
);
/** /**
* @var string the default CSS class that indicates an input is currently being validated. * @var string the CSS class that is added to a field container when the associated attribute is required.
*/ */
public $validatingClass = 'yii-validating'; public $requiredCssClass = 'required';
/** /**
* @var boolean whether to enable client-side data validation. Defaults to false. * @var string the CSS class that is added to a field container when the associated attribute has validation error.
* When this property is set true, client-side validation will be performed by validators
* that support it (see {@link CValidator::enableClientValidation} and {@link CValidator::clientValidateAttribute}).
*/ */
public $enableClientValidation = false; public $errorCssClass = 'error';
public $options = array();
/** /**
* @param Model|Model[] $models * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
* @param array $options
* @return string
*/ */
public function errorSummary($models, $options = array()) public $successCssClass = 'success';
{ /**
if (!is_array($models)) { * @var string the CSS class that is added to a field container when the associated attribute is being validated.
$models = array($models); */
} public $validatingCssClass = 'validating';
$showAll = !empty($options['showAll']);
$lines = array();
/** @var $model Model */
foreach ($models as $model) {
if ($showAll) {
foreach ($model->getErrors() as $errors) {
$lines = array_merge($lines, $errors);
}
} else {
$lines = array_merge($lines, $model->getFirstErrors());
}
}
$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii|Please fix the following errors:') . '</p>';
$footer = isset($options['footer']) ? $options['footer'] : '';
$tag = isset($options['tag']) ? $options['tag'] : 'div';
unset($options['showAll'], $options['header'], $options['footer'], $options['container']);
if (!isset($options['class'])) {
$options['class'] = $this->errorSummaryClass;
} else {
$options['class'] .= ' ' . $this->errorSummaryClass;
}
if ($lines !== array()) {
$content = "<ul><li>" . implode("</li>\n<li>", ArrayHelper::htmlEncode($lines)) . "</li><ul>";
return Html::tag($tag, $header . $content . $footer, $options);
} else {
$content = "<ul></ul>";
$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
return Html::tag($tag, $header . $content . $footer, $options);
}
}
/** /**
* @param Model $model * Initializes the widget.
* @param string $attribute * This renders the form open tag.
* @param array $options
* @return string
*/ */
public function error($model, $attribute, $options = array()) public function init()
{ {
$attribute = $this->getAttributeName($attribute); $this->options['id'] = $this->getId();
$tag = isset($options['tag']) ? $options['tag'] : 'div'; echo Html::beginForm($this->action, $this->method, $this->options);
unset($options['tag']);
$error = $model->getFirstError($attribute);
return Html::tag($tag, Html::encode($error), $options);
} }
/** /**
* @param Model $model * Runs the widget.
* @param string $attribute * This registers the necessary javascript code and renders the form close tag.
* @param array $options
* @return string
*/ */
public function label($model, $attribute, $options = array()) public function run()
{ {
$attribute = $this->getAttributeName($attribute); $id = $this->getId();
$label = isset($options['label']) ? $options['label'] : Html::encode($model->getAttributeLabel($attribute)); $options = array();
$for = array_key_exists('for', $options) ? $options['for'] : $this->getInputId($model, $attribute); $options = json_encode($options);
return Html::label($label, $for, $options); $this->view->registerAssetBundle('yii/form');
$this->view->registerJs("jQuery('#$id').yii.form($options);");
echo Html::endForm();
} }
/** /**
* @param string $type * Generates a summary of the validation errors.
* @param Model $model * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
* @param string $attribute * @param Model|Model[] $models the model(s) associated with this form
* @param array $options * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
* *
* @return string * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
* - footer: string, the footer HTML for the error summary.
*
* The rest of the options will be rendered as the attributes of the container 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 error summary
*/ */
public function input($type, $model, $attribute, $options = array()) public function errorSummary($models, $options = array())
{
$value = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::input($type, $name, $value, $options);
}
public function textInput($model, $attribute, $options = array())
{
return $this->input('text', $model, $attribute, $options);
}
public function hiddenInput($model, $attribute, $options = array())
{
return $this->input('hidden', $model, $attribute, $options);
}
public function passwordInput($model, $attribute, $options = array())
{
return $this->input('password', $model, $attribute, $options);
}
public function fileInput($model, $attribute, $options = array())
{
return $this->input('file', $model, $attribute, $options);
}
public function textarea($model, $attribute, $options = array())
{
$value = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::textarea($name, $value, $options);
}
public function radio($model, $attribute, $value = '1', $options = array())
{
$checked = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('uncheck', $options)) {
$options['unchecked'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::radio($name, $checked, $value, $options);
}
public function checkbox($model, $attribute, $value = '1', $options = array())
{
$checked = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('uncheck', $options)) {
$options['unchecked'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::checkbox($name, $checked, $value, $options);
}
public function dropDownList($model, $attribute, $items, $options = array())
{
$checked = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::dropDownList($name, $checked, $items, $options);
}
public function listBox($model, $attribute, $items, $options = array())
{ {
$checked = $this->getAttributeValue($model, $attribute); if (!is_array($models)) {
$name = $this->getInputName($model, $attribute); $models = array($models);
if (!array_key_exists('unselect', $options)) {
$options['unselect'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::listBox($name, $checked, $items, $options);
} }
public function checkboxList($model, $attribute, $items, $options = array()) $lines = array();
{ foreach ($models as $model) {
$checked = $this->getAttributeValue($model, $attribute); /** @var $model Model */
$name = $this->getInputName($model, $attribute); foreach ($model->getFirstErrors() as $error) {
if (!array_key_exists('unselect', $options)) { $lines[] = Html::encode($error);
$options['unselect'] = '0';
} }
return Html::checkboxList($name, $checked, $items, $options);
} }
public function radioList($model, $attribute, $items, $options = array()) $header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii|Please fix the following errors:') . '</p>';
{ $footer = isset($options['footer']) ? $options['footer'] : '';
$checked = $this->getAttributeValue($model, $attribute); unset($options['header'], $options['footer']);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('unselect', $options)) {
$options['unselect'] = '0';
}
return Html::radioList($name, $checked, $items, $options);
}
public function getAttributeValue($model, $attribute) if (!isset($options['class'])) {
{ $options['class'] = $this->errorSummaryCssClass;
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 { } else {
return null; $options['class'] .= ' ' . $this->errorSummaryCssClass;
}
}
return $value;
}
} }
public function getAttributeName($attribute) if ($lines !== array()) {
{ $content = "<ul><li>" . implode("</li>\n<li>", $lines) . "</li><ul>";
if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { return Html::tag('div', $header . $content . $footer, $options);
return $matches[2];
} else { } else {
throw new InvalidParamException('Attribute name must contain word characters only.'); $content = "<ul></ul>";
$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
return Html::tag('div', $header . $content . $footer, $options);
} }
} }
/** /**
* @param Model $model * Generates a form field.
* @param string $attribute * A form field is associated with a model and an attribute. It contains a label, an input and an error message
* @return string * and use them to interact with end users to collect their inputs for the attribute.
* @throws \yii\base\InvalidParamException * @param Model $model the data model
* @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
* about attribute expression.
* @return ActiveField the created ActiveField object
*/ */
public static function getInputName($model, $attribute) public function field($model, $attribute, $options = array())
{
$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.');
}
}
public static function getInputId($model, $attribute)
{ {
$name = static::getInputName($model, $attribute); return Yii::createObject(array_merge($this->fieldConfig, $options, array(
return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); 'model' => $model,
'attribute' => $attribute,
'form' => $this,
)));
} }
} }

20
tests/unit/framework/helpers/HtmlTest.php

@ -157,20 +157,20 @@ class HtmlTest extends \yii\test\TestCase
public function testButton() public function testButton()
{ {
$this->assertEquals('<button type="button">Button</button>', Html::button()); $this->assertEquals('<button type="button">Button</button>', Html::button());
$this->assertEquals('<button type="button" name="test" value="value">content<></button>', Html::button('test', 'value', 'content<>')); $this->assertEquals('<button type="button" name="test" value="value">content<></button>', Html::button('content<>', 'test', 'value'));
$this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::button('test', 'value', 'content<>', array('type' => 'submit', 'class' => "t"))); $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::button('content<>', 'test', 'value', array('type' => 'submit', 'class' => "t")));
} }
public function testSubmitButton() public function testSubmitButton()
{ {
$this->assertEquals('<button type="submit">Submit</button>', Html::submitButton()); $this->assertEquals('<button type="submit">Submit</button>', Html::submitButton());
$this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::submitButton('test', 'value', 'content<>', array('class' => 't'))); $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::submitButton('content<>', 'test', 'value', array('class' => 't')));
} }
public function testResetButton() public function testResetButton()
{ {
$this->assertEquals('<button type="reset">Reset</button>', Html::resetButton()); $this->assertEquals('<button type="reset">Reset</button>', Html::resetButton());
$this->assertEquals('<button type="reset" class="t" name="test" value="value">content<></button>', Html::resetButton('test', 'value', 'content<>', array('class' => 't'))); $this->assertEquals('<button type="reset" class="t" name="test" value="value">content<></button>', Html::resetButton('content<>', 'test', 'value', array('class' => 't')));
} }
public function testInput() public function testInput()
@ -230,15 +230,15 @@ class HtmlTest extends \yii\test\TestCase
public function testRadio() public function testRadio()
{ {
$this->assertEquals('<input type="radio" name="test" value="1" />', Html::radio('test')); $this->assertEquals('<input type="radio" name="test" value="1" />', Html::radio('test'));
$this->assertEquals('<input type="radio" class="a" name="test" checked="checked" />', Html::radio('test', true, null, array('class' => 'a'))); $this->assertEquals('<input type="radio" class="a" name="test" checked="checked" />', Html::radio('test', true, array('class' => 'a', 'value' => null)));
$this->assertEquals('<input type="hidden" name="test" value="0" /><input type="radio" class="a" name="test" value="2" checked="checked" />', Html::radio('test', true, 2, array('class' => 'a' , 'uncheck' => '0'))); $this->assertEquals('<input type="hidden" name="test" value="0" /><input type="radio" class="a" name="test" value="2" checked="checked" />', Html::radio('test', true, array('class' => 'a' , 'uncheck' => '0', 'value' => 2)));
} }
public function testCheckbox() public function testCheckbox()
{ {
$this->assertEquals('<input type="checkbox" name="test" value="1" />', Html::checkbox('test')); $this->assertEquals('<input type="checkbox" name="test" value="1" />', Html::checkbox('test'));
$this->assertEquals('<input type="checkbox" class="a" name="test" checked="checked" />', Html::checkbox('test', true, null, array('class' => 'a'))); $this->assertEquals('<input type="checkbox" class="a" name="test" checked="checked" />', Html::checkbox('test', true, array('class' => 'a', 'value' => null)));
$this->assertEquals('<input type="hidden" name="test" value="0" /><input type="checkbox" class="a" name="test" value="2" checked="checked" />', Html::checkbox('test', true, 2, array('class' => 'a', 'uncheck' => '0'))); $this->assertEquals('<input type="hidden" name="test" value="0" /><input type="checkbox" class="a" name="test" value="2" checked="checked" />', Html::checkbox('test', true, array('class' => 'a', 'uncheck' => '0', 'value' => 2)));
} }
public function testDropDownList() public function testDropDownList()
@ -347,7 +347,7 @@ EOD;
EOD; EOD;
$this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array(
'item' => function ($index, $label, $name, $checked, $value) { 'item' => function ($index, $label, $name, $checked, $value) {
return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, $value)); return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, array('value' => $value)));
} }
))); )));
} }
@ -383,7 +383,7 @@ EOD;
EOD; EOD;
$this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array(
'item' => function ($index, $label, $name, $checked, $value) { 'item' => function ($index, $label, $name, $checked, $value) {
return $index . Html::label($label . ' ' . Html::radio($name, $checked, $value)); return $index . Html::label($label . ' ' . Html::radio($name, $checked, array('value' => $value)));
} }
))); )));
} }

Loading…
Cancel
Save