Browse Source

Merge remote branch 'upstream/master' into pgsql-driver

tags/2.0.0-beta
Gevik Babakhani 12 years ago
parent
commit
3d79d992c4
  1. 41
      apps/advanced/README.md
  2. 2
      apps/advanced/backstage/config/main.php
  3. 8
      apps/advanced/composer.json
  4. 10
      apps/advanced/composer.lock
  5. 2
      apps/advanced/console/config/main.php
  6. 2
      apps/advanced/frontend/config/main.php
  7. 14
      apps/advanced/init
  8. 0
      apps/advanced/init.bat
  9. 15
      apps/basic/README.md
  10. 5
      apps/basic/composer.json
  11. 10
      apps/basic/composer.lock
  12. 1
      apps/basic/config/main.php
  13. 12
      apps/basic/views/site/contact.php
  14. 10
      apps/benchmark/composer.lock
  15. 4
      extensions/smarty/yii/smarty/ViewRenderer.php
  16. 2
      extensions/twig/yii/twig/ViewRenderer.php
  17. 7
      framework/yii/assets.php
  18. 338
      framework/yii/assets/jquery.maskedinput.js
  19. 14
      framework/yii/assets/yii.activeForm.js
  20. 4
      framework/yii/assets/yii.validation.js
  21. 4
      framework/yii/base/ActionFilter.php
  22. 64
      framework/yii/base/Application.php
  23. 44
      framework/yii/base/Controller.php
  24. 300
      framework/yii/base/Formatter.php
  25. 26
      framework/yii/base/InvalidRequestException.php
  26. 20
      framework/yii/base/Module.php
  27. 10
      framework/yii/base/View.php
  28. 63
      framework/yii/bootstrap/Button.php
  29. 128
      framework/yii/bootstrap/ButtonDropdown.php
  30. 99
      framework/yii/bootstrap/ButtonGroup.php
  31. 2
      framework/yii/bootstrap/Carousel.php
  32. 2
      framework/yii/bootstrap/Collapse.php
  33. 95
      framework/yii/bootstrap/Dropdown.php
  34. 140
      framework/yii/bootstrap/Nav.php
  35. 188
      framework/yii/bootstrap/NavBar.php
  36. 147
      framework/yii/bootstrap/Progress.php
  37. 195
      framework/yii/bootstrap/Tabs.php
  38. 3
      framework/yii/caching/FileCache.php
  39. 69
      framework/yii/console/controllers/AssetController.php
  40. 8
      framework/yii/db/ActiveRecord.php
  41. 9
      framework/yii/db/Connection.php
  42. 10
      framework/yii/db/QueryBuilder.php
  43. 2
      framework/yii/db/mssql/QueryBuilder.php
  44. 11
      framework/yii/db/mysql/QueryBuilder.php
  45. 8
      framework/yii/db/mysql/Schema.php
  46. 4
      framework/yii/db/sqlite/QueryBuilder.php
  47. 4
      framework/yii/helpers/base/Html.php
  48. 21
      framework/yii/helpers/base/SecurityHelper.php
  49. 270
      framework/yii/i18n/Formatter.php
  50. 52
      framework/yii/jui/Draggable.php
  51. 52
      framework/yii/jui/Droppable.php
  52. 52
      framework/yii/jui/Resizable.php
  53. 116
      framework/yii/jui/Selectable.php
  54. 66
      framework/yii/jui/Spinner.php
  55. 149
      framework/yii/jui/Tabs.php
  56. 2
      framework/yii/requirements/requirements.php
  57. 2
      framework/yii/validators/DateValidator.php
  58. 27
      framework/yii/web/Application.php
  59. 44
      framework/yii/web/Controller.php
  60. 4
      framework/yii/web/Request.php
  61. 32
      framework/yii/web/UrlManager.php
  62. 90
      framework/yii/web/VerbFilter.php
  63. 12
      framework/yii/widgets/ActiveField.php
  64. 4
      framework/yii/widgets/ActiveForm.php
  65. 64
      framework/yii/widgets/Captcha.php
  66. 64
      framework/yii/widgets/InputWidget.php
  67. 136
      framework/yii/widgets/MaskedInput.php
  68. 36
      framework/yii/widgets/Menu.php
  69. 3
      tests/unit/TestCase.php
  70. 2
      tests/unit/data/config.php
  71. 179
      tests/unit/framework/base/FormatterTest.php
  72. 1
      tests/unit/framework/console/controllers/AssetControllerTest.php
  73. 109
      tests/unit/framework/db/QueryBuilderTest.php
  74. 74
      tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
  75. 88
      tests/unit/framework/i18n/FormatterTest.php
  76. 54
      tests/unit/framework/web/UrlManagerTest.php

41
apps/advanced/README.md

@ -67,32 +67,47 @@ If you do not have [Composer](http://getcomposer.org/), you may download it from
curl -s http://getcomposer.org/installer | php
~~~
You can then install the Bootstrap Application using the following command:
You can then install the application using the following command:
~~~
php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced
~~~
Now you should be able to access:
- the frontend using the URL `http://localhost/yii-advanced/frontend/www/`
- the backstage using the URL `http://localhost/yii-advanced/backstage/www/`
### Install from an Archive File
assuming `yii-advanced` is directly under the document root of your Web server.
This is not currently available. We will provide it when Yii 2 is formally released.
### Install from an Archive File
### Install from development repository
If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you
can bootstrap your application with:
~~~
cd yii2/apps/advanced
php composer.phar create-project
~~~
*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run
`php composer.phar self-update` to obtain an updated version of composer which supports creating projects
from local packages.*
This is not currently available. We will provide it when Yii 2 is formally released.
GETTING STARTED
---------------
After template application and its dependencies are downloaded you need to initialize it and set some config values to
match your application requirements.
After you install the application, you have to conduct the following steps to initialize
the installed application. You only need to do these once for all.
1. Execute the `init` command and select `dev` as environment.
2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`.
3. In `common/config/params.php` set your database details in `components.db` values.
Now you should be able to access:
1. Execute `install` command selecting `dev` as environment.
2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`.
3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`.
4. In `common/config/params.php` set your database details in `components.db` values.
- the frontend using the URL `http://localhost/yii-advanced/frontend/www/`
- the backstage using the URL `http://localhost/yii-advanced/backstage/www/`
assuming `yii-advanced` is directly under the document root of your Web server.

2
apps/advanced/backstage/config/main.php

@ -9,7 +9,7 @@ $params = array_merge(
);
return array(
'id' => 'change-me',
'id' => 'app-backend',
'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),

8
apps/advanced/composer.json

@ -19,10 +19,7 @@
"yiisoft/yii2-composer": "dev-master"
},
"scripts": {
"post-install-cmd": [
"yii\\composer\\InstallHandler::setPermissions"
],
"post-update-cmd": [
"post-create-project-cmd": [
"yii\\composer\\InstallHandler::setPermissions"
]
},
@ -36,9 +33,6 @@
"frontend/runtime",
"frontend/www/assets"
],
"yii-install-executable": [
"yii"
]
}
}

10
apps/advanced/composer.lock generated

@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "0b96a35ac23eae4e84ffd588653e88d2",
"hash": "2d1053fbaaf2044054f273a71d0ccde0",
"packages": [
{
"name": "yiisoft/yii2",
@ -11,12 +11,12 @@
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b"
"reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b",
"reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989",
"reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989",
"shasum": ""
},
"require": {
@ -97,7 +97,7 @@
"framework",
"yii"
],
"time": "2013-05-25 20:59:05"
"time": "2013-06-02 19:19:29"
},
{
"name": "yiisoft/yii2-composer",

2
apps/advanced/console/config/main.php

@ -9,7 +9,7 @@ $params = array_merge(
);
return array(
'id' => 'change-me',
'id' => 'app-console',
'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),

2
apps/advanced/frontend/config/main.php

@ -9,7 +9,7 @@ $params = array_merge(
);
return array(
'id' => 'change-me',
'id' => 'app-frontend',
'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),

14
apps/advanced/install → apps/advanced/init

@ -4,27 +4,27 @@ $root = str_replace('\\', '/', __DIR__);
$envs = require("$root/environments/index.php");
$envNames = array_keys($envs);
echo "Yii Application Installation Tool v1.0\n\n";
echo "Which environment do you want to install the application to?\n\n";
echo "Yii Application Init Tool v1.0\n\n";
echo "Which environment do you want the application to be initialized in?\n\n";
foreach ($envNames as $i => $name) {
echo " [$i] $name\n";
}
echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] ';
$answer = trim(fgets(STDIN));
if (!ctype_digit($answer) || !isset($envNames[$answer])) {
echo "\n Quit installation.\n";
echo "\n Quit initialization.\n";
return;
}
$env = $envs[$envNames[$answer]];
echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] ";
echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] ";
$answer = trim(fgets(STDIN));
if (strncasecmp($answer, 'y', 1)) {
echo "\n Quit installation.\n";
echo "\n Quit initialization.\n";
return;
}
echo "\n Start installation ...\n\n";
echo "\n Start initialization ...\n\n";
$files = getFileList("$root/environments/{$env['path']}");
$all = false;
foreach ($files as $file) {
@ -47,7 +47,7 @@ if (isset($env['executable'])) {
}
}
echo "\n ... installation completed.\n\n";
echo "\n ... initialization completed.\n\n";
function getFileList($root, $basePath = '')
{

0
apps/advanced/install.bat → apps/advanced/init.bat

15
apps/basic/README.md

@ -59,3 +59,18 @@ assuming `yii-basic` is directly under the document root of your Web server.
### Install from an Archive File
This is not currently available. We will provide it when Yii 2 is formally released.
### Install from development repository
If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you
can bootstrap your application with:
~~~
cd yii2/apps/basic
php composer.phar create-project
~~~
*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run
`php composer.phar self-update` to obtain an updated version of composer which supports creating projects
from local packages.*

5
apps/basic/composer.json

@ -19,10 +19,7 @@
"yiisoft/yii2-composer": "dev-master"
},
"scripts": {
"post-install-cmd": [
"yii\\composer\\InstallHandler::setPermissions"
],
"post-update-cmd": [
"post-create-project-cmd": [
"yii\\composer\\InstallHandler::setPermissions"
]
},

10
apps/basic/composer.lock generated

@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "0411dbbd774aa1c89256c77c68023940",
"hash": "91ba258de768b93025f86071f3bb4b84",
"packages": [
{
"name": "yiisoft/yii2",
@ -11,12 +11,12 @@
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b"
"reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b",
"reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989",
"reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989",
"shasum": ""
},
"require": {
@ -97,7 +97,7 @@
"framework",
"yii"
],
"time": "2013-05-25 20:59:05"
"time": "2013-06-02 19:19:29"
},
{
"name": "yiisoft/yii2-composer",

1
apps/basic/config/main.php

@ -4,7 +4,6 @@ return array(
'id' => 'bootstrap',
'basePath' => dirname(__DIR__),
'preload' => array('log'),
'controllerNamespace' => 'app\controllers',
'modules' => array(
// 'debug' => array(
// 'class' => 'yii\debug\Module',

12
apps/basic/views/site/contact.php

@ -31,15 +31,9 @@ $this->params['breadcrumbs'][] = $this->title;
<?php echo $form->field($model, 'email')->textInput(); ?>
<?php echo $form->field($model, 'subject')->textInput(); ?>
<?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?>
<?php
$field = $form->field($model, 'verifyCode');
echo $field->begin()
. $field->label()
. Captcha::widget()
. Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium'))
. $field->error()
. $field->end();
?>
<?php echo $form->field($model, 'verifyCode')->widget(Captcha::className(), array(
'options' => array('class' => 'input-medium'),
)); ?>
<div class="form-actions">
<?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?>
</div>

10
apps/benchmark/composer.lock generated

@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "5ce5f1ad2aa7d7e31c3e216b8ce23387",
"hash": "25a338a613af40ef87f2e280057f0d8c",
"packages": [
{
"name": "yiisoft/yii2",
@ -11,12 +11,12 @@
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "f3c3d9d764de25fc46711bce2259274bcceade1c"
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f3c3d9d764de25fc46711bce2259274bcceade1c",
"reference": "f3c3d9d764de25fc46711bce2259274bcceade1c",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"shasum": ""
},
"require": {
@ -97,7 +97,7 @@
"framework",
"yii"
],
"time": "2013-05-26 21:57:00"
"time": "2013-05-29 02:55:14"
}
],
"packages-dev": [

4
extensions/smarty/yii/smarty/ViewRenderer.php

@ -26,12 +26,12 @@ class ViewRenderer extends BaseViewRenderer
/**
* @var string the directory or path alias pointing to where Smarty cache will be stored.
*/
public $cachePath = '@app/runtime/Smarty/cache';
public $cachePath = '@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';
public $compilePath = '@runtime/Smarty/compile';
/**
* @var Smarty

2
extensions/twig/yii/twig/ViewRenderer.php

@ -25,7 +25,7 @@ class ViewRenderer extends BaseViewRenderer
/**
* @var string the directory or path alias pointing to where Twig cache will be stored.
*/
public $cachePath = '@app/runtime/Twig/cache';
public $cachePath = '@runtime/Twig/cache';
/**
* @var array Twig options

7
framework/yii/assets.php

@ -48,4 +48,11 @@ return array(
YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js',
),
),
'yii/maskedinput' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'jquery.maskedinput.js',
),
'depends' => array('yii/jquery'),
),
);

338
framework/yii/assets/jquery.maskedinput.js

@ -0,0 +1,338 @@
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function($) {
function getPasteEvent() {
var el = document.createElement('input'),
name = 'onpaste';
el.setAttribute(name, '');
return (typeof el[name] === 'function')?'paste':'input';
}
var pasteEventName = getPasteEvent() + ".mask",
ua = navigator.userAgent,
iPhone = /iphone/i.test(ua),
android=/android/i.test(ua),
caretTimeoutId;
$.mask = {
//Predefined character definitions
definitions: {
'9': "[0-9]",
'a': "[A-Za-z]",
'*': "[A-Za-z0-9]"
},
dataName: "rawMaskFn",
placeholder: '_',
};
$.fn.extend({
//Helper Function for Caret positioning
caret: function(begin, end) {
var range;
if (this.length === 0 || this.is(":hidden")) {
return;
}
if (typeof begin == 'number') {
end = (typeof end === 'number') ? end : begin;
return this.each(function() {
if (this.setSelectionRange) {
this.setSelectionRange(begin, end);
} else if (this.createTextRange) {
range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', begin);
range.select();
}
});
} else {
if (this[0].setSelectionRange) {
begin = this[0].selectionStart;
end = this[0].selectionEnd;
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
begin = 0 - range.duplicate().moveStart('character', -100000);
end = begin + range.text.length;
}
return { begin: begin, end: end };
}
},
unmask: function() {
return this.trigger("unmask");
},
mask: function(mask, settings) {
var input,
defs,
tests,
partialPosition,
firstNonMaskPos,
len;
if (!mask && this.length > 0) {
input = $(this[0]);
return input.data($.mask.dataName)();
}
settings = $.extend({
placeholder: $.mask.placeholder, // Load default placeholder
completed: null
}, settings);
defs = $.mask.definitions;
tests = [];
partialPosition = len = mask.length;
firstNonMaskPos = null;
$.each(mask.split(""), function(i, c) {
if (c == '?') {
len--;
partialPosition = i;
} else if (defs[c]) {
tests.push(new RegExp(defs[c]));
if (firstNonMaskPos === null) {
firstNonMaskPos = tests.length - 1;
}
} else {
tests.push(null);
}
});
return this.trigger("unmask").each(function() {
var input = $(this),
buffer = $.map(
mask.split(""),
function(c, i) {
if (c != '?') {
return defs[c] ? settings.placeholder : c;
}
}),
focusText = input.val();
function seekNext(pos) {
while (++pos < len && !tests[pos]);
return pos;
}
function seekPrev(pos) {
while (--pos >= 0 && !tests[pos]);
return pos;
}
function shiftL(begin,end) {
var i,
j;
if (begin<0) {
return;
}
for (i = begin, j = seekNext(end); i < len; i++) {
if (tests[i]) {
if (j < len && tests[i].test(buffer[j])) {
buffer[i] = buffer[j];
buffer[j] = settings.placeholder;
} else {
break;
}
j = seekNext(j);
}
}
writeBuffer();
input.caret(Math.max(firstNonMaskPos, begin));
}
function shiftR(pos) {
var i,
c,
j,
t;
for (i = pos, c = settings.placeholder; i < len; i++) {
if (tests[i]) {
j = seekNext(i);
t = buffer[i];
buffer[i] = c;
if (j < len && tests[j].test(t)) {
c = t;
} else {
break;
}
}
}
}
function keydownEvent(e) {
var k = e.which,
pos,
begin,
end;
//backspace, delete, and escape get special treatment
if (k === 8 || k === 46 || (iPhone && k === 127)) {
pos = input.caret();
begin = pos.begin;
end = pos.end;
if (end - begin === 0) {
begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
end=k===46?seekNext(end):end;
}
clearBuffer(begin, end);
shiftL(begin, end - 1);
e.preventDefault();
} else if (k == 27) {//escape
input.val(focusText);
input.caret(0, checkVal());
e.preventDefault();
}
}
function keypressEvent(e) {
var k = e.which,
pos = input.caret(),
p,
c,
next;
if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
return;
} else if (k) {
if (pos.end - pos.begin !== 0){
clearBuffer(pos.begin, pos.end);
shiftL(pos.begin, pos.end-1);
}
p = seekNext(pos.begin - 1);
if (p < len) {
c = String.fromCharCode(k);
if (tests[p].test(c)) {
shiftR(p);
buffer[p] = c;
writeBuffer();
next = seekNext(p);
if(android){
setTimeout($.proxy($.fn.caret,input,next),0);
}else{
input.caret(next);
}
if (settings.completed && next >= len) {
settings.completed.call(input);
}
}
}
e.preventDefault();
}
}
function clearBuffer(start, end) {
var i;
for (i = start; i < end && i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
}
}
}
function writeBuffer() { input.val(buffer.join('')); }
function checkVal(allow) {
//try to place characters where they belong
var test = input.val(),
lastMatch = -1,
i,
c;
for (i = 0, pos = 0; i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
while (pos++ < test.length) {
c = test.charAt(pos - 1);
if (tests[i].test(c)) {
buffer[i] = c;
lastMatch = i;
break;
}
}
if (pos > test.length) {
break;
}
} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
pos++;
lastMatch = i;
}
}
if (allow) {
writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
input.val("");
clearBuffer(0, len);
} else {
writeBuffer();
input.val(input.val().substring(0, lastMatch + 1));
}
return (partialPosition ? i : firstNonMaskPos);
}
input.data($.mask.dataName,function(){
return $.map(buffer, function(c, i) {
return tests[i]&&c!=settings.placeholder ? c : null;
}).join('');
});
if (!input.attr("readonly"))
input
.one("unmask", function() {
input
.unbind(".mask")
.removeData($.mask.dataName);
})
.bind("focus.mask", function() {
clearTimeout(caretTimeoutId);
var pos,
moveCaret;
focusText = input.val();
pos = checkVal();
caretTimeoutId = setTimeout(function(){
writeBuffer();
if (pos == mask.length) {
input.caret(0, pos);
} else {
input.caret(pos);
}
}, 10);
})
.bind("blur.mask", function() {
checkVal();
if (input.val() != focusText)
input.change();
})
.bind("keydown.mask", keydownEvent)
.bind("keypress.mask", keypressEvent)
.bind(pasteEventName, function() {
setTimeout(function() {
var pos=checkVal(true);
input.caret(pos);
if (settings.completed && pos == input.val().length)
settings.completed.call(input);
}, 0);
});
checkVal(); //Perform initial check for existing values
});
}
});
})(jQuery);

14
framework/yii/assets/yii.activeForm.js

@ -135,12 +135,20 @@
data.submitting = true;
if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) {
validate($form, function (messages) {
var hasError = false;
var errors = [];
$.each(data.attributes, function () {
hasError = updateInput($form, this, messages) || hasError;
if (updateInput($form, this, messages)) {
errors.push(this.input);
}
});
updateSummary($form, messages);
if (!hasError) {
if (errors.length) {
var top = $form.find(errors.join(',')).first().offset().top;
var wtop = $(window).scrollTop();
if (top < wtop || top > wtop + $(window).height) {
$(window).scrollTop(top);
}
} else {
data.validated = true;
var $button = data.submitObject || $form.find(':submit:first');
// TODO: if the submission is caused by "change" event, it will not work

4
framework/yii/assets/yii.validation.js

@ -87,8 +87,8 @@ yii.validation = (function ($) {
if (options.skipOnEmpty && isEmpty(value)) {
return;
}
var valid = !options.not && $.inArray(value, options.range)
|| options.not && !$.inArray(value, options.range);
var valid = !options.not && $.inArray(value, options.range) > -1
|| options.not && $.inArray(value, options.range) == -1;
if (!valid) {
messages.push(options.message);

4
framework/yii/base/ActionFilter.php

@ -30,8 +30,8 @@ class ActionFilter extends Behavior
public function events()
{
return array(
'beforeAction' => 'beforeFilter',
'afterAction' => 'afterFilter',
Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
Controller::EVENT_AFTER_ACTION => 'afterFilter',
);
}

64
framework/yii/base/Application.php

@ -67,35 +67,59 @@ class Application extends Module
* Constructor.
* @param array $config name-value pairs that will be used to initialize the object properties.
* Note that the configuration must contain both [[id]] and [[basePath]].
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function __construct($config = array())
{
Yii::$app = $this;
$this->preInit($config);
$this->registerErrorHandlers();
$this->registerCoreComponents();
Component::__construct($config);
}
/**
* Pre-initializes the application.
* This method is called at the beginning of the application constructor.
* When this method is called, none of the application properties are initialized yet.
* The default implementation will initialize a few important properties
* that may be referenced during the initialization of the rest of the properties.
* @param array $config the application configuration
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function preInit($config)
{
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration is required.');
}
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
Yii::setAlias('@app', $this->getBasePath());
unset($config['basePath']);
} else {
if (!isset($config['basePath'])) {
throw new InvalidConfigException('The "basePath" configuration is required.');
}
$this->setBasePath($config['basePath']);
Yii::setAlias('@app', $this->getBasePath());
unset($config['basePath']);
if (isset($config['vendor'])) {
$this->setVendorPath($config['vendor']);
unset($config['vendorPath']);
}
Yii::setAlias('@vendor', $this->getVendorPath());
if (isset($config['runtime'])) {
$this->setRuntimePath($config['runtime']);
unset($config['runtime']);
}
Yii::setAlias('@runtime', $this->getRuntimePath());
if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}
$this->registerErrorHandlers();
$this->registerCoreComponents();
Component::__construct($config);
}
}
/**
@ -279,6 +303,15 @@ class Application extends Module
}
/**
* Returns the formatter component.
* @return \yii\base\Formatter the formatter application component.
*/
public function getFormatter()
{
return $this->getComponent('formatter');
}
/**
* Returns the request component.
* @return \yii\web\Request|\yii\console\Request the request component
*/
@ -333,6 +366,9 @@ class Application extends Module
'errorHandler' => array(
'class' => 'yii\base\ErrorHandler',
),
'formatter' => array(
'class' => 'yii\base\Formatter',
),
'i18n' => array(
'class' => 'yii\i18n\I18N',
),

44
framework/yii/base/Controller.php

@ -151,43 +151,13 @@ class Controller extends Component
/**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with.
* @throws InvalidRequestException if there are missing parameters.
*/
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
return array();
}
/**
@ -272,18 +242,6 @@ class Controller extends Component
}
/**
* Validates the parameter being bound to actions.
* This method is invoked when parameters are being bound to the currently requested action.
* Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param Action $action the currently requested action
* @param array $missingParams the names of the missing parameters
* @param array $unknownParams the unknown parameters (name => value)
*/
public function validateActionParams($action, $missingParams, $unknownParams)
{
}
/**
* @return string the controller ID that is prefixed with the module ID (if any).
*/
public function getUniqueId()

300
framework/yii/base/Formatter.php

@ -0,0 +1,300 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use DateTime;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
/**
* Formatter provides a set of commonly used data formatting methods.
*
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
* The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends Component
{
/**
* @var string the default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'Y/m/d';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'h:i:s A';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'Y/m/d h:i:s A';
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`.
*/
public $booleanFormat;
/**
* @var string the character displayed as the decimal point when formatting a number.
* If not set, "." will be used.
*/
public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
* If not set, "," will be used.
*/
public $thousandSeparator;
/**
* Initializes the component.
*/
public function init()
{
if (empty($this->booleanFormat)) {
$this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes'));
}
}
/**
* Formats the value as is without any formatting.
* This method simply returns back the parameter without any format.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asRaw($value)
{
return $value;
}
/**
* Formats the value as an HTML-encoded plain text.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asText($value)
{
return Html::encode($value);
}
/**
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asNtext($value)
{
return nl2br(Html::encode($value));
}
/**
* Formats the value as HTML-encoded text paragraphs.
* Each text paragraph is enclosed within a `<p>` tag.
* One or multiple consecutive empty lines divide two paragraphs.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asParagraphs($value)
{
return str_replace('<p></p>', '',
'<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>'
);
}
/**
* Formats the value as HTML text.
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
* Use [[asRaw()]] if you do not want any purification of the value.
* @param mixed $value the value to be formatted
* @param array|null $config the configuration for the HTMLPurifier class.
* @return string the formatted result
*/
public function asHtml($value, $config = null)
{
return HtmlPurifier::process($value, $config);
}
/**
* Formats the value as a mailto link.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asEmail($value)
{
return Html::mailto($value);
}
/**
* Formats the value as an image tag.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asImage($value)
{
return Html::img($value);
}
/**
* Formats the value as a hyperlink.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asUrl($value)
{
$url = $value;
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$url = 'http://' . $url;
}
return Html::a(Html::encode($value), $url);
}
/**
* Formats the value as a boolean.
* @param mixed $value the value to be formatted
* @return string the formatted result
* @see booleanFormat
*/
public function asBoolean($value)
{
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
}
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->dateFormat : $format, $value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[timeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->timeFormat : $format, $value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[datetimeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->datetimeFormat : $format, $value);
}
/**
* Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
* @param mixed $value the datetime value to be normalized.
* @return mixed the normalized datetime value
*/
protected function normalizeDatetimeValue($value)
{
if (is_string($value)) {
if (ctype_digit($value) || $value[0] === '-' && ctype_digit(substr($value, 1))) {
return (int)$value;
} else {
return strtotime($value);
}
} elseif ($value instanceof DateTime) {
return $value->getTimestamp();
} else {
return (int)$value;
}
}
/**
* Formats the value as an integer.
* @param mixed $value the value to be formatted
* @return string the formatting result.
*/
public function asInteger($value)
{
if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
return $matches[1];
} else {
$value = (int)$value;
return "$value";
}
}
/**
* Formats the value as a double number.
* Property [[decimalSeparator]] will be used to represent the decimal point.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatting result.
* @see decimalSeparator
*/
public function asDouble($value, $decimals = 2)
{
if ($this->decimalSeparator === null) {
return sprintf("%.{$decimals}f", $value);
} else {
return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
}
}
/**
* Formats the value as a number with decimal and thousand separators.
* This method calls the PHP number_format() function to do the formatting.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatted result
* @see decimalSeparator
* @see thousandSeparator
*/
public function asNumber($value, $decimals = 0)
{
$ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
$ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
return number_format($value, $decimals, $ds, $ts);
}
}

26
framework/yii/base/InvalidRequestException.php

@ -1,26 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* InvalidRequestException represents an exception caused by incorrect end user request.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InvalidRequestException extends UserException
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Request');
}
}

20
framework/yii/base/Module.php

@ -88,7 +88,11 @@ abstract class Module extends Component
*/
public $controllerMap = array();
/**
* @var string the namespace that controller classes are in. Default is to use global namespace.
* @var string the namespace that controller classes are in. If not set,
* it will use the "controllers" sub-namespace under the namespace of this module.
* For example, if the namespace of this module is "foo\bar", then the default
* controller namespace would be "foo\bar\controllers".
* If the module is an application, it will default to "app\controllers".
*/
public $controllerNamespace;
/**
@ -178,6 +182,16 @@ abstract class Module extends Component
public function init()
{
$this->preloadComponents();
if ($this->controllerNamespace === null) {
if ($this instanceof Application) {
$this->controllerNamespace = 'app\\controllers';
} else {
$class = get_class($this);
if (($pos = strrpos($class, '\\')) !== false) {
$this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
}
}
}
}
/**
@ -409,11 +423,11 @@ abstract class Module extends Component
* ~~~
* array(
* 'comment' => array(
* 'class' => 'app\modules\CommentModule',
* 'class' => 'app\modules\comment\CommentModule',
* 'db' => 'db',
* ),
* 'booking' => array(
* 'class' => 'app\modules\BookingModule',
* 'class' => 'app\modules\booking\BookingModule',
* ),
* )
* ~~~

10
framework/yii/base/View.php

@ -130,8 +130,8 @@ class View extends Component
*/
public $dynamicPlaceholders = array();
/**
* @var array the registered asset bundles. The keys are the bundle names, and the values
* are the corresponding [[AssetBundle]] objects.
* @var array list of the registered asset bundles. The keys are the bundle names, and the values
* are booleans indicating whether the bundles have been registered.
* @see registerAssetBundle
*/
public $assetBundles;
@ -235,10 +235,10 @@ class View extends Component
public function renderFile($viewFile, $params = array(), $context = null)
{
$viewFile = Yii::getAlias($viewFile);
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}
if (is_file($viewFile)) {
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}
$viewFile = FileHelper::localize($viewFile);
} else {
throw new InvalidParamException("The view file does not exist: $viewFile");

63
framework/yii/bootstrap/Button.php

@ -0,0 +1,63 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
/**
* Button renders a bootstrap button.
*
* For example,
*
* ```php
* echo Button::widget(array(
* 'label' => 'Action',
* 'options' => array('class' => 'btn-large'),
* ));
* ```
* @see http://twitter.github.io/bootstrap/javascript.html#buttons
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Button extends Widget
{
/**
* @var string the tag to use to render the button
*/
public $tagName = 'button';
/**
* @var string the button label
*/
public $label = 'Button';
/**
* @var boolean whether the label should be HTML-encoded.
*/
public $encodeLabel = true;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
$this->addCssClass($this->options, 'btn');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options);
$this->registerPlugin('button');
}
}

128
framework/yii/bootstrap/ButtonDropdown.php

@ -0,0 +1,128 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\Html;
/**
* ButtonDropdown renders a group or split button dropdown bootstrap component.
*
* For example,
*
* ```php
* // a button group using Dropdown widget
* echo ButtonDropdown::widget(array(
* 'label' => 'Action',
* 'dropdown' => array(
* 'items' => array(
* array(
* 'label' => 'DropdownA',
* 'url' => '/',
* ),
* array(
* 'label' => 'DropdownB',
* 'url' => '#',
* ),
* ),
* ),
* ));
* ```
* @see http://twitter.github.io/bootstrap/javascript.html#buttons
* @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class ButtonDropdown extends Widget
{
/**
* @var string the button label
*/
public $label = 'Button';
/**
* @var array the HTML attributes of the button.
*/
public $buttonOptions = array();
/**
* @var array the configuration array for [[Dropdown]].
*/
public $dropdown = array();
/**
* @var boolean whether to display a group of split-styled button group.
*/
public $split = false;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'btn-group');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::beginTag('div', $this->options) . "\n";
echo $this->renderButton() . "\n";
echo $this->renderDropdown() . "\n";
echo Html::endTag('div') . "\n";
$this->registerPlugin('button');
}
/**
* Generates the button dropdown.
* @return string the rendering result.
*/
protected function renderButton()
{
$this->addCssClass($this->buttonOptions, 'btn');
if ($this->split) {
$tag = 'button';
$options = $this->buttonOptions;
$this->buttonOptions['data-toggle'] = 'dropdown';
$this->addCssClass($this->buttonOptions, 'dropdown-toggle');
$splitButton = Button::widget(array(
'label' => '<span class="caret"></span>',
'encodeLabel' => false,
'options' => $this->buttonOptions,
));
} else {
$tag = 'a';
$this->label .= ' <span class="caret"></span>';
$options = $this->buttonOptions;
if (!isset($options['href'])) {
$options['href'] = '#';
}
$this->addCssClass($options, 'dropdown-toggle');
$options['data-toggle'] = 'dropdown';
$splitButton = '';
}
return Button::widget(array(
'tagName' => $tag,
'label' => $this->label,
'options' => $options,
'encodeLabel' => false,
)) . "\n" . $splitButton;
}
/**
* Generates the dropdown menu.
* @return string the rendering result.
*/
protected function renderDropdown()
{
$config = $this->dropdown;
$config['clientOptions'] = false;
return Dropdown::widget($config);
}
}

99
framework/yii/bootstrap/ButtonGroup.php

@ -0,0 +1,99 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\helpers\base\ArrayHelper;
use yii\helpers\Html;
/**
* ButtonGroup renders a button group bootstrap component.
*
* For example,
*
* ```php
* // a button group with items configuration
* echo ButtonGroup::::widget(array(
* 'items' => array(
* array('label' => 'A'),
* array('label' => 'B'),
* )
* ));
*
* // button group with an item as a string
* echo ButtonGroup::::widget(array(
* 'items' => array(
* Button::widget(array('label' => 'A')),
* array('label' => 'B'),
* )
* ));
* ```
* @see http://twitter.github.io/bootstrap/javascript.html#buttons
* @see http://twitter.github.io/bootstrap/components.html#buttonGroups
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class ButtonGroup extends Widget
{
/**
* @var array list of buttons. Each array element represents a single button
* which can be specified as a string or an array of the following structure:
*
* - label: string, required, the button label.
* - options: array, optional, the HTML attributes of the button.
*/
public $buttons = array();
/**
* @var boolean whether to HTML-encode the button labels.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
$this->addCssClass($this->options, 'btn-group');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::tag('div', $this->renderButtons(), $this->options);
$this->registerPlugin('button');
}
/**
* Generates the buttons that compound the group as specified on [[items]].
* @return string the rendering result.
*/
protected function renderButtons()
{
$buttons = array();
foreach ($this->buttons as $button) {
if (is_array($button)) {
$label = ArrayHelper::getValue($button, 'label');
$options = ArrayHelper::getValue($button, 'options');
$buttons[] = Button::widget(array(
'label' => $label,
'options' => $options,
'encodeLabel' => $this->encodeLabels
));
} else {
$buttons[] = $button;
}
}
return implode("\n", $buttons);
}
}

2
framework/yii/bootstrap/Carousel.php

@ -8,7 +8,7 @@
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\base\ArrayHelper;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**

2
framework/yii/bootstrap/Collapse.php

@ -8,7 +8,7 @@
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\base\ArrayHelper;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**

95
framework/yii/bootstrap/Dropdown.php

@ -0,0 +1,95 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Dropdown renders a Bootstrap dropdown menu component.
*
* @see http://twitter.github.io/bootstrap/javascript.html#dropdowns
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Dropdown extends Widget
{
/**
* @var array list of menu items in the dropdown. Each array element represents a single
* menu with the following structure:
* - label: string, required, the label of the item link
* - url: string, optional, the url of the item link. Defaults to "#".
* - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the dropdown items configuration array. if `items` is set, then `url` of the parent
* item will be ignored and automatically set to "#"
*
* @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727
*/
public $items = array();
/**
* @var boolean whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'dropdown-menu');
}
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderItems($this->items);
$this->registerPlugin('dropdown');
}
/**
* Renders menu items.
* @param array $items the menu items to be rendered
* @return string the rendering result.
* @throws InvalidConfigException if the label option is not specified in one of the items.
*/
protected function renderItems($items)
{
$lines = array();
foreach ($items as $item) {
if (is_string($item)) {
$lines[] = $item;
continue;
}
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', array());
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
$linkOptions['tabindex'] = '-1';
if (isset($item['items'])) {
$this->addCssClass($options, 'dropdown-submenu');
$content = Html::a($label, '#', $linkOptions) . $this->renderItems($item['items']);
} else {
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
}
$lines[] = Html::tag('li', $content, $options);
}
return Html::tag('ul', implode("\n", $lines), $this->options);
}
}

140
framework/yii/bootstrap/Nav.php

@ -0,0 +1,140 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Nav renders a nav HTML component.
*
* For example:
*
* ```php
* echo Nav::widget(array(
* 'items' => array(
* array(
* 'label' => 'Home',
* 'url' => '/',
* 'linkOptions' => array(...),
* 'active' => true,
* ),
* array(
* 'label' => 'Dropdown',
* 'dropdown' => array(
* array(
* 'label' => 'DropdownA',
* 'url' => '#',
* ),
* array(
* 'label' => 'DropdownB',
* 'url' => '#',
* ),
* ),
* ),
* ),
* ));
* ```
*
* @see http://twitter.github.io/bootstrap/components.html#nav
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Nav extends Widget
{
/**
* @var array list of items in the nav widget. Each array element represents a single
* menu item with the following structure:
*
* - label: string, required, the nav item label.
* - url: optional, the item's URL. Defaults to "#".
* - linkOptions: array, optional, the HTML attributes of the item's link.
* - options: array, optional, the HTML attributes of the item container (LI).
* - active: boolean, optional, whether the item should be on active state or not.
* - dropdown: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
* or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
*/
public $items = array();
/**
* @var boolean whether the nav items labels should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'nav');
}
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderItems();
$this->getView()->registerAssetBundle('yii/bootstrap');
}
/**
* Renders widget items.
*/
public function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$items[] = $this->renderItem($item);
}
return Html::tag('ul', implode("\n", $items), $this->options);
}
/**
* Renders a widget's item.
* @param mixed $item the item to render.
* @return string the rendering result.
* @throws InvalidConfigException
*/
public function renderItem($item)
{
if (is_string($item)) {
return $item;
}
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', array());
$dropdown = ArrayHelper::getValue($item, 'dropdown');
$url = Html::url(ArrayHelper::getValue($item, 'url', '#'));
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
if(ArrayHelper::getValue($item, 'active')) {
$this->addCssClass($options, 'active');
}
if ($dropdown !== null) {
$linkOptions['data-toggle'] = 'dropdown';
$this->addCssClass($options, 'dropdown');
$this->addCssClass($urlOptions, 'dropdown-toggle');
$label .= ' ' . Html::tag('b', '', array('class' => 'caret'));
if (is_array($dropdown)) {
$dropdown = Dropdown::widget(array(
'items' => $dropdown,
'clientOptions' => false,
));
}
}
return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options);
}
}

188
framework/yii/bootstrap/NavBar.php

@ -0,0 +1,188 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* NavBar renders a navbar HTML component.
*
* For example:
*
* ```php
* echo NavBar::widget(array(
* 'brandLabel' => 'NavBar Test',
* 'items' => array(
* // a Nav widget
* array(
* // defaults to Nav anyway.
* 'class' => 'yii\bootstrap\Nav',
* // widget configuration
* 'options' => array(
* 'items' => array(
* array(
* 'label' => 'Home',
* 'url' => '/',
* 'options' => array('class' => 'active'),
* ),
* array(
* 'label' => 'Dropdown',
* 'content' => new Dropdown(array(
* 'items' => array(
* array(
* 'label' => 'DropdownA',
* 'url' => '#',
* ),
* array(
* 'label' => 'DropdownB',
* 'url' => '#'
* ),
* )
* ),
* ),
* )
* ),
* ),
* // you can also use strings
* '<form class="navbar-search pull-left" action="">' .
* '<input type="text" class="search-query" placeholder="Search">' .
* '</form>',
* ),
* ));
* ```
*
* @see http://twitter.github.io/bootstrap/components.html#navbar
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class NavBar extends Widget
{
/**
* @var string the text of the brand.
* @see http://twitter.github.io/bootstrap/components.html#navbar
*/
public $brandLabel;
/**
* @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]]
* and will be used for the "href" attribute of the brand link. Defaults to site root.
*/
public $brandUrl = '/';
/**
* @var array the HTML attributes of the brand link.
*/
public $brandOptions = array();
/**
* @var array list of menu items in the navbar widget. Each array element represents a single
* menu item with the following structure:
*
* ```php
* array(
* // optional, the menu item class type of the widget to render. Defaults to "Nav" widget.
* 'class' => 'Menu item class type',
* // required, the configuration options of the widget.
* 'options'=> array(...),
* ),
* // optionally, you can pass a string
* '<form class="navbar-search pull-left" action="">' .
* '<input type="text" class="search-query span2" placeholder="Search">' .
* '</form>',
* ```
*
* Optionally, you can also use a plain string instead of an array element.
*/
public $items = array();
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
$this->addCssClass($this->options, 'navbar');
$this->addCssClass($this->brandOptions, 'brand');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::beginTag('div', $this->options);
echo $this->renderItems();
echo Html::endTag('div');
$this->getView()->registerAssetBundle(self::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap');
}
/**
* Renders the items.
* @return string the rendering items.
*/
protected function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$items[] = $this->renderItem($item);
}
$contents = implode("\n", $items);
$brand = Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions);
if (self::$responsive) {
$this->getView()->registerAssetBundle('yii/bootstrap/collapse');
$contents = Html::tag('div',
$this->renderToggleButton() .
$brand . "\n" .
Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')),
array('class' => 'container'));
} else {
$contents = $brand . "\n" . $contents;
}
return Html::tag('div', $contents, array('class' => 'navbar-inner'));
}
/**
* Renders a item. The item can be a string, a custom class or a Nav widget (defaults if no class specified.
* @param mixed $item the item to render. If array, it is assumed the configuration of a widget being `class`
* required and if not specified, then defaults to `yii\bootstrap\Nav`.
* @return string the rendering result.
* @throws InvalidConfigException
*/
protected function renderItem($item)
{
if (is_string($item)) {
return $item;
}
$config = ArrayHelper::getValue($item, 'options', array());
$config['clientOptions'] = false;
$class = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav');
return $class::widget($config);
}
/**
* Renders collapsible toggle button.
* @return string the rendering toggle button.
*/
protected function renderToggleButton()
{
$items = array();
for ($i = 0; $i < 3; $i++) {
$items[] = Html::tag('span', '', array('class' => 'icon-bar'));
}
return Html::a(implode("\n", $items), null, array(
'class' => 'btn btn-navbar',
'data-toggle' => 'collapse',
'data-target' => 'div.navbar-collapse',
));
}
}

147
framework/yii/bootstrap/Progress.php

@ -0,0 +1,147 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Progress renders a bootstrap progress bar component.
*
* For example,
*
* ```php
* // default with label
* echo Progress::widget(array(
* 'percent' => 60,
* 'label' => 'test',
* ));
*
* // styled
* echo Progress::widget(array(
* 'percent' => 65,
* 'barOptions' => array('class' => 'bar-danger')
* ));
*
* // striped
* echo Progress::widget(array(
* 'percent' => 70,
* 'barOptions' => array('class' => 'bar-warning'),
* 'options' => array('class' => 'progress-striped')
* ));
*
* // striped animated
* echo Progress::widget(array(
* 'percent' => 70,
* 'barOptions' => array('class' => 'bar-success'),
* 'options' => array('class' => 'active progress-striped')
* ));
*
* // stacked bars
* echo Progress::widget(array(
* 'bars' => array(
* array('percent' => 30, 'options' => array('class' => 'bar-danger')),
* array('percent' => 30, 'label'=>'test', 'options' => array('class' => 'bar-success')),
* array('percent' => 35, 'options' => array('class' => 'bar-warning'))
* )
* ));
* ```
* @see http://twitter.github.io/bootstrap/components.html#progress
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Progress extends Widget
{
/**
* @var string the button label
*/
public $label;
/**
* @var integer the amount of progress as a percentage.
*/
public $percent = 0;
/**
* @var array the HTML attributes of the
*/
public $barOptions = array();
/**
* @var array a set of bars that are stacked together to form a single progress bar.
* Each bar is an array of the following structure:
*
* ```php
* array(
* // required, the amount of progress as a percentage.
* 'percent' => 30,
* // optional, the label to be displayed on the bar
* 'label' => '30%',
* // optional, array, additional HTML attributes for the bar tag
* 'options' => array(),
* )
*/
public $bars;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'progress');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::beginTag('div', $this->options) . "\n";
echo $this->renderProgress() . "\n";
echo Html::endTag('div') . "\n";
$this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap');
}
/**
* Renders the progress.
* @return string the rendering result.
* @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
*/
protected function renderProgress()
{
if (empty($this->bars)) {
return $this->renderBar($this->percent, $this->label, $this->barOptions);
}
$bars = array();
foreach ($this->bars as $bar) {
$label = ArrayHelper::getValue($bar, 'label', '');
if (!isset($bar['percent'])) {
throw new InvalidConfigException("The 'percent' option is required.");
}
$options = ArrayHelper::getValue($bar, 'options', array());
$bars[] = $this->renderBar($bar['percent'], $label, $options);
}
return implode("\n", $bars);
}
/**
* Generates a bar
* @param int $percent the percentage of the bar
* @param string $label, optional, the label to display at the bar
* @param array $options the HTML attributes of the bar
* @return string the rendering result.
*/
protected function renderBar($percent, $label = '', $options = array())
{
$this->addCssClass($options, 'bar');
$options['style'] = "width:{$percent}%";
return Html::tag('div', $label, $options);
}
}

195
framework/yii/bootstrap/Tabs.php

@ -0,0 +1,195 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Tabs renders a Tab bootstrap javascript component.
*
* For example:
*
* ```php
* echo Tabs::widget(array(
* 'items' => array(
* array(
* 'label' => 'One',
* 'content' => 'Anim pariatur cliche...',
* 'active' => true
* ),
* array(
* 'label' => 'Two',
* 'content' => 'Anim pariatur cliche...',
* 'headerOptions' => array(...),
* 'options' => array('id'=>'myveryownID'),
* ),
* array(
* 'label' => 'Dropdown',
* 'items' => array(
* array(
* 'label' => 'DropdownA',
* 'content' => 'DropdownA, Anim pariatur cliche...',
* ),
* array(
* 'label' => 'DropdownB',
* 'content' => 'DropdownB, Anim pariatur cliche...',
* ),
* ),
* ),
* ),
* ));
* ```
*
* @see http://twitter.github.io/bootstrap/javascript.html#tabs
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Tabs extends Widget
{
/**
* @var array list of tabs in the tabs widget. Each array element represents a single
* tab with the following structure:
*
* - label: string, required, the tab header label.
* - headerOptions: array, optional, the HTML attributes of the tab header.
* - content: array, required if `items` is not set. The content (HTML) of the tab pane.
* - options: array, optional, the HTML attributes of the tab pane container.
* - active: boolean, optional, whether the item tab header and pane should be visible or not.
* - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items
* configuration array. Each item can hold two extra keys, besides the above ones:
* * active: boolean, optional, whether the item tab header and pane should be visible or not.
* * content: string, required if `items` is not set. The content (HTML) of the tab pane.
* * contentOptions: optional, array, the HTML attributes of the tab content container.
*/
public $items = array();
/**
* @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "div", the tag name of the item container tags.
*/
public $itemOptions = array();
/**
* @var array list of HTML attributes for the header container tags. This will be overwritten
* by the "headerOptions" set in individual [[items]].
*/
public $headerOptions = array();
/**
* @var boolean whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'nav nav-tabs');
}
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderItems();
$this->registerPlugin('tab');
}
/**
* Renders tab items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
protected function renderItems()
{
$headers = array();
$panes = array();
foreach ($this->items as $n => $item) {
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array()));
if (isset($item['items'])) {
$label .= ' <b class="caret"></b>';
$this->addCssClass($headerOptions, 'dropdown');
if ($this->renderDropdown($item['items'], $panes)) {
$this->addCssClass($headerOptions, 'active');
}
$header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"
. Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false));
} elseif (isset($item['content'])) {
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array()));
$options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
$this->addCssClass($options, 'tab-pane');
if (ArrayHelper::remove($item, 'active')) {
$this->addCssClass($options, 'active');
$this->addCssClass($headerOptions, 'active');
}
$header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab', 'tabindex' => '-1'));
$panes[] = Html::tag('div', $item['content'], $options);
} else {
throw new InvalidConfigException("Either the 'content' or 'items' option must be set.");
}
$headers[] = Html::tag('li', $header, $headerOptions);
}
return Html::tag('ul', implode("\n", $headers), $this->options) . "\n"
. Html::tag('div', implode("\n", $panes), array('class' => 'tab-content'));
}
/**
* Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
* configure `panes` accordingly.
* @param array $items the dropdown items configuration.
* @param array $panes the panes reference array.
* @return boolean whether any of the dropdown items is `active` or not.
* @throws InvalidConfigException
*/
protected function renderDropdown(&$items, &$panes)
{
$itemActive = false;
foreach ($items as $n => &$item) {
if (is_string($item)) {
continue;
}
if (!isset($item['content'])) {
throw new InvalidConfigException("The 'content' option is required.");
}
$content = ArrayHelper::remove($item, 'content');
$options = ArrayHelper::remove($item, 'contentOptions', array());
$this->addCssClass($options, 'tab-pane');
if (ArrayHelper::remove($item, 'active')) {
$this->addCssClass($options, 'active');
$this->addCssClass($item['options'], 'active');
$itemActive = true;
}
$options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n);
$item['url'] = '#' . $options['id'];
$item['linkOptions']['data-toggle'] = 'tab';
$panes[] = Html::tag('div', $content, $options);
unset($item);
}
return $itemActive;
}
}

3
framework/yii/caching/FileCache.php

@ -25,8 +25,9 @@ class FileCache extends Cache
{
/**
* @var string the directory to store cache files. You may use path alias here.
* If not set, it will use the "cache" subdirectory under the application runtime path.
*/
public $cachePath = '@app/runtime/cache';
public $cachePath = '@runtime/cache';
/**
* @var string cache file suffix. Defaults to '.bin'.
*/

69
framework/yii/console/controllers/AssetController.php

@ -14,6 +14,20 @@ use yii\console\Controller;
/**
* This command allows you to combine and compress your JavaScript and CSS files.
*
* Usage:
* 1. Create a configuration file using 'template' action:
* yii asset/template /path/to/myapp/config.php
* 2. Edit the created config file, adjusting it for your web application needs.
* 3. Run the 'compress' action, using created config:
* yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php
* 4. Adjust your web application config to use compressed assets.
*
* Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist,
* so corresponding paths inside the configuration should be specified directly.
*
* Note: by default this command relies on an external tools to perform actual files compression,
* check [[jsCompressor]] and [[cssCompressor]] for more details.
*
* @property array|\yii\web\AssetManager $assetManager asset manager, which will be used for assets processing.
*
* @author Qiang Xue <qiang.xue@gmail.com>
@ -43,7 +57,7 @@ class AssetController extends Controller
* ~~~
* 'all' => array(
* 'css' => 'all.css',
* 'js' => 'js.css',
* 'js' => 'all.js',
* 'depends' => array( ... ),
* )
* ~~~
@ -57,7 +71,7 @@ class AssetController extends Controller
*/
private $_assetManager = array();
/**
* @var string|callback Java Script file compressor.
* @var string|callback JavaScript file compressor.
* If a string, it is treated as shell command template, which should contain
* placeholders {from} - source file name - and {to} - output file name.
* Otherwise, it is treated as PHP callback, which should perform the compression.
@ -159,7 +173,7 @@ class AssetController extends Controller
}
}
$this->getAssetManager(); // check asset manager configuration
$this->getAssetManager(); // check if asset manager configuration is correct
}
/**
@ -308,7 +322,7 @@ class AssetController extends Controller
/**
* Builds output asset bundle.
* @param \yii\web\AssetBundle $target output asset bundle
* @param string $type either "js" or "css".
* @param string $type either 'js' or 'css'.
* @param \yii\web\AssetBundle[] $bundles source asset bundles.
* @param integer $timestamp current timestamp.
* @throws Exception on failure.
@ -420,24 +434,23 @@ class AssetController extends Controller
}
$array = var_export($array, true);
$version = date('Y-m-d H:i:s', time());
$bytesWritten = file_put_contents($bundleFile, <<<EOD
$bundleFileContent = <<<EOD
<?php
/**
* This file is generated by the "yii script" command.
* This file is generated by the "yii {$this->id}" command.
* DO NOT MODIFY THIS FILE DIRECTLY.
* @version $version
* @version {$version}
*/
return $array;
EOD
);
if ($bytesWritten <= 0) {
return {$array};
EOD;
if (!file_put_contents($bundleFile, $bundleFileContent)) {
throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
}
echo "Output bundle configuration created at '{$bundleFile}'.\n";
}
/**
* Compresses given Java Script files and combines them into the single one.
* Compresses given JavaScript files and combines them into the single one.
* @param array $inputFiles list of source file names.
* @param string $outputFile output file name.
* @throws \yii\console\Exception on failure
@ -495,9 +508,10 @@ EOD
}
/**
* Combines Java Script files into a single one.
* Combines JavaScript files into a single one.
* @param array $inputFiles source file names.
* @param string $outputFile output file name.
* @throws \yii\console\Exception on failure.
*/
public function combineJsFiles($inputFiles, $outputFile)
{
@ -507,13 +521,16 @@ EOD
. file_get_contents($file)
. "/*** END FILE: $file ***/\n";
}
file_put_contents($outputFile, $content);
if (!file_put_contents($outputFile, $content)) {
throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
}
}
/**
* Combines CSS files into a single one.
* @param array $inputFiles source file names.
* @param string $outputFile output file name.
* @throws \yii\console\Exception on failure.
*/
public function combineCssFiles($inputFiles, $outputFile)
{
@ -523,7 +540,9 @@ EOD
. $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
. "/*** END FILE: $file ***/\n";
}
file_put_contents($outputFile, $content);
if (!file_put_contents($outputFile, $content)) {
throw new Exception("Unable to write output CSS file '{$outputFile}'.");
}
}
/**
@ -590,18 +609,23 @@ EOD
/**
* Creates template of configuration file for [[actionCompress]].
* @param string $configFile output file name.
* @throws \yii\console\Exception on failure.
*/
public function actionTemplate($configFile)
{
$template = <<<EOD
<?php
/**
* Configuration file for the "yii asset" console command.
* Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist,
* so corresponding paths should be specified directly.
*/
return array(
//
// The list of asset bundles to compress:
'bundles' => require('path/to/bundles.php'),
//
// The list of extensions to compress:
'extensions' => require('path/to/namespaces.php'),
//
// Asset bundle for compression output:
'targets' => array(
'all' => array(
'basePath' => __DIR__,
@ -610,7 +634,7 @@ return array(
'css' => 'all-{ts}.css',
),
),
// Asset manager configuration:
'assetManager' => array(
'basePath' => __DIR__,
'baseUrl' => '/test',
@ -622,9 +646,8 @@ EOD;
return;
}
}
$bytesWritten = file_put_contents($configFile, $template);
if ($bytesWritten<=0) {
echo "Error: unable to write file '{$configFile}'!\n\n";
if (!file_put_contents($configFile, $template)) {
throw new Exception("Unable to write template file '{$configFile}'.");
} else {
echo "Configuration file template created at '{$configFile}'.\n\n";
}

8
framework/yii/db/ActiveRecord.php

@ -275,10 +275,16 @@ class ActiveRecord extends Model
/**
* Returns the schema information of the DB table associated with this AR class.
* @return TableSchema the schema information of the DB table associated with this AR class.
* @throws InvalidConfigException if the table for the AR class does not exist.
*/
public static function getTableSchema()
{
return static::getDb()->getTableSchema(static::tableName());
$schema = static::getDb()->getTableSchema(static::tableName());
if ($schema !== null) {
return $schema;
} else {
throw new InvalidConfigException("The table does not exist: " . static::tableName());
}
}
/**

9
framework/yii/db/Connection.php

@ -251,15 +251,6 @@ class Connection extends Component
*/
private $_schema;
/**
* Closes the connection when this component is being serialized.
* @return array
*/
public function __sleep()
{
$this->close();
return array_keys(get_object_vars($this));
}
/**
* Returns a value indicating whether the DB connection is established.

10
framework/yii/db/QueryBuilder.php

@ -464,6 +464,12 @@ class QueryBuilder extends \yii\base\Object
* the first part will be converted, and the rest of the parts will be appended to the converted result.
* For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
*
* For some of the abstract types you can also specify a length or precision constraint
* by prepending it in round brackets directly to the type.
* For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
* If the underlying DBMS does not support these kind of constraints for a type it will
* be ignored.
*
* If a type cannot be found in [[typeMap]], it will be returned without any change.
* @param string $type abstract column type
* @return string physical column type.
@ -472,6 +478,10 @@ class QueryBuilder extends \yii\base\Object
{
if (isset($this->typeMap[$type])) {
return $this->typeMap[$type];
} elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
if (isset($this->typeMap[$matches[1]])) {
return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
}
} elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
if (isset($this->typeMap[$matches[1]])) {
return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);

2
framework/yii/db/mssql/QueryBuilder.php

@ -28,7 +28,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'int(11)',
Schema::TYPE_BIGINT => 'bigint(20)',
Schema::TYPE_FLOAT => 'float',
Schema::TYPE_DECIMAL => 'decimal',
Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',

11
framework/yii/db/mysql/QueryBuilder.php

@ -29,7 +29,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'int(11)',
Schema::TYPE_BIGINT => 'bigint(20)',
Schema::TYPE_FLOAT => 'float',
Schema::TYPE_DECIMAL => 'decimal',
Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
@ -150,17 +150,20 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/
public function batchInsert($table, $columns, $rows)
{
foreach ($columns as $i => $name) {
$columns[$i] = $this->db->quoteColumnName($name);
}
$values = array();
foreach ($rows as $row) {
$vs = array();
foreach ($row as $value) {
$vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
}
$values[] = $vs;
$values[] = '(' . implode(', ', $vs) . ')';
}
return 'INSERT INTO ' . $this->db->quoteTableName($table)
. ' (' . implode(', ', $columns) . ') VALUES ('
. implode(', ', $values) . ')';
. ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
}
}

8
framework/yii/db/mysql/Schema.php

@ -178,6 +178,7 @@ class Schema extends \yii\db\Schema
* Collects the metadata of table columns.
* @param TableSchema $table the table metadata
* @return boolean whether the table exists in the database
* @throws \Exception if DB query fails
*/
protected function findColumns($table)
{
@ -185,7 +186,12 @@ class Schema extends \yii\db\Schema
try {
$columns = $this->db->createCommand($sql)->queryAll();
} catch (\Exception $e) {
return false;
$previous = $e->getPrevious();
if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
// table does not exist
return false;
}
throw $e;
}
foreach ($columns as $info) {
$column = $this->loadColumnSchema($info);

4
framework/yii/db/sqlite/QueryBuilder.php

@ -30,13 +30,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'integer',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'float',
Schema::TYPE_DECIMAL => 'decimal',
Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'blob',
Schema::TYPE_BOOLEAN => 'tinyint(1)',
Schema::TYPE_BOOLEAN => 'boolean',
Schema::TYPE_MONEY => 'decimal(19,4)',
);

4
framework/yii/helpers/base/Html.php

@ -344,7 +344,7 @@ class Html
/**
* Generates a hyperlink tag.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]]
* such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks.
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
@ -366,7 +366,7 @@ class Html
/**
* Generates a mailto hyperlink.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]]
* such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used.

21
framework/yii/helpers/base/SecurityHelper.php

@ -131,15 +131,30 @@ class SecurityHelper
$keys = is_file($keyFile) ? require($keyFile) : array();
}
if (!isset($keys[$name])) {
// generate a 32-char random key
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length);
$keys[$name] = static::generateRandomKey($length);
file_put_contents($keyFile, "<?php\nreturn " . var_export($keys, true) . ";\n");
}
return $keys[$name];
}
/**
* Generates a random key.
* @param integer $length the length of the key that should be generated
* @return string the generated random key
*/
public static function generateRandomKey($length = 32)
{
if (function_exists('openssl_random_pseudo_bytes')) {
$key = base64_encode(openssl_random_pseudo_bytes($length, $strong));
if ($strong) {
return substr($key, 0, $length);
}
}
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return substr(str_shuffle(str_repeat($chars, 5)), 0, $length);
}
/**
* Opens the mcrypt module.
* @return resource the mcrypt module handle.
* @throws InvalidConfigException if mcrypt extension is not installed

270
framework/yii/i18n/Formatter.php

@ -0,0 +1,270 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
use Yii;
use IntlDateFormatter;
use NumberFormatter;
use DateTime;
use yii\base\InvalidConfigException;
/**
* Formatter is the localized version of [[\yii\base\Formatter]].
*
* Formatter requires the PHP "intl" extension to be installed. Formatter supports localized
* formatting of date, time and numbers, based on the current [[locale]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends \yii\base\Formatter
{
/**
* @var string the locale ID that is used to localize the date and number formatting.
* If not set, [[\yii\base\Application::language]] will be used.
*/
public $locale;
/**
* @var string the default format string to be used to format a date.
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $dateFormat = 'short';
/**
* @var string the default format string to be used to format a time.
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $timeFormat = 'short';
/**
* @var string the default format string to be used to format a date and time.
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $datetimeFormat = 'short';
/**
* @var array the options to be set for the NumberFormatter objects. Please refer to
* [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
* for the possible options. This property is used by [[createNumberFormatter]] when
* creating a new number formatter to format decimals, currencies, etc.
*/
public $numberFormatOptions = array();
/**
* @var string the character displayed as the decimal point when formatting a number.
* If not set, the decimal separator corresponding to [[locale]] will be used.
*/
public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
* If not set, the thousand separator corresponding to [[locale]] will be used.
*/
public $thousandSeparator;
/**
* Initializes the component.
* This method will check if the "intl" PHP extension is installed and set the
* default value of [[locale]].
* @throws InvalidConfigException if the "intl" PHP extension is not installed.
*/
public function init()
{
if (!extension_loaded('intl')) {
throw new InvalidConfigException('The "intl" PHP extension is not install. It is required to format data values in localized formats.');
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
if ($this->decimalSeparator === null || $this->thousandSeparator === null) {
$formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL);
if ($this->decimalSeparator === null) {
$this->decimalSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
}
if ($this->thousandSeparator === null) {
$this->thousandSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
}
}
parent::init();
}
private $_dateFormats = array(
'short' => IntlDateFormatter::SHORT,
'medium' => IntlDateFormatter::MEDIUM,
'long' => IntlDateFormatter::LONG,
'full' => IntlDateFormatter::FULL,
);
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used.
*
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->dateFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used.
*
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->timeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used.
*
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->datetimeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a decimal number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asDecimal($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value);
}
/**
* Formats the value as a currency number.
* @param mixed $value the value to be formatted
* @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asCurrency($value, $currency = 'USD', $format = null)
{
return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency);
}
/**
* Formats the value as a percent number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asPercent($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value);
}
/**
* Formats the value as a scientific number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asScientific($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value);
}
/**
* Creates a number formatter based on the given type and format.
* @param integer $type the type of the number formatter
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
{
$formatter = new NumberFormatter($this->locale, $type);
if ($format !== null) {
$formatter->setPattern($format);
}
if (!empty($this->numberFormatOptions)) {
foreach ($this->numberFormatOptions as $name => $attribute) {
$formatter->setAttribute($name, $attribute);
}
}
return $formatter;
}
}

52
framework/yii/jui/Draggable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Draggable renders an draggable jQuery UI widget.
*
* For example:
*
* ```php
* Draggable::begin(array(
* 'clientOptions' => array(
* 'grid' => array(50, 20),
* ),
* ));
*
* echo 'Draggable contents here...';
*
* Draggable::end();
* ```
*
* @see http://api.jqueryui.com/draggable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Draggable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('draggable', false);
}
}

52
framework/yii/jui/Droppable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Droppable renders an droppable jQuery UI widget.
*
* For example:
*
* ```php
* Droppable::begin(array(
* 'clientOptions' => array(
* 'accept' => '.special',
* ),
* ));
*
* echo 'Droppable body here...';
*
* Droppable::end();
* ```
*
* @see http://api.jqueryui.com/droppable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Droppable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('droppable', false);
}
}

52
framework/yii/jui/Resizable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Resizable renders an resizable jQuery UI widget.
*
* For example:
*
* ```php
* Resizable::begin(array(
* 'clientOptions' => array(
* 'grid' => array(20, 10),
* ),
* ));
*
* echo 'Resizable contents here...';
*
* Resizable::end();
* ```
*
* @see http://api.jqueryui.com/resizable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Resizable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('resizable');
}
}

116
framework/yii/jui/Selectable.php

@ -0,0 +1,116 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Selectable renders a selectable jQuery UI widget.
*
* For example:
*
* ```php
* echo Selectable::widget(array(
* 'items' => array(
* 'Item 1',
* array(
* 'content' => 'Item2',
* ),
* array(
* 'content' => 'Item3',
* 'options' => array(
* 'tag' => 'li',
* ),
* ),
* ),
* 'options' => array(
* 'tag' => 'ul',
* ),
* 'itemOptions' => array(
* 'tag' => 'li',
* ),
* 'clientOptions' => array(
* 'tolerance' => 'fit',
* ),
* ));
* ```
*
* @see http://api.jqueryui.com/selectable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Selectable extends Widget
{
/**
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - tag: string, defaults to "ul", the tag name of the container tag of this widget
*/
public $options = array();
/**
* @var array list of selectable items. Each item can be a string representing the item content
* or an array of the following structure:
*
* ~~~
* array(
* 'content' => 'item content',
* // the HTML attributes of the item container tag. This will overwrite "itemOptions".
* 'options' => array(),
* )
* ~~~
*/
public $items = array();
/**
* @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "li", the tag name of the item container tags.
*/
public $itemOptions = array();
/**
* Renders the widget.
*/
public function run()
{
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
echo Html::beginTag($tag, $options) . "\n";
echo $this->renderItems() . "\n";
echo Html::endTag($tag) . "\n";
$this->registerWidget('selectable');
}
/**
* Renders selectable items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
public function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$options = $this->itemOptions;
$tag = ArrayHelper::remove($options, 'tag', 'li');
if (is_array($item)) {
if (!isset($item['content'])) {
throw new InvalidConfigException("The 'content' option is required.");
}
$options = array_merge($options, ArrayHelper::getValue($item, 'options', array()));
$tag = ArrayHelper::remove($options, 'tag', $tag);
$items[] = Html::tag($tag, $item['content'], $options);
} else {
$items[] = Html::tag($tag, $item, $options);
}
}
return implode("\n", $items);
}
}

66
framework/yii/jui/Spinner.php

@ -0,0 +1,66 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use Yii;
use yii\helpers\Html;
/**
* Spinner renders an spinner jQuery UI widget.
*
* For example:
*
* ```php
* echo Spinner::widget(array(
* 'model' => $model,
* 'attribute' => 'country',
* 'clientOptions' => array(
* 'step' => 2,
* ),
* ));
* ```
*
* The following example will use the name property instead:
*
* ```php
* echo Spinner::widget(array(
* 'name' => 'country',
* 'clientOptions' => array(
* 'step' => 2,
* ),
* ));
*```
*
* @see http://api.jqueryui.com/spinner/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Spinner extends InputWidget
{
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderWidget();
$this->registerWidget('spinner');
}
/**
* Renders the Spinner widget.
* @return string the rendering result.
*/
public function renderWidget()
{
if ($this->hasModel()) {
return Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
return Html::textInput($this->name, $this->value, $this->options);
}
}
}

149
framework/yii/jui/Tabs.php

@ -20,15 +20,42 @@ use yii\helpers\Html;
* echo Tabs::widget(array(
* 'items' => array(
* array(
* 'header' => 'One',
* 'label' => 'Tab one',
* 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...',
* ),
* array(
* 'header' => 'Two',
* 'headerOptions' => array(...),
* 'label' => 'Tab two',
* 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...',
* 'options' => array(...),
* 'options' => array(
* 'tag' => 'div',
* ),
* 'headerOptions' => array(
* 'class' => 'my-class',
* ),
* ),
* array(
* 'label' => 'Tab with custom id',
* 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...',
* 'options' => array(
* 'id' => 'my-tab',
* ),
* ),
* array(
* 'label' => 'Ajax tab',
* 'url' => array('ajax/content'),
* ),
* ),
* 'options' => array(
* 'tag' => 'div',
* ),
* 'itemOptions' => array(
* 'tag' => 'div',
* ),
* 'headerOptions' => array(
* 'class' => 'my-class',
* ),
* 'clientOptions' => array(
* 'collapsible' => false,
* ),
* ));
* ```
@ -40,23 +67,44 @@ use yii\helpers\Html;
class Tabs extends Widget
{
/**
* @var array list of tabs in the tabs widget. Each array element represents a single
* tab with the following structure:
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* ```php
* array(
* // required, the header (HTML) of the tab
* 'header' => 'Tab label',
* // required, the content (HTML) of the tab
* 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...',
* // optional the HTML attributes of the tab content container
* 'options'=> array(...),
* // optional the HTML attributes of the tab header container
* 'headerOptions'=> array(...),
* )
* ```
* - tag: string, defaults to "div", the tag name of the container tag of this widget
*/
public $options = array();
/**
* @var array list of tab items. Each item can be an array of the following structure:
*
* - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label
* will be HTML-encoded.
* - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified.
* - url: mixed, mixed, optional, the url to load tab contents via AJAX. It is required if no content is specified.
* - template: string, optional, the header link template to render the header link. If none specified
* [[linkTemplate]] will be used instead.
* - options: array, optional, the HTML attributes of the header.
* - headerOptions: array, optional, the HTML attributes for the header container tag.
*/
public $items = array();
/**
* @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "div", the tag name of the item container tags.
*/
public $itemOptions = array();
/**
* @var array list of HTML attributes for the header container tags. This will be overwritten
* by the "headerOptions" set in individual [[items]].
*/
public $headerOptions = array();
/**
* @var string the default header template to render the link.
*/
public $linkTemplate = '<a href="{url}">{label}</a>';
/**
* @var boolean whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
@ -64,53 +112,48 @@ class Tabs extends Widget
*/
public function run()
{
echo Html::beginTag('div', $this->options) . "\n";
echo $this->renderHeaders() . "\n";
echo $this->renderContents() . "\n";
echo Html::endTag('div') . "\n";
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'div');
echo Html::beginTag($tag, $options) . "\n";
echo $this->renderItems() . "\n";
echo Html::endTag($tag) . "\n";
$this->registerWidget('tabs');
}
/**
* Renders tabs headers as specified on [[items]].
* Renders tab items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
protected function renderHeaders()
protected function renderItems()
{
$headers = array();
$items = array();
foreach ($this->items as $n => $item) {
if (!isset($item['header'])) {
throw new InvalidConfigException("The 'header' option is required.");
}
$options = ArrayHelper::getValue($item, 'options', array());
$id = isset($options['id']) ? $options['id'] : $this->options['id'] . '-tab' . $n;
$headerOptions = ArrayHelper::getValue($item, 'headerOptions', array());
$headers[] = Html::tag('li', Html::a($item['header'], "#$id"), $headerOptions);
}
return Html::tag('ul', implode("\n", $headers));
}
/**
* Renders tabs contents as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
protected function renderContents()
{
$contents = array();
foreach ($this->items as $n => $item) {
if (!isset($item['content'])) {
throw new InvalidConfigException("The 'content' option is required.");
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$options = ArrayHelper::getValue($item, 'options', array());
if (!isset($options['id'])) {
$options['id'] = $this->options['id'] . '-tab' . $n;
if (isset($item['url'])) {
$url = Html::url($item['url']);
} else {
if (!isset($item['content'])) {
throw new InvalidConfigException("The 'content' or 'url' option is required.");
}
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array()));
$tag = ArrayHelper::remove($options, 'tag', 'div');
if (!isset($options['id'])) {
$options['id'] = $this->options['id'] . '-tab' . $n;
}
$url = '#' . $options['id'];
$items[] = Html::tag($tag, $item['content'], $options);
}
$contents[] = Html::tag('div', $item['content'], $options);
$headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array()));
$template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
$headers[] = Html::tag('li', strtr($template, array(
'{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'],
'{url}' => $url,
)), $headerOptions);
}
return implode("\n", $contents);
return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items);
}
}

2
framework/yii/requirements/requirements.php

@ -43,6 +43,6 @@ return array(
'mandatory' => false,
'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator.'
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.'
),
);

2
framework/yii/validators/DateValidator.php

@ -58,7 +58,7 @@ class DateValidator extends Validator
$date = DateTime::createFromFormat($this->format, $value);
if ($date === false) {
$this->addError($object, $attribute, $this->message);
} elseif ($this->timestampAttribute !== false) {
} elseif ($this->timestampAttribute !== null) {
$object->{$this->timestampAttribute} = $date->getTimestamp();
}
}

27
framework/yii/web/Application.php

@ -23,6 +23,26 @@ class Application extends \yii\base\Application
* @var string the default route of this application. Defaults to 'site'.
*/
public $defaultRoute = 'site';
/**
* @var array the configuration specifying a controller action which should handle
* all user requests. This is mainly used when the application is in maintenance mode
* and needs to handle all incoming requests via a single action.
* The configuration is an array whose first element specifies the route of the action.
* The rest of the array elements (key-value pairs) specify the parameters to be bound
* to the action. For example,
*
* ~~~
* array(
* 'offline/notice',
* 'param1' => 'value1',
* 'param2' => 'value2',
* )
* ~~~
*
* Defaults to null, meaning catch-all is not effective.
*/
public $catchAll;
/**
* Processes the request.
@ -34,7 +54,12 @@ class Application extends \yii\base\Application
$request = $this->getRequest();
Yii::setAlias('@wwwroot', dirname($request->getScriptFile()));
Yii::setAlias('@www', $request->getBaseUrl());
list ($route, $params) = $request->resolve();
if (empty($this->catchAll)) {
list ($route, $params) = $request->resolve();
} else {
$route = $this->catchAll[0];
$params = array_splice($this->catchAll, 1);
}
try {
return $this->runAction($route, $params);
} catch (InvalidRouteException $e) {

44
framework/yii/web/Controller.php

@ -8,6 +8,8 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\base\InlineAction;
/**
* Controller is the base class of Web controllers.
@ -19,6 +21,48 @@ use Yii;
class Controller extends \yii\base\Controller
{
/**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param \yii\base\Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with.
* @throws HttpException if there are missing parameters.
*/
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
}
/**
* Creates a URL using the given route and parameters.
*
* This method enhances [[UrlManager::createUrl()]] by supporting relative routes.

4
framework/yii/web/Request.php

@ -533,8 +533,8 @@ class Request extends \yii\base\Request
*/
public function getIsSecureConnection()
{
return isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off')
|| isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']==='https';
return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)
|| isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https';
}
/**

32
framework/yii/web/UrlManager.php

@ -43,6 +43,31 @@ class UrlManager extends Component
* array, one can use the key to represent the pattern and the value the corresponding route.
* For example, `'post/<id:\d+>' => 'post/view'`.
*
* For RESTful routing the mentioned shortcut format also allows you to specify the
* [[UrlRule::verb|HTTP verb]] that the rule should apply for.
* You can do that by prepending it to the pattern, separated by space.
* For example, `'PUT post/<id:\d+>' => 'post/update'`.
* You may specify multiple verbs by separating them with comma
* like this: `'POST,PUT post/index' => 'post/create'`.
* The supported verbs in the shortcut format are: GET, HEAD, POST, PUT and DELETE.
* Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
* so you normally would not specify a verb for normal GET request.
*
* Here is an example configuration for RESTful CRUD controller:
*
* ~~~php
* array(
* 'dashboard' => 'site/index',
*
* 'POST <controller:\w+>s' => '<controller>/create',
* '<controller:\w+>s' => '<controller>/index',
*
* 'PUT <controller:\w+>/<id:\d+>' => '<controller>/update',
* 'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',
* '<controller:\w+>/<id:\d+>' => '<controller>/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.
*/
@ -115,9 +140,14 @@ class UrlManager extends Component
foreach ($this->rules as $key => $rule) {
if (!is_array($rule)) {
$rule = array(
'pattern' => $key,
'route' => $rule,
);
if (preg_match('/^((?:(GET|HEAD|POST|PUT|DELETE),)*(GET|HEAD|POST|PUT|DELETE))\s+(.*)$/', $key, $matches)) {
$rule['verb'] = explode(',', $matches[1]);
$rule['mode'] = UrlRule::PARSING_ONLY;
$key = $matches[4];
}
$rule['pattern'] = $key;
}
$rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule));
}

90
framework/yii/web/VerbFilter.php

@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\ActionEvent;
use yii\base\Behavior;
use yii\base\HttpException;
/**
* VerbFilter is an action filter that filters by HTTP request methods.
*
* It allows to define allowed HTTP request methods for each action and will throw
* an HTTP 405 error when the method is not allowed.
*
* To use VerbFilter, declare it in the `behaviors()` method of your controller class.
* For example, the following declarations will define a typical set of allowed
* request methods for REST CRUD actions.
*
* ~~~
* public function behaviors()
* {
* return array(
* 'verbs' => array(
* 'class' => \yii\web\VerbFilter::className(),
* 'actions' => array(
* 'index' => array('get'),
* 'view' => array('get'),
* 'create' => array('get', 'post'),
* 'update' => array('get', 'put', 'post'),
* 'delete' => array('post', 'delete'),
* ),
* ),
* );
* }
* ~~~
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class VerbFilter extends Behavior
{
/**
* @var array this property defines the allowed request methods for each action.
* For each action that should only support limited set of request methods
* you add an entry with the action id as array key and an array of
* allowed methods (e.g. GET, HEAD, PUT) as the value.
* If an action is not listed all request methods are considered allowed.
*/
public $actions = array();
/**
* Declares event handlers for the [[owner]]'s events.
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
return array(
Controller::EVENT_BEFORE_ACTION => 'beforeAction',
);
}
/**
* @param ActionEvent $event
* @return boolean
* @throws \yii\base\HttpException when the request method is not allowed.
*/
public function beforeAction($event)
{
$action = $event->action->id;
if (isset($this->actions[$action])) {
$verb = Yii::$app->getRequest()->getRequestMethod();
$allowed = array_map('strtoupper', $this->actions[$action]);
if (!in_array($verb, $allowed)) {
$event->isValid = false;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
header('Allow: ' . implode(', ', $allowed));
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
}
}
return $event->isValid;
}
}

12
framework/yii/widgets/ActiveField.php

@ -557,7 +557,14 @@ class ActiveField extends Component
}
/**
* Renders a field containing a widget.
* Renders a field containing an input widget.
*
* Note that the widget must have both `model` and `attribute` properties. They will
* be initialized with [[model]] and [[attribute]] of this field, respectively.
*
* If you want to use a widget that does not have `model` and `attribute` properties,
* please use [[render()]] instead.
*
* @param string $class the widget class name
* @param array $config name-value pairs that will be used to initialize the widget
* @return string the rendering result
@ -565,6 +572,9 @@ class ActiveField extends Component
public function widget($class, $config = array())
{
/** @var \yii\base\Widget $class */
$config['model'] = $this->model;
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
return $this->render($class::widget($config));
}
}

4
framework/yii/widgets/ActiveForm.php

@ -134,8 +134,8 @@ class ActiveForm extends Widget
$id = $this->options['id'];
$options = Json::encode($this->getClientOptions());
$attributes = Json::encode($this->attributes);
$this->view->registerAssetBundle('yii/form');
$this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
$this->getView()->registerAssetBundle('yii/form');
$this->getView()->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
}
echo Html::endForm();
}

64
framework/yii/widgets/Captcha.php

@ -9,13 +9,12 @@ namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\CaptchaAction;
/**
* Captcha renders a CAPTCHA image element.
* Captcha renders a CAPTCHA image and an input field that takes user-entered verification code.
*
* Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha)
* - a way of preventing Website spamming.
@ -32,7 +31,7 @@ use yii\web\CaptchaAction;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Captcha extends Widget
class Captcha extends InputWidget
{
/**
* @var string the route of the action that generates the CAPTCHA images.
@ -40,27 +39,66 @@ class Captcha extends Widget
*/
public $captchaAction = 'site/captcha';
/**
* @var array HTML attributes to be applied to the rendered image element.
* @var array HTML attributes to be applied to the text input field.
*/
public $options = array();
/**
* @var array HTML attributes to be applied to the CAPTCHA image tag.
*/
public $imageOptions = array();
/**
* @var string the template for arranging the CAPTCHA image tag and the text input tag.
* In this template, the token `{image}` will be replaced with the actual image tag,
* while `{input}` will be replaced with the text input tag.
*/
public $template = '{image} {input}';
/**
* Renders the widget.
* Initializes the widget.
*/
public function run()
public function init()
{
parent::init();
$this->checkRequirements();
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
if (!isset($this->imageOptions['id'])) {
$this->imageOptions['id'] = $this->options['id'] . '-image';
}
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerClientScript();
if ($this->hasModel()) {
$input = Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
$input = Html::textInput($this->name, $this->value, $this->options);
}
$id = $this->options['id'];
$options = Json::encode($this->getClientOptions());
$this->view->registerAssetBundle('yii/captcha');
$this->view->registerJs("jQuery('#$id').yiiCaptcha($options);");
$url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid()));
echo Html::img($url, $this->options);
$image = Html::img($url, $this->imageOptions);
echo strtr($this->template, array(
'{input}' => $input,
'{image}' => $image,
));
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$options = $this->getClientOptions();
$options = empty($options) ? '' : Json::encode($options);
$id = $this->imageOptions['id'];
$this->getView()->registerAssetBundle('yii/captcha');
$this->getView()->registerJs("jQuery('#$id').yiiCaptcha($options);");
}
/**

64
framework/yii/widgets/InputWidget.php

@ -0,0 +1,64 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Widget;
use yii\base\Model;
use yii\base\InvalidConfigException;
/**
* InputWidget is the base class for widgets that collect user inputs.
*
* An input widget can be associated with a data model and an attribute,
* or a name and a value. If the former, the name and the value will
* be generated automatically.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InputWidget extends Widget
{
/**
* @var Model the data model that this widget is associated with.
*/
public $model;
/**
* @var string the model attribute that this widget is associated with.
*/
public $attribute;
/**
* @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
*/
public $name;
/**
* @var string the input value.
*/
public $value;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
if (!$this->hasModel() && $this->name === null) {
throw new InvalidConfigException("Either 'name' or 'model' and 'attribute' properties must be specified.");
}
parent::init();
}
/**
* @return boolean whether this widget is associated with a data model.
*/
protected function hasModel()
{
return $this->model instanceof Model && $this->attribute !== null;
}
}

136
framework/yii/widgets/MaskedInput.php

@ -0,0 +1,136 @@
<?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\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* MaskedInput generates a masked text input.
*
* MaskedInput is similar to [[Html::textInput()]] except that
* an input mask will be used to force users to enter properly formatted data,
* such as phone numbers, social security numbers.
*
* To use MaskedInput, you must set the [[mask]] property. The following example
* shows how to use MaskedInput to collect phone numbers:
*
* ~~~
* echo MaskedInput::widget(array(
* 'name' => 'phone',
* 'mask' => '999-999-9999',
* ));
* ~~~
*
* The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class MaskedInput extends InputWidget
{
/**
* @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
*
* - `a`: represents an alpha character (A-Z,a-z)
* - `9`: represents a numeric character (0-9)
* - `*`: represents an alphanumeric character (A-Z,a-z,0-9)
* - `?`: anything listed after '?' within the mask is considered optional user input
*
* Additional characters can be defined by specifying the [[charMap]] property.
*/
public $mask;
/**
* @var array the mapping between mask characters and the corresponding patterns.
* For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input.
* Defaults to null, meaning using the map as described in [[mask]].
*/
public $charMap;
/**
* @var string the character prompting for user input. Defaults to underscore '_'.
*/
public $placeholder;
/**
* @var string a JavaScript function callback that will be invoked when user finishes the input.
*/
public $completed;
/**
* @var array the HTML attributes for the input tag.
*/
public $options = array();
/**
* Initializes the widget.
* @throws InvalidConfigException if the "mask" property is not set.
*/
public function init()
{
parent::init();
if (empty($this->mask)) {
throw new InvalidConfigException('The "mask" property must be set.');
}
if (!isset($this->options['id'])) {
$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
}
/**
* Runs the widget.
*/
public function run()
{
if ($this->hasModel()) {
echo Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
echo Html::textInput($this->name, $this->value, $this->options);
}
$this->registerClientScript();
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$options = $this->getClientOptions();
$options = empty($options) ? '' : ',' . Json::encode($options);
$js = '';
if (is_array($this->charMap) && !empty($this->charMap)) {
$js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
}
$id = $this->options['id'];
$js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
$this->getView()->registerAssetBundle('yii/maskedinput');
$this->getView()->registerJs($js);
}
/**
* @return array the options for the text field
*/
protected function getClientOptions()
{
$options = array();
if ($this->placeholder !== null) {
$options['placeholder'] = $this->placeholder;
}
if ($this->completed !== null) {
if ($this->completed instanceof JsExpression) {
$options['completed'] = $this->completed;
} else {
$options['completed'] = new JsExpression($this->completed);
}
}
return $options;
}
}

36
framework/yii/widgets/Menu.php

@ -9,6 +9,7 @@ namespace yii\widgets;
use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
@ -16,15 +17,15 @@ use yii\helpers\Html;
*
* The main property of Menu is [[items]], which specifies the possible items in the menu.
* A menu item can contain sub-items which specify the sub-menu under that menu item.
*
*
* Menu checks the current route and request parameters to toggle certain menu items
* with active state.
*
*
* Note that Menu only renders the HTML tags about the menu. It does do any styling.
* You are responsible to provide CSS styles to make it look like a real menu.
*
* The following example shows how to use Menu:
*
*
* ~~~
* echo Menu::widget(array(
* 'items' => array(
@ -40,7 +41,7 @@ use yii\helpers\Html;
* ),
* ));
* ~~~
*
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
@ -68,6 +69,13 @@ class Menu extends Widget
*/
public $items = array();
/**
* @var array list of HTML attributes for the menu container tag. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "li", the tag name of the item container tags.
*/
public $itemOptions = array();
/**
* @var string the template used to render the body of a menu which is a link.
* In this template, the token `{url}` will be replaced with the corresponding link URL;
* while `{label}` will be replaced with the link text.
@ -110,7 +118,9 @@ class Menu extends Widget
*/
public $hideEmptyItems = true;
/**
* @var array the HTML attributes for the menu's container tag.
* @var array the HTML attributes for the menu's container tag. The following special options are recognized:
*
* - tag: string, defaults to "ul", the tag name of the item container tags.
*/
public $options = array();
/**
@ -125,7 +135,7 @@ class Menu extends Widget
public $lastItemCssClass;
/**
* @var string the route used to determine if a menu item is active or not.
* If not set, it will use the route of the current request.
* If not set, it will use the route of the current request.
* @see params
* @see isItemActive
*/
@ -151,7 +161,9 @@ class Menu extends Widget
$this->params = $_GET;
}
$items = $this->normalizeItems($this->items, $hasActiveChild);
echo Html::tag('ul', $this->renderItems($items), $this->options);
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
echo Html::tag($tag, $this->renderItems($items), $options);
}
/**
@ -164,7 +176,8 @@ class Menu extends Widget
$n = count($items);
$lines = array();
foreach ($items as $i => $item) {
$options = isset($item['options']) ? $item['options'] : array();
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array()));
$tag = ArrayHelper::remove($options, 'tag', 'li');
$class = array();
if ($item['active']) {
$class[] = $this->activeCssClass;
@ -189,7 +202,7 @@ class Menu extends Widget
'{items}' => $this->renderItems($item['items']),
));
}
$lines[] = Html::tag('li', $menu, $options);
$lines[] = Html::tag($tag, $menu, $options);
}
return implode("\n", $lines);
}
@ -203,13 +216,13 @@ class Menu extends Widget
protected function renderItem($item)
{
if (isset($item['url'])) {
$template = isset($item['template']) ? $item['template'] : $this->linkTemplate;
$template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
return strtr($template, array(
'{url}' => Html::url($item['url']),
'{label}' => $item['label'],
));
} else {
$template = isset($item['template']) ? $item['template'] : $this->labelTemplate;
$template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
return strtr($template, array(
'{label}' => $item['label'],
));
@ -284,5 +297,4 @@ class Menu extends Widget
}
return false;
}
}

3
tests/unit/TestCase.php

@ -38,14 +38,13 @@ abstract class TestCase extends \yii\test\TestCase
* The application will be destroyed on tearDown() automatically.
* @param array $config The application configuration, if needed
*/
protected function mockApplication($config=array())
protected function mockApplication($config = array(), $appClass = '\yii\console\Application')
{
static $defaultConfig = array(
'id' => 'testapp',
'basePath' => __DIR__,
);
$appClass = $this->getParam( 'appClass', '\yii\web\Application' );
new $appClass(array_merge($defaultConfig,$config));
}

2
tests/unit/data/config.php

@ -1,8 +1,6 @@
<?php
return array(
//'appClass' => '\yii\web\Application',
'appClass' => '\yii\console\Application',
'databases' => array(
'mysql' => array(
'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest',

179
tests/unit/framework/base/FormatterTest.php

@ -0,0 +1,179 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\base;
use yii\base\Formatter;
use yiiunit\TestCase;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FormatterTest extends TestCase
{
/**
* @var Formatter
*/
protected $formatter;
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->formatter = new Formatter();
}
protected function tearDown()
{
parent::tearDown();
$this->formatter = null;
}
public function testAsRaw()
{
$value = '123';
$this->assertSame($value, $this->formatter->asRaw($value));
$value = 123;
$this->assertSame($value, $this->formatter->asRaw($value));
$value = '<>';
$this->assertSame($value, $this->formatter->asRaw($value));
}
public function testAsText()
{
$value = '123';
$this->assertSame($value, $this->formatter->asText($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asText($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asText($value));
}
public function testAsNtext()
{
$value = '123';
$this->assertSame($value, $this->formatter->asNtext($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asNtext($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asNtext($value));
$value = "123\n456";
$this->assertSame("123<br />\n456", $this->formatter->asNtext($value));
}
public function testAsParagraphs()
{
$value = '123';
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = 123;
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = '<>';
$this->assertSame('<p>&lt;&gt;</p>', $this->formatter->asParagraphs($value));
$value = "123\n456";
$this->assertSame("<p>123\n456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
}
public function testAsHtml()
{
// todo: dependency on HtmlPurifier
}
public function testAsEmail()
{
$value = 'test@sample.com';
$this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value));
}
public function testAsImage()
{
$value = 'http://sample.com/img.jpg';
$this->assertSame("<img src=\"$value\" alt=\"\" />", $this->formatter->asImage($value));
}
public function testAsBoolean()
{
$value = true;
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = false;
$this->assertSame('No', $this->formatter->asBoolean($value));
$value = "111";
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = "";
$this->assertSame('No', $this->formatter->asBoolean($value));
}
public function testAsDate()
{
$value = time();
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d'));
}
public function testAsTime()
{
$value = time();
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value));
$this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s'));
}
public function testAsDatetime()
{
$value = time();
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s'));
}
public function testAsInteger()
{
$value = 123;
$this->assertSame("$value", $this->formatter->asInteger($value));
$value = 123.23;
$this->assertSame("123", $this->formatter->asInteger($value));
$value = 'a';
$this->assertSame("0", $this->formatter->asInteger($value));
$value = -123.23;
$this->assertSame("-123", $this->formatter->asInteger($value));
$value = "-123abc";
$this->assertSame("-123", $this->formatter->asInteger($value));
}
public function testAsDouble()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDouble($value));
$this->assertSame("123.1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123;
$this->assertSame("123.00", $this->formatter->asDouble($value));
$this->formatter->decimalSeparator = ',';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDouble($value));
$this->assertSame("123,1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123123.123;
$this->assertSame("123123,12", $this->formatter->asDouble($value));
}
public function testAsNumber()
{
$value = 123123.123;
$this->assertSame("123,123", $this->formatter->asNumber($value));
$this->assertSame("123,123.12", $this->formatter->asNumber($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123", $this->formatter->asNumber($value));
$this->assertSame("123 123,12", $this->formatter->asNumber($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123", $this->formatter->asNumber($value));
$this->assertSame("123123,12", $this->formatter->asNumber($value, 2));
}
}

1
tests/unit/framework/console/controllers/AssetControllerTest.php

@ -239,6 +239,7 @@ class AssetControllerTest extends TestCase
// Then :
$this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!');
$this->assertTrue(is_array(require($bundleFile)), 'Output bundle file has incorrect format!');
$compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css';
$this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!');

109
tests/unit/framework/db/QueryBuilderTest.php

@ -0,0 +1,109 @@
<?php
namespace yiiunit\framework\db;
use yii\db\QueryBuilder;
use yii\db\Schema;
use yii\db\mysql\QueryBuilder as MysqlQueryBuilder;
use yii\db\sqlite\QueryBuilder as SqliteQueryBuilder;
use yii\db\mssql\QueryBuilder as MssqlQueryBuilder;
class QueryBuilderTest extends DatabaseTestCase
{
/**
* @throws \Exception
* @return QueryBuilder
*/
protected function getQueryBuilder()
{
switch($this->driverName)
{
case 'mysql':
return new MysqlQueryBuilder($this->getConnection());
case 'sqlite':
return new SqliteQueryBuilder($this->getConnection());
case 'mssql':
return new MssqlQueryBuilder($this->getConnection());
}
throw new \Exception('Test is not implemented for ' . $this->driverName);
}
/**
* this is not used as a dataprovider for testGetColumnType to speed up the test
* when used as dataprovider every single line will cause a reconnect with the database which is not needed here
*/
public function columnTypes()
{
return array(
array(Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'),
array(Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'),
array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'),
array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'),
array(Schema::TYPE_STRING, 'varchar(255)'),
array(Schema::TYPE_STRING . '(32)', 'varchar(32)'),
array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'),
array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'),
array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'),
array(Schema::TYPE_TEXT, 'text'),
array(Schema::TYPE_TEXT . '(255)', 'text'),
array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'),
array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'),
array(Schema::TYPE_SMALLINT, 'smallint(6)'),
array(Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'),
array(Schema::TYPE_INTEGER, 'int(11)'),
array(Schema::TYPE_INTEGER . '(8)', 'int(8)'),
array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'),
array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'),
array(Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'),
array(Schema::TYPE_BIGINT, 'bigint(20)'),
array(Schema::TYPE_BIGINT . '(8)', 'bigint(8)'),
array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'),
array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'),
array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'),
array(Schema::TYPE_FLOAT, 'float'),
array(Schema::TYPE_FLOAT . '(16,5)', 'float'),
array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'),
array(Schema::TYPE_DECIMAL, 'decimal(10,0)'),
array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'),
array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'),
array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'),
array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'),
array(Schema::TYPE_DATETIME, 'datetime'),
array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'),
array(Schema::TYPE_TIMESTAMP, 'timestamp'),
array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'),
array(Schema::TYPE_TIME, 'time'),
array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"),
array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'),
array(Schema::TYPE_DATE, 'date'),
array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'),
array(Schema::TYPE_BINARY, 'blob'),
array(Schema::TYPE_BOOLEAN, 'tinyint(1)'),
array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'),
array(Schema::TYPE_MONEY, 'decimal(19,4)'),
array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'),
array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'),
array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'),
array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'),
);
}
/**
*
*/
public function testGetColumnType()
{
$qb = $this->getQueryBuilder();
foreach($this->columnTypes() as $item) {
list ($column, $expected) = $item;
$this->assertEquals($expected, $qb->getColumnType($column));
}
}
}

74
tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php

@ -0,0 +1,74 @@
<?php
namespace yiiunit\framework\db\sqlite;
use yii\db\sqlite\Schema;
use yiiunit\framework\db\QueryBuilderTest;
class SqliteQueryBuilderTest extends QueryBuilderTest
{
public $driverName = 'sqlite';
public function columnTypes()
{
return array(
array(Schema::TYPE_PK, 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'),
array(Schema::TYPE_PK . '(8)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'),
array(Schema::TYPE_PK . ' CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'),
array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'),
array(Schema::TYPE_STRING, 'varchar(255)'),
array(Schema::TYPE_STRING . '(32)', 'varchar(32)'),
array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'),
array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'),
array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'),
array(Schema::TYPE_TEXT, 'text'),
array(Schema::TYPE_TEXT . '(255)', 'text'),
array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'),
array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'),
array(Schema::TYPE_SMALLINT, 'smallint'),
array(Schema::TYPE_SMALLINT . '(8)', 'smallint'),
array(Schema::TYPE_INTEGER, 'integer'),
array(Schema::TYPE_INTEGER . '(8)', 'integer'),
array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'),
array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'),
array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'),
array(Schema::TYPE_BIGINT, 'bigint'),
array(Schema::TYPE_BIGINT . '(8)', 'bigint'),
array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'),
array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'),
array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'),
array(Schema::TYPE_FLOAT, 'float'),
array(Schema::TYPE_FLOAT . '(16,5)', 'float'),
array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'),
array(Schema::TYPE_DECIMAL, 'decimal(10,0)'),
array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'),
array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'),
array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'),
array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'),
array(Schema::TYPE_DATETIME, 'datetime'),
array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'),
array(Schema::TYPE_TIMESTAMP, 'timestamp'),
array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'),
array(Schema::TYPE_TIME, 'time'),
array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"),
array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'),
array(Schema::TYPE_DATE, 'date'),
array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'),
array(Schema::TYPE_BINARY, 'blob'),
array(Schema::TYPE_BOOLEAN, 'boolean'),
array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'),
array(Schema::TYPE_MONEY, 'decimal(19,4)'),
array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'),
array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'),
array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'),
array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'),
);
}
}

88
tests/unit/framework/i18n/FormatterTest.php

@ -0,0 +1,88 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\i18n;
use yii\i18n\Formatter;
use yiiunit\TestCase;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FormatterTest extends TestCase
{
/**
* @var Formatter
*/
protected $formatter;
protected function setUp()
{
parent::setUp();
if (!extension_loaded('intl')) {
$this->markTestSkipped('intl extension is required.');
}
$this->mockApplication();
$this->formatter = new Formatter(array(
'locale' => 'en_US',
));
}
protected function tearDown()
{
parent::tearDown();
$this->formatter = null;
}
public function testAsDecimal()
{
$value = '123';
$this->assertSame($value, $this->formatter->asDecimal($value));
$value = '123456';
$this->assertSame("123,456", $this->formatter->asDecimal($value));
$value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
}
public function testAsPercent()
{
$value = '123';
$this->assertSame('12,300%', $this->formatter->asPercent($value));
$value = '0.1234';
$this->assertSame("12%", $this->formatter->asPercent($value));
$value = '-0.009343';
$this->assertSame("-1%", $this->formatter->asPercent($value));
}
public function testAsScientific()
{
$value = '123';
$this->assertSame('1.23E2', $this->formatter->asScientific($value));
$value = '123456';
$this->assertSame("1.23456E5", $this->formatter->asScientific($value));
$value = '-123456.123';
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
}
public function testAsCurrency()
{
$value = '123';
$this->assertSame('$123.00', $this->formatter->asCurrency($value));
$value = '123.456';
$this->assertSame("$123.46", $this->formatter->asCurrency($value));
$value = '-123456.123';
$this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
}
public function testDate()
{
$time = time();
$this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time));
$this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long'));
}
}

54
tests/unit/framework/web/UrlManagerTest.php

@ -248,4 +248,58 @@ class UrlManagerTest extends TestCase
$result = $manager->parseRequest($request);
$this->assertFalse($result);
}
public function testParseRESTRequest()
{
$manager = new UrlManager(array(
'cache' => null,
));
$request = new Request;
// pretty URL rules
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'cache' => null,
'rules' => array(
'PUT,POST post/<id>/<title>' => 'post/create',
'DELETE post/<id>' => 'post/delete',
'post/<id>/<title>' => 'post/view',
'POST/GET' => 'post/get',
),
));
// matching pathinfo GET request
$_SERVER['REQUEST_METHOD'] = 'GET';
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result);
// matching pathinfo PUT/POST request
$_SERVER['REQUEST_METHOD'] = 'PUT';
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result);
$_SERVER['REQUEST_METHOD'] = 'POST';
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result);
// no wrong matching
$_SERVER['REQUEST_METHOD'] = 'POST';
$request->pathInfo = 'POST/GET';
$result = $manager->parseRequest($request);
$this->assertEquals(array('post/get', array()), $result);
// createUrl should ignore REST rules
$this->mockApplication(array(
'components' => array(
'request' => array(
'hostInfo' => 'http://localhost/',
'baseUrl' => '/app'
)
)
), \yii\web\Application::className());
$this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123)));
$this->destroyApplication();
unset($_SERVER['REQUEST_METHOD']);
}
}

Loading…
Cancel
Save