diff --git a/.gitignore b/.gitignore index 89fc2a8..9291d3e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ nbproject .settings # windows thumbnail cache -Thumbs.db \ No newline at end of file +Thumbs.db + +# composer vendor dir +vendor \ No newline at end of file diff --git a/apps/bootstrap/protected/views/layouts/main.php b/apps/bootstrap/protected/views/layouts/main.php index 8fb915a..1240053 100644 --- a/apps/bootstrap/protected/views/layouts/main.php +++ b/apps/bootstrap/protected/views/layouts/main.php @@ -39,6 +39,9 @@ $this->registerAssetBundle('app'); + widget('yii\widgets\Breadcrumbs', array( + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), + )); ?>
diff --git a/apps/bootstrap/protected/views/site/about.php b/apps/bootstrap/protected/views/site/about.php index 7ebc5d5..86e19e1 100644 --- a/apps/bootstrap/protected/views/site/about.php +++ b/apps/bootstrap/protected/views/site/about.php @@ -4,6 +4,7 @@ use yii\helpers\Html; * @var yii\base\View $this */ $this->title = 'About'; +$this->params['breadcrumbs'][] = $this->title; ?>

title); ?>

diff --git a/apps/bootstrap/protected/views/site/contact.php b/apps/bootstrap/protected/views/site/contact.php index 4ca40f1..5cb5a8e 100644 --- a/apps/bootstrap/protected/views/site/contact.php +++ b/apps/bootstrap/protected/views/site/contact.php @@ -6,6 +6,7 @@ use yii\helpers\Html; * @var app\models\ContactForm $model */ $this->title = 'Contact'; +$this->params['breadcrumbs'][] = $this->title; ?>

title); ?>

diff --git a/apps/bootstrap/protected/views/site/login.php b/apps/bootstrap/protected/views/site/login.php index 8672eeb..f7f842e 100644 --- a/apps/bootstrap/protected/views/site/login.php +++ b/apps/bootstrap/protected/views/site/login.php @@ -6,6 +6,7 @@ use yii\helpers\Html; * @var app\models\LoginForm $model */ $this->title = 'Login'; +$this->params['breadcrumbs'][] = $this->title; ?>

title); ?>

diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6a5c383 --- /dev/null +++ b/composer.json @@ -0,0 +1,75 @@ +{ + "name": "yiisoft/yii2", + "description": "Yii2 Web Programming Framework", + "keywords": ["yii", "framework"], + "homepage": "http://www.yiiframework.com/", + "type": "library", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Wei Zhuo", + "email": "weizhuo@gmail.com", + "role": "Project site maintenance and development" + }, + { + "name": "Sebastián Thierer", + "email": "sebas@artfos.com", + "role": "Component development" + }, + { + "name": "Jeffrey Winesett", + "email": "jefftulsa@gmail.com", + "role": "Documentation and marketing" + } + ], + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "bin": [ + "framework/yiic" + ], + "require": { + "php": ">=5.3.0", + "michelf/php-markdown": "1.3", + "twig/twig": "1.12.*", + "smarty/smarty": "3.1.*" + } +} diff --git a/docs/guide/index.md b/docs/guide/index.md index 6ad733c..dd72ca3 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -27,4 +27,4 @@ * [Performance Tuning](performance.md) * [Testing](testing.md) * [Automatic Code Generation](gii.md) -* [Upgrading from 1.1 to 2.0](upgrade.md) +* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md new file mode 100644 index 0000000..d35e6e0 --- /dev/null +++ b/docs/guide/upgrade-from-v1.md @@ -0,0 +1,459 @@ +Upgrading from Yii 1.1 +====================== + +In this chapter, we list the major changes introduced in Yii 2.0 since version 1.1. +We hope this list will make it easier for you to upgrade from Yii 1.1 and quickly +master Yii 2.0 based on your existing Yii knowledge. + + +Namespace +--------- + +The most obvious change in Yii 2.0 is the use of namespaces. Almost every core class +is namespaced, e.g., `yii\web\Request`. The "C" prefix is no longer used in class names. +The naming of the namespaces follows the directory structure. For example, `yii\web\Request` +indicates the corresponding class file is `web/Request.php` under the Yii framework folder. +You can use any core class without explicitly include that class file, thanks to the Yii +class loader. + + +Component and Object +-------------------- + +Yii 2.0 breaks the `CComponent` class in 1.1 into two classes: `Object` and `Component`. +The `Object` class is a lightweight base class that allows defining class properties +via getters and setters. The `Component` class extends from `Object` and supports +the event feature and the behavior feature. + +If your class does not need the event or behavior feature, you should consider using +`Object` as the base class. This is usually the case for classes that represent basic +data structures. + + +Object Configuration +-------------------- + +The `Object` class introduces a uniform way of configuring objects. Any descendant class +of `Object` should declare its constructor (if needed) in the following way so that +it can be properly configured: + +```php +class MyClass extends \yii\Object +{ + public function __construct($param1, $param2, $config = array()) + { + // ... initialization before configuration is applied + + parent::__construct($config); + } + + public function init() + { + parent::init(); + + // ... initialization after configuration is applied + } +} +``` + +In the above, the last parameter of the constructor must take a configuration array +which contains name-value pairs for initializing the properties at the end of the constructor. +You can override the `init()` method to do initialization work that should be done after +the configuration is applied. + +By following this convention, you will be able to create and configure a new object +using a configuration array like the following: + +```php +$object = Yii::createObject(array( + 'class' => 'MyClass', + 'property1' => 'abc', + 'property2' => 'cde', +), $param1, $param2); +``` + + + +Events +------ + +There is no longer the need to define an `on`-method in order to define an event in Yii 2.0. +Instead, you can use whatever event names. To attach a handler to an event, you should +use the `on` method now: + +```php +$component->on($eventName, $handler); +// To detach the handler, use: +// $component->off($eventName, $handler); +``` + + +When you attach a handler, you can now associate it with some parameters which can be later +accessed via the event parameter by the handler: + +```php +$component->on($eventName, $handler, $params); +``` + + +Because of this change, you can now use "global" events. Simply trigger and attach handlers to +an event of the application instance: + +```php +Yii::$app->on($eventName, $handler); +.... +// this will trigger the event and cause $handler to be invoked. +Yii::$app->trigger($eventName); +``` + + +Path Alias +---------- + +Yii 2.0 expands the usage of path aliases to both file/directory paths and URLs. An alias +must start with a `@` character so that it can be differentiated from file/directory paths and URLs. +For example, the alias `@yii` refers to the Yii installation directory. Path aliases are +supported in most places in the Yii core code. For example, `FileCache::cachePath` can take +both a path alias and a normal directory path. + +Path alias is also closely related with class namespaces. It is recommended that a path +alias defined for each root namespace so that you can use Yii class autoloader without +any further configuration. For example, because `@yii` refers to the Yii installation directory, +a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library +such as Zend Framework, you may define a path alias `@Zend` which refers to its installation directory. +And Yii will be able to autoload any class in this library. + + +View +---- + +Yii 2.0 introduces a `View` class to represent the view part in the MVC pattern. +It can be configured globally through the "view" application component. It is also +accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1: +**`$this` in a view file no longer refers to the controller or widget object.** +It refers to the view object that is used to render the view file. To access the controller +or the widget object, you have to use `$this->context` now. + +Because you can access the view object through the "view" application component, +you can now render a view file like the following anywhere in your code, not necessarily +in controllers or widgets: + +```php +$content = Yii::$app->view->renderFile($viewFile, $params); +// You can also explicitly create a new View instance to do the rendering +// $view = new View; +// $view->renderFile($viewFile, $params); +``` + + +Also, there is no more `CClientScript` in Yii 2.0. The `View` class has taken over its role +with significant improvements. For more details, please see the "assets" subsection. + +While Yii 2.0 continues to use PHP as its main template language, it comes with built-in +support for two popular template engines: Smarty and Twig. The Prado template engine is +no longer supported. To use these template engines, you just need to use `tpl` as the file +extension for your Smarty views, or `twig` for Twig views. You may also configure the +`View::renderers` property to use other template engines. + + +Models +------ + +A model is now associated with a form name returned its `formName()` method. This is +mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1, +this is usually hardcoded as the class name of the model. + + +Yii 2.0 introduces a new method called `scenarios()` to declare which attributes require +validation under which scenario. Child classes should overwrite `scenarios()` to return +a list of scenarios and the corresponding attributes that need to be validated when +`validate()` is called. For example, + +```php +public function scenarios() +{ + return array( + 'backend' => array('email', 'role'), + 'frontend' => array('email', '!name'), + ); +} +``` + + +This method also determines which attributes are safe and which are not. In particular, +given a scenario, if an attribute appears in the corresponding attribute list in `scenarios()` +and the name is not prefixed with `!`, it is considered *safe*. + +Because of the above change, Yii 2.0 no longer has "safe" and "unsafe" validators. + +If your model only has one scenario (very common), you do not have to overwrite `scenarios()`, +and everything will still work like the 1.1 way. + + +Controllers +----------- + +The `render()` and `renderPartial()` methods now return the rendering results instead of directly +sending them out. You have to `echo` them explicitly, e.g., `echo $this->render(...);`. + +A new method called `populate()` is introduced to simplify the data population from user inputs +to a model. For example, + +```php +$model = new Post; +if ($this->populate($_POST, $model)) {...} +// which is equivalent to: +if (isset($_POST['Post'])) { + $model->attributes = $_POST['Post']; +} +``` + + + +Themes +------ + +Theme works completely different in 2.0. It is now based on a path map to "translate" a source +view into a themed view. For example, if the path map for a theme is +`array('/www/views' => '/www/themes/basic')`, then the themed version for a view file +`/www/views/site/index.php` will be `/www/themes/basic/site/index.php`. + +For this reason, theme can now be applied to any view file, even if a view rendered outside +of the context of a controller or a widget. + +There is no more `CThemeManager`. Instead, `theme` is a configurable property of the "view" +application component. + + +Console Applications +-------------------- + +Console applications are now composed by controllers, too, like Web applications. In fact, +console controllers and Web controllers share the same base controller class. + +Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several +actions. You use the `yiic ` command to execute a console command, where `` +stands for a controller route (e.g. `sitemap/index`). Additional anonymous arguments +are passed as the parameters to the corresponding controller action method, and named arguments +are treated as global options declared in `globalOptions()`. + +Yii 2.0 supports automatic generation of command help information from comment blocks. + + +I18N +---- + +Yii 2.0 removes date formatter and number formatter in favor of the PECL intl PHP module. + +Message translation is still supported, but managed via the "i18n" application component. +The component manages a set of message sources, which allows you to use different message +sources based on message categories. For more information, see the class documentation for `I18N`. + +The message translation method is changed by merging the message category into the message being +translated. For example, `Yii::t('yii|message to be translated')`. + + + +Action Filters +-------------- + +Action filters are implemented via behaviors now. You should extend from `ActionFilter` to +define a new filter. To use a filter, you should attach the filter class to the controller +as a behavior. For example, to use the `AccessControl` filter, you should have the following +code in a controller: + +```php +public function behaviors() +{ + return array( + 'access' => array( + 'class' => 'yii\web\AccessControl', + 'rules' => array( + array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')), + array('allow' => false), + ), + ), + ); +} +``` + + + +Assets +------ + +Yii 2.0 introduces a new concept called *asset bundle*. It is a bit similar to script +packages (managed by `CClientScript`) in 1.1, but with better support. + +An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.) +under a directory. By registering an asset bundle via `View::registerAssetBundle()`, you +will be able to make the assets in that bundle accessible via Web, and the current page +will automatically contain references to the JavaScript and CSS files in that bundle. + + + +Static Helpers +-------------- + +Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`, +`StringHelper`. These classes are designed to be easily extended. Note that static classes +are usually hard to be extended because of the fixed class name references. But Yii 2.0 +introduces the class map (via `Yii::$classMap`) to overcome this difficulty. + + +`ActiveForm` +------------ + +Yii 2.0 introduces the *field* concept for building a form using `ActiveForm`. A field +is a container consisting of a label, an input, and an error message. It is represented +as an `ActiveField` object. Using fields, you can build a form more cleanly than before: + +```php +beginWidget('yii\widgets\ActiveForm'); ?> + field($model, 'username')->textInput(); ?> + field($model, 'password')->passwordInput(); ?> +
+ +
+endWidget(); ?> +``` + + + +Query Builder +------------- + +In 1.1, query building is scattered among several classes, including `CDbCommand`, +`CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query +and `QueryBuilder` to generate SQL statements from query objects. For example, + +```php +$query = new \yii\db\Query; +$query->select('id, name') + ->from('tbl_user') + ->limit(10); + +$command = $query->createCommand(); +$sql = $command->sql; +$rows = $command->queryAll(); +``` + + +Best of all, such query building methods can be used together with `ActiveRecord`, +as explained in the next sub-section. + + +ActiveRecord +------------ + +ActiveRecord has undergone significant changes in Yii 2.0. The most important one +is about relational ActiveRecord query. In 1.1, you have to declare the relations +in the `relations()` method. In 2.0, this is done via getter methods that return +an `ActiveQuery` object. For example, the following method declares an "orders" relation: + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getOrders() + { + return $this->hasMany('Order', array('customer_id' => 'id')); + } +} +``` + + +You can use `$customer->orders` to access the customer's orders. You can also +use `$customer->getOrders()->andWhere('status=1')->all()` to perform on-the-fly +relational query with customized query conditions. + +When loading relational records in an eager way, Yii 2.0 does it differently from 1.1. +In particular, in 1.1 a JOIN query would be used to bring both the primary and the relational +records; while in 2.0, two SQL statements are executed without using JOIN: the first +statement brings back the primary records and the second brings back the relational records +by filtering with the primary keys of the primary records. + + +Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you +use the `find()` method like the following: + +```php +// to retrieve all *active* customers and order them by their ID: +$customers = Customer::find() + ->where(array('status' => $active)) + ->orderBy('id') + ->all(); +// return the customer whose PK is 1 +$customer = Customer::find(1); +``` + + +The `find()` method returns an instance of `ActiveQuery` which is a subclass of `Query`. +Therefore, you can use all query methods of `Query`. + +Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to +return results in terms of arrays. This is more efficient and is especially useful +when you need to return large number of records. For example, + +```php +$customers = Customer::find()->asArray()->all(); +``` + +By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes +would be saved to database when you call `save()`, regardless they are changed or not, +unless you explicitly list the attributes to save. + + +Auto-quoting Table and Column Names +------------------------------------ + +Yii 2.0 supports automatic quoting of database table and column names. A name enclosed +within double curly brackets is treated as a table name, and a name enclosed within +double square brackets is treated as a column name. They will be quoted according to +the database driver being used. For example, + +```php +$command = $connection->createCommand('SELECT [[id]] FROM {{posts}}'); +echo $command->sql; // MySQL: SELECT `id` FROM `posts` +``` + +This feature is especially useful if you are developing an application that supports +different DBMS. + + +User and Identity +----------------- + +The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more +`CUserIdentity` class. Instead, you should implement the `Identity` interface which +is much more straightforward to implement. The bootstrap application provides such an example. + + +URL Management +-------------- + +URL management is similar to 1.1. A major enhancement is that it now supports optional +parameters. For example, if you have rule declared as follows, then it will match +both `post/popular` and `post/1/popular`. In 1.1, you would have to use two rules to achieve +the same goal. + +```php +array( + 'pattern' => 'post//', + 'route' => 'post/index', + 'defaults' => array('page' => 1), +) +``` + + + +Response +-------- + +Extensions +---------- + +Integration with Composer +------------------------- + +TBD + diff --git a/framework/YiiBase.php b/framework/YiiBase.php index ed975c9..c911f78 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -4,6 +4,8 @@ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ +namespace yii; + use yii\base\Exception; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; @@ -60,7 +62,7 @@ class YiiBase */ public static $enableIncludePath = true; /** - * @var yii\console\Application|yii\web\Application the application instance + * @var \yii\console\Application|\yii\web\Application the application instance */ public static $app; /** diff --git a/framework/base/Component.php b/framework/base/Component.php index 582cf03..8e75835 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -90,6 +90,7 @@ class Component extends Object // as behavior: attach behavior $name = trim(substr($name, 3)); $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value)); + return; } else { // behavior property $this->ensureBehaviors(); diff --git a/framework/base/View.php b/framework/base/View.php index 4718d90..af1f0d5 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -72,23 +72,21 @@ class View extends Component /** * @var array a list of available renderers indexed by their corresponding supported file extensions. * Each renderer may be a view renderer object or the configuration for creating the renderer object. - * For example, - * - * ~~~ - * array( - * 'tpl' => array( - * 'class' => 'yii\renderers\SmartyRenderer', - * ), - * 'twig' => array( - * 'class' => 'yii\renderers\TwigRenderer', - * ), - * ) - * ~~~ + * The default setting supports both Smarty and Twig (their corresponding file extension is "tpl" + * and "twig" respectively. Please refer to [[SmartyRenderer]] and [[TwigRenderer]] on how to install + * the needed libraries for these template engines. * * If no renderer is available for the given view file, the view file will be treated as a normal PHP * and rendered via [[renderPhpFile()]]. */ - public $renderers = array(); + public $renderers = array( + 'tpl' => array( + 'class' => 'yii\renderers\SmartyRenderer', + ), + 'twig' => array( + 'class' => 'yii\renderers\TwigRenderer', + ), + ); /** * @var Theme|array the theme object or the configuration array for creating the theme object. * If not set, it means theming is not enabled. diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 45c53fb..709d139 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -8,6 +8,7 @@ namespace yii\db; +use yii\base\InvalidConfigException; use yii\base\Model; use yii\base\InvalidParamException; use yii\base\ModelEvent; @@ -112,6 +113,7 @@ class ActiveRecord extends Model * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be * returned (null will be returned if there is no matching). + * @throws InvalidConfigException if the AR class does not have a primary key * @see createQuery() */ public static function find($q = null) @@ -122,7 +124,11 @@ class ActiveRecord extends Model } elseif ($q !== null) { // query by primary key $primaryKey = static::primaryKey(); - return $query->where(array($primaryKey[0] => $q))->one(); + if (isset($primaryKey[0])) { + return $query->where(array($primaryKey[0] => $q))->one(); + } else { + throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); + } } return $query; } diff --git a/framework/helpers/base/Json.php b/framework/helpers/base/Json.php index c92e208..262dd81 100644 --- a/framework/helpers/base/Json.php +++ b/framework/helpers/base/Json.php @@ -8,7 +8,7 @@ namespace yii\helpers\base; use yii\base\InvalidParamException; -use yii\helpers\JsExpression; +use yii\web\JsExpression; /** * Json is a helper class providing JSON data encoding and decoding. diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php index ad74dd6..949b3f9 100644 --- a/framework/validators/EmailValidator.php +++ b/framework/validators/EmailValidator.php @@ -9,7 +9,7 @@ namespace yii\validators; use Yii; use yii\helpers\Html; -use yii\helpers\JsExpression; +use yii\web\JsExpression; use yii\helpers\Json; /** diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index c0f81cd..10f0e52 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -9,7 +9,7 @@ namespace yii\validators; use Yii; use yii\helpers\Html; -use yii\helpers\JsExpression; +use yii\web\JsExpression; use yii\helpers\Json; /** diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php index 79a1a3c..23419b9 100644 --- a/framework/validators/RegularExpressionValidator.php +++ b/framework/validators/RegularExpressionValidator.php @@ -10,7 +10,7 @@ namespace yii\validators; use Yii; use yii\base\InvalidConfigException; use yii\helpers\Html; -use yii\helpers\JsExpression; +use yii\web\JsExpression; use yii\helpers\Json; /** diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php index 0ed59bd..c418353 100644 --- a/framework/validators/UrlValidator.php +++ b/framework/validators/UrlValidator.php @@ -9,7 +9,7 @@ namespace yii\validators; use Yii; use yii\helpers\Html; -use yii\helpers\JsExpression; +use yii\web\JsExpression; use yii\helpers\Json; /** diff --git a/framework/helpers/JsExpression.php b/framework/web/JsExpression.php similarity index 97% rename from framework/helpers/JsExpression.php rename to framework/web/JsExpression.php index 5a1f9bd..027c065 100644 --- a/framework/helpers/JsExpression.php +++ b/framework/web/JsExpression.php @@ -5,7 +5,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\helpers; +namespace yii\web; use yii\base\Object; diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 336966f..0e0381f 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -10,7 +10,7 @@ use yii\base\Component; use yii\db\ActiveRecord; use yii\helpers\Html; use yii\base\Model; -use yii\helpers\JsExpression; +use yii\web\JsExpression; /** * @author Qiang Xue diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index c11bceb..61416e2 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -12,7 +12,6 @@ use yii\base\Widget; use yii\base\Model; use yii\helpers\Html; use yii\helpers\Json; -use yii\helpers\JsExpression; /** * ActiveForm ... diff --git a/framework/widgets/Breadcrumbs.php b/framework/widgets/Breadcrumbs.php new file mode 100644 index 0000000..22d09b3 --- /dev/null +++ b/framework/widgets/Breadcrumbs.php @@ -0,0 +1,139 @@ +widget('yii\widgets\Breadcrumbs', array( + * 'links' => array( + * array('label' => 'Sample Post', 'url' => array('post/edit', 'id' => 1)), + * 'Edit', + * ), + * )); + * ~~~ + * + * Because breadcrumbs usually appears in nearly every page of a website, you may consider place it in a layout view. + * You can then use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different + * views. In the layout view, you assign this view parameter to the [[links]] property like the following: + * + * ~~~ + * $this->widget('yii\widgets\Breadcrumbs', array( + * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), + * )); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Breadcrumbs extends Widget +{ + /** + * @var string the name of the breadcrumb container tag. + */ + public $tag = 'ul'; + /** + * @var array the HTML attributes for the breadcrumb container tag. + */ + public $options = array('class' => 'breadcrumb'); + /** + * @var boolean whether to HTML-encode the link labels. + */ + public $encodeLabels = true; + /** + * @var string the first hyperlink in the breadcrumbs (called home link). + * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]] + * with the label 'Home'. If this property is false, the home link will not be rendered. + */ + public $homeLink; + /** + * @var array list of links to appear in the breadcrumbs. If this property is empty, + * the widget will not render anything. Each array element represents a single link in the breadcrumbs + * with the following structure: + * + * ~~~ + * array( + * 'label' => 'label of the link', // required + * 'url' => 'url of the link', // optional, will be processed by Html::url() + * ) + * ~~~ + * + * If a link is active, you only need to specify its "label", and instead of writing `array('label' => $label)`, + * you should simply use `$label`. + */ + public $links = array(); + /** + * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}` + * will be replaced with the actual HTML link for each inactive item. + */ + public $itemTemplate = "
  • {link} /
  • \n"; + /** + * @var string the template used to render each active item in the breadcrumbs. The token `{link}` + * will be replaced with the actual HTML link for each active item. + */ + public $activeItemTemplate = "
  • {link}
  • \n"; + + /** + * Renders the widget. + */ + public function run() + { + if (empty($this->links)) { + return; + } + $links = array(); + if ($this->homeLink === null) { + $links[] = $this->renderItem(array( + 'label' => Yii::t('yii|Home'), + 'url' => Yii::$app->homeUrl, + ), $this->itemTemplate); + } elseif ($this->homeLink !== false) { + $links[] = $this->renderItem($this->homeLink, $this->itemTemplate); + } + foreach ($this->links as $link) { + if (!is_array($link)) { + $link = array('label' => $link); + } + $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate); + } + echo Html::tag($this->tag, implode('', $links), $this->options); + } + + /** + * Renders a single breadcrumb item. + * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional. + * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link. + * @return string the rendering result + * @throws InvalidConfigException if `$link` does not have "label" element. + */ + protected function renderItem($link, $template) + { + if (isset($link['label'])) { + $label = $this->encodeLabels ? Html::encode($link['label']) : $link['label']; + } else { + throw new InvalidConfigException('The "label" element is required for each link.'); + } + if (isset($link['url'])) { + return strtr($template, array('{link}' => Html::a($label, $link['url']))); + } else { + return strtr($template, array('{link}' => $label)); + } + } +} diff --git a/framework/widgets/Menu.php b/framework/widgets/Menu.php new file mode 100644 index 0000000..3af620d --- /dev/null +++ b/framework/widgets/Menu.php @@ -0,0 +1,282 @@ + + * @since 2.0 + */ +class Menu extends Widget +{ + /** + * @var array list of menu items. Each menu item is specified as an array of name-value pairs. + * Possible option names include the following: + *
      + *
    • label: string, optional, specifies the menu item label. When {@link encodeLabel} is true, the label + * will be HTML-encoded. If the label is not specified, it defaults to an empty string.
    • + *
    • url: string or array, optional, specifies the URL of the menu item. It is passed to {@link Html::normalizeUrl} + * to generate a valid URL. If this is not set, the menu item will be rendered as a span text.
    • + *
    • visible: boolean, optional, whether this menu item is visible. Defaults to true. + * This can be used to control the visibility of menu items based on user permissions.
    • + *
    • items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
    • + *
    • active: boolean, optional, whether this menu item is in active state (currently selected). + * If a menu item is active and {@link activeClass} is not empty, its CSS class will be appended with {@link activeClass}. + * If this option is not set, the menu item will be set active automatically when the current request + * is triggered by {@link url}. Note that the GET parameters not specified in the 'url' option will be ignored.
    • + *
    • template: string, optional, the template used to render this menu item. + * When this option is set, it will override the global setting {@link itemTemplate}. + * Please see {@link itemTemplate} for more details. This option has been available since version 1.1.1.
    • + *
    • linkOptions: array, optional, additional HTML attributes to be rendered for the link or span tag of the menu item.
    • + *
    • itemOptions: array, optional, additional HTML attributes to be rendered for the container tag of the menu item.
    • + *
    • submenuOptions: array, optional, additional HTML attributes to be rendered for the container of the submenu if this menu item has one. + * When this option is set, the {@link submenuHtmlOptions} property will be ignored for this particular submenu. + * This option has been available since version 1.1.6.
    • + *
    + */ + public $items = array(); + /** + * @var string the template used to render an individual menu item. In this template, + * the token "{menu}" will be replaced with the corresponding menu link or text. + * If this property is not set, each menu will be rendered without any decoration. + * This property will be overridden by the 'template' option set in individual menu items via {@items}. + * @since 1.1.1 + */ + public $itemTemplate; + /** + * @var boolean whether the labels for menu items should be HTML-encoded. Defaults to true. + */ + public $encodeLabel = true; + /** + * @var string the CSS class to be appended to the active menu item. Defaults to 'active'. + * If empty, the CSS class of menu items will not be changed. + */ + public $activeCssClass = 'active'; + /** + * @var boolean whether to automatically activate items according to whether their route setting + * matches the currently requested route. Defaults to true. + * @since 1.1.3 + */ + public $activateItems = true; + /** + * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active. + * The activated parent menu items will also have its CSS classes appended with {@link activeCssClass}. + * Defaults to false. + */ + public $activateParents = false; + /** + * @var boolean whether to hide empty menu items. An empty menu item is one whose 'url' option is not + * set and which doesn't contain visible child menu items. Defaults to true. + */ + public $hideEmptyItems = true; + /** + * @var array HTML attributes for the menu's root container tag + */ + public $options = array(); + /** + * @var array HTML attributes for the submenu's container tag. + */ + public $submenuHtmlOptions = array(); + /** + * @var string the HTML element name that will be used to wrap the label of all menu links. + * For example, if this property is set as 'span', a menu item may be rendered as + * <li><a href="url"><span>label</span></a></li> + * This is useful when implementing menu items using the sliding window technique. + * Defaults to null, meaning no wrapper tag will be generated. + * @since 1.1.4 + */ + public $linkLabelWrapper; + /** + * @var array HTML attributes for the links' wrap element specified in + * {@link linkLabelWrapper}. + * @since 1.1.13 + */ + public $linkLabelWrapperHtmlOptions = array(); + /** + * @var string the CSS class that will be assigned to the first item in the main menu or each submenu. + * Defaults to null, meaning no such CSS class will be assigned. + * @since 1.1.4 + */ + public $firstItemCssClass; + /** + * @var string the CSS class that will be assigned to the last item in the main menu or each submenu. + * Defaults to null, meaning no such CSS class will be assigned. + * @since 1.1.4 + */ + public $lastItemCssClass; + /** + * @var string the CSS class that will be assigned to every item. + * Defaults to null, meaning no such CSS class will be assigned. + * @since 1.1.9 + */ + public $itemCssClass; + + /** + * Initializes the menu widget. + * This method mainly normalizes the {@link items} property. + * If this method is overridden, make sure the parent implementation is invoked. + */ + public function init() + { + $route = $this->getController()->getRoute(); + $this->items = $this->normalizeItems($this->items, $route, $hasActiveChild); + } + + /** + * Calls {@link renderMenu} to render the menu. + */ + public function run() + { + if (count($this->items)) { + echo Html::beginTag('ul', $this->options) . "\n"; + $this->renderItems($this->items); + echo Html::endTag('ul'); + } + } + + /** + * Recursively renders the menu items. + * @param array $items the menu items to be rendered recursively + */ + protected function renderItems($items) + { + $count = 0; + $n = count($items); + foreach ($items as $item) { + $count++; + $options = isset($item['itemOptions']) ? $item['itemOptions'] : array(); + $class = array(); + if ($item['active'] && $this->activeCssClass != '') { + $class[] = $this->activeCssClass; + } + if ($count === 1 && $this->firstItemCssClass !== null) { + $class[] = $this->firstItemCssClass; + } + if ($count === $n && $this->lastItemCssClass !== null) { + $class[] = $this->lastItemCssClass; + } + if ($this->itemCssClass !== null) { + $class[] = $this->itemCssClass; + } + if ($class !== array()) { + if (empty($options['class'])) { + $options['class'] = implode(' ', $class); + } else { + $options['class'] .= ' ' . implode(' ', $class); + } + } + + echo Html::beginTag('li', $options); + + $menu = $this->renderItem($item); + if (isset($this->itemTemplate) || isset($item['template'])) { + $template = isset($item['template']) ? $item['template'] : $this->itemTemplate; + echo strtr($template, array('{menu}' => $menu)); + } else { + echo $menu; + } + + if (isset($item['items']) && count($item['items'])) { + echo "\n" . Html::beginTag('ul', isset($item['submenuOptions']) ? $item['submenuOptions'] : $this->submenuHtmlOptions) . "\n"; + $this->renderItems($item['items']); + echo Html::endTag('ul') . "\n"; + } + + echo Html::endTag('li') . "\n"; + } + } + + /** + * Renders the content of a menu item. + * Note that the container and the sub-menus are not rendered here. + * @param array $item the menu item to be rendered. Please see {@link items} on what data might be in the item. + * @return string + * @since 1.1.6 + */ + protected function renderItem($item) + { + if (isset($item['url'])) { + $label = $this->linkLabelWrapper === null ? $item['label'] : Html::tag($this->linkLabelWrapper, $this->linkLabelWrapperHtmlOptions, $item['label']); + return Html::a($label, $item['url'], isset($item['linkOptions']) ? $item['linkOptions'] : array()); + } else { + return Html::tag('span', isset($item['linkOptions']) ? $item['linkOptions'] : array(), $item['label']); + } + } + + /** + * Normalizes the {@link items} property so that the 'active' state is properly identified for every menu item. + * @param array $items the items to be normalized. + * @param string $route the route of the current request. + * @param boolean $active whether there is an active child menu item. + * @return array the normalized menu items + */ + protected function normalizeItems($items, $route, &$active) + { + foreach ($items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + if (!isset($item['label'])) { + $item['label'] = ''; + } + if ($this->encodeLabel) { + $items[$i]['label'] = Html::encode($item['label']); + } + $hasActiveChild = false; + if (isset($item['items'])) { + $items[$i]['items'] = $this->normalizeItems($item['items'], $route, $hasActiveChild); + if (empty($items[$i]['items']) && $this->hideEmptyItems) { + unset($items[$i]['items']); + if (!isset($item['url'])) { + unset($items[$i]); + continue; + } + } + } + if (!isset($item['active'])) { + if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item, $route)) { + $active = $items[$i]['active'] = true; + } else { + $items[$i]['active'] = false; + } + } elseif ($item['active']) { + $active = true; + } + } + return array_values($items); + } + + /** + * Checks whether a menu item is active. + * This is done by checking if the currently requested URL is generated by the 'url' option + * of the menu item. Note that the GET parameters not specified in the 'url' option will be ignored. + * @param array $item the menu item to be checked + * @param string $route the route of the current request + * @return boolean whether the menu item is active + */ + protected function isItemActive($item, $route) + { + if (isset($item['url']) && is_array($item['url']) && !strcasecmp(trim($item['url'][0], '/'), $route)) { + unset($item['url']['#']); + if (count($item['url']) > 1) { + foreach (array_splice($item['url'], 1) as $name => $value) { + if (!isset($_GET[$name]) || $_GET[$name] != $value) { + return false; + } + } + } + return true; + } + return false; + } + +} diff --git a/framework/yii.php b/framework/yii.php index 828dc4f..109e2fd 100644 --- a/framework/yii.php +++ b/framework/yii.php @@ -18,7 +18,7 @@ require(__DIR__ . '/YiiBase.php'); * @author Qiang Xue * @since 2.0 */ -class Yii extends YiiBase +class Yii extends \yii\YiiBase { } diff --git a/readme.md b/readme.md index f395e95..178acd4 100644 --- a/readme.md +++ b/readme.md @@ -27,10 +27,21 @@ REQUIREMENTS The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +DOCUMENTATION +------------- + +For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md) +to have a general idea of what has changed in 2.0. + +We are writing more documentation to get you started and learn more in depth. + + HOW TO PARTICIPATE ------------------ -You are welcome to participate in Yii 2 development in the following ways: +**Your participation to Yii 2 development is very welcome!** + +You may participate in the following ways: * [Report issues](https://github.com/yiisoft/yii2/issues) * [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-design-discussions-for-yii-20/) diff --git a/tests/unit/framework/helpers/JsonTest.php b/tests/unit/framework/helpers/JsonTest.php index 6a78cd1..1795ce6 100644 --- a/tests/unit/framework/helpers/JsonTest.php +++ b/tests/unit/framework/helpers/JsonTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\helpers\Json; -use yii\helpers\JsExpression; +use yii\web\JsExpression; class JsonTest extends \yii\test\TestCase {