Commit db11cfc5 by Qiang Xue

Merge branch 'master' of github.com:yiisoft/yii2

parents 872ade6f 38cfdb9b
......@@ -68,11 +68,11 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* @return int|string current user ID
* @return int|string|array current user ID
*/
public function getId()
{
return $this->id;
return $this->getPrimaryKey();
}
/**
......@@ -104,6 +104,12 @@ class User extends ActiveRecord implements IdentityInterface
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
['role', 'default', 'value' => self::ROLE_USER],
['role', 'in', 'range' => [self::ROLE_USER]],
['username', 'filter', 'filter' => 'trim'],
['username', 'required'],
['username', 'string', 'min' => 2, 'max' => 255],
......
......@@ -21,6 +21,9 @@ component's behavior:
'class' => 'yii\twig\ViewRenderer',
//'cachePath' => '@runtime/Twig/cache',
//'options' => [], /* Array of twig options */
'globals' => ['html' => '\yii\helpers\Html'],
* Example:
* Than in template: {{ html.link('Login', 'site/login') }}
],
// ...
],
......@@ -67,6 +70,41 @@ Within Twig templates, you can also make use of these variables:
- `app`, which equates to `\Yii::$app`
- `this`, which equates to the current `View` object
### Globals
You can add global helpers or values via config's `globals`. It allows both using Yii helpers and setting your own
values:
```php
'globals' => [
'html' => '\yii\helpers\Html',
'name' => 'Carsten',
],
```
Then in your template you can use it the following way:
```
Hello, {{name}}! {{ html.link('Please login', 'site/login') }}.
```
### Additional filters
Additional filters may be added via config's `filters` option:
```php
'filters' => [
'jsonEncode' => '\yii\helpers\Json::encode',
],
```
Then in the template you can use
```
{{ model|jsonEncode }}
```
Smarty
------
......
<?php
namespace yii\debug\models\search;
use yii\base\Model;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matches;
class Base extends Model
{
/**
* @param Filter $filter
* @param string $attribute
* @param boolean $partial
*/
public function addCondition($filter, $attribute, $partial = false)
{
$value = $this->$attribute;
if (mb_strpos($value, '>') !== false) {
$value = intval(str_replace('>', '', $value));
$filter->addMatch($attribute, new matches\Greater(['value' => $value]));
} elseif (mb_strpos($value, '<') !== false) {
$value = intval(str_replace('<', '', $value));
$filter->addMatch($attribute, new matches\Lower(['value' => $value]));
} else {
$filter->addMatch($attribute, new matches\Exact(['value' => $value, 'partial' => $partial]));
}
}
}
......@@ -2,15 +2,13 @@
namespace yii\debug\models\search;
use yii\base\Model;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matches;
/**
* Debug represents the model behind the search form about requests manifest data.
*/
class Debug extends Model
class Debug extends Base
{
/**
* @var string tag attribute input search value
......@@ -121,29 +119,4 @@ class Debug extends Model
return in_array($code, $this->criticalCodes);
}
/**
* @param Filter $filter
* @param string $attribute
* @param boolean $partial
*/
public function addCondition($filter, $attribute, $partial = false)
{
$value = $this->$attribute;
if (mb_strpos($value, '>') !== false) {
$value = intval(str_replace('>', '', $value));
$filter->addMatch($attribute, new matches\Greater(['value' => $value]));
} elseif (mb_strpos($value, '<') !== false) {
$value = intval(str_replace('<', '', $value));
$filter->addMatch($attribute, new matches\Lower(['value' => $value]));
} else {
$filter->addMatch($attribute, new matches\Exact(['value' => $value, 'partial' => $partial]));
}
}
}
<?php
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Log represents the model behind the search form about current request log.
*/
class Log extends Base
{
/**
* @var string ip attribute input search value
*/
public $level;
/**
* @var string method attribute input search value
*/
public $category;
/**
* @var integer ajax attribute input search value
*/
public $message;
public function rules()
{
return [
[['level', 'message', 'category'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'level' => 'Level',
'category' => 'Category',
'message' => 'Message',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params
* @param array $models
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'attributes' => ['time','level','category','message'],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'level');
$this->addCondition($filter, 'category', true);
$this->addCondition($filter, 'message', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
......@@ -9,9 +9,8 @@ namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\helpers\Html;
use yii\log\Logger;
use yii\log\Target;
use yii\debug\models\search\Log;
/**
* Debugger panel that collects and displays logs.
......@@ -21,6 +20,12 @@ use yii\log\Target;
*/
class LogPanel extends Panel
{
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models ;
public function getName()
{
return 'Logs';
......@@ -28,72 +33,19 @@ class LogPanel extends Panel
public function getSummary()
{
$output = ['<span class="label">' . count($this->data['messages']) . '</span>'];
$title = 'Logged ' . count($this->data['messages']) . ' messages';
$errorCount = count(Target::filterMessages($this->data['messages'], Logger::LEVEL_ERROR));
if ($errorCount) {
$output[] = '<span class="label label-important">' . $errorCount . '</span>';
$title .= ", $errorCount errors";
}
$warningCount = count(Target::filterMessages($this->data['messages'], Logger::LEVEL_WARNING));
if ($warningCount) {
$output[] = '<span class="label label-warning">' . $warningCount . '</span>';
$title .= ", $warningCount warnings";
}
$log = implode('&nbsp;', $output);
$url = $this->getUrl();
return <<<EOD
<div class="yii-debug-toolbar-block">
<a href="$url" title="$title">Log $log</a>
</div>
EOD;
return Yii::$app->view->render('panels/log/summary',['data' => $this->data, 'panel' => $this]);
}
public function getDetail()
{
$rows = [];
foreach ($this->data['messages'] as $log) {
list ($message, $level, $category, $time, $traces) = $log;
$time = date('H:i:s.', $time) . sprintf('%03d', (int)(($time - (int)$time) * 1000));
$message = nl2br(Html::encode($message));
if (!empty($traces)) {
$message .= Html::ul($traces, [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
if ($level == Logger::LEVEL_ERROR) {
$class = ' class="danger"';
} elseif ($level == Logger::LEVEL_WARNING) {
$class = ' class="warning"';
} elseif ($level == Logger::LEVEL_INFO) {
$class = ' class="success"';
} else {
$class = '';
}
$level = Logger::getLevelName($level);
$rows[] = "<tr$class><td style=\"width: 100px;\">$time</td><td style=\"width: 100px;\">$level</td><td style=\"width: 250px;\">$category</td><td><div>$message</div></td></tr>";
}
$rows = implode("\n", $rows);
return <<<EOD
<h1>Log Messages</h1>
$searchModel = new Log();
$dataProvider = $searchModel->search($_GET, $this->getModels());
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 100px;">Time</th>
<th style="width: 65px;">Level</th>
<th style="width: 250px;">Category</th>
<th>Message</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
EOD;
return Yii::$app->view->render('panels/log/detail',[
'dataProvider' => $dataProvider,
'panel' => $this,
'searchModel' => $searchModel,
]);
}
public function save()
......@@ -102,4 +54,29 @@ EOD;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE);
return ['messages' => $messages];
}
/**
* Returns array of models that represents logs of the current request. Can be used with data providers,
* like yii\data\ArrayDataProvider.
* @param boolean $refresh if needed to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh=false)
{
if ($this->_models === null || $refresh) {
$this->_models = [];
foreach($this->data['messages'] as $message) {
$this->_models[] = [
'message' => $message[0],
'level' => $message[1],
'category' => $message[2],
'time' => ($message[3] * 1000), #time in milliseconds
'trace' => $message[4]
];
}
}
return $this->_models;
}
}
<?php
use yii\helpers\Html;
use yii\grid\GridView;
use yii\data\ArrayDataProvider;
use yii\log\Logger;
?>
<h1>Log Messages</h1>
<?php
echo GridView::widget([
'dataProvider' => $dataProvider,
'id' => 'log-panel-detailed-grid',
'filterModel' => $searchModel,
'filterUrl' => $panel->getUrl(),
'rowOptions' => function ($model, $key, $index, $grid){
switch($model['level']) {
case Logger::LEVEL_ERROR : return ['class' => 'danger'];
case Logger::LEVEL_WARNING : return ['class' => 'warning'];
case Logger::LEVEL_INFO : return ['class' => 'success'];
default: return [];
}
},
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'attribute' => 'time',
'value' => function ($data)
{
$timeInSeconds = $data['time'] / 1000;
$millisecondsDiff = (int)(($timeInSeconds - (int)$timeInSeconds) * 1000);
return date('H:i:s.',$timeInSeconds) . sprintf('%03d',$millisecondsDiff);
},
],
[
'attribute' => 'level',
'value' => function ($data)
{
return Logger::getLevelName($data['level']);
},
'filter' => [
Logger::LEVEL_TRACE => ' Trace ',
Logger::LEVEL_PROFILE => ' Profile ',
Logger::LEVEL_INFO => ' Info ',
Logger::LEVEL_ERROR => ' Error ',
],
],
'category',
[
'attribute' => 'message',
'value' => function ($data)
{
$message = nl2br(Html::encode($data['message']));
if (!empty($data['trace'])) {
$message .= Html::ul($data['trace'], [
'class' => 'trace',
'item' => function ($trace)
{
return "<li>{$trace['file']} ({$trace['line']})</li>";
}
]);
};
return $message;
},
'format' => 'html',
'options' => [
'width' => '50%',
],
],
],
]);
?>
\ No newline at end of file
<?php
use yii\log\Target;
use yii\log\Logger;
?>
<?php
$title = 'Logged ' . count($data['messages']) . ' messages';
$errorCount = count(Target::filterMessages($data['messages'], Logger::LEVEL_ERROR));
$warningCount = count(Target::filterMessages($data['messages'], Logger::LEVEL_WARNING));
$output = [];
if ($errorCount) {
$output[] = "<span class=\"label label-important\">$errorCount</span>";
$title .= ", $errorCount errors";
}
if ($warningCount) {
$output[] = "<span class=\"label label-warning\">$warningCount</span>";
$title .= ", $warningCount warnings";
}
?>
<div class="yii-debug-toolbar-block">
<a href="<?php echo $panel->getUrl(); ?>" title="<?php echo $title ?>">Log
<span class="label"><?php echo count($data['messages']); ?></span>
<?php echo implode('&nbsp;', $output); ?>
</a>
</div>
\ No newline at end of file
......@@ -18,7 +18,7 @@ $this->title = 'Yii Debugger';
<div class="default-view">
<div id="yii-debug-toolbar">
<div class="yii-debug-toolbar-block title">
Yii Debugger
<?php echo Html::a('Yii Debugger', ['index'],['title' => 'Back to main debug page']);?>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
......
......@@ -5,6 +5,7 @@ Yii Framework 2 gii extension Change Log
----------------------------
- Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue)
- Enh #1624: generate rules for unique indexes (lucianobaraglia)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -15,7 +15,7 @@ use yii\helpers\Html;
* For example,
*
* ```
* echo Slider::widget([
* echo SliderInput::widget([
* 'model' => $model,
* 'attribute' => 'amount',
* 'clientOptions' => [
......@@ -28,7 +28,7 @@ use yii\helpers\Html;
* The following example will use the name property instead:
*
* ```
* echo Slider::widget([
* echo SliderInput::widget([
* 'name' => 'amount',
* 'clientOptions' => [
* 'min' => 1,
......@@ -75,8 +75,10 @@ class SliderInput extends InputWidget
if ($this->hasModel()) {
echo Html::activeHiddenInput($this->model, $this->attribute, $this->options);
$this->clientOptions['value'] = $this->model{$this->attribute};
} else {
echo Html::hiddenInput($this->name, $this->value, $this->options);
$this->clientOptions['value'] = $this->value;
}
if (!isset($this->clientEvents['slide'])) {
......@@ -86,6 +88,5 @@ class SliderInput extends InputWidget
}
$this->registerWidget('slider', SliderAsset::className(), $this->containerOptions['id']);
$this->getView()->registerJs('$("#' . $this->options['id'] . '").val($("#' . $this->id . '").slider("value"));');
}
}
......@@ -4,7 +4,7 @@ Yii Framework 2 twig extension Change Log
2.0.0 beta under development
----------------------------
- no changes in this release.
- Add File based Twig loader for better caching and usability of twig's file based function (dev-mraj, samdark)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -15,6 +15,7 @@ return [
'class' => 'yii\twig\ViewRenderer',
//'cachePath' => '@runtime/Twig/cache',
//'options' => [], /* Array of twig options */
// ... see ViewRenderer for more options
],
],
],
......
<?php
/**
* Simple file system wrapper for twig to process twig files
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\twig;
/**
* Twig view file loader class.
*
* @author dev-mraj <dev.meghraj@gmail.com>
*/
class TwigSimpleFileLoader implements \Twig_LoaderInterface
{
/**
* @var string Path to directory
*/
private $_dir;
/**
* @param string $dir path to directory
*/
public function __construct($dir)
{
$this->_dir = $dir;
}
/**
* Compare a file's freshness with previously stored timestamp
*
* @param $name string file name to check
* @param $time int timestamp to compare with
* @return boolean true if file is still fresh and not changes, false otherwise
*/
public function isFresh($name, $time)
{
return filemtime($this->getFilePath($name)) <= $time;
}
/**
* Get the source of given file name
*
* @param string $name file name
* @return string contents of given file name
*/
public function getSource($name)
{
return file_get_contents($this->getFilePath($name));
}
/**
* Get unique key that can represent this file uniquely among other files.
* @param string $name
* @return string
*/
public function getCacheKey($name)
{
return $this->getFilePath($name);
}
/**
* internally used to get absolute path of given file name
* @param string $name file name
* @return string absolute path of file
*/
protected function getFilePath($name){
return $this->_dir . '/' . $name;
}
}
\ No newline at end of file
......@@ -28,35 +28,102 @@ class ViewRenderer extends BaseViewRenderer
public $cachePath = '@runtime/Twig/cache';
/**
* @var array extentions list.
*/
public $extensions = [];
/**
* @var array Twig options
* @see http://twig.sensiolabs.org/doc/api.html#environment-options
*/
public $options = [];
/**
* @var \Twig_Environment
/**
* @var array Objects or static classes
* Keys of array are names to call in template, values - objects or names of static class as string
* Example: ['html' => '\yii\helpers\Html']
* Than in template: {{ html.link('Login', 'site/login') }}
*/
public $globals = [];
/**
* @var array Custom functions
* Keys of array are names to call in template, values - names of functions or static methods of some class
* Example: ['rot13' => 'str_rot13', 'link' => '\yii\helpers\Html::link']
* Than in template: {{ rot13('test') }} or {{ link('Login', 'site/login') }}
*/
public $functions = [];
/**
* @var array Custom filters
* Keys of array are names to call in template, values - names of functions or static methods of some class
* Example: ['rot13' => 'str_rot13', 'jsonEncode' => '\yii\helpers\Json::encode']
* Then in template: {{ 'test'|rot13 }} or {{ model|jsonEncode }}
*/
public $filters = [];
/**
* @var array Custom extensions
* Example: ['Twig_Extension_Sandbox', 'Twig_Extension_Text']
*/
public $extensions = [];
/**
* @var array Twig lexer options
* @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax
* Example: Smarty-like syntax
* array(
* 'tag_comment' => ['{*', '*}'],
* 'tag_block' => ['{', '}'],
* 'tag_variable' => ['{$', '}']
* )
*/
public $lexerOptions = [];
/**
* @var \Twig_Environment twig environment object that do all rendering twig templates
*/
public $twig;
public function init()
{
$loader = new \Twig_Loader_String();
$this->twig = new \Twig_Environment($loader, array_merge([
$this->twig = new \Twig_Environment(null, array_merge([
'cache' => Yii::getAlias($this->cachePath),
'charset' => Yii::$app->charset,
], $this->options));
// Adding custom extensions
if (!empty($this->extensions)) {
foreach ($this->extensions as $extension) {
$this->twig->addExtension(new $extension());
}
}
// Adding custom globals (objects or static classes)
if (!empty($this->globals)) {
$this->addGlobals($this->globals);
}
// Adding custom functions
if (!empty($this->functions)) {
$this->addFunctions($this->functions);
}
// Adding custom filters
if (!empty($this->filters)) {
$this->addFilters($this->filters);
}
// Adding custom extensions
if (!empty($this->extensions)) {
$this->addExtensions($this->extensions);
}
// Change lexer syntax
if (!empty($this->lexerOptions)) {
$this->setLexerOptions($this->lexerOptions);
}
// Adding global 'void' function (usage: {{void(App.clientScript.registerScriptFile(...))}})
$this->twig->addFunction('void', new \Twig_Function_Function(function($argument){
}));
$this->twig->addFunction('path', new \Twig_Function_Function(function ($path, $args = []) {
return Html::url(array_merge([$path], $args));
}));
......@@ -79,6 +146,92 @@ class ViewRenderer extends BaseViewRenderer
public function render($view, $file, $params)
{
$this->twig->addGlobal('this', $view);
return $this->twig->render(file_get_contents($file), $params);
$this->twig->setLoader(new TwigSimpleFileLoader(dirname($file)));
return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
}
/**
* Adds global objects or static classes
* @param array $globals @see self::$globals
*/
public function addGlobals($globals)
{
foreach ($globals as $name => $value) {
if (!is_object($value)) {
$value = new ViewRendererStaticClassProxy($value);
}
$this->twig->addGlobal($name, $value);
}
}
/**
* Adds custom functions
* @param array $functions @see self::$functions
*/
public function addFunctions($functions)
{
$this->_addCustom('Function', $functions);
}
/**
* Adds custom filters
* @param array $filters @see self::$filters
*/
public function addFilters($filters)
{
$this->_addCustom('Filter', $filters);
}
/**
* Adds custom extensions
* @param array $extensions @see self::$extensions
*/
public function addExtensions($extensions)
{
foreach ($extensions as $extName) {
$this->twig->addExtension(new $extName());
}
}
/**
* Sets Twig lexer options to change templates syntax
* @param array $options @see self::$lexerOptions
*/
public function setLexerOptions($options)
{
$lexer = new \Twig_Lexer($this->twig, $options);
$this->twig->setLexer($lexer);
}
/**
* Adds custom function or filter
* @param string $classType 'Function' or 'Filter'
* @param array $elements Parameters of elements to add
* @throws \Exception
*/
private function _addCustom($classType, $elements)
{
$classFunction = 'Twig_' . $classType . '_Function';
foreach ($elements as $name => $func) {
$twigElement = null;
switch ($func) {
// Just a name of function
case is_string($func):
$twigElement = new $classFunction($func);
break;
// Name of function + options array
case is_array($func) && is_string($func[0]) && isset($func[1]) && is_array($func[1]):
$twigElement = new $classFunction($func[0], $func[1]);
break;
}
if ($twigElement !== null) {
$this->twig->{'add'.$classType}($name, $twigElement);
} else {
throw new \Exception("Incorrect options for \"$classType\" $name.");
}
}
}
}
<?php
/**
* Twig ViewRendererStaticClassProxy class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\twig;
/**
* Class-proxy for static classes
* Needed because you can't pass static class to Twig other way
*
* @author Leonid Svyatov <leonid@svyatov.ru>
*/
class ViewRendererStaticClassProxy
{
private $_staticClassName;
public function __construct($staticClassName) {
$this->_staticClassName = $staticClassName;
}
public function __get($property)
{
$class = new \ReflectionClass($this->_staticClassName);
return $class->getStaticPropertyValue($property);
}
public function __set($property, $value)
{
$class = new \ReflectionClass($this->_staticClassName);
$class->setStaticPropertyValue($property, $value);
return $value;
}
public function __call($method, $arguments)
{
return call_user_func_array(array($this->_staticClassName, $method), $arguments);
}
}
\ No newline at end of file
......@@ -16,6 +16,7 @@ Yii Framework 2 Change Log
- Bug #1591: StringValidator is accessing undefined property (qiangxue)
- Bug #1597: Added `enableAutoLogin` to basic and advanced application templates so "remember me" now works properly (samdark)
- Bug #1631: Charset is now explicitly set to UTF-8 when serving JSON (samdark)
- Bug #1635: `yii\jui\SliderInput` wasn't properly initialized (samdark)
- Bug #1686: ActiveForm is creating duplicated messages in error summary (qiangxue)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
......@@ -36,12 +37,13 @@ Yii Framework 2 Change Log
- Enh #1581: Added `ActiveQuery::joinWith()` and `ActiveQuery::innerJoinWith()` to support joining with relations (qiangxue)
- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight)
- Enh #1611: Added `BaseActiveRecord::markAttributeDirty()` (qiangxue)
- Enh #1633: Advanced application template now works with MongoDB by default (samdark)
- Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue)
- Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue)
- Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo)
- Enh #1645: Added `Connection::$pdoClass` property (Ragazzo)
- Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight)
- Enh: Sort and Pagination can now create absolute URLs (cebe)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment