Browse Source

Merge branch 'master'

tags/2.0.0-beta
Alexander Makarov 12 years ago
parent
commit
cc450431f6
  1. 26
      docs/api/base/Component.md
  2. 30
      docs/api/base/Object.md
  3. 328
      docs/code_style.md
  4. BIN
      docs/full_2011_11_12.png
  5. BIN
      docs/hierarchy_2011_11_12.png
  6. 192
      docs/review_2011_11_12_alex.md
  7. 77
      framework/YiiBase.php
  8. 4
      framework/base/ActionEvent.php
  9. 105
      framework/base/Application.php
  10. 146
      framework/base/Component.php
  11. 3
      framework/base/Controller.php
  12. 56
      framework/base/ErrorException.php
  13. 9
      framework/base/Event.php
  14. 22
      framework/base/Model.php
  15. 25
      framework/base/Module.php
  16. 3
      framework/base/Object.php
  17. 35
      framework/base/Theme.php
  18. 65
      framework/base/View.php
  19. 136
      framework/base/ViewContent.php
  20. 44
      framework/base/ViewEvent.php
  21. 20
      framework/caching/ChainedDependency.php
  22. 18
      framework/caching/DbDependency.php
  23. 13
      framework/caching/ExpressionDependency.php
  24. 16
      framework/caching/FileDependency.php
  25. 24
      framework/logging/FileTarget.php
  26. 39
      framework/validators/BooleanValidator.php
  27. 67
      framework/validators/CaptchaValidator.php
  28. 191
      framework/validators/CompareValidator.php
  29. 66
      framework/validators/DateValidator.php
  30. 15
      framework/validators/DefaultValueValidator.php
  31. 33
      framework/validators/EmailValidator.php
  32. 58
      framework/validators/ExistValidator.php
  33. 283
      framework/validators/FileValidator.php
  34. 20
      framework/validators/FilterValidator.php
  35. 21
      framework/validators/InlineValidator.php
  36. 76
      framework/validators/NumberValidator.php
  37. 58
      framework/validators/RangeValidator.php
  38. 59
      framework/validators/RegularExpressionValidator.php
  39. 52
      framework/validators/RequiredValidator.php
  40. 91
      framework/validators/StringValidator.php
  41. 27
      framework/validators/UniqueValidator.php
  42. 42
      framework/validators/UrlValidator.php
  43. 30
      framework/validators/Validator.php
  44. 14
      framework/web/Application.php
  45. 33
      framework/web/Request.php
  46. 246
      framework/web/UploadedFile.php
  47. 63
      framework/widgets/ActiveForm.php
  48. 44
      tests/unit/framework/base/ComponentTest.php
  49. 4
      tests/unit/framework/helpers/HtmlTest.php

26
docs/api/base/Component.md

@ -1,3 +1,5 @@
Component is the base class that implements the *property*, *event* and *behavior* features.
Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in
its parent class [[Object]]. its parent class [[Object]].
@ -7,20 +9,20 @@ is triggered (i.e. comment will be added), our custom code will be executed.
An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*. An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*.
One or multiple PHP callbacks, called *event handlers*, could be attached to an event. You can call [[trigger()]] to One or multiple PHP callbacks, called *event handlers*, can be attached to an event. You can call [[trigger()]] to
raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were
attached. attached.
To attach an event handler to an event, call [[on()]]: To attach an event handler to an event, call [[on()]]:
~~~ ~~~
$comment->on('add', function($event) { $post->on('update', function($event) {
// send email notification // send email notification
}); });
~~~ ~~~
In the above, we attach an anonymous function to the "add" event of the comment. In the above, an anonymous function is attached to the "update" event of the post. You may attach
Valid event handlers include: the following types of event handlers:
- anonymous function: `function($event) { ... }` - anonymous function: `function($event) { ... }`
- object method: `array($object, 'handleAdd')` - object method: `array($object, 'handleAdd')`
@ -35,8 +37,8 @@ function foo($event)
where `$event` is an [[Event]] object which includes parameters associated with the event. where `$event` is an [[Event]] object which includes parameters associated with the event.
You can also attach an event handler to an event when configuring a component with a configuration array. The syntax is You can also attach a handler to an event when configuring a component with a configuration array.
like the following: The syntax is like the following:
~~~ ~~~
array( array(
@ -46,15 +48,13 @@ array(
where `on add` stands for attaching an event to the `add` event. where `on add` stands for attaching an event to the `add` event.
You can call [[getEventHandlers()]] to retrieve all event handlers that are attached to a specified event. Because this Sometimes, you may want to associate extra data with an event handler when you attach it to an event
method returns a [[Vector]] object, we can manipulate this object to attach/detach event handlers, or adjust their and then access it when the handler is invoked. You may do so by
relative orders.
~~~ ~~~
$handlers = $comment->getEventHandlers('add'); $post->on('update', function($event) {
$handlers->insertAt(0, $callback); // attach a handler as the first one // the data can be accessed via $event->data
$handlers[] = $callback; // attach a handler as the last one }, $data);
unset($handlers[0]); // detach the first handler
~~~ ~~~

30
docs/api/base/Object.md

@ -1,3 +1,5 @@
Object is the base class that implements the *property* feature.
A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example, A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example,
the following getter and setter methods define a property named `label`: the following getter and setter methods define a property named `label`:
@ -30,4 +32,30 @@ $object->label = 'abc';
If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying
to modify the property value will cause an exception. to modify the property value will cause an exception.
One can call [[hasProperty]], [[canGetProperty]] and/or [[canSetProperty]] to check the existence of a property. One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property.
Besides the property feature, Object also introduces an important object initialization life cycle. In particular,
creating an new instance of Object or its derived class will involve the following life cycles sequentially:
1. the class constructor is invoked;
2. object properties are initialized according to the given configuration;
3. the `init()` method is invoked.
In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that
you perform object initialization in the `init()` method because at that stage, the object configuration
is already applied.
In order to ensure the above life cycles, if a child class of Object needs to override the constructor,
it should be done like the following:
~~~
public function __construct($param1, $param2, ..., $config = array())
{
...
parent::__construct($config);
}
~~~
That is, a `$config` parameter (defaults to `array()`) should be declared as the last parameter
of the constructor, and the parent implementation should be called at the end of the constructor.

328
docs/code_style.md

@ -1,328 +0,0 @@
Yii2 code standard
==================
This code standard is used for all the Yii2 core classes and can be applied to
your application in order to achieve consistency among your team. Also it will
help in case you want to opensource code.
PHP file formatting
-------------------
### General
- Do not end file with `?>` if it contains PHP code only.
- Do not use `<?`. Use `<?php` instead.
- Files should be encoded in UTF-8.
- Any file that contains PHP code should end with the extension `.php`.
- Do not add trailing spaces to the end of the lines.
#### Indentation
All code must be indented with tabs. That includes both PHP and JavaScript code.
#### Maximum Line Length
We're not strictly limiting maximum line length but sticking to 80 characters
where possible.
### PHP types
All PHP types and values should be used lowercase. That includes `true`, `false`,
`null` and `array`.
### Strings
- If string doesn't contain variables or single quotes, use single quotes.
~~~
$str = 'Like this.';
~~~
- If string contains single quotes you can use double quotes to avoid extra escaping.
- You can use the following forms of variable substitution:
~~~
$str1 = "Hello $username!";
$str2 = "Hello {$username}!";
~~~
The following is not permitted:
~~~
$str3 = "Hello ${username}!";
~~~
### String concatenation
Add spaces around dot when concatenating strings:
~~~
$name = 'Yii' . ' Framework';
~~~
When string is long format is the following:
~~~
$sql = "SELECT *"
. "FROM `post` "
. "WHERE `id` = 121 ";
~~~
### Numerically indexed arrays
- Do not use negative numbers as array indexes.
Use the following formatting when declaring array:
~~~
$arr = array(3, 14, 15, 'Yii', 'Framework');
~~~
If there are too many elements for a single line:
~~~
$arr = array(
3, 14, 15,
92, 6, $test,
'Yii', 'Framework',
);
~~~
### Associative arrays
Use the following format for associative arrays:
~~~
$config = array(
'name' => 'Yii',
'options' => array(
'usePHP' => true,
),
);
~~~
### Classes
- Classes should be named using `CamelCase`.
- The brace should always be written on the line underneath the class name.
- Every class must have a documentation block that conforms to the PHPDoc.
- All code in a class must be indented with a single tab.
- There should be only one class in a single PHP file.
- All classes should be namespaced.
- Class name should match file name. Class namespace should match directory structure.
~~~
/**
* Documentation
*/
class MyClass extends \yii\Object implements MyInterface
{
// code
}
~~~
### Class members and variables
- When declaring public class members specify `public` keyword explicitly.
- Variables should be declared at the top of the class before any method declarations.
- Private and protected variables should be named like `$_varName`.
- Public class members and standalone variables should be named using `$camelCase`
with first letter lowercase.
- Use descriptive names. Variables such as `$i` and `$j` are better not to be used.
### Constants
Both class level constants and global constants should be named in uppercase. Words
are separated by underscore.
~~~
class User {
const STATUS_ACTIVE = 1;
const STATUS_BANNED = 2;
}
~~~
It's preferable to define class level constants rather than global ones.
### Functions and methods
- Functions and methods should be named using `camelCase` with first letter lowercase.
- Name should be descriptive by itself indicating the purpose of the function.
- Class methods should always declare visibility using `private`, `protected` and
`public` modifiers. `var` is not allowed.
- Opening brace of a function should be on the line after the function declaration.
~~~
/**
* Documentation
*/
class Foo
{
/**
* Documentation
*/
public function bar()
{
// code
return $value;
}
}
~~~
Use type hinting where possible:
~~~
public function __construct(CDbConnection $connection)
{
$this->connection = $connection;
}
~~~
### Function and method calls
~~~
doIt(2, 3);
doIt(array(
'a' => 'b',
));
doIt('a', array(
'a' => 'b',
));
~~~
### Control statements
- Control statement condition must have single space before and after parenthesis.
- Operators inside of parenthesis should be separated by spaces.
- Opening brace is on the same line.
- Closing brace is on a new line.
- Always use braces for single line statements.
~~~
if ($event === null) {
return new Event();
} elseif ($event instanceof CoolEvent) {
return $event->instance();
} else {
return null;
}
// the following is NOT allowed:
if(!$model)
throw new Exception('test');
~~~
### Switch
Use the following formatting for switch:
~~~
switch ($this->phpType) {
case 'string':
$a = (string)$value;
break;
case 'integer':
case 'int':
$a = (integer)$value;
break;
case 'boolean':
$a = (boolean)$value;
break;
default:
$a = null;
}
~~~
### Code documentation
- Refer ot [phpDoc](http://phpdoc.org/) for documentation syntax.
- Code without documentation is not allowed.
- All class files must contain a "file-level" docblock at the top of each file
and a "class-level" docblock immediately above each class.
- There is no need to use `@return` if method does return nothing.
#### File
~~~
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
~~~
#### Class
~~~
/**
* Component is the base class that provides the *property*, *event* and *behavior* features.
*
* @include @yii/docs/base-Component.md
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Component extends \yii\base\Object
~~~
#### Function / method
~~~
/**
* Returns the list of attached event handlers for an event.
* You may manipulate the returned [[Vector]] object by adding or removing handlers.
* For example,
*
* ~~~
* $component->getEventHandlers($eventName)->insertAt(0, $eventHandler);
* ~~~
*
* @param string $name the event name
* @return Vector list of attached event handlers for the event
* @throws Exception if the event is not defined
*/
public function getEventHandlers($name)
{
if (!isset($this->_e[$name])) {
$this->_e[$name] = new Vector;
}
$this->ensureBehaviors();
return $this->_e[$name];
}
~~~
#### Comments
- One-line comments should be started with `//` and not `#`.
- One-line comment should be on its own line.
Yii application naming conventions
----------------------------------
Other library and framework standards
-------------------------------------
It's good to be consistent with other frameworks and libraries whose components
will be possibly used with Yii2. That's why when there are no objective reasons
to use different style we should use one that's common among most of the popular
libraries and frameworks.
That's not only about PHP but about JavaScript as well. Since we're using jQuery
a lot it's better to be consistent with its style as well.
Application style consistency is much more important than consistency with other frameworks and libraries.
- [Symfony 2](http://symfony.com/doc/current/contributing/code/standards.html)
- [Zend Framework 1](http://framework.zend.com/manual/en/coding-standard.coding-style.html)
- [Zend Framework 2](http://framework.zend.com/wiki/display/ZFDEV2/Coding+Standards)
- [Pear](http://pear.php.net/manual/en/standards.php)
- [jQuery](http://docs.jquery.com/JQuery_Core_Style_Guidelines)

BIN
docs/full_2011_11_12.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

BIN
docs/hierarchy_2011_11_12.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

192
docs/review_2011_11_12_alex.md

@ -1,192 +0,0 @@
Alex's Code Review, 2011.11.12
==============================
Overall hierarchy
------------------
Generally is OK. Like that `Object` and `Component` are now separated.
I've generated 2 diagrams under `docs/` to see it better as a whole.
> The purpose of separating `Object` from `Component` is to make `Object`
> a super-light base class that supports properties defined by getter/setters.
> Note that `Component` is a bit of heavy because it uses two extra member
> variables to support events and behaviors.
Object
------
### property feature
Is it OK that `canGetProperty` and `canSetProperty` will return `false` for real
class members?
> Added $checkVar parameter
### callbacks and expressions
We're using 5.3. What's the reason to support `eval()` in `evaluateExpression` if
we have anonymous functions? Is that for storing code as string inside of DB (RBAC)?
If we're going to get rid of `eval()`, cosider remaning method to something about callback.
If not then we definitely need to use anonymous functions in API docs and the guide
where possible.
> The purpose of evaluateExpression() is to provide a way of evaluating a PHP expression
> in the context of an object. Will remove it before release if we find no use of it.
>> mdomba:
>> As eval() is controversial, and anonymous functions can replace all Yii 1 usage of eval()
>> how about removing it from the beginning and add it only if we find it necessary.
>> This way we would not be tempted to stick with eval() and will be forced to first try to find alternatives
### Object::create()
#### `__construct` issue
Often a class doesn't have `__construct` implementation and `stdClass` doesn't have
default one either but Object::create() always expects constructor to be
defined. See `ObjectTest`. Either `method_exists` call or `Object::__construct` needed.
> Added Object::__construct.
#### How to support object factory like we do with CWidgetFactory?
~~~
class ObjectConfig
{
public function configure($class)
{
$config = $this->load($class);
// apply config to $class
}
private function load($class)
{
// get class properties from a config file
// in this method we need to walk all the
// inheritance hierarchy down to Object itself
return array(
'property' => 'value',
// …
);
}
}
~~~
Then we need to add `__construct` to `Object` (or implement `Initalbe`):
~~~
class Object
{
public function __construct()
{
$conf = new ObjectConfig();
$conf->configure($this);
}
}
~~~
This way we'll be able to set defaults for any object.
> The key issue here is about how to process the config file. Clearly, we cannot
> do this for every type of component because it would mean an extra file access
> for every component type
#### Do we need to support lazy class injection?
Currently there's no way to lazy-inject class into another class property via
config. Do we need it? If yes then we can probably extend component config to support
the following:
~~~
class Foo extends Object
{
public $prop;
}
class Bar extends Object
{
public $prop;
}
$config = array(
'prop' => array(
'class' => 'Bar',
'prop' => 'Hello!',
),
);
$foo = Foo::create($config);
echo $foo->bar->prop;
// will output Hello!
~~~
Should it support infinite nesting level?
> I don't think we need this. Foo::$prop cannot be an object unless it needs it to be.
> In that case, it can be defined with a setter in which it can handle the object creation
> based on a configuration array. This is a bit inconvenient, but I think such usage is
> not very common.
### Why `Event` is `Object`?
There's no need to extend from `Object`. Is there a plan to use `Object` features
later?
> To use properties defined via getter/setter.
Behaviors
---------
Overall I wasn't able to use behaviors. See `BehaviorTest`.
### Should behaviors be able to define events for owner components?
Why not? Should be a very good feature in order to make behaviors customizable.
> It's a bit hard to implement it efficiently. I tend not to support it for now
> unless enough people are requesting for it.
### Multiple behaviors can be attached to the same component
What if we'll have multiple methods / properties / events with the same name?
> The first one takes precedence. This is the same as we do in 1.1.
### How to use Behavior::attach?
Looks like it is used by `Component::attachBehavior` but can't be used without it.
Why it's public then? Can we move it to `Component?`
> It's public because it is called by Component. It is in Behavior such that
> it can be overridden by behavior classes to customize the attach process.
Events
------
Class itself looks OK. Component part is OK as well but I've not tested
it carefully. Overall it seems concept is the same as in Yii1.
### Event declaration: the on-method is mostly repetitive for every event. Should we choose a different way of declaring events?
Maybe. People complained previously about too many code for event declaration.
### Should we implement some additional event mechanism, such as global events?
Why use two different implementations in a single application?
Exceptions
----------
- Should we convert all errors, warnings and notices to exceptions?
> I think not. We used to do this in early versions of 1.0. We found sometimes
> very mysterious things would happen which makes error fixing harder rather than
> easier.
Coding style
------------
See `docs/code_style.md`.

77
framework/YiiBase.php

@ -46,8 +46,9 @@ class YiiBase
{ {
/** /**
* @var array class map used by the Yii autoloading mechanism. * @var array class map used by the Yii autoloading mechanism.
* The array keys are the class names, and the array values are the corresponding class file paths. * The array keys are the class names (without leading backslashes), and the array values
* This property mainly affects how [[autoload]] works. * are the corresponding class file paths (or path aliases). This property mainly affects
* how [[autoload()]] works.
* @see import * @see import
* @see autoload * @see autoload
*/ */
@ -113,7 +114,7 @@ class YiiBase
* includes the class file when the class is referenced in the code the first time. * includes the class file when the class is referenced in the code the first time.
* *
* Importing a directory will add the directory to the front of the [[classPath]] array. * Importing a directory will add the directory to the front of the [[classPath]] array.
* When [[autoload]] is loading an unknown class, it will search in the directories * When [[autoload()]] is loading an unknown class, it will search in the directories
* specified in [[classPath]] to find the corresponding class file to include. * specified in [[classPath]] to find the corresponding class file to include.
* For this reason, if multiple directories are imported, the directories imported later * For this reason, if multiple directories are imported, the directories imported later
* will take precedence in class file searching. * will take precedence in class file searching.
@ -188,10 +189,10 @@ class YiiBase
* it will be returned back without change. * it will be returned back without change.
* *
* Note, this method does not ensure the existence of the resulting path. * Note, this method does not ensure the existence of the resulting path.
* @param string $alias alias * @param string $alias the alias to be translated.
* @param boolean $throwException whether to throw an exception if the given alias is invalid. * @param boolean $throwException whether to throw an exception if the given alias is invalid.
* If this is false and an invalid alias is given, false will be returned by this method. * If this is false and an invalid alias is given, false will be returned by this method.
* @return string|boolean path corresponding to the alias, false if the root alias is not previously registered. * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered.
* @throws InvalidParamException if the alias is invalid while $throwException is true. * @throws InvalidParamException if the alias is invalid while $throwException is true.
* @see setAlias * @see setAlias
*/ */
@ -225,13 +226,14 @@ class YiiBase
* Note that this method neither checks the existence of the path nor normalizes the path. * Note that this method neither checks the existence of the path nor normalizes the path.
* Any trailing '/' and '\' characters in the path will be trimmed. * Any trailing '/' and '\' characters in the path will be trimmed.
* *
* @param string $alias alias to the path. The alias must start with '@'. * @param string $alias the alias name (e.g. "@yii"). It should start with a '@' character
* and should NOT contain the forward slash "/" or the backward slash "\".
* @param string $path the path corresponding to the alias. This can be * @param string $path the path corresponding to the alias. This can be
* *
* - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
* - a URL (e.g. `http://www.yiiframework.com`) * - a URL (e.g. `http://www.yiiframework.com`)
* - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the
* actual path first by calling [[getAlias]]. * actual path first by calling [[getAlias()]].
* *
* @throws Exception if $path is an invalid alias * @throws Exception if $path is an invalid alias
* @see getAlias * @see getAlias
@ -268,48 +270,35 @@ class YiiBase
*/ */
public static function autoload($className) public static function autoload($className)
{ {
if (isset(self::$classMap[$className])) { $className = ltrim($className, '\\');
include(self::$classMap[$className]);
return true;
}
if (strpos($className, '\\') !== false) { if (isset(self::$classMap[$className])) {
// namespaced class, e.g. yii\base\Component $classFile = self::$classMap[$className];
// convert namespace to path alias, e.g. yii\base\Component to @yii/base/Component } else {
$alias = '@' . str_replace('\\', '/', ltrim($className, '\\')); if (($pos = strrpos($className, '\\')) !== false) {
if (($path = static::getAlias($alias, false)) !== false) { // namespaced class, e.g. yii\base\Component
$classFile = $path . '.php'; $classFile = str_replace('\\', '/', substr($className, 0, $pos + 1))
} . str_replace('_', '/', substr($className, $pos + 1)) . '.php';
} elseif (($pos = strpos($className, '_')) !== false) { } else {
// PEAR-styled class, e.g. PHPUnit_Framework_TestCase $classFile = str_replace('_', '/', $className) . '.php';
// convert class name to path alias, e.g. PHPUnit_Framework_TestCase to @PHPUnit/Framework/TestCase
$alias = '@' . str_replace('_', '/', $className);
if (($path = static::getAlias($alias, false)) !== false) {
$classFile = $path . '.php';
} }
} if (strpos($classFile, '/') !== false) {
// make it into a path alias
if (!isset($classFile)) { $classFile = '@' . $classFile;
// search in include paths
foreach (self::$classPath as $path) {
$path .= DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($path)) {
$classFile = $path;
$alias = $className;
}
} }
} }
if (isset($classFile, $alias) && is_file($classFile)) { $classFile = static::getAlias($classFile);
if (!YII_DEBUG || basename(realpath($classFile)) === basename($alias) . '.php') { if ($classFile !== false && is_file($classFile)) {
include($classFile); include($classFile);
if (class_exists($className, false) || interface_exists($className, false)) {
return true; return true;
} else { } else {
throw new Exception("Class name '$className' does not match the class file '" . realpath($classFile) . "'. Have you checked their case sensitivity?"); throw new Exception("Unable to find '$className' in file: $classFile");
} }
} else {
return false;
} }
return false;
} }
/** /**
@ -371,6 +360,12 @@ class YiiBase
$class = static::import($class, true); $class = static::import($class, true);
} }
$class = ltrim($class, '\\');
if (isset(self::$objectConfig[$class])) {
$config = array_merge(self::$objectConfig[$class], $config);
}
if (($n = func_num_args()) > 1) { if (($n = func_num_args()) > 1) {
/** @var $reflection \ReflectionClass */ /** @var $reflection \ReflectionClass */
if (isset($reflections[$class])) { if (isset($reflections[$class])) {
@ -531,6 +526,6 @@ class YiiBase
*/ */
public static function t($message, $params = array(), $language = null) public static function t($message, $params = array(), $language = null)
{ {
return Yii::$app->getI18N()->translate($message, $params, $language); return self::$app->getI18N()->translate($message, $params, $language);
} }
} }

4
framework/base/ActionEvent.php

@ -22,7 +22,9 @@ class ActionEvent extends Event
*/ */
public $action; public $action;
/** /**
* @var boolean whether to continue running the action. * @var boolean whether to continue running the action. Event handlers of
* [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether
* to continue running the current action.
*/ */
public $isValid = true; public $isValid = true;

105
framework/base/Application.php

@ -87,9 +87,6 @@ class Application extends Module
*/ */
public $layout = 'main'; public $layout = 'main';
// todo
public $localeDataPath = '@yii/i18n/data';
private $_runtimePath; private $_runtimePath;
private $_ended = false; private $_ended = false;
@ -101,36 +98,42 @@ class Application extends Module
/** /**
* Constructor. * Constructor.
* @param string $id the ID of this application. The ID should uniquely identify the application from others. * @param array $config name-value pairs that will be used to initialize the object properties.
* @param string $basePath the base path of this application. This should point to * Note that the configuration must contain both [[id]] and [[basePath]].
* the directory containing all application logic, template and data. * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
* @param array $config name-value pairs that will be used to initialize the object properties
*/ */
public function __construct($id, $basePath, $config = array()) public function __construct($config = array())
{ {
Yii::$app = $this; Yii::$app = $this;
$this->id = $id;
$this->setBasePath($basePath);
if (YII_ENABLE_ERROR_HANDLER) { if (!isset($config['id'])) {
ini_set('display_errors', 0); throw new InvalidConfigException('The "id" configuration is required.');
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
} }
$this->registerDefaultAliases(); if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
Yii::$aliases['@app'] = $this->getBasePath();
} else {
throw new InvalidConfigException('The "basePath" configuration is required.');
}
$this->registerErrorHandlers();
$this->registerCoreComponents(); $this->registerCoreComponents();
Component::__construct($config); Component::__construct($config);
} }
/** /**
* Initializes the application by loading components declared in [[preload]]. * Registers error handlers.
* If you override this method, make sure the parent implementation is invoked.
*/ */
public function init() public function registerErrorHandlers()
{ {
$this->preloadComponents(); if (YII_ENABLE_ERROR_HANDLER) {
ini_set('display_errors', 0);
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
}
} }
/** /**
@ -165,31 +168,6 @@ class Application extends Module
if (ErrorException::isFatalErorr($error)) { if (ErrorException::isFatalErorr($error)) {
unset($this->_memoryReserve); unset($this->_memoryReserve);
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4, -1);
foreach ($trace as &$frame) {
if (!isset($frame['function'])) {
$frame['function'] = 'unknown';
}
// XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type'])) {
$frame['type'] = '::';
}
// XDebug has a different key name
$frame['args'] = array();
if (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
}
}
$ref = new \ReflectionProperty('Exception', 'trace');
$ref->setAccessible(true);
$ref->setValue($exception, $trace);
}
$this->logException($exception); $this->logException($exception);
if (($handler = $this->getErrorHandler()) !== null) { if (($handler = $this->getErrorHandler()) !== null) {
@ -295,37 +273,6 @@ class Application extends Module
date_default_timezone_set($value); date_default_timezone_set($value);
} }
//
// /**
// * Returns the locale instance.
// * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used.
// * @return CLocale the locale instance
// */
// public function getLocale($localeID = null)
// {
// return CLocale::getInstance($localeID === null ? $this->getLanguage() : $localeID);
// }
//
// /**
// * @return CNumberFormatter the locale-dependent number formatter.
// * The current {@link getLocale application locale} will be used.
// */
// public function getNumberFormatter()
// {
// return $this->getLocale()->getNumberFormatter();
// }
//
// /**
// * Returns the locale-dependent date formatter.
// * @return CDateFormatter the locale-dependent date formatter.
// * The current {@link getLocale application locale} will be used.
// */
// public function getDateFormatter()
// {
// return $this->getLocale()->getDateFormatter();
// }
//
/** /**
* Returns the database connection component. * Returns the database connection component.
* @return \yii\db\Connection the database connection * @return \yii\db\Connection the database connection
@ -390,14 +337,6 @@ class Application extends Module
} }
/** /**
* Sets default path aliases.
*/
public function registerDefaultAliases()
{
Yii::$aliases['@app'] = $this->getBasePath();
}
/**
* Registers the core application components. * Registers the core application components.
* @see setComponents * @see setComponents
*/ */

146
framework/base/Component.php

@ -7,26 +7,23 @@
namespace yii\base; namespace yii\base;
use Yii;
/** /**
* Component is the base class that provides the *property*, *event* and *behavior* features.
*
* @include @yii/base/Component.md * @include @yii/base/Component.md
*
* @property Behavior[] behaviors list of behaviors currently attached to this component
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Component extends \yii\base\Object class Component extends Object
{ {
/** /**
* @var Vector[] the attached event handlers (event name => handlers) * @var array the attached event handlers (event name => handlers)
*/ */
private $_e; private $_events;
/** /**
* @var Behavior[] the attached behaviors (behavior name => behavior) * @var Behavior[] the attached behaviors (behavior name => behavior)
*/ */
private $_b; private $_behaviors;
/** /**
* Returns the value of a component property. * Returns the value of a component property.
@ -52,7 +49,7 @@ class Component extends \yii\base\Object
} else { } else {
// behavior property // behavior property
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) { if ($behavior->canGetProperty($name)) {
return $behavior->$name; return $behavior->$name;
} }
@ -87,17 +84,16 @@ class Component extends \yii\base\Object
return; return;
} elseif (strncmp($name, 'on ', 3) === 0) { } elseif (strncmp($name, 'on ', 3) === 0) {
// on event: attach event handler // on event: attach event handler
$name = trim(substr($name, 3)); $this->on(trim(substr($name, 3)), $value);
$this->getEventHandlers($name)->add($value);
return; return;
} elseif (strncmp($name, 'as ', 3) === 0) { } elseif (strncmp($name, 'as ', 3) === 0) {
// as behavior: attach behavior // as behavior: attach behavior
$name = trim(substr($name, 3)); $name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : \Yii::createObject($value)); $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
} else { } else {
// behavior property // behavior property
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) { if ($behavior->canSetProperty($name)) {
$behavior->$name = $value; $behavior->$name = $value;
return; return;
@ -131,7 +127,7 @@ class Component extends \yii\base\Object
} else { } else {
// behavior property // behavior property
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) { if ($behavior->canGetProperty($name)) {
return $behavior->$name !== null; return $behavior->$name !== null;
} }
@ -161,7 +157,7 @@ class Component extends \yii\base\Object
} else { } else {
// behavior property // behavior property
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) { if ($behavior->canSetProperty($name)) {
$behavior->$name = null; $behavior->$name = null;
return; return;
@ -198,7 +194,7 @@ class Component extends \yii\base\Object
} }
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $object) { foreach ($this->_behaviors as $object) {
if (method_exists($object, $name)) { if (method_exists($object, $name)) {
return call_user_func_array(array($object, $name), $params); return call_user_func_array(array($object, $name), $params);
} }
@ -213,8 +209,8 @@ class Component extends \yii\base\Object
*/ */
public function __clone() public function __clone()
{ {
$this->_e = null; $this->_events = null;
$this->_b = null; $this->_behaviors = null;
} }
/** /**
@ -259,7 +255,7 @@ class Component extends \yii\base\Object
return true; return true;
} else { } else {
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name, $checkVar)) { if ($behavior->canGetProperty($name, $checkVar)) {
return true; return true;
} }
@ -289,7 +285,7 @@ class Component extends \yii\base\Object
return true; return true;
} else { } else {
$this->ensureBehaviors(); $this->ensureBehaviors();
foreach ($this->_b as $behavior) { foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name, $checkVar)) { if ($behavior->canSetProperty($name, $checkVar)) {
return true; return true;
} }
@ -337,44 +333,17 @@ class Component extends \yii\base\Object
public function hasEventHandlers($name) public function hasEventHandlers($name)
{ {
$this->ensureBehaviors(); $this->ensureBehaviors();
return isset($this->_e[$name]) && $this->_e[$name]->getCount(); return !empty($this->_events[$name]);
}
/**
* Returns the list of attached event handlers for an event.
* You may manipulate the returned [[Vector]] object by adding or removing handlers.
* For example,
*
* ~~~
* $component->getEventHandlers($eventName)->insertAt(0, $eventHandler);
* ~~~
*
* @param string $name the event name
* @return Vector list of attached event handlers for the event
*/
public function getEventHandlers($name)
{
if (!isset($this->_e[$name])) {
$this->_e[$name] = new Vector;
}
$this->ensureBehaviors();
return $this->_e[$name];
} }
/** /**
* Attaches an event handler to an event. * Attaches an event handler to an event.
* *
* This is equivalent to the following code:
*
* ~~~
* $component->getEventHandlers($eventName)->add($eventHandler);
* ~~~
*
* An event handler must be a valid PHP callback. The followings are * An event handler must be a valid PHP callback. The followings are
* some examples: * some examples:
* *
* ~~~ * ~~~
* function($event) { ... } // anonymous function * function ($event) { ... } // anonymous function
* array($object, 'handleClick') // $object->handleClick() * array($object, 'handleClick') // $object->handleClick()
* array('Page', 'handleClick') // Page::handleClick() * array('Page', 'handleClick') // Page::handleClick()
* 'handleClick' // global function handleClick() * 'handleClick' // global function handleClick()
@ -383,31 +352,53 @@ class Component extends \yii\base\Object
* An event handler must be defined with the following signature, * An event handler must be defined with the following signature,
* *
* ~~~ * ~~~
* function handlerName($event) {} * function ($event)
* ~~~ * ~~~
* *
* where `$event` is an [[Event]] object which includes parameters associated with the event. * where `$event` is an [[Event]] object which includes parameters associated with the event.
* *
* @param string $name the event name * @param string $name the event name
* @param string|array|\Closure $handler the event handler * @param callback $handler the event handler
* @param mixed $data the data to be passed to the event handler when the event is triggered.
* When the event handler is invoked, this data can be accessed via [[Event::data]].
* @see off() * @see off()
*/ */
public function on($name, $handler) public function on($name, $handler, $data = null)
{ {
$this->getEventHandlers($name)->add($handler); $this->ensureBehaviors();
$this->_events[$name][] = array($handler, $data);
} }
/** /**
* Detaches an existing event handler from this component. * Detaches an existing event handler from this component.
* This method is the opposite of [[on()]]. * This method is the opposite of [[on()]].
* @param string $name event name * @param string $name event name
* @param string|array|\Closure $handler the event handler to be removed * @param callback $handler the event handler to be removed.
* If it is null, all handlers attached to the named event will be removed.
* @return boolean if a handler is found and detached * @return boolean if a handler is found and detached
* @see on() * @see on()
*/ */
public function off($name, $handler) public function off($name, $handler = null)
{ {
return $this->getEventHandlers($name)->remove($handler) !== false; $this->ensureBehaviors();
if (isset($this->_events[$name])) {
if ($handler === null) {
$this->_events[$name] = array();
} else {
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
}
return $removed;
}
}
return false;
} }
/** /**
@ -420,7 +411,7 @@ class Component extends \yii\base\Object
public function trigger($name, $event = null) public function trigger($name, $event = null)
{ {
$this->ensureBehaviors(); $this->ensureBehaviors();
if (isset($this->_e[$name]) && $this->_e[$name]->getCount()) { if (!empty($this->_events[$name])) {
if ($event === null) { if ($event === null) {
$event = new Event; $event = new Event;
} }
@ -429,8 +420,9 @@ class Component extends \yii\base\Object
} }
$event->handled = false; $event->handled = false;
$event->name = $name; $event->name = $name;
foreach ($this->_e[$name] as $handler) { foreach ($this->_events[$name] as $handler) {
call_user_func($handler, $event); $event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled // stop further handling if the event is handled
if ($event instanceof Event && $event->handled) { if ($event instanceof Event && $event->handled) {
return; return;
@ -447,7 +439,7 @@ class Component extends \yii\base\Object
public function getBehavior($name) public function getBehavior($name)
{ {
$this->ensureBehaviors(); $this->ensureBehaviors();
return isset($this->_b[$name]) ? $this->_b[$name] : null; return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
} }
/** /**
@ -457,20 +449,20 @@ class Component extends \yii\base\Object
public function getBehaviors() public function getBehaviors()
{ {
$this->ensureBehaviors(); $this->ensureBehaviors();
return $this->_b; return $this->_behaviors;
} }
/** /**
* Attaches a behavior to this component. * Attaches a behavior to this component.
* This method will create the behavior object based on the given * This method will create the behavior object based on the given
* configuration. After that, the behavior object will be attached to * configuration. After that, the behavior object will be attached to
* this component by calling the [[Behavior::attach]] method. * this component by calling the [[Behavior::attach()]] method.
* @param string $name the name of the behavior. * @param string $name the name of the behavior.
* @param string|array|Behavior $behavior the behavior configuration. This can be one of the following: * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
* *
* - a [[Behavior]] object * - a [[Behavior]] object
* - a string specifying the behavior class * - a string specifying the behavior class
* - an object configuration array that will be passed to [[\Yii::createObject()]] to create the behavior object. * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
* *
* @return Behavior the behavior object * @return Behavior the behavior object
* @see detachBehavior * @see detachBehavior
@ -498,15 +490,15 @@ class Component extends \yii\base\Object
/** /**
* Detaches a behavior from the component. * Detaches a behavior from the component.
* The behavior's [[Behavior::detach]] method will be invoked. * The behavior's [[Behavior::detach()]] method will be invoked.
* @param string $name the behavior's name. * @param string $name the behavior's name.
* @return Behavior the detached behavior. Null if the behavior does not exist. * @return Behavior the detached behavior. Null if the behavior does not exist.
*/ */
public function detachBehavior($name) public function detachBehavior($name)
{ {
if (isset($this->_b[$name])) { if (isset($this->_behaviors[$name])) {
$behavior = $this->_b[$name]; $behavior = $this->_behaviors[$name];
unset($this->_b[$name]); unset($this->_behaviors[$name]);
$behavior->detach(); $behavior->detach();
return $behavior; return $behavior;
} else { } else {
@ -519,12 +511,12 @@ class Component extends \yii\base\Object
*/ */
public function detachBehaviors() public function detachBehaviors()
{ {
if ($this->_b !== null) { if ($this->_behaviors !== null) {
foreach ($this->_b as $name => $behavior) { foreach ($this->_behaviors as $name => $behavior) {
$this->detachBehavior($name); $this->detachBehavior($name);
} }
} }
$this->_b = array(); $this->_behaviors = array();
} }
/** /**
@ -532,8 +524,8 @@ class Component extends \yii\base\Object
*/ */
public function ensureBehaviors() public function ensureBehaviors()
{ {
if ($this->_b === null) { if ($this->_behaviors === null) {
$this->_b = array(); $this->_behaviors = array();
foreach ($this->behaviors() as $name => $behavior) { foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior); $this->attachBehaviorInternal($name, $behavior);
} }
@ -549,12 +541,12 @@ class Component extends \yii\base\Object
private function attachBehaviorInternal($name, $behavior) private function attachBehaviorInternal($name, $behavior)
{ {
if (!($behavior instanceof Behavior)) { if (!($behavior instanceof Behavior)) {
$behavior = \Yii::createObject($behavior); $behavior = Yii::createObject($behavior);
} }
if (isset($this->_b[$name])) { if (isset($this->_behaviors[$name])) {
$this->_b[$name]->detach(); $this->_behaviors[$name]->detach();
} }
$behavior->attach($this); $behavior->attach($this);
return $this->_b[$name] = $behavior; return $this->_behaviors[$name] = $behavior;
} }
} }

3
framework/base/Controller.php

@ -357,7 +357,8 @@ class Controller extends Component
*/ */
public function renderPartial($view, $params = array()) public function renderPartial($view, $params = array())
{ {
return $this->getView()->render($view, $params, $this); $viewFile = $this->findViewFile($view);
return $this->getView()->renderFile($viewFile, $params, $this);
} }
/** /**

56
framework/base/ErrorException.php

@ -7,6 +7,8 @@
namespace yii\base; namespace yii\base;
use Yii;
/** /**
* ErrorException represents a PHP error. * ErrorException represents a PHP error.
* *
@ -33,6 +35,32 @@ class ErrorException extends Exception
$this->severity = $severity; $this->severity = $severity;
$this->file = $filename; $this->file = $filename;
$this->line = $lineno; $this->line = $lineno;
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
foreach ($trace as &$frame) {
if (!isset($frame['function'])) {
$frame['function'] = 'unknown';
}
// XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type']) || $frame['type'] === 'static') {
$frame['type'] = '::';
} elseif ($frame['type'] === 'dynamic') {
$frame['type'] = '->';
}
// XDebug has a different key name
$frame['args'] = array();
if (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
}
}
$ref = new \ReflectionProperty('Exception', 'trace');
$ref->setAccessible(true);
$ref->setValue($this, $trace);
}
} }
/** /**
@ -62,20 +90,20 @@ class ErrorException extends Exception
public function getName() public function getName()
{ {
$names = array( $names = array(
E_ERROR => \Yii::t('yii|Fatal Error'), E_ERROR => Yii::t('yii|Fatal Error'),
E_PARSE => \Yii::t('yii|Parse Error'), E_PARSE => Yii::t('yii|Parse Error'),
E_CORE_ERROR => \Yii::t('yii|Core Error'), E_CORE_ERROR => Yii::t('yii|Core Error'),
E_COMPILE_ERROR => \Yii::t('yii|Compile Error'), E_COMPILE_ERROR => Yii::t('yii|Compile Error'),
E_USER_ERROR => \Yii::t('yii|User Error'), E_USER_ERROR => Yii::t('yii|User Error'),
E_WARNING => \Yii::t('yii|Warning'), E_WARNING => Yii::t('yii|Warning'),
E_CORE_WARNING => \Yii::t('yii|Core Warning'), E_CORE_WARNING => Yii::t('yii|Core Warning'),
E_COMPILE_WARNING => \Yii::t('yii|Compile Warning'), E_COMPILE_WARNING => Yii::t('yii|Compile Warning'),
E_USER_WARNING => \Yii::t('yii|User Warning'), E_USER_WARNING => Yii::t('yii|User Warning'),
E_STRICT => \Yii::t('yii|Strict'), E_STRICT => Yii::t('yii|Strict'),
E_NOTICE => \Yii::t('yii|Notice'), E_NOTICE => Yii::t('yii|Notice'),
E_RECOVERABLE_ERROR => \Yii::t('yii|Recoverable Error'), E_RECOVERABLE_ERROR => Yii::t('yii|Recoverable Error'),
E_DEPRECATED => \Yii::t('yii|Deprecated'), E_DEPRECATED => Yii::t('yii|Deprecated'),
); );
return isset($names[$this->getCode()]) ? $names[$this->getCode()] : \Yii::t('yii|Error'); return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii|Error');
} }
} }

9
framework/base/Event.php

@ -15,12 +15,14 @@ namespace yii\base;
* And the [[handled]] property indicates if the event is handled. * And the [[handled]] property indicates if the event is handled.
* If an event handler sets [[handled]] to be true, the rest of the * If an event handler sets [[handled]] to be true, the rest of the
* uninvoked handlers will no longer be called to handle the event. * uninvoked handlers will no longer be called to handle the event.
* Additionally, an event may specify extra parameters via the [[data]] property. *
* Additionally, when attaching an event handler, extra data may be passed
* and be available via the [[data]] property when the event handler is invoked.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Event extends \yii\base\Object class Event extends Object
{ {
/** /**
* @var string the event name. This property is set by [[Component::trigger()]]. * @var string the event name. This property is set by [[Component::trigger()]].
@ -39,7 +41,8 @@ class Event extends \yii\base\Object
*/ */
public $handled = false; public $handled = false;
/** /**
* @var mixed extra custom data associated with the event. * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
* Note that this varies according to which event handler is currently executing.
*/ */
public $data; public $data;
} }

22
framework/base/Model.php

@ -8,8 +8,8 @@
namespace yii\base; namespace yii\base;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
use yii\validators\Validator;
use yii\validators\RequiredValidator; use yii\validators\RequiredValidator;
use yii\validators\Validator;
/** /**
* Model is the base class for data models. * Model is the base class for data models.
@ -169,6 +169,26 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
} }
/** /**
* Returns the form name that this model class should use.
*
* The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
* the input fields for the attributes in a model. If the form name is "A" and an attribute
* name is "b", then the corresponding input name would be "A[b]". If the form name is
* an empty string, then the input name would be "b".
*
* By default, this method returns the model class name (without the namespace part)
* as the form name. You may override it when the model is used in different forms.
*
* @return string the form name of this model class.
*/
public function formName()
{
$class = get_class($this);
$pos = strrpos($class, '\\');
return $pos === false ? $class : substr($class, $pos + 1);
}
/**
* Returns the list of attribute names. * Returns the list of attribute names.
* By default, this method returns all public non-static properties of the class. * By default, this method returns all public non-static properties of the class.
* You may override this method to change the default behavior. * You may override this method to change the default behavior.

25
framework/base/Module.php

@ -170,7 +170,6 @@ abstract class Module extends Component
*/ */
public function init() public function init()
{ {
Yii::setAlias('@' . $this->id, $this->getBasePath());
$this->preloadComponents(); $this->preloadComponents();
} }
@ -580,8 +579,9 @@ abstract class Module extends Component
* instance of it. * instance of it.
* *
* @param string $route the route consisting of module, controller and action IDs. * @param string $route the route consisting of module, controller and action IDs.
* @return array|boolean if the controller is created successfully, it will be returned together * @return array|boolean If the controller is created successfully, it will be returned together
* with the remainder of the route which represents the action ID. Otherwise false will be returned. * with the requested action ID. Otherwise false will be returned.
* @throws InvalidConfigException if the controller class and its file do not match.
*/ */
public function createController($route) public function createController($route)
{ {
@ -605,21 +605,14 @@ abstract class Module extends Component
$controller = Yii::createObject($this->controllerMap[$id], $id, $this); $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$className = StringHelper::id2camel($id) . 'Controller'; $className = StringHelper::id2camel($id) . 'Controller';
$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) { $className = ltrim($this->controllerNamespace . '\\' . $className, '\\');
$className = $this->controllerNamespace . '\\' . $className; Yii::$classMap[$className] = $classFile;
if (!class_exists($className, false)) { if (class_exists($className)) {
require($classFile); if (is_subclass_of($className, 'yii\base\Controller')) {
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
$controller = new $className($id, $this); $controller = new $className($id, $this);
} elseif (YII_DEBUG) { } elseif (YII_DEBUG && !is_subclass_of($className, 'yii\base\Controller')) {
if (!class_exists($className, false)) { throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
throw new InvalidConfigException("Class file name does not match class name: $className.");
} elseif (!is_subclass_of($className, '\yii\base\Controller')) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
}
} }
} }
} }

3
framework/base/Object.php

@ -8,10 +8,7 @@
namespace yii\base; namespace yii\base;
/** /**
* Object is the base class that provides the *property* feature.
*
* @include @yii/base/Object.md * @include @yii/base/Object.md
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

35
framework/base/Theme.php

@ -33,11 +33,17 @@ use yii\helpers\FileHelper;
class Theme extends Component class Theme extends Component
{ {
/** /**
* @var string the root path of this theme. * @var string the root path or path alias of this theme. All resources of this theme are located
* under this directory. This property must be set if [[pathMap]] is not set.
* @see pathMap * @see pathMap
*/ */
public $basePath; public $basePath;
/** /**
* @var string the base URL (or path alias) for this theme. All resources of this theme are considered
* to be under this base URL. This property must be set. It is mainly used by [[getUrl()]].
*/
public $baseUrl;
/**
* @var array the mapping between view directories and their corresponding themed versions. * @var array the mapping between view directories and their corresponding themed versions.
* If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]]. * If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
* This property is used by [[applyTo()]] when a view is trying to apply the theme. * This property is used by [[applyTo()]] when a view is trying to apply the theme.
@ -45,7 +51,6 @@ class Theme extends Component
*/ */
public $pathMap; public $pathMap;
private $_baseUrl;
/** /**
* Initializes the theme. * Initializes the theme.
@ -69,25 +74,11 @@ class Theme extends Component
$paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR; $paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR;
} }
$this->pathMap = $paths; $this->pathMap = $paths;
} if ($this->baseUrl === null) {
throw new InvalidConfigException("Theme::baseUrl must be set.");
/** } else {
* Returns the base URL for this theme. $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
* The method [[getUrl()]] will prefix this to the given URL. }
* @return string the base URL for this theme.
*/
public function getBaseUrl()
{
return $this->_baseUrl;
}
/**
* Sets the base URL for this theme.
* @param string $value the base URL for this theme.
*/
public function setBaseUrl($value)
{
$this->_baseUrl = rtrim(Yii::getAlias($value), '/');
} }
/** /**
@ -112,7 +103,7 @@ class Theme extends Component
} }
/** /**
* Converts a relative URL into an absolute URL using [[basePath]]. * Converts a relative URL into an absolute URL using [[baseUrl]].
* @param string $url the relative URL to be converted. * @param string $url the relative URL to be converted.
* @return string the absolute URL * @return string the absolute URL
*/ */

65
framework/base/View.php

@ -22,10 +22,23 @@ use yii\helpers\FileHelper;
class View extends Component class View extends Component
{ {
/** /**
* @event Event an event that is triggered by [[renderFile()]] right before it renders a view file.
*/
const EVENT_BEFORE_RENDER = 'beforeRender';
/**
* @event Event an event that is triggered by [[renderFile()]] right after it renders a view file.
*/
const EVENT_AFTER_RENDER = 'afterRender';
/**
* @var object the object that owns this view. This can be a controller, a widget, or any other object. * @var object the object that owns this view. This can be a controller, a widget, or any other object.
*/ */
public $context; public $context;
/** /**
* @var ViewContent
*/
public $content;
/**
* @var mixed custom parameters that are shared among view templates. * @var mixed custom parameters that are shared among view templates.
*/ */
public $params; public $params;
@ -47,7 +60,7 @@ class View extends Component
public $clips; public $clips;
/** /**
* @var Widget[] the widgets that are currently being rendered (not ended). This property * @var Widget[] the widgets that are currently being rendered (not ended). This property
* is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly. * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it.
*/ */
public $widgetStack = array(); public $widgetStack = array();
/** /**
@ -74,6 +87,11 @@ class View extends Component
if (is_array($this->theme)) { if (is_array($this->theme)) {
$this->theme = Yii::createObject($this->theme); $this->theme = Yii::createObject($this->theme);
} }
if (is_array($this->content)) {
$this->content = Yii::createObject($this->content);
} else {
$this->content = new ViewContent;
}
} }
/** /**
@ -140,10 +158,15 @@ class View extends Component
$this->context = $context; $this->context = $context;
} }
if ($this->renderer !== null) { $output = '';
$output = $this->renderer->render($this, $viewFile, $params); if ($this->beforeRender($viewFile)) {
} else { if ($this->renderer !== null) {
$output = $this->renderPhpFile($viewFile, $params); $output = $this->renderer->render($this, $viewFile, $params);
} else {
$output = $this->renderPhpFile($viewFile, $params);
}
$output = $this->content->populate($output);
$this->afterRender($viewFile, $output);
} }
$this->context = $oldContext; $this->context = $oldContext;
@ -152,6 +175,38 @@ class View extends Component
} }
/** /**
* This method is invoked right before [[renderFile()]] renders a view file.
* The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
* If you override this method, make sure you call the parent implementation first.
* @param string $viewFile the view file to be rendered
* @return boolean whether to continue rendering the view file.
*/
public function beforeRender($viewFile)
{
$event = new ViewEvent($viewFile);
$this->trigger(self::EVENT_BEFORE_RENDER, $event);
return $event->isValid;
}
/**
* This method is invoked right after [[renderFile()]] renders a view file.
* The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
* If you override this method, make sure you call the parent implementation first.
* @param string $viewFile the view file to be rendered
* @param string $output the rendering result of the view file. Updates to this parameter
* will be passed back and returned by [[renderFile()]].
*/
public function afterRender($viewFile, &$output)
{
if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
$event = new ViewEvent($viewFile);
$event->output = $output;
$this->trigger(self::EVENT_AFTER_RENDER, $event);
$output = $event->output;
}
}
/**
* Renders a view file as a PHP script. * Renders a view file as a PHP script.
* *
* This method treats the view file as a PHP script and includes the file. * This method treats the view file as a PHP script and includes the file.

136
framework/base/ViewContent.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\base;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ViewContent extends Component
{
const POS_HEAD = 1;
const POS_BEGIN = 2;
const POS_END = 3;
/**
* @var array
*
* Each asset bundle should be declared with the following structure:
*
* ~~~
* array(
* 'basePath' => '...',
* 'baseUrl' => '...', // if missing, the bundle will be published to the "www/assets" folder
* 'js' => array(
* 'js/main.js',
* 'js/menu.js',
* 'js/base.js' => self::POS_HEAD,
* 'css' => array(
* 'css/main.css',
* 'css/menu.css',
* ),
* 'depends' => array(
* 'jquery',
* 'yii',
* 'yii/treeview',
* ),
* )
* ~~~
*/
public $bundles;
public $title;
public $metaTags;
public $linkTags;
public $css;
public $js;
public $cssFiles;
public $jsFiles;
public function populate($content)
{
return $content;
}
public function reset()
{
$this->title = null;
$this->metaTags = null;
$this->linkTags = null;
$this->css = null;
$this->js = null;
$this->cssFiles = null;
$this->jsFiles = null;
}
public function registerBundle($name)
{
}
public function getMetaTag($key)
{
return isset($this->metaTags[$key]) ? $this->metaTags[$key] : null;
}
public function setMetaTag($key, $tag)
{
$this->metaTags[$key] = $tag;
}
public function getLinkTag($key)
{
return isset($this->linkTags[$key]) ? $this->linkTags[$key] : null;
}
public function setLinkTag($key, $tag)
{
$this->linkTags[$key] = $tag;
}
public function getCss($key)
{
return isset($this->css[$key]) ? $this->css[$key]: null;
}
public function setCss($key, $css)
{
$this->css[$key] = $css;
}
public function getCssFile($key)
{
return isset($this->cssFiles[$key]) ? $this->cssFiles[$key]: null;
}
public function setCssFile($key, $file)
{
$this->cssFiles[$key] = $file;
}
public function getJs($key, $position = self::POS_END)
{
return isset($this->js[$position][$key]) ? $this->js[$position][$key] : null;
}
public function setJs($key, $js, $position = self::POS_END)
{
$this->js[$position][$key] = $js;
}
public function getJsFile($key, $position = self::POS_END)
{
return isset($this->jsFiles[$position][$key]) ? $this->jsFiles[$position][$key] : null;
}
public function setJsFile($key, $file, $position = self::POS_END)
{
$this->jsFiles[$position][$key] = $file;
}
}

44
framework/base/ViewEvent.php

@ -0,0 +1,44 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ViewEvent extends Event
{
/**
* @var string the rendering result of [[View::renderFile()]].
* Event handlers may modify this property and the modified output will be
* returned by [[View::renderFile()]]. This property is only used
* by [[View::EVENT_AFTER_RENDER]] event.
*/
public $output;
/**
* @var string the view file path that is being rendered by [[View::renderFile()]].
*/
public $viewFile;
/**
* @var boolean whether to continue rendering the view file. Event handlers of
* [[View::EVENT_BEFORE_RENDER]] may set this property to decide whether
* to continue rendering the current view file.
*/
public $isValid = true;
/**
* Constructor.
* @param string $viewFile the view file path that is being rendered by [[View::renderFile()]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($viewFile, $config = array())
{
$this->viewFile = $viewFile;
parent::__construct($config);
}
}

20
framework/caching/ChainedDependency.php

@ -22,11 +22,10 @@ namespace yii\caching;
class ChainedDependency extends Dependency class ChainedDependency extends Dependency
{ {
/** /**
* @var array list of dependencies that this dependency is composed of. * @var Dependency[] list of dependencies that this dependency is composed of.
* Each array element should be a dependency object or a configuration array * Each array element must be a dependency object.
* that can be used to create a dependency object via [[\Yii::createObject()]].
*/ */
public $dependencies = array(); public $dependencies;
/** /**
* @var boolean whether this dependency is depending on every dependency in [[dependencies]]. * @var boolean whether this dependency is depending on every dependency in [[dependencies]].
* Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed. * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
@ -37,9 +36,8 @@ class ChainedDependency extends Dependency
/** /**
* Constructor. * Constructor.
* @param array $dependencies list of dependencies that this dependency is composed of. * @param Dependency[] $dependencies list of dependencies that this dependency is composed of.
* Each array element should be a dependency object or a configuration array * Each array element should be a dependency object.
* that can be used to create a dependency object via [[\Yii::createObject()]].
* @param array $config name-value pairs that will be used to initialize the object properties * @param array $config name-value pairs that will be used to initialize the object properties
*/ */
public function __construct($dependencies = array(), $config = array()) public function __construct($dependencies = array(), $config = array())
@ -54,9 +52,6 @@ class ChainedDependency extends Dependency
public function evaluateDependency() public function evaluateDependency()
{ {
foreach ($this->dependencies as $dependency) { foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Dependency) {
$dependency = \Yii::createObject($dependency);
}
$dependency->evaluateDependency(); $dependency->evaluateDependency();
} }
} }
@ -79,10 +74,7 @@ class ChainedDependency extends Dependency
*/ */
public function getHasChanged() public function getHasChanged()
{ {
foreach ($this->dependencies as $i => $dependency) { foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Dependency) {
$this->dependencies[$i] = $dependency = \Yii::createObject($dependency);
}
if ($this->dependOnAll && $dependency->getHasChanged()) { if ($this->dependOnAll && $dependency->getHasChanged()) {
return true; return true;
} elseif (!$this->dependOnAll && !$dependency->getHasChanged()) { } elseif (!$this->dependOnAll && !$dependency->getHasChanged()) {

18
framework/caching/DbDependency.php

@ -28,23 +28,25 @@ class DbDependency extends Dependency
public $db = 'db'; public $db = 'db';
/** /**
* @var string the SQL query whose result is used to determine if the dependency has been changed. * @var string the SQL query whose result is used to determine if the dependency has been changed.
* Only the first row of the query result will be used. This property must be always set, otherwise * Only the first row of the query result will be used.
* an exception would be raised.
*/ */
public $sql; public $sql;
/** /**
* @var array the parameters (name=>value) to be bound to the SQL statement specified by [[sql]]. * @var array the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
*/ */
public $params = array(); public $params;
/** /**
* Initializes the database dependency object. * Constructor.
* @param string $sql the SQL query whose result is used to determine if the dependency has been changed.
* @param array $params the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/ */
public function init() public function __construct($sql, $params = array(), $config = array())
{ {
if ($this->sql === null) { $this->sql = $sql;
throw new InvalidConfigException('DbDependency::sql must be set.'); $this->params = $params;
} parent::__construct($config);
} }
/** /**

13
framework/caching/ExpressionDependency.php

@ -22,7 +22,18 @@ class ExpressionDependency extends Dependency
/** /**
* @var string the PHP expression whose result is used to determine the dependency. * @var string the PHP expression whose result is used to determine the dependency.
*/ */
public $expression = 'true'; public $expression;
/**
* Constructor.
* @param string $expression the PHP expression whose result is used to determine the dependency.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($expression = 'true', $config = array())
{
$this->expression = $expression;
parent::__construct($config);
}
/** /**
* Generates the data needed to determine if dependency has been changed. * Generates the data needed to determine if dependency has been changed.

16
framework/caching/FileDependency.php

@ -7,8 +7,6 @@
namespace yii\caching; namespace yii\caching;
use yii\base\InvalidConfigException;
/** /**
* FileDependency represents a dependency based on a file's last modification time. * FileDependency represents a dependency based on a file's last modification time.
* *
@ -22,19 +20,19 @@ class FileDependency extends Dependency
{ {
/** /**
* @var string the name of the file whose last modification time is used to * @var string the name of the file whose last modification time is used to
* check if the dependency has been changed. This property must be always set, * check if the dependency has been changed.
* otherwise an exception would be raised.
*/ */
public $fileName; public $fileName;
/** /**
* Initializes the database dependency object. * Constructor.
* @param string $fileName name of the file whose change is to be checked.
* @param array $config name-value pairs that will be used to initialize the object properties
*/ */
public function init() public function __construct($fileName = null, $config = array())
{ {
if ($this->file === null) { $this->fileName = $fileName;
throw new InvalidConfigException('FileDependency::fileName must be set.'); parent::__construct($config);
}
} }
/** /**

24
framework/logging/FileTarget.php

@ -6,6 +6,8 @@
*/ */
namespace yii\logging; namespace yii\logging;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
/** /**
@ -23,15 +25,14 @@ use yii\base\InvalidConfigException;
class FileTarget extends Target class FileTarget extends Target
{ {
/** /**
* @var string log file path or path alias. If not set, it means the 'application.log' file under * @var string log file path or path alias. If not set, it will use the "runtime/logs/app.log" file.
* the application runtime directory. Please make sure the directory containing * The directory containing the log files will be automatically created if not existing.
* the log file is writable by the Web server process.
*/ */
public $logFile; public $logFile;
/** /**
* @var integer maximum log file size, in kilo-bytes. Defaults to 1024, meaning 1MB. * @var integer maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB.
*/ */
public $maxFileSize = 1024; // in KB public $maxFileSize = 10240; // in KB
/** /**
* @var integer number of log files used for rotation. Defaults to 5. * @var integer number of log files used for rotation. Defaults to 5.
*/ */
@ -46,13 +47,13 @@ class FileTarget extends Target
{ {
parent::init(); parent::init();
if ($this->logFile === null) { if ($this->logFile === null) {
$this->logFile = \Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'application.log'; $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log';
} else { } else {
$this->logFile = \Yii::getAlias($this->logFile); $this->logFile = Yii::getAlias($this->logFile);
} }
$logPath = dirname($this->logFile); $logPath = dirname($this->logFile);
if (!is_dir($logPath) || !is_writable($logPath)) { if (!is_dir($logPath)) {
throw new InvalidConfigException("Directory '$logPath' does not exist or is not writable."); @mkdir($logPath, 0777, true);
} }
if ($this->maxLogFiles < 1) { if ($this->maxLogFiles < 1) {
$this->maxLogFiles = 1; $this->maxLogFiles = 1;
@ -66,6 +67,7 @@ class FileTarget extends Target
* Sends log messages to specified email addresses. * Sends log messages to specified email addresses.
* @param array $messages the messages to be exported. See [[Logger::messages]] for the structure * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message. * of each message.
* @throws InvalidConfigException if unable to open the log file for writing
*/ */
public function export($messages) public function export($messages)
{ {
@ -73,7 +75,9 @@ class FileTarget extends Target
foreach ($messages as $message) { foreach ($messages as $message) {
$text .= $this->formatMessage($message); $text .= $this->formatMessage($message);
} }
$fp = @fopen($this->logFile, 'a'); if (($fp = @fopen($this->logFile, 'a')) === false) {
throw new InvalidConfigException("Unable to append to log file: {$this->logFile}");
}
@flock($fp, LOCK_EX); @flock($fp, LOCK_EX);
if (@filesize($this->logFile) > $this->maxFileSize * 1024) { if (@filesize($this->logFile) > $this->maxFileSize * 1024) {
$this->rotateFiles(); $this->rotateFiles();

39
framework/validators/BooleanValidator.php

@ -7,6 +7,8 @@
namespace yii\validators; namespace yii\validators;
use Yii;
/** /**
* BooleanValidator checks if the attribute value is a boolean value. * BooleanValidator checks if the attribute value is a boolean value.
* *
@ -32,11 +34,17 @@ class BooleanValidator extends Validator
* Defaults to false, meaning only the value needs to be matched. * Defaults to false, meaning only the value needs to be matched.
*/ */
public $strict = false; public $strict = false;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true, * Initializes the validator.
* meaning that if the attribute is empty, it is considered valid.
*/ */
public $allowEmpty = true; public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} must be either "{true}" or "{false}".');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -47,13 +55,8 @@ class BooleanValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (!$this->validateValue($value)) {
return; $this->addError($object, $attribute, $this->message, array(
}
if (!$this->strict && $value != $this->trueValue && $value != $this->falseValue
|| $this->strict && $value !== $this->trueValue && $value !== $this->falseValue) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be either {true} or {false}.');
$this->addError($object, $attribute, $message, array(
'{true}' => $this->trueValue, '{true}' => $this->trueValue,
'{false}' => $this->falseValue, '{false}' => $this->falseValue,
)); ));
@ -61,6 +64,17 @@ class BooleanValidator extends Validator
} }
/** /**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
return $this->strict && ($value == $this->trueValue || $value == $this->falseValue)
|| !$this->strict && ($value === $this->trueValue || $value === $this->falseValue);
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
@ -68,15 +82,14 @@ class BooleanValidator extends Validator
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be either {true} or {false}.'); $message = strtr($this->message, array(
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
'{true}' => $this->trueValue, '{true}' => $this->trueValue,
'{false}' => $this->falseValue, '{false}' => $this->falseValue,
)); ));
return " return "
if(" . ($this->allowEmpty ? "$.trim(value)!='' && " : '') . "value!=" . json_encode($this->trueValue) . " && value!=" . json_encode($this->falseValue) . ") { if(" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . "value!=" . json_encode($this->trueValue) . " && value!=" . json_encode($this->falseValue) . ") {
messages.push(" . json_encode($message) . "); messages.push(" . json_encode($message) . ");
} }
"; ";

67
framework/validators/CaptchaValidator.php

@ -7,6 +7,9 @@
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
/** /**
* CaptchaValidator validates that the attribute value is the same as the verification code displayed in the CAPTCHA. * CaptchaValidator validates that the attribute value is the same as the verification code displayed in the CAPTCHA.
* *
@ -22,16 +25,21 @@ class CaptchaValidator extends Validator
*/ */
public $caseSensitive = false; public $caseSensitive = false;
/** /**
* @var string the ID of the action that renders the CAPTCHA image. Defaults to 'captcha', * @var string the route of the controller action that renders the CAPTCHA image.
* meaning the `captcha` action declared in the current controller.
* This can also be a route consisting of controller ID and action ID (e.g. 'site/captcha').
*/ */
public $captchaAction = 'captcha'; public $captchaAction = 'site/captcha';
/** /**
* @var boolean whether the attribute value can be null or empty. * Initializes the validator.
* Defaults to false, meaning the attribute is invalid if it is empty.
*/ */
public $allowEmpty = false; public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|The verification code is incorrect.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -42,36 +50,38 @@ class CaptchaValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (!$this->validateValue($value)) {
return; $this->addError($object, $attribute, $this->message);
} }
}
/**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
$captcha = $this->getCaptchaAction(); $captcha = $this->getCaptchaAction();
if (!$captcha->validate($value, $this->caseSensitive)) { return !is_array($value) && $captcha->validate($value, $this->caseSensitive);
$message = $this->message !== null ? $this->message : \Yii::t('yii|The verification code is incorrect.');
$this->addError($object, $attribute, $message);
}
} }
/** /**
* Returns the CAPTCHA action object. * Returns the CAPTCHA action object.
* @return CCaptchaAction the action object * @return CaptchaAction the action object
*/ */
public function getCaptchaAction() public function getCaptchaAction()
{ {
if (strpos($this->captchaAction, '/') !== false) { // contains controller or module $ca = Yii::$app->createController($this->captchaAction);
$ca = \Yii::$app->createController($this->captchaAction); if ($ca !== false) {
if ($ca !== null) { /** @var \yii\base\Controller $controller */
list($controller, $actionID) = $ca; list($controller, $actionID) = $ca;
$action = $controller->createAction($actionID); $action = $controller->createAction($actionID);
if ($action !== null) {
return $action;
} }
} else {
$action = \Yii::$app->getController()->createAction($this->captchaAction);
}
if ($action === null) {
throw new \yii\base\Exception('Invalid captcha action ID: ' . $this->captchaAction);
} }
return $action; throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction);
} }
/** /**
@ -83,8 +93,7 @@ class CaptchaValidator extends Validator
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$captcha = $this->getCaptchaAction(); $captcha = $this->getCaptchaAction();
$message = $this->message !== null ? $this->message : \Yii::t('yii|The verification code is incorrect.'); $message = strtr($this->message, array(
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));
@ -102,7 +111,7 @@ if(h != hash) {
} }
"; ";
if ($this->allowEmpty) { if ($this->skipOnEmpty) {
$js = " $js = "
if($.trim(value)!='') { if($.trim(value)!='') {
$js $js

191
framework/validators/CompareValidator.php

@ -6,6 +6,7 @@
*/ */
namespace yii\validators; namespace yii\validators;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
@ -45,23 +46,12 @@ class CompareValidator extends Validator
*/ */
public $compareValue; public $compareValue;
/** /**
* @var boolean whether the comparison is strict (both value and type must be the same.) * @var string the operator for comparison. The following operators are supported:
* Defaults to false.
*/
public $strict = false;
/**
* @var boolean whether the attribute value can be null or empty. Defaults to false.
* If this is true, it means the attribute is considered valid when it is empty.
*/
public $allowEmpty = false;
/**
* @var string the operator for comparison. Defaults to '='.
* The followings are valid operators:
* *
* - `=` or `==`: validates to see if the two values are equal. If [[strict]] is true, the comparison * - '==': validates to see if the two values are equal. The comparison is done is non-strict mode.
* will be done in strict mode (i.e. checking value type as well). * - '===': validates to see if the two values are equal. The comparison is done is strict mode.
* - `!=`: validates to see if the two values are NOT equal. If [[strict]] is true, the comparison * - '!=': validates to see if the two values are NOT equal. The comparison is done is non-strict mode.
* will be done in strict mode (i.e. checking value type as well). * - '!==': validates to see if the two values are NOT equal. The comparison is done is strict mode.
* - `>`: validates to see if the value being validated is greater than the value being compared with. * - `>`: validates to see if the value being validated is greater than the value being compared with.
* - `>=`: validates to see if the value being validated is greater than or equal to the value being compared with. * - `>=`: validates to see if the value being validated is greater than or equal to the value being compared with.
* - `<`: validates to see if the value being validated is less than the value being compared with. * - `<`: validates to see if the value being validated is less than the value being compared with.
@ -69,6 +59,45 @@ class CompareValidator extends Validator
*/ */
public $operator = '='; public $operator = '=';
/**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
switch ($this->operator) {
case '==':
$this->message = Yii::t('yii|{attribute} must be repeated exactly.');
break;
case '===':
$this->message = Yii::t('yii|{attribute} must be repeated exactly.');
break;
case '!=':
$this->message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".');
break;
case '!==':
$this->message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".');
break;
case '>':
$this->message = Yii::t('yii|{attribute} must be greater than "{compareValue}".');
break;
case '>=':
$this->message = Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".');
break;
case '<':
$this->message = Yii::t('yii|{attribute} must be less than "{compareValue}".');
break;
case '<=':
$this->message = Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".');
break;
default:
throw new InvalidConfigException("Unknown operator: {$this->operator}");
}
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
@ -79,7 +108,8 @@ class CompareValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (is_array($value)) {
$this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.'));
return; return;
} }
if ($this->compareValue !== null) { if ($this->compareValue !== null) {
@ -91,45 +121,45 @@ class CompareValidator extends Validator
} }
switch ($this->operator) { switch ($this->operator) {
case '=': case '==': $valid = $value == $compareValue; break;
case '==': case '===': $valid = $value === $compareValue; break;
if (($this->strict && $value !== $compareValue) || (!$this->strict && $value != $compareValue)) { case '!=': $valid = $value != $compareValue; break;
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be repeated exactly.'); case '!==': $valid = $value !== $compareValue; break;
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel)); case '>': $valid = $value > $compareValue; break;
} case '>=': $valid = $value >= $compareValue; break;
break; case '<': $valid = $value < $compareValue; break;
case '!=': case '<=': $valid = $value <= $compareValue; break;
if (($this->strict && $value === $compareValue) || (!$this->strict && $value == $compareValue)) { default: $valid = false; break;
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must not be equal to "{compareValue}".'); }
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue)); if (!$valid) {
} $this->addError($object, $attribute, $this->message, array(
break; '{compareAttribute}' => $compareLabel,
case '>': '{compareValue}' => $compareValue,
if ($value <= $compareValue) { ));
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be greater than "{compareValue}".'); }
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue)); }
}
break; /**
case '>=': * Validates the given value.
if ($value < $compareValue) { * @param mixed $value the value to be validated.
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".'); * @return boolean whether the value is valid.
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue)); * @throws InvalidConfigException if [[compareValue]] is not set.
} */
break; public function validateValue($value)
case '<': {
if ($value >= $compareValue) { if ($this->compareValue === null) {
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be less than "{compareValue}".'); throw new InvalidConfigException('CompareValidator::compareValue must be set.');
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue)); }
}
break; switch ($this->operator) {
case '<=': case '==': return $value == $this->compareValue;
if ($value > $compareValue) { case '===': return $value === $this->compareValue;
$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".'); case '!=': return $value != $this->compareValue;
$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue)); case '!==': return $value !== $this->compareValue;
} case '>': return $value > $this->compareValue;
break; case '>=': return $value >= $this->compareValue;
default: case '<': return $value < $this->compareValue;
throw new InvalidConfigException("Unknown operator: {$this->operator}"); case '<=': return $value <= $this->compareValue;
} }
} }
@ -150,57 +180,14 @@ class CompareValidator extends Validator
$compareValue = "\$('#" . (CHtml::activeId($object, $compareAttribute)) . "').val()"; $compareValue = "\$('#" . (CHtml::activeId($object, $compareAttribute)) . "').val()";
$compareLabel = $object->getAttributeLabel($compareAttribute); $compareLabel = $object->getAttributeLabel($compareAttribute);
} }
$condition = "value {$this->operator} $compareValue";
$message = $this->message; $message = strtr($this->message, array(
switch ($this->operator) {
case '=':
case '==':
if ($message === null) {
$message = Yii::t('yii|{attribute} must be repeated exactly.');
}
$condition = 'value!=' . $compareValue;
break;
case '!=':
if ($message === null) {
$message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".');
}
$condition = 'value==' . $compareValue;
break;
case '>':
if ($message === null) {
$message = Yii::t('yii|{attribute} must be greater than "{compareValue}".');
}
$condition = 'value<=' . $compareValue;
break;
case '>=':
if ($message === null) {
$message = Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".');
}
$condition = 'value<' . $compareValue;
break;
case '<':
if ($message === null) {
$message = Yii::t('yii|{attribute} must be less than "{compareValue}".');
}
$condition = 'value>=' . $compareValue;
break;
case '<=':
if ($message === null) {
$message = Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".');
}
$condition = 'value>' . $compareValue;
break;
default:
throw new InvalidConfigException("Unknown operator: {$this->operator}");
}
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{compareValue}' => $compareLabel, '{compareValue}' => $compareLabel,
)); ));
return " return "
if (" . ($this->allowEmpty ? "$.trim(value)!='' && " : '') . $condition . ") { if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . $condition . ") {
messages.push(" . json_encode($message) . "); messages.push(" . json_encode($message) . ");
} }
"; ";

66
framework/validators/DateValidator.php

@ -7,11 +7,11 @@
namespace yii\validators; namespace yii\validators;
use Yii;
use DateTime;
/** /**
* DateValidator verifies if the attribute represents a date, time or datetime. * DateValidator verifies if the attribute represents a date, time or datetime in a proper format.
*
* By setting the {@link format} property, one can specify what format the date value
* must be in. If the given date value doesn't follow the format, the attribute is considered as invalid.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -19,17 +19,11 @@ namespace yii\validators;
class DateValidator extends Validator class DateValidator extends Validator
{ {
/** /**
* @var mixed the format pattern that the date value should follow. * @var string the date format that the value being validated should follow.
* This can be either a string or an array representing multiple formats. * Please refer to [[http://www.php.net/manual/en/datetime.createfromformat.php]] on
* Defaults to 'MM/dd/yyyy'. Please see {@link CDateTimeParser} for details * supported formats.
* about how to specify a date format.
*/
public $format = 'MM/dd/yyyy';
/**
* @var boolean whether the attribute value can be null or empty. Defaults to true,
* meaning that if the attribute is empty, it is considered valid.
*/ */
public $allowEmpty = true; public $format = 'Y-m-d';
/** /**
* @var string the name of the attribute to receive the parsing result. * @var string the name of the attribute to receive the parsing result.
* When this property is not null and the validation is successful, the named attribute will * When this property is not null and the validation is successful, the named attribute will
@ -38,6 +32,17 @@ class DateValidator extends Validator
public $timestampAttribute; public $timestampAttribute;
/** /**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|The format of {attribute} is invalid.');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
@ -46,27 +51,26 @@ class DateValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (is_array($value)) {
$this->addError($object, $attribute, $this->message);
return; return;
} }
$date = DateTime::createFromFormat($this->format, $value);
$formats = is_string($this->format) ? array($this->format) : $this->format; if ($date === false) {
$valid = false; $this->addError($object, $attribute, $this->message);
foreach ($formats as $format) { } elseif ($this->timestampAttribute !== false) {
$timestamp = CDateTimeParser::parse($value, $format, array('month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0)); $object->{$this->timestampAttribute} = $date->getTimestamp();
if ($timestamp !== false) {
$valid = true;
if ($this->timestampAttribute !== null) {
$object-> {$this->timestampAttribute} = $timestamp;
}
break;
}
} }
}
if (!$valid) { /**
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|The format of {attribute} is invalid.'); * Validates the given value.
$this->addError($object, $attribute, $message); * @param mixed $value the value to be validated.
} * @return boolean whether the value is valid.
*/
public function validateValue($value)
{
return DateTime::createFromFormat($this->format, $value) !== false;
} }
} }

15
framework/validators/DefaultValueValidator.php

@ -10,12 +10,8 @@ namespace yii\validators;
/** /**
* DefaultValueValidator sets the attribute to be the specified default value. * DefaultValueValidator sets the attribute to be the specified default value.
* *
* By default, when the attribute being validated is [[isEmpty|empty]], the validator
* will assign a default [[value]] to it. However, if [[setOnEmpty]] is false, the validator
* will always assign the default [[value]] to the attribute, no matter it is empty or not.
*
* DefaultValueValidator is not really a validator. It is provided mainly to allow * DefaultValueValidator is not really a validator. It is provided mainly to allow
* specifying attribute default values in a dynamic way. * specifying attribute default values when they are empty.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -27,11 +23,10 @@ class DefaultValueValidator extends Validator
*/ */
public $value; public $value;
/** /**
* @var boolean whether to set the default [[value]] only when the attribute is [[isEmpty|empty]]. * @var boolean this property is overwritten to be false so that this validator will
* Defaults to true. If false, the attribute will always be assigned with the default [[value]], * be applied when the value being validated is empty.
* no matter it is empty or not.
*/ */
public $setOnEmpty = true; public $skipOnEmpty = false;
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -40,7 +35,7 @@ class DefaultValueValidator extends Validator
*/ */
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
if (!$this->setOnEmpty || $this->isEmpty($object->$attribute)) { if ($this->isEmpty($object->$attribute)) {
$object->$attribute = $this->value; $object->$attribute = $this->value;
} }
} }

33
framework/validators/EmailValidator.php

@ -7,6 +7,8 @@
namespace yii\validators; namespace yii\validators;
use Yii;
/** /**
* EmailValidator validates that the attribute value is a valid email address. * EmailValidator validates that the attribute value is a valid email address.
* *
@ -42,11 +44,17 @@ class EmailValidator extends Validator
* Defaults to false. * Defaults to false.
*/ */
public $checkPort = false; public $checkPort = false;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true, * Initializes the validator.
* meaning that if the attribute is empty, it is considered valid.
*/ */
public $allowEmpty = true; public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} is not a valid email address.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -57,21 +65,15 @@ class EmailValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) {
return;
}
if (!$this->validateValue($value)) { if (!$this->validateValue($value)) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid email address.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} }
/** /**
* Validates a static value to see if it is a valid email. * Validates the given value.
* Note that this method does not respect [[allowEmpty]] property. * @param mixed $value the value to be validated.
* This method is provided so that you can call it directly without going through the model validation rule mechanism. * @return boolean whether the value is valid.
* @param mixed $value the value to be validated
* @return boolean whether the value is a valid email
*/ */
public function validateValue($value) public function validateValue($value)
{ {
@ -98,8 +100,7 @@ class EmailValidator extends Validator
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid email address.'); $message = strtr($this->message, array(
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));
@ -110,7 +111,7 @@ class EmailValidator extends Validator
} }
return " return "
if(" . ($this->allowEmpty ? "$.trim(value)!='' && " : '') . $condition . ") { if(" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . $condition . ") {
messages.push(" . json_encode($message) . "); messages.push(" . json_encode($message) . ");
} }
"; ";

58
framework/validators/ExistValidator.php

@ -6,6 +6,8 @@
*/ */
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
/** /**
@ -34,11 +36,18 @@ class ExistValidator extends Validator
* @see className * @see className
*/ */
public $attributeName; public $attributeName;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true, * Initializes the validator.
* meaning that if the attribute is empty, it is considered valid.
*/ */
public $allowEmpty = true; public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} is invalid.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -46,29 +55,48 @@ class ExistValidator extends Validator
* *
* @param \yii\db\ActiveRecord $object the object being validated * @param \yii\db\ActiveRecord $object the object being validated
* @param string $attribute the attribute being validated * @param string $attribute the attribute being validated
* @throws InvalidConfigException if table doesn't have column specified
*/ */
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) {
if (is_array($value)) {
$this->addError($object, $attribute, $this->message);
return; return;
} }
/** @var $className \yii\db\ActiveRecord */ /** @var $className \yii\db\ActiveRecord */
$className = ($this->className === null) ? get_class($object) : \Yii::import($this->className); $className = $this->className === null ? get_class($object) : Yii::import($this->className);
$attributeName = ($this->attributeName === null) ? $attribute : $this->attributeName; $attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
$table = $className::getTableSchema();
if (($column = $table->getColumn($attributeName)) === null) {
throw new InvalidConfigException('Table "' . $table->name . '" does not have a column named "' . $attributeName . '"');
}
$query = $className::find(); $query = $className::find();
$query->where(array($column->name => $value)); $query->where(array($attributeName => $value));
if (!$query->exists()) { if (!$query->exists()) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} "{value}" is invalid.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} }
/**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
* @throws InvalidConfigException if either [[className]] or [[attributeName]] is not set.
*/
public function validateValue($value)
{
if (is_array($value)) {
return false;
}
if ($this->className === null) {
throw new InvalidConfigException('The "className" property must be set.');
}
if ($this->attributeName === null) {
throw new InvalidConfigException('The "attributeName" property must be set.');
}
/** @var $className \yii\db\ActiveRecord */
$className = $this->className;
$query = $className::find();
$query->where(array($this->attributeName => $value));
return $query->exists();
}
} }

283
framework/validators/FileValidator.php

@ -7,47 +7,19 @@
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\helpers\FileHelper;
use yii\web\UploadedFile;
/** /**
* CFileValidator verifies if an attribute is receiving a valid uploaded file. * FileValidator verifies if an attribute is receiving a valid uploaded file.
*
* It uses the model class and attribute name to retrieve the information
* about the uploaded file. It then checks if a file is uploaded successfully,
* if the file size is within the limit and if the file type is allowed.
*
* This validator will attempt to fetch uploaded data if attribute is not
* previously set. Please note that this cannot be done if input is tabular:
* <pre>
* foreach($models as $i=>$model)
* $model->attribute = CUploadedFile::getInstance($model, "[$i]attribute");
* </pre>
* Please note that you must use {@link CUploadedFile::getInstances} for multiple
* file uploads.
*
* When using CFileValidator with an active record, the following code is often used:
* <pre>
* if($model->save())
* {
* // single upload
* $model->attribute->saveAs($path);
* // multiple upload
* foreach($model->attribute as $file)
* $file->saveAs($path);
* }
* </pre>
*
* You can use {@link CFileValidator} to validate the file attribute.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class CFileValidator extends Validator class FileValidator extends Validator
{ {
/** /**
* @var boolean whether the attribute requires a file to be uploaded or not.
* Defaults to false, meaning a file is required to be uploaded.
*/
public $allowEmpty = false;
/**
* @var mixed a list of file name extensions that are allowed to be uploaded. * @var mixed a list of file name extensions that are allowed to be uploaded.
* This can be either an array or a string consisting of file extension names * This can be either an array or a string consisting of file extension names
* separated by space or comma (e.g. "gif, jpg"). * separated by space or comma (e.g. "gif, jpg").
@ -66,136 +38,179 @@ class CFileValidator extends Validator
* Defaults to null, meaning no limit. * Defaults to null, meaning no limit.
* Note, the size limit is also affected by 'upload_max_filesize' INI setting * Note, the size limit is also affected by 'upload_max_filesize' INI setting
* and the 'MAX_FILE_SIZE' hidden field value. * and the 'MAX_FILE_SIZE' hidden field value.
* @see tooLarge * @see tooBig
*/ */
public $maxSize; public $maxSize;
/** /**
* @var integer the maximum file count the given attribute can hold.
* It defaults to 1, meaning single file upload. By defining a higher number,
* multiple uploads become possible.
*/
public $maxFiles = 1;
/**
* @var string the error message used when a file is not uploaded correctly.
*/
public $message;
/**
* @var string the error message used when no file is uploaded.
*/
public $uploadRequired;
/**
* @var string the error message used when the uploaded file is too large. * @var string the error message used when the uploaded file is too large.
* @see maxSize * You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the maximum size allowed (see [[getSizeLimit()]])
*/ */
public $tooLarge; public $tooBig;
/** /**
* @var string the error message used when the uploaded file is too small. * @var string the error message used when the uploaded file is too small.
* @see minSize * You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[minSize]]
*/ */
public $tooSmall; public $tooSmall;
/** /**
* @var string the error message used when the uploaded file has an extension name * @var string the error message used when the uploaded file has an extension name
* that is not listed among {@link extensions}. * that is not listed in [[extensions]]. You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {extensions}: the list of the allowed extensions.
*/ */
public $wrongType; public $wrongType;
/** /**
* @var integer the maximum file count the given attribute can hold. * @var string the error message used if the count of multiple uploads exceeds limit.
* It defaults to 1, meaning single file upload. By defining a higher number, * You may use the following tokens in the message:
* multiple uploads become possible. *
*/ * - {attribute}: the attribute name
public $maxFiles = 1; * - {file}: the uploaded file name
/** * - {limit}: the value of [[maxFiles]]
* @var string the error message used if the count of multiple uploads exceeds
* limit.
*/ */
public $tooMany; public $tooMany;
/** /**
* Set the attribute and then validates using {@link validateFile}. * Initializes the validator.
* If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated
* @param string $attribute the attribute being validated
*/ */
public function validateAttribute($object, $attribute) public function init()
{ {
if ($this->maxFiles > 1) parent::init();
{ if ($this->message === null) {
$files = $object->$attribute; $this->message = Yii::t('yii|File upload failed.');
if (!is_array($files) || !isset($files[0]) || !$files[0] instanceof CUploadedFile) }
$files = CUploadedFile::getInstances($object, $attribute); if ($this->uploadRequired === null) {
if (array() === $files) $this->uploadRequired = Yii::t('yii|Please upload a file.');
return $this->emptyAttribute($object, $attribute); }
if (count($files) > $this->maxFiles) if ($this->tooMany === null) {
{ $this->tooMany = Yii::t('yii|You can upload at most {limit} files.');
$message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii|{attribute} cannot accept more than {limit} files.'); }
$this->addError($object, $attribute, $message, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles)); if ($this->wrongType === null) {
} else $this->wrongType = Yii::t('yii|Only files with these extensions are allowed: {extensions}.');
foreach ($files as $file) }
$this->validateFile($object, $attribute, $file); if ($this->tooBig === null) {
} else $this->tooBig = Yii::t('yii|The file "{file}" is too big. Its size cannot exceed {limit} bytes.');
{ }
$file = $object->$attribute; if ($this->tooSmall === null) {
if (!$file instanceof CUploadedFile) $this->tooSmall = Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
{ }
$file = CUploadedFile::getInstance($object, $attribute); if (!is_array($this->types)) {
if (null === $file) $this->types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY);
return $this->emptyAttribute($object, $attribute);
}
$this->validateFile($object, $attribute, $file);
} }
} }
/** /**
* Internally validates a file object. * Validates the attribute.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
* @param string $attribute the attribute being validated * @param string $attribute the attribute being validated
* @param CUploadedFile $file uploaded file passed to check against a set of rules
*/ */
public function validateFile($object, $attribute, $file) public function validateAttribute($object, $attribute)
{ {
if (null === $file || ($error = $file->getError()) == UPLOAD_ERR_NO_FILE) if ($this->maxFiles > 1) {
return $this->emptyAttribute($object, $attribute); $files = $object->$attribute;
elseif ($error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE || $this->maxSize !== null && $file->getSize() > $this->maxSize) if (!is_array($files)) {
{ $this->addError($object, $attribute, $this->uploadRequired);
$message = $this->tooLarge !== null ? $this->tooLarge : \Yii::t('yii|The file "{file}" is too large. Its size cannot exceed {limit} bytes.'); return;
$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); }
} elseif ($error == UPLOAD_ERR_PARTIAL) foreach ($files as $i => $file) {
throw new CException(\Yii::t('yii|The file "{file}" was only partially uploaded.', array('{file}' => $file->getName()))); if (!$file instanceof UploadedFile || $file->getError() == UPLOAD_ERR_NO_FILE) {
elseif ($error == UPLOAD_ERR_NO_TMP_DIR) unset($files[$i]);
throw new CException(\Yii::t('yii|Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName()))); }
elseif ($error == UPLOAD_ERR_CANT_WRITE) }
throw new CException(\Yii::t('yii|Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName()))); $object->$attribute = array_values($files);
elseif (defined('UPLOAD_ERR_EXTENSION') && $error == UPLOAD_ERR_EXTENSION) // available for PHP 5.2.0 or above if ($files === array()) {
throw new CException(\Yii::t('yii|File upload was stopped by extension.')); $this->addError($object, $attribute, $this->uploadRequired);
}
if ($this->minSize !== null && $file->getSize() < $this->minSize) if (count($files) > $this->maxFiles) {
{ $this->addError($object, $attribute, $this->tooMany, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles));
$message = $this->tooSmall !== null ? $this->tooSmall : \Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.'); } else {
$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->minSize)); foreach ($files as $file) {
} $this->validateFile($object, $attribute, $file);
}
if ($this->types !== null) }
{ } else {
if (is_string($this->types)) $file = $object->$attribute;
$types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY); if ($file instanceof UploadedFile && $file->getError() != UPLOAD_ERR_NO_FILE) {
else $this->validateFile($object, $attribute, $file);
$types = $this->types; } else {
if (!in_array(strtolower($file->getExtensionName()), $types)) $this->addError($object, $attribute, $this->uploadRequired);
{
$message = $this->wrongType !== null ? $this->wrongType : \Yii::t('yii|The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $types)));
} }
} }
} }
/** /**
* Raises an error to inform end user about blank attribute. * Internally validates a file object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
* @param string $attribute the attribute being validated * @param string $attribute the attribute being validated
* @param UploadedFile $file uploaded file passed to check against a set of rules
*/ */
public function emptyAttribute($object, $attribute) protected function validateFile($object, $attribute, $file)
{ {
if (!$this->allowEmpty) switch ($file->getError()) {
{ case UPLOAD_ERR_OK:
$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} cannot be blank.'); if ($this->maxSize !== null && $file->getSize() > $this->maxSize) {
$this->addError($object, $attribute, $message); $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit()));
}
if ($this->minSize !== null && $file->getSize() < $this->minSize) {
$this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->getName(), '{limit}' => $this->minSize));
}
if (!empty($this->types) && !in_array(strtolower(FileHelper::getExtension($file->getName())), $this->types, true)) {
$this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $this->types)));
}
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit()));
break;
case UPLOAD_ERR_PARTIAL:
$this->addError($object, $attribute, $this->message);
Yii::warning('File was only partially uploaded: ' . $file->getName(), __METHOD__);
break;
case UPLOAD_ERR_NO_TMP_DIR:
$this->addError($object, $attribute, $this->message);
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->getName(), __METHOD__);
break;
case UPLOAD_ERR_CANT_WRITE:
$this->addError($object, $attribute, $this->message);
Yii::warning('Failed to write the uploaded file to disk: ', $file->getName(), __METHOD__);
break;
case UPLOAD_ERR_EXTENSION:
$this->addError($object, $attribute, $this->message);
Yii::warning('File upload was stopped by some PHP extension: ', $file->getName(), __METHOD__);
break;
default:
break;
} }
} }
/** /**
* Returns the maximum size allowed for uploaded files. * Returns the maximum size allowed for uploaded files.
* This is determined based on three factors: * This is determined based on three factors:
* <ul> *
* <li>'upload_max_filesize' in php.ini</li> * - 'upload_max_filesize' in php.ini
* <li>'MAX_FILE_SIZE' hidden field</li> * - 'MAX_FILE_SIZE' hidden field
* <li>{@link maxSize}</li> * - [[maxSize]]
* </ul>
* *
* @return integer the size limit for uploaded files. * @return integer the size limit for uploaded files.
*/ */
@ -203,10 +218,12 @@ class CFileValidator extends Validator
{ {
$limit = ini_get('upload_max_filesize'); $limit = ini_get('upload_max_filesize');
$limit = $this->sizeToBytes($limit); $limit = $this->sizeToBytes($limit);
if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) {
$limit = $this->maxSize; $limit = $this->maxSize;
if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) }
$limit = $_POST['MAX_FILE_SIZE']; if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) {
$limit = (int)$_POST['MAX_FILE_SIZE'];
}
return $limit; return $limit;
} }
@ -218,12 +235,18 @@ class CFileValidator extends Validator
*/ */
private function sizeToBytes($sizeStr) private function sizeToBytes($sizeStr)
{ {
switch (substr($sizeStr, -1)) switch (substr($sizeStr, -1)) {
{ case 'M':
case 'M': case 'm': return (int)$sizeStr * 1048576; case 'm':
case 'K': case 'k': return (int)$sizeStr * 1024; return (int)$sizeStr * 1048576;
case 'G': case 'g': return (int)$sizeStr * 1073741824; case 'K':
default: return (int)$sizeStr; case 'k':
return (int)$sizeStr * 1024;
case 'G':
case 'g':
return (int)$sizeStr * 1073741824;
default:
return (int)$sizeStr;
} }
} }
} }

20
framework/validators/FilterValidator.php

@ -38,6 +38,23 @@ class FilterValidator extends Validator
* ~~~ * ~~~
*/ */
public $filter; public $filter;
/**
* @var boolean this property is overwritten to be false so that this validator will
* be applied when the value being validated is empty.
*/
public $skipOnEmpty = false;
/**
* Initializes the validator.
* @throws InvalidConfigException if [[filter]] is not set.
*/
public function init()
{
parent::init();
if ($this->filter === null) {
throw new InvalidConfigException('The "filter" property must be set.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -48,9 +65,6 @@ class FilterValidator extends Validator
*/ */
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
if ($this->filter === null) {
throw new InvalidConfigException('The "filter" property must be specified with a valid callback.');
}
$object->$attribute = call_user_func($this->filter, $object->$attribute); $object->$attribute = call_user_func($this->filter, $object->$attribute);
} }
} }

21
framework/validators/InlineValidator.php

@ -25,8 +25,9 @@ namespace yii\validators;
class InlineValidator extends Validator class InlineValidator extends Validator
{ {
/** /**
* @var string the name of the validation method defined in the * @var string|\Closure an anonymous function or the name of a model class method that will be
* \yii\base\Model class * called to perform the actual validation. Note that if you use anonymous function, you cannot
* use `$this` in it unless you are using PHP 5.4 or above.
*/ */
public $method; public $method;
/** /**
@ -34,8 +35,8 @@ class InlineValidator extends Validator
*/ */
public $params; public $params;
/** /**
* @var string the name of the method that returns the client validation code (see [[clientValidateAttribute()]] * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
* for details on how to return client validation code). The signature of the method should be like the following: * The signature of the method should be like the following:
* *
* ~~~ * ~~~
* function foo($attribute) * function foo($attribute)
@ -45,6 +46,8 @@ class InlineValidator extends Validator
* ~~~ * ~~~
* *
* where `$attribute` refers to the attribute name to be validated. * where `$attribute` refers to the attribute name to be validated.
*
* Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
*/ */
public $clientValidate; public $clientValidate;
@ -56,7 +59,10 @@ class InlineValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$method = $this->method; $method = $this->method;
$object->$method($attribute, $this->params); if (is_string($method)) {
$method = array($object, $method);
}
call_user_func($method, $attribute, $this->params);
} }
/** /**
@ -82,7 +88,10 @@ class InlineValidator extends Validator
{ {
if ($this->clientValidate !== null) { if ($this->clientValidate !== null) {
$method = $this->clientValidate; $method = $this->clientValidate;
return $object->$method($attribute); if (is_string($method)) {
$method = array($object, $method);
}
return call_user_func($method, $attribute);
} else { } else {
return null; return null;
} }

76
framework/validators/NumberValidator.php

@ -26,11 +26,6 @@ class NumberValidator extends Validator
*/ */
public $integerOnly = false; public $integerOnly = false;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true,
* meaning that if the attribute is empty, it is considered valid.
*/
public $allowEmpty = true;
/**
* @var integer|float upper limit of the number. Defaults to null, meaning no upper limit. * @var integer|float upper limit of the number. Defaults to null, meaning no upper limit.
*/ */
public $max; public $max;
@ -58,6 +53,24 @@ class NumberValidator extends Validator
/** /**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = $this->integerOnly ? Yii::t('yii|{attribute} must be an integer.')
: Yii::t('yii|{attribute} must be a number.');
}
if ($this->min !== null && $this->tooSmall === null) {
$this->tooSmall = Yii::t('yii|{attribute} must be no less than {min}.');
}
if ($this->max !== null && $this->tooBig === null) {
$this->tooBig = Yii::t('yii|{attribute} must be no greater than {max}.');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
@ -66,31 +79,35 @@ class NumberValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (is_array($value)) {
$this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.'));
return; return;
} }
if ($this->integerOnly) { $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($this->integerPattern, "$value")) { if (!preg_match($pattern, "$value")) {
$message = $this->message !== null ? $this->message : Yii::t('yii|{attribute} must be an integer.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
}
} else {
if (!preg_match($this->numberPattern, "$value")) {
$message = $this->message !== null ? $this->message : Yii::t('yii|{attribute} must be a number.');
$this->addError($object, $attribute, $message);
}
} }
if ($this->min !== null && $value < $this->min) { if ($this->min !== null && $value < $this->min) {
$message = $this->tooSmall !== null ? $this->tooSmall : Yii::t('yii|{attribute} is too small (minimum is {min}).'); $this->addError($object, $attribute, $this->tooSmall, array('{min}' => $this->min));
$this->addError($object, $attribute, $message, array('{min}' => $this->min));
} }
if ($this->max !== null && $value > $this->max) { if ($this->max !== null && $value > $this->max) {
$message = $this->tooBig !== null ? $this->tooBig : Yii::t('yii|{attribute} is too big (maximum is {max}).'); $this->addError($object, $attribute, $this->tooBig, array('{max}' => $this->max));
$this->addError($object, $attribute, $message, array('{max}' => $this->max));
} }
} }
/** /**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
return preg_match($this->integerOnly ? $this->integerPattern : $this->numberPattern, "$value")
&& ($this->min === null || $value >= $this->min)
&& ($this->max === null || $value <= $this->max);
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
@ -99,12 +116,7 @@ class NumberValidator extends Validator
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
$message = strtr($this->message, array(
if (($message = $this->message) === null) {
$message = $this->integerOnly ? Yii::t('yii|{attribute} must be an integer.')
: Yii::t('yii|{attribute} must be a number.');
}
$message = strtr($message, array(
'{attribute}' => $label, '{attribute}' => $label,
)); ));
@ -115,10 +127,7 @@ if(!value.match($pattern)) {
} }
"; ";
if ($this->min !== null) { if ($this->min !== null) {
if (($tooSmall = $this->tooSmall) === null) { $tooSmall = strtr($this->tooSmall, array(
$tooSmall = Yii::t('yii|{attribute} is too small (minimum is {min}).');
}
$tooSmall = strtr($tooSmall, array(
'{attribute}' => $label, '{attribute}' => $label,
'{min}' => $this->min, '{min}' => $this->min,
)); ));
@ -130,10 +139,7 @@ if(value<{$this->min}) {
"; ";
} }
if ($this->max !== null) { if ($this->max !== null) {
if (($tooBig = $this->tooBig) === null) { $tooBig = strtr($this->tooBig, array(
$tooBig = Yii::t('yii|{attribute} is too big (maximum is {max}).');
}
$tooBig = strtr($tooBig, array(
'{attribute}' => $label, '{attribute}' => $label,
'{max}' => $this->max, '{max}' => $this->max,
)); ));
@ -144,7 +150,7 @@ if(value>{$this->max}) {
"; ";
} }
if ($this->allowEmpty) { if ($this->skipOnEmpty) {
$js = " $js = "
if(jQuery.trim(value)!='') { if(jQuery.trim(value)!='') {
$js $js

58
framework/validators/RangeValidator.php

@ -6,6 +6,8 @@
*/ */
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
/** /**
@ -29,58 +31,62 @@ class RangeValidator extends Validator
*/ */
public $strict = false; public $strict = false;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true,
* meaning that if the attribute is empty, it is considered valid.
*/
public $allowEmpty = true;
/**
* @var boolean whether to invert the validation logic. Defaults to false. If set to true, * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
* the attribute value should NOT be among the list of values defined via [[range]]. * the attribute value should NOT be among the list of values defined via [[range]].
**/ **/
public $not = false; public $not = false;
/** /**
* Initializes the validator.
* @throws InvalidConfigException if [[range]] is not set.
*/
public function init()
{
parent::init();
if (!is_array($this->range)) {
throw new InvalidConfigException('The "range" property must be set.');
}
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} is invalid.');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
* @param string $attribute the attribute being validated * @param string $attribute the attribute being validated
* @throws InvalidConfigException if the "range" property is not an array
*/ */
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) {
return;
}
if (!is_array($this->range)) {
throw new InvalidConfigException('The "range" property must be specified as an array.');
}
if (!$this->not && !in_array($value, $this->range, $this->strict)) { if (!$this->not && !in_array($value, $this->range, $this->strict)) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} should be in the list.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} elseif ($this->not && in_array($value, $this->range, $this->strict)) { } elseif ($this->not && in_array($value, $this->range, $this->strict)) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} should NOT be in the list.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} }
/** /**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
return !$this->not && in_array($value, $this->range, $this->strict)
|| $this->not && !in_array($value, $this->range, $this->strict);
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @return string the client-side validation script. * @return string the client-side validation script.
* @throws InvalidConfigException if the "range" property is not an array
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
if (!is_array($this->range)) { $message = strtr($this->message, array(
throw new InvalidConfigException('The "range" property must be specified as an array.');
}
if (($message = $this->message) === null) {
$message = $this->not ? \Yii::t('yii|{attribute} should NOT be in the list.') : \Yii::t('yii|{attribute} should be in the list.');
}
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));
@ -92,7 +98,7 @@ class RangeValidator extends Validator
$range = json_encode($range); $range = json_encode($range);
return " return "
if (" . ($this->allowEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? "$.inArray(value, $range)>=0" : "$.inArray(value, $range)<0") . ") { if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? "$.inArray(value, $range)>=0" : "$.inArray(value, $range)<0") . ") {
messages.push(" . json_encode($message) . "); messages.push(" . json_encode($message) . ");
} }
"; ";

59
framework/validators/RegularExpressionValidator.php

@ -7,6 +7,9 @@
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
/** /**
* RegularExpressionValidator validates that the attribute value matches the specified [[pattern]]. * RegularExpressionValidator validates that the attribute value matches the specified [[pattern]].
* *
@ -22,53 +25,63 @@ class RegularExpressionValidator extends Validator
*/ */
public $pattern; public $pattern;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true,
* meaning that if the attribute is empty, it is considered valid.
*/
public $allowEmpty = true;
/**
* @var boolean whether to invert the validation logic. Defaults to false. If set to true, * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
* the regular expression defined via [[pattern]] should NOT match the attribute value. * the regular expression defined via [[pattern]] should NOT match the attribute value.
* @throws InvalidConfigException if the "pattern" is not a valid regular expression
**/ **/
public $not = false; public $not = false;
/** /**
* Initializes the validator.
* @throws InvalidConfigException if [[pattern]] is not set.
*/
public function init()
{
parent::init();
if ($this->pattern === null) {
throw new InvalidConfigException('The "pattern" property must be set.');
}
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} is invalid.');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
* @param string $attribute the attribute being validated * @param string $attribute the attribute being validated
* @throws \yii\base\Exception if the "pattern" is not a valid regular expression
*/ */
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if (!$this->validateValue($value)) {
return; $this->addError($object, $attribute, $this->message);
}
if ($this->pattern === null) {
throw new \yii\base\Exception('The "pattern" property must be specified with a valid regular expression.');
}
if ((!$this->not && !preg_match($this->pattern, $value)) || ($this->not && preg_match($this->pattern, $value))) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is invalid.');
$this->addError($object, $attribute, $message);
} }
} }
/** /**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
return !is_array($value) &&
(!$this->not && preg_match($this->pattern, $value)
|| $this->not && !preg_match($this->pattern, $value));
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @return string the client-side validation script. * @return string the client-side validation script.
* @throws \yii\base\Exception if the "pattern" is not a valid regular expression * @throws InvalidConfigException if the "pattern" is not a valid regular expression
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
if ($this->pattern === null) { $message = strtr($this->message, array(
throw new \yii\base\Exception('The "pattern" property must be specified with a valid regular expression.');
}
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is invalid.');
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));
@ -88,7 +101,7 @@ class RegularExpressionValidator extends Validator
} }
return " return "
if (" . ($this->allowEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? '' : '!') . "value.match($pattern)) { if (" . ($this->skipOnEmpty ? "$.trim(value)!='' && " : '') . ($this->not ? '' : '!') . "value.match($pattern)) {
messages.push(" . json_encode($message) . "); messages.push(" . json_encode($message) . ");
} }
"; ";

52
framework/validators/RequiredValidator.php

@ -7,6 +7,8 @@
namespace yii\validators; namespace yii\validators;
use Yii;
/** /**
* RequiredValidator validates that the specified attribute does not have null or empty value. * RequiredValidator validates that the specified attribute does not have null or empty value.
* *
@ -16,6 +18,10 @@ namespace yii\validators;
class RequiredValidator extends Validator class RequiredValidator extends Validator
{ {
/** /**
* @var boolean whether to skip this validator if the value being validated is empty.
*/
public $skipOnEmpty = false;
/**
* @var mixed the desired value that the attribute must have. * @var mixed the desired value that the attribute must have.
* If this is null, the validator will validate that the specified attribute is not empty. * If this is null, the validator will validate that the specified attribute is not empty.
* If this is set as a value that is not null, the validator will validate that * If this is set as a value that is not null, the validator will validate that
@ -35,6 +41,18 @@ class RequiredValidator extends Validator
public $strict = false; public $strict = false;
/** /**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = $this->requiredValue === null ? Yii::t('yii|{attribute} is invalid.')
: Yii::t('yii|{attribute} must be "{requiredValue}".');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\base\Model $object the object being validated * @param \yii\base\Model $object the object being validated
@ -45,13 +63,11 @@ class RequiredValidator extends Validator
$value = $object->$attribute; $value = $object->$attribute;
if ($this->requiredValue === null) { if ($this->requiredValue === null) {
if ($this->strict && $value === null || !$this->strict && $this->isEmpty($value, true)) { if ($this->strict && $value === null || !$this->strict && $this->isEmpty($value, true)) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} cannot be blank.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} else { } else {
if (!$this->strict && $value != $this->requiredValue || $this->strict && $value !== $this->requiredValue) { if (!$this->strict && $value != $this->requiredValue || $this->strict && $value !== $this->requiredValue) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be "{requiredValue}".'); $this->addError($object, $attribute, $this->message, array(
$this->addError($object, $attribute, $message, array(
'{requiredValue}' => $this->requiredValue, '{requiredValue}' => $this->requiredValue,
)); ));
} }
@ -59,6 +75,23 @@ class RequiredValidator extends Validator
} }
/** /**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
if ($this->requiredValue === null) {
if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty($value, true)) {
return true;
}
} elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
return true;
}
return false;
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
@ -66,12 +99,8 @@ class RequiredValidator extends Validator
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$message = $this->message;
if ($this->requiredValue !== null) { if ($this->requiredValue !== null) {
if ($message === null) { $message = strtr($this->message, array(
$message = \Yii::t('yii|{attribute} must be "{requiredValue}".');
}
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
'{requiredValue}' => $this->requiredValue, '{requiredValue}' => $this->requiredValue,
@ -82,10 +111,7 @@ if (value != " . json_encode($this->requiredValue) . ") {
} }
"; ";
} else { } else {
if ($message === null) { $message = strtr($this->message, array(
$message = \Yii::t('yii|{attribute} cannot be blank.');
}
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));

91
framework/validators/StringValidator.php

@ -7,6 +7,8 @@
namespace yii\validators; namespace yii\validators;
use Yii;
/** /**
* StringValidator validates that the attribute value is of certain length. * StringValidator validates that the attribute value is of certain length.
* *
@ -46,19 +48,34 @@ class StringValidator extends Validator
*/ */
public $notEqual; public $notEqual;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true, * @var string the encoding of the string value to be validated (e.g. 'UTF-8').
* meaning that if the attribute is empty, it is considered valid. * If this property is not set, [[\yii\base\Application::charset]] will be used.
*/ */
public $allowEmpty = true; public $encoding;
/** /**
* @var mixed the encoding of the string value to be validated (e.g. 'UTF-8'). * Initializes the validator.
* This property is used only when mbstring PHP extension is enabled.
* The value of this property will be used as the 2nd parameter of the
* mb_strlen() function. If this property is not set, the application charset
* will be used. If this property is set false, then strlen() will be used even
* if mbstring is enabled.
*/ */
public $encoding; public function init()
{
parent::init();
if ($this->encoding === null) {
$this->encoding = Yii::$app->charset;
}
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} must be a string.');
}
if ($this->min !== null && $this->tooShort === null) {
$this->tooShort = Yii::t('yii|{attribute} should contain at least {min} characters.');
}
if ($this->max !== null && $this->tooLong === null) {
$this->tooLong = Yii::t('yii|{attribute} should contain at most {max} characters.');
}
if ($this->is !== null && $this->notEqual === null) {
$this->notEqual = Yii::t('yii|{attribute} should contain {length} characters.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -69,34 +86,39 @@ class StringValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) {
return;
}
if (!is_string($value)) { if (!is_string($value)) {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be a string.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
return; return;
} }
if (function_exists('mb_strlen') && $this->encoding !== false) { $length = mb_strlen($value, $this->encoding);
$length = mb_strlen($value, $this->encoding ? $this->encoding : \Yii::$app->charset);
} else {
$length = strlen($value);
}
if ($this->min !== null && $length < $this->min) { if ($this->min !== null && $length < $this->min) {
$message = ($this->tooShort !== null) ? $this->tooShort : \Yii::t('yii|{attribute} is too short (minimum is {min} characters).'); $this->addError($object, $attribute, $this->tooShort, array('{min}' => $this->min));
$this->addError($object, $attribute, $message, array('{min}' => $this->min));
} }
if ($this->max !== null && $length > $this->max) { if ($this->max !== null && $length > $this->max) {
$message = ($this->tooLong !== null) ? $this->tooLong : \Yii::t('yii|{attribute} is too long (maximum is {max} characters).'); $this->addError($object, $attribute, $this->tooLong, array('{max}' => $this->max));
$this->addError($object, $attribute, $message, array('{max}' => $this->max));
} }
if ($this->is !== null && $length !== $this->is) { if ($this->is !== null && $length !== $this->is) {
$message = ($this->notEqual !== null) ? $this->notEqual : \Yii::t('yii|{attribute} is of the wrong length (should be {length} characters).'); $this->addError($object, $attribute, $this->notEqual, array('{length}' => $this->is));
$this->addError($object, $attribute, $message, array('{length}' => $this->is)); }
}
/**
* Validates the given value.
* @param mixed $value the value to be validated.
* @return boolean whether the value is valid.
*/
public function validateValue($value)
{
if (!is_string($value)) {
return false;
} }
$length = mb_strlen($value, $this->encoding);
return ($this->min === null || $length >= $this->min)
&& ($this->max === null || $length <= $this->max)
&& ($this->is === null || $length === $this->is);
} }
/** /**
@ -110,28 +132,19 @@ class StringValidator extends Validator
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
$value = $object->$attribute; $value = $object->$attribute;
if (($notEqual = $this->notEqual) === null) { $notEqual = strtr($this->notEqual, array(
$notEqual = \Yii::t('yii|{attribute} is of the wrong length (should be {length} characters).');
}
$notEqual = strtr($notEqual, array(
'{attribute}' => $label, '{attribute}' => $label,
'{value}' => $value, '{value}' => $value,
'{length}' => $this->is, '{length}' => $this->is,
)); ));
if (($tooShort = $this->tooShort) === null) { $tooShort = strtr($this->tooShort, array(
$tooShort = \Yii::t('yii|{attribute} is too short (minimum is {min} characters).');
}
$tooShort = strtr($tooShort, array(
'{attribute}' => $label, '{attribute}' => $label,
'{value}' => $value, '{value}' => $value,
'{min}' => $this->min, '{min}' => $this->min,
)); ));
if (($tooLong = $this->tooLong) === null) { $tooLong = strtr($this->tooLong, array(
$tooLong = \Yii::t('yii|{attribute} is too long (maximum is {max} characters).');
}
$tooLong = strtr($tooLong, array(
'{attribute}' => $label, '{attribute}' => $label,
'{value}' => $value, '{value}' => $value,
'{max}' => $this->max, '{max}' => $this->max,
@ -160,7 +173,7 @@ if(value.length!= {$this->is}) {
"; ";
} }
if ($this->allowEmpty) { if ($this->skipOnEmpty) {
$js = " $js = "
if($.trim(value)!='') { if($.trim(value)!='') {
$js $js

27
framework/validators/UniqueValidator.php

@ -6,6 +6,8 @@
*/ */
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
/** /**
@ -17,11 +19,6 @@ use yii\base\InvalidConfigException;
class UniqueValidator extends Validator class UniqueValidator extends Validator
{ {
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true,
* meaning that if the attribute is empty, it is considered valid.
*/
public $allowEmpty = true;
/**
* @var string the ActiveRecord class name or alias of the class * @var string the ActiveRecord class name or alias of the class
* that should be used to look for the attribute value being validated. * that should be used to look for the attribute value being validated.
* Defaults to null, meaning using the ActiveRecord class of the attribute being validated. * Defaults to null, meaning using the ActiveRecord class of the attribute being validated.
@ -36,6 +33,17 @@ class UniqueValidator extends Validator
public $attributeName; public $attributeName;
/** /**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} "{value}" has already been taken.');
}
}
/**
* Validates the attribute of the object. * Validates the attribute of the object.
* If there is any error, the error message is added to the object. * If there is any error, the error message is added to the object.
* @param \yii\db\ActiveRecord $object the object being validated * @param \yii\db\ActiveRecord $object the object being validated
@ -45,7 +53,9 @@ class UniqueValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) {
if (is_array($value)) {
$this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.'));
return; return;
} }
@ -55,7 +65,7 @@ class UniqueValidator extends Validator
$table = $className::getTableSchema(); $table = $className::getTableSchema();
if (($column = $table->getColumn($attributeName)) === null) { if (($column = $table->getColumn($attributeName)) === null) {
throw new InvalidConfigException('Table "' . $table->name . '" does not have a column named "' . $attributeName . '"'); throw new InvalidConfigException("Table '{$table->name}' does not have a column named '$attributeName'.");
} }
$query = $className::find(); $query = $className::find();
@ -84,8 +94,7 @@ class UniqueValidator extends Validator
} }
if ($exists) { if ($exists) {
$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} "{value}" has already been taken.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} }
} }

42
framework/validators/UrlValidator.php

@ -7,6 +7,8 @@
namespace yii\validators; namespace yii\validators;
use Yii;
/** /**
* UrlValidator validates that the attribute value is a valid http or https URL. * UrlValidator validates that the attribute value is a valid http or https URL.
* *
@ -32,11 +34,18 @@ class UrlValidator extends Validator
* contain the scheme part. * contain the scheme part.
**/ **/
public $defaultScheme; public $defaultScheme;
/** /**
* @var boolean whether the attribute value can be null or empty. Defaults to true, * Initializes the validator.
* meaning that if the attribute is empty, it is considered valid.
*/ */
public $allowEmpty = true; public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii|{attribute} is not a valid URL.');
}
}
/** /**
* Validates the attribute of the object. * Validates the attribute of the object.
@ -47,23 +56,19 @@ class UrlValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
if ($this->allowEmpty && $this->isEmpty($value)) { if ($this->validateValue($value)) {
return; if ($this->defaultScheme !== null && strpos($value, '://') === false) {
} $object->$attribute = $this->defaultScheme . '://' . $value;
if (($value = $this->validateValue($value)) !== false) { }
$object->$attribute = $value;
} else { } else {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid URL.'); $this->addError($object, $attribute, $this->message);
$this->addError($object, $attribute, $message);
} }
} }
/** /**
* Validates a static value to see if it is a valid URL. * Validates the given value.
* Note that this method does not respect [[allowEmpty]] property. * @param mixed $value the value to be validated.
* This method is provided so that you can call it directly without going through the model validation rule mechanism. * @return boolean whether the value is valid.
* @param mixed $value the value to be validated
* @return mixed false if the the value is not a valid URL, otherwise the possibly modified value ({@see defaultScheme})
*/ */
public function validateValue($value) public function validateValue($value)
{ {
@ -80,7 +85,7 @@ class UrlValidator extends Validator
} }
if (preg_match($pattern, $value)) { if (preg_match($pattern, $value)) {
return $value; return true;
} }
} }
return false; return false;
@ -95,8 +100,7 @@ class UrlValidator extends Validator
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute)
{ {
$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid URL.'); $message = strtr($this->message, array(
$message = strtr($message, array(
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
)); ));
@ -121,7 +125,7 @@ $js
"; ";
} }
if ($this->allowEmpty) { if ($this->skipOnEmpty) {
$js = " $js = "
if($.trim(value)!='') { if($.trim(value)!='') {
$js $js

30
framework/validators/Validator.php

@ -7,7 +7,9 @@
namespace yii\validators; namespace yii\validators;
use Yii;
use yii\base\Component; use yii\base\Component;
use yii\base\NotSupportedException;
/** /**
* Validator is the base class for all validators. * Validator is the base class for all validators.
@ -81,7 +83,7 @@ abstract class Validator extends Component
*/ */
public $message; public $message;
/** /**
* @var array list of scenarios that the validator should be applied. * @var array list of scenarios that the validator can be applied to.
*/ */
public $on = array(); public $on = array();
/** /**
@ -94,6 +96,12 @@ abstract class Validator extends Component
*/ */
public $skipOnError = true; public $skipOnError = true;
/** /**
* @var boolean whether this validation rule should be skipped if the attribute value
* is null or an empty string.
*/
public $skipOnEmpty = true;
/**
* @var boolean whether to enable client-side validation. Defaults to null, meaning * @var boolean whether to enable client-side validation. Defaults to null, meaning
* its actual value inherits from that of [[\yii\web\ActiveForm::enableClientValidation]]. * its actual value inherits from that of [[\yii\web\ActiveForm::enableClientValidation]].
*/ */
@ -149,7 +157,7 @@ abstract class Validator extends Component
} }
} }
return \Yii::createObject($params); return Yii::createObject($params);
} }
/** /**
@ -168,13 +176,26 @@ abstract class Validator extends Component
$attributes = $this->attributes; $attributes = $this->attributes;
} }
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
if (!($this->skipOnError && $object->hasErrors($attribute))) { $skip = $this->skipOnError && $object->hasErrors($attribute)
|| $this->skipOnEmpty && $this->isEmpty($object->$attribute);
if (!$skip) {
$this->validateAttribute($object, $attribute); $this->validateAttribute($object, $attribute);
} }
} }
} }
/** /**
* Validates a value.
* A validator class can implement this method to support data validation out of the context of a data model.
* @param mixed $value the data value to be validated.
* @throws NotSupportedException if data validation without a model is not supported
*/
public function validateValue($value)
{
throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
}
/**
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* *
* You may override this method to return the JavaScript validation code if * You may override this method to return the JavaScript validation code if
@ -224,8 +245,9 @@ abstract class Validator extends Component
*/ */
public function addError($object, $attribute, $message, $params = array()) public function addError($object, $attribute, $message, $params = array())
{ {
$value = $object->$attribute;
$params['{attribute}'] = $object->getAttributeLabel($attribute); $params['{attribute}'] = $object->getAttributeLabel($attribute);
$params['{value}'] = $object->$attribute; $params['{value}'] = is_array($value) ? 'array()' : $value;
$object->addError($attribute, strtr($message, $params)); $object->addError($attribute, strtr($message, $params));
} }

14
framework/web/Application.php

@ -23,21 +23,15 @@ class Application extends \yii\base\Application
public $defaultRoute = 'site'; public $defaultRoute = 'site';
/** /**
* Sets default path aliases.
*/
public function registerDefaultAliases()
{
parent::registerDefaultAliases();
Yii::$aliases['@webroot'] = dirname($_SERVER['SCRIPT_FILENAME']);
}
/**
* Processes the request. * Processes the request.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
*/ */
public function processRequest() public function processRequest()
{ {
list ($route, $params) = $this->getRequest()->resolve(); $request = $this->getRequest();
Yii::setAlias('@wwwroot', dirname($request->getScriptFile()));
Yii::setAlias('@www', $request->getBaseUrl());
list ($route, $params) = $request->resolve();
return $this->runAction($route, $params); return $this->runAction($route, $params);
} }

33
framework/web/Request.php

@ -43,8 +43,6 @@ class Request extends \yii\base\Request
*/ */
public function resolve() public function resolve()
{ {
Yii::setAlias('@www', $this->getBaseUrl());
$result = Yii::$app->getUrlManager()->parseRequest($this); $result = Yii::$app->getUrlManager()->parseRequest($this);
if ($result !== false) { if ($result !== false) {
list ($route, $params) = $result; list ($route, $params) = $result;
@ -301,7 +299,8 @@ class Request extends \yii\base\Request
public function getScriptUrl() public function getScriptUrl()
{ {
if ($this->_scriptUrl === null) { if ($this->_scriptUrl === null) {
$scriptName = basename($_SERVER['SCRIPT_FILENAME']); $scriptFile = $this->getScriptFile();
$scriptName = basename($scriptFile);
if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) { if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['SCRIPT_NAME']; $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
} elseif (basename($_SERVER['PHP_SELF']) === $scriptName) { } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) {
@ -310,8 +309,8 @@ class Request extends \yii\base\Request
$this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME']; $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
} elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) { } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
$this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName; $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
} elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
$this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
} else { } else {
throw new InvalidConfigException('Unable to determine the entry script URL.'); throw new InvalidConfigException('Unable to determine the entry script URL.');
} }
@ -330,6 +329,30 @@ class Request extends \yii\base\Request
$this->_scriptUrl = '/' . trim($value, '/'); $this->_scriptUrl = '/' . trim($value, '/');
} }
private $_scriptFile;
/**
* Returns the entry script file path.
* The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
* @return string the entry script file path
*/
public function getScriptFile()
{
return isset($this->_scriptFile) ? $this->_scriptFile : $_SERVER['SCRIPT_FILENAME'];
}
/**
* Sets the entry script file path.
* The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
* If your server configuration does not return the correct value, you may configure
* this property to make it right.
* @param string $value the entry script file path.
*/
public function setScriptFile($value)
{
$this->_scriptFile = $value;
}
private $_pathInfo; private $_pathInfo;
/** /**

246
framework/web/UploadedFile.php

@ -0,0 +1,246 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use yii\widgets\ActiveForm;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class UploadedFile extends \yii\base\Object
{
private static $_files;
private $_name;
private $_tempName;
private $_type;
private $_size;
private $_error;
/**
* Constructor.
* Instead of using the constructor to create a new instance,
* you should normally call [[getInstance()]] or [[getInstances()]]
* to obtain new instances.
* @param string $name the original name of the file being uploaded
* @param string $tempName the path of the uploaded file on the server.
* @param string $type the MIME-type of the uploaded file (such as "image/gif").
* @param integer $size the actual size of the uploaded file in bytes
* @param integer $error the error code
*/
public function __construct($name, $tempName, $type, $size, $error)
{
$this->_name = $name;
$this->_tempName = $tempName;
$this->_type = $type;
$this->_size = $size;
$this->_error = $error;
}
/**
* String output.
* This is PHP magic method that returns string representation of an object.
* The implementation here returns the uploaded file's name.
* @return string the string representation of the object
*/
public function __toString()
{
return $this->_name;
}
/**
* Returns an uploaded file for the given model attribute.
* The file should be uploaded using [[ActiveForm::fileInput()]].
* @param \yii\base\Model $model the data model
* @param string $attribute the attribute name. The attribute name may contain array indexes.
* For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
* @return UploadedFile the instance of the uploaded file.
* Null is returned if no file is uploaded for the specified model attribute.
* @see getInstanceByName
*/
public static function getInstance($model, $attribute)
{
$name = ActiveForm::getInputName($model, $attribute);
return static::getInstanceByName($name);
}
/**
* Returns all uploaded files for the given model attribute.
* @param \yii\base\Model $model the data model
* @param string $attribute the attribute name. The attribute name may contain array indexes
* for tabular file uploading, e.g. '[1]file'.
* @return UploadedFile[] array of UploadedFile objects.
* Empty array is returned if no available file was found for the given attribute.
*/
public static function getInstances($model, $attribute)
{
$name = ActiveForm::getInputName($model, $attribute);
return static::getInstancesByName($name);
}
/**
* Returns an uploaded file according to the given file input name.
* The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
* @param string $name the name of the file input field.
* @return UploadedFile the instance of the uploaded file.
* Null is returned if no file is uploaded for the specified name.
*/
public static function getInstanceByName($name)
{
$files = static::loadFiles();
return isset($files[$name]) ? $files[$name] : null;
}
/**
* Returns an array of uploaded files corresponding to the specified file input name.
* This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
* 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
* @param string $name the name of the array of files
* @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned
* if no adequate upload was found. Please note that this array will contain
* all files from all sub-arrays regardless how deeply nested they are.
*/
public static function getInstancesByName($name)
{
$files = static::loadFiles();
if (isset($files[$name])) {
return array($files[$name]);
}
$results = array();
foreach ($files as $key => $file) {
if (strpos($key, "{$name}[") === 0) {
$results[] = self::$_files[$key];
}
}
return $results;
}
/**
* Cleans up the loaded UploadedFile instances.
* This method is mainly used by test scripts to set up a fixture.
*/
public static function reset()
{
self::$_files = null;
}
/**
* Saves the uploaded file.
* Note that this method uses php's move_uploaded_file() method. If the target file `$file`
* already exists, it will be overwritten.
* @param string $file the file path used to save the uploaded file
* @param boolean $deleteTempFile whether to delete the temporary file after saving.
* If true, you will not be able to save the uploaded file again in the current request.
* @return boolean true whether the file is saved successfully
* @see error
*/
public function saveAs($file, $deleteTempFile = true)
{
if ($this->_error == UPLOAD_ERR_OK) {
if ($deleteTempFile) {
return move_uploaded_file($this->_tempName, $file);
} elseif (is_uploaded_file($this->_tempName)) {
return copy($this->_tempName, $file);
}
}
return false;
}
/**
* @return string the original name of the file being uploaded
*/
public function getName()
{
return $this->_name;
}
/**
* @return string the path of the uploaded file on the server.
* Note, this is a temporary file which will be automatically deleted by PHP
* after the current request is processed.
*/
public function getTempName()
{
return $this->_tempName;
}
/**
* @return string the MIME-type of the uploaded file (such as "image/gif").
* Since this MIME type is not checked on the server side, do not take this value for granted.
* Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type.
*/
public function getType()
{
return $this->_type;
}
/**
* @return integer the actual size of the uploaded file in bytes
*/
public function getSize()
{
return $this->_size;
}
/**
* Returns an error code describing the status of this file uploading.
* @return integer the error code
* @see http://www.php.net/manual/en/features.file-upload.errors.php
*/
public function getError()
{
return $this->_error;
}
/**
* @return boolean whether there is an error with the uploaded file.
* Check [[error]] for detailed error code information.
*/
public function getHasError()
{
return $this->_error != UPLOAD_ERR_OK;
}
/**
* Creates UploadedFile instances from $_FILE.
* @return array the UploadedFile instances
*/
private static function loadFiles()
{
if (self::$_files === null) {
self::$_files = array();
if (isset($_FILES) && is_array($_FILES)) {
foreach ($_FILES as $class => $info) {
self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
}
}
}
return self::$_files;
}
/**
* Creates UploadedFile instances from $_FILE recursively.
* @param string $key key for identifying uploaded file: class name and sub-array indexes
* @param mixed $names file names provided by PHP
* @param mixed $tempNames temporary file names provided by PHP
* @param mixed $types file types provided by PHP
* @param mixed $sizes file sizes provided by PHP
* @param mixed $errors uploading issues provided by PHP
*/
private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors)
{
if (is_array($names)) {
foreach ($names as $i => $name) {
self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
}
} else {
self::$_files[$key] = new self($names, $tempNames, $types, $sizes, $errors);
}
}
}

63
framework/widgets/ActiveForm.php

@ -52,10 +52,6 @@ class ActiveForm extends Widget
public $enableClientValidation = false; public $enableClientValidation = false;
public $options = array(); public $options = array();
/**
* @var array model-class mapped to name prefix
*/
public $modelMap;
/** /**
* @param Model|Model[] $models * @param Model|Model[] $models
@ -240,35 +236,6 @@ class ActiveForm extends Widget
return Html::radioList($name, $checked, $items, $options); return Html::radioList($name, $checked, $items, $options);
} }
public function getInputName($model, $attribute)
{
$class = get_class($model);
if (isset($this->modelMap[$class])) {
$class = $this->modelMap[$class];
} elseif (($pos = strrpos($class, '\\')) !== false) {
$class = substr($class, $pos + 1);
}
if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
throw new InvalidParamException('Attribute name must contain word characters only.');
}
$prefix = $matches[1];
$attribute = $matches[2];
$suffix = $matches[3];
if ($class === '' && $prefix === '') {
return $attribute . $suffix;
} elseif ($class !== '') {
return $class . $prefix . "[$attribute]" . $suffix;
} else {
throw new InvalidParamException('Model name cannot be mapped to empty for tabular inputs.');
}
}
public function getInputId($model, $attribute)
{
$name = $this->getInputName($model, $attribute);
return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name);
}
public function getAttributeValue($model, $attribute) public function getAttributeValue($model, $attribute)
{ {
if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
@ -299,4 +266,34 @@ class ActiveForm extends Widget
throw new InvalidParamException('Attribute name must contain word characters only.'); throw new InvalidParamException('Attribute name must contain word characters only.');
} }
} }
/**
* @param Model $model
* @param string $attribute
* @return string
* @throws \yii\base\InvalidParamException
*/
public static function getInputName($model, $attribute)
{
$formName = $model->formName();
if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
throw new InvalidParamException('Attribute name must contain word characters only.');
}
$prefix = $matches[1];
$attribute = $matches[2];
$suffix = $matches[3];
if ($formName === '' && $prefix === '') {
return $attribute . $suffix;
} elseif ($formName !== '') {
return $formName . $prefix . "[$attribute]" . $suffix;
} else {
throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.');
}
}
public static function getInputId($model, $attribute)
{
$name = static::getInputName($model, $attribute);
return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name);
}
} }

44
tests/unit/framework/base/ComponentTest.php

@ -41,12 +41,12 @@ class ComponentTest extends TestCase
$component->attachBehavior('a', $behavior); $component->attachBehavior('a', $behavior);
$this->assertSame($behavior, $component->getBehavior('a')); $this->assertSame($behavior, $component->getBehavior('a'));
$component->on('test', 'fake'); $component->on('test', 'fake');
$this->assertEquals(1, $component->getEventHandlers('test')->count); $this->assertTrue($component->hasEventHandlers('test'));
$clone = clone $component; $clone = clone $component;
$this->assertNotSame($component, $clone); $this->assertNotSame($component, $clone);
$this->assertNull($clone->getBehavior('a')); $this->assertNull($clone->getBehavior('a'));
$this->assertEquals(0, $clone->getEventHandlers('test')->count); $this->assertFalse($clone->hasEventHandlers('test'));
} }
public function testHasProperty() public function testHasProperty()
@ -151,34 +151,32 @@ class ComponentTest extends TestCase
public function testOn() public function testOn()
{ {
$this->assertEquals(0, $this->component->getEventHandlers('click')->getCount()); $this->assertFalse($this->component->hasEventHandlers('click'));
$this->component->on('click', 'foo'); $this->component->on('click', 'foo');
$this->assertEquals(1, $this->component->getEventHandlers('click')->getCount()); $this->assertTrue($this->component->hasEventHandlers('click'));
$this->component->on('click', 'bar');
$this->assertEquals(2, $this->component->getEventHandlers('click')->getCount());
$p = 'on click';
$this->component->$p = 'foo2';
$this->assertEquals(3, $this->component->getEventHandlers('click')->getCount());
$this->component->getEventHandlers('click')->add('test'); $this->assertFalse($this->component->hasEventHandlers('click2'));
$this->assertEquals(4, $this->component->getEventHandlers('click')->getCount()); $p = 'on click2';
$this->component->$p = 'foo2';
$this->assertTrue($this->component->hasEventHandlers('click2'));
} }
public function testOff() public function testOff()
{ {
$this->assertFalse($this->component->hasEventHandlers('click'));
$this->component->on('click', 'foo'); $this->component->on('click', 'foo');
$this->component->on('click', array($this->component, 'myEventHandler')); $this->assertTrue($this->component->hasEventHandlers('click'));
$this->assertEquals(2, $this->component->getEventHandlers('click')->getCount()); $this->component->off('click', 'foo');
$this->assertFalse($this->component->hasEventHandlers('click'));
$result = $this->component->off('click', 'foo');
$this->assertTrue($result); $this->component->on('click2', 'foo');
$this->assertEquals(1, $this->component->getEventHandlers('click')->getCount()); $this->component->on('click2', 'foo2');
$result = $this->component->off('click', 'foo'); $this->component->on('click2', 'foo3');
$this->assertFalse($result); $this->assertTrue($this->component->hasEventHandlers('click2'));
$this->assertEquals(1, $this->component->getEventHandlers('click')->getCount()); $this->component->off('click2', 'foo3');
$result = $this->component->off('click', array($this->component, 'myEventHandler')); $this->assertTrue($this->component->hasEventHandlers('click2'));
$this->assertTrue($result); $this->component->off('click2');
$this->assertEquals(0, $this->component->getEventHandlers('click')->getCount()); $this->assertFalse($this->component->hasEventHandlers('click2'));
} }
public function testTrigger() public function testTrigger()

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

@ -10,7 +10,9 @@ class HtmlTest extends \yii\test\TestCase
{ {
public function setUp() public function setUp()
{ {
new Application('test', '@yiiunit/runtime', array( new Application(array(
'id' => 'test',
'basePath' => '@yiiunit/runtime',
'components' => array( 'components' => array(
'request' => array( 'request' => array(
'class' => 'yii\web\Request', 'class' => 'yii\web\Request',

Loading…
Cancel
Save