From c30eae62d25677c095aebc6819c5c95573d3606d Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Wed, 29 May 2013 00:01:02 +0200 Subject: [PATCH 01/91] Added initial unit testing files for the pgsql driver. --- framework/yii/db/pgsql/Schema.php | 36 ++++ tests/unit/data/config.php | 6 + tests/unit/data/postgres.sql | 218 +++++++-------------- .../db/pgsql/PostgreSQLActiveRecordTest.php | 14 ++ .../db/pgsql/PostgreSQLConnectionTest.php | 18 ++ 5 files changed, 143 insertions(+), 149 deletions(-) create mode 100644 framework/yii/db/pgsql/Schema.php create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php new file mode 100644 index 0000000..4ccf33a --- /dev/null +++ b/framework/yii/db/pgsql/Schema.php @@ -0,0 +1,36 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. + */ + public function loadTableSchema($name) + { + $table = new TableSchema(); + $this->resolveTableNames($table, $name); + $this->findPrimaryKeys($table); + if ($this->findColumns($table)) { + $this->findForeignKeys($table); + return $table; + } + } +} \ No newline at end of file diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 88c8127..cb612d4 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -20,5 +20,11 @@ return array( 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', ), + 'pgsql' => array( + 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432', + 'username' => 'postgres', + 'password' => 'postgres', + 'fixture' => __DIR__ . '/postgres.sql', + ) ) ); diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index e46b284..ce75206 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -1,165 +1,85 @@ /** * This is the database schema for testing PostgreSQL support of yii Active Record. - * To test this feature, you need to create a database named 'yii' on 'localhost' - * and create an account 'test/test' which owns this test database. + * To test this feature, you need to create a database named 'yiitest' on 'localhost' + * and create an account 'postgres/postgres' which owns this test database. */ -CREATE SCHEMA test; -CREATE TABLE test.users -( - id SERIAL NOT NULL PRIMARY KEY, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL +DROP TABLE IF EXISTS tbl_order_item CASCADE; +DROP TABLE IF EXISTS tbl_item CASCADE; +DROP TABLE IF EXISTS tbl_order CASCADE; +DROP TABLE IF EXISTS tbl_category CASCADE; +DROP TABLE IF EXISTS tbl_customer CASCADE; +DROP TABLE IF EXISTS tbl_type CASCADE; + +CREATE TABLE tbl_customer ( + id serial not null primary key, + email varchar(128) NOT NULL, + name varchar(128) NOT NULL, + address text, + status integer DEFAULT 0 ); -INSERT INTO test.users (username, password, email) VALUES ('user1','pass1','email1'); -INSERT INTO test.users (username, password, email) VALUES ('user2','pass2','email2'); -INSERT INTO test.users (username, password, email) VALUES ('user3','pass3','email3'); - -CREATE TABLE test.user_friends -( - id INTEGER NOT NULL, - friend INTEGER NOT NULL, - PRIMARY KEY (id, friend), - CONSTRAINT FK_user_id FOREIGN KEY (id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_friend_id FOREIGN KEY (friend) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT -); - -INSERT INTO test.user_friends VALUES (1,2); -INSERT INTO test.user_friends VALUES (1,3); -INSERT INTO test.user_friends VALUES (2,3); - -CREATE TABLE test.profiles -( - id SERIAL NOT NULL PRIMARY KEY, - first_name VARCHAR(128) NOT NULL, - last_name VARCHAR(128) NOT NULL, - user_id INTEGER NOT NULL, - CONSTRAINT FK_profile_user FOREIGN KEY (user_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_category ( + id serial not null primary key, + name varchar(128) NOT NULL ); -INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1); -INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2); - -CREATE TABLE test.posts -( - id SERIAL NOT NULL PRIMARY KEY, - title VARCHAR(128) NOT NULL, - create_time TIMESTAMP NOT NULL, - author_id INTEGER NOT NULL, - content TEXT, - CONSTRAINT FK_post_author FOREIGN KEY (author_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_item ( + id serial not null primary key, + name varchar(128) NOT NULL, + category_id integer NOT NULL references tbl_category(id) on UPDATE CASCADE on DELETE CASCADE ); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 1',TIMESTAMP '2004-10-19 10:23:54',1,'content 1'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 2',TIMESTAMP '2004-10-19 10:23:54',2,'content 2'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 3',TIMESTAMP '2004-10-19 10:23:54',2,'content 3'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 4',TIMESTAMP '2004-10-19 10:23:54',2,'content 4'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 5',TIMESTAMP '2004-10-19 10:23:54',3,'content 5'); - -CREATE TABLE test.comments -( - id SERIAL NOT NULL PRIMARY KEY, - content TEXT NOT NULL, - post_id INTEGER NOT NULL, - author_id INTEGER NOT NULL, - CONSTRAINT FK_post_comment FOREIGN KEY (post_id) - REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_user_comment FOREIGN KEY (author_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_order ( + id serial not null primary key, + customer_id integer NOT NULL references tbl_customer(id) on UPDATE CASCADE on DELETE CASCADE, + create_time integer NOT NULL, + total decimal(10,0) NOT NULL ); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 1',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 2',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 3',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 4',2, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 5',2, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 6',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 7',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 8',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 9',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 10',5, 3); - -CREATE TABLE test.categories -( - id SERIAL NOT NULL PRIMARY KEY, - name VARCHAR(128) NOT NULL, - parent_id INTEGER, - CONSTRAINT FK_category_category FOREIGN KEY (parent_id) - REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_order_item ( + order_id integer NOT NULL references tbl_order(id) on UPDATE CASCADE on DELETE CASCADE, + item_id integer NOT NULL references tbl_item(id) on UPDATE CASCADE on DELETE CASCADE, + quantity integer NOT NULL, + subtotal decimal(10,0) NOT NULL, + PRIMARY KEY (order_id,item_id) ); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 1',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 2',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 3',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 4',1); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 5',1); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 6',5); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 7',5); - -CREATE TABLE test.post_category -( - category_id INTEGER NOT NULL, - post_id INTEGER NOT NULL, - PRIMARY KEY (category_id, post_id), - CONSTRAINT FK_post_category_post FOREIGN KEY (post_id) - REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_post_category_category FOREIGN KEY (category_id) - REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_type ( + int_col integer NOT NULL, + int_col2 integer DEFAULT '1', + char_col char(100) NOT NULL, + char_col2 varchar(100) DEFAULT 'something', + char_col3 text, + float_col double precision NOT NULL, + float_col2 double precision DEFAULT '1.23', + blob_col bytea, + numeric_col decimal(5,2) DEFAULT '33.22', + time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + bool_col smallint NOT NULL, + bool_col2 smallint DEFAULT '1' ); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (2,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (3,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (4,2); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,2); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,3); - -CREATE TABLE test.orders -( - key1 INTEGER NOT NULL, - key2 INTEGER NOT NULL, - name VARCHAR(128), - PRIMARY KEY (key1, key2) -); - -INSERT INTO test.orders (key1,key2,name) VALUES (1,2,'order 12'); -INSERT INTO test.orders (key1,key2,name) VALUES (1,3,'order 13'); -INSERT INTO test.orders (key1,key2,name) VALUES (2,1,'order 21'); -INSERT INTO test.orders (key1,key2,name) VALUES (2,2,'order 22'); - -CREATE TABLE test.items -( - id SERIAL NOT NULL PRIMARY KEY, - name VARCHAR(128), - col1 INTEGER NOT NULL, - col2 INTEGER NOT NULL, - CONSTRAINT FK_order_item FOREIGN KEY (col1,col2) - REFERENCES test.orders (key1,key2) ON DELETE CASCADE ON UPDATE RESTRICT -); - -INSERT INTO test.items (name,col1,col2) VALUES ('item 1',1,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 2',1,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 3',1,3); -INSERT INTO test.items (name,col1,col2) VALUES ('item 4',2,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 5',2,2); - -CREATE TABLE public.yii_types -( - int_col INT NOT NULL, - int_col2 INTEGER DEFAULT 1, - char_col CHAR(100) NOT NULL, - char_col2 VARCHAR(100) DEFAULT 'something', - char_col3 TEXT, - numeric_col NUMERIC(4,3) NOT NULL, - real_col REAL DEFAULT 1.23, - blob_col BYTEA, - time TIMESTAMP, - bool_col BOOL NOT NULL, - bool_col2 BOOLEAN DEFAULT TRUE -); \ No newline at end of file +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); + +INSERT INTO tbl_category (name) VALUES ('Books'); +INSERT INTO tbl_category (name) VALUES ('Movies'); + +INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2); + +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0); + +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php new file mode 100644 index 0000000..18bfbd8 --- /dev/null +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -0,0 +1,14 @@ +driverName = 'pgsql'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php new file mode 100644 index 0000000..64ceb05 --- /dev/null +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -0,0 +1,18 @@ +driverName = 'pgsql'; + parent::setUp(); + } + + public function testConnection() { + $connection = $this->getConnection(true); + } +} From 4602b575ad46143312a9faa4cedc992e8d24ab36 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Wed, 29 May 2013 17:44:53 +0200 Subject: [PATCH 02/91] Save point: WIP implementing db schema functionality. --- framework/yii/db/Connection.php | 2 + framework/yii/db/pgsql/PDO.php | 61 ++++++++++++++++++++++ framework/yii/db/pgsql/Schema.php | 36 +++++++++++-- tests/unit/data/config.php | 5 +- tests/unit/framework/db/DatabaseTestCase.php | 3 ++ .../db/pgsql/PostgreSQLConnectionTest.php | 55 +++++++++++++++---- 6 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 framework/yii/db/pgsql/PDO.php diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index e14eeb7..014bbaa 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -352,6 +352,8 @@ class Connection extends Component $driver = strtolower(substr($this->dsn, 0, $pos)); if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { $pdoClass = 'yii\db\mssql\PDO'; + } else if ($driver === 'pgsql') { + $pdoClass = 'yii\db\pgsql\PDO'; } } return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php new file mode 100644 index 0000000..3ff9d01 --- /dev/null +++ b/framework/yii/db/pgsql/PDO.php @@ -0,0 +1,61 @@ + + * @since 2.0 + */ +class PDO extends \PDO { + + /** + * Here we override the default PDO constructor in order to + * find and set the default schema search path. + */ + public function __construct($dsn, $username, $passwd, $options) { + $searchPath = null; + if (is_array($options) && isset($options['search_path'])) { + $matches = null; + if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options['search_path'], $matches) === 1) { + $searchPath = $matches[0]; + } + } + parent::__construct($dsn, $username, $passwd, $options); + if (!is_null($searchPath)) { + $this->setSchemaSearchPath($searchPath); + } + } + + /** + * Sets the schema search path of the current users session. + * The syntax of the path is a comma separated string with + * your custom search path at the beginning and the "public" + * schema at the end. + * + * This method automatically adds the "public" schema at the + * end of the search path if it is not provied. + * @param string custom schema search path. defaults to public + */ + public function setSchemaSearchPath($searchPath = 'public') { + $schemas = explode(',', str_replace(' ', '', $searchPath)); + if (end($schemas) !== 'public') { + $schemas[] = 'public'; + } + foreach ($schemas as $k => $item) { + $schemas[$k] = '"' . str_replace(array('"', "'", ';'), '', $item) . '"'; + } + $path = implode(', ', $schemas); + $this->exec('SET search_path TO ' . $path); + } + +} diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 4ccf33a..32044ee 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -1,4 +1,5 @@ * @since 2.0 */ -class Schema extends \yii\db\Schema -{ +class Schema extends \yii\db\Schema { + + /** + * Resolves the table name and schema name (if any). + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) { + $parts = explode('.', str_replace('"', '', $name)); + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + } else { + $table->name = $parts[0]; + } + } + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + /** * Loads the metadata for the specified table. * @param string $name table name * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. */ - public function loadTableSchema($name) - { + public function loadTableSchema($name) { $table = new TableSchema(); $this->resolveTableNames($table, $name); $this->findPrimaryKeys($table); @@ -32,5 +57,6 @@ class Schema extends \yii\db\Schema $this->findForeignKeys($table); return $table; } - } + } + } \ No newline at end of file diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index cb612d4..330a464 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -21,9 +21,12 @@ return array( 'fixture' => __DIR__ . '/mssql.sql', ), 'pgsql' => array( - 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432', + 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', + 'attributes' => array( + 'search_path' => 'master,hello' + ), 'fixture' => __DIR__ . '/postgres.sql', ) ) diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 429d961..7ce9bec 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -37,6 +37,9 @@ abstract class DatabaseTestCase extends TestCase $db->username = $this->database['username']; $db->password = $this->database['password']; } + if (isset($this->database['attributes'])) { + $db->attributes = $this->database['attributes']; + } if ($open) { $db->open(); $lines = explode(';', file_get_contents($this->database['fixture'])); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php index 64ceb05..678b197 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -4,15 +4,48 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ConnectionTest; -class PostgreSQLConnectionTest extends ConnectionTest -{ - public function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } - - public function testConnection() { - $connection = $this->getConnection(true); - } +class PostgreSQLConnectionTest extends ConnectionTest { + + public function setUp() { + $this->driverName = 'pgsql'; + parent::setUp(); + } + + public function testConnection() { + $connection = $this->getConnection(true); + } + + function testQuoteValue() { + $connection = $this->getConnection(false); + $this->assertEquals(123, $connection->quoteValue(123)); + $this->assertEquals("'string'", $connection->quoteValue('string')); + $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); + } + + function testQuoteTableName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"table"', $connection->quoteTableName('table')); + $this->assertEquals('"table"', $connection->quoteTableName('"table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('"schema"."table"')); + $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); + $this->assertEquals('(table)', $connection->quoteTableName('(table)')); + } + + function testQuoteColumnName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"column"', $connection->quoteColumnName('column')); + $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); + $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + } + + } From 09e4229e16cfa174ee83c9217d1d2dbdd19e9b93 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 30 May 2013 13:33:38 +0200 Subject: [PATCH 03/91] [WIP] RESTful routing syntax for UrlManager issue #303 Here is the proposal for RESTful routing syntax: https://gist.github.com/cebe/5674918 --- framework/yii/web/UrlManager.php | 32 ++++++++++++++++++++++++++++- tests/unit/framework/web/UrlManagerTest.php | 2 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index 47f5c5d..a6ef0cf 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,6 +43,28 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * + * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] + * the rule should apply for by prepending it to the pattern, separated by a blank. + * For example, `'PUT post/' => 'post/update'`. + * You may specify multiple verbs by separating them with comma + * like this: `'POST,PUT post/index' => 'post/create'`. + * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way + * so you normally would not specify a verb for normal GET request. + * + * Here is an example configuration for RESTful CRUD controller: + * ~~~php + * array( + * 'dashboard' => 'site/index', + * + * 'POST s' => '/create', + * 's' => '/index', + * + * 'PUT /' => '/update', + * 'DELETE /' => '/delete', + * '/' => '/view', + * ); + * ~~~ + * * Note that if you modify this property after the UrlManager object is created, make sure * you populate the array with rule objects instead of rule configurations. */ @@ -115,9 +137,17 @@ class UrlManager extends Component foreach ($this->rules as $key => $rule) { if (!is_array($rule)) { $rule = array( - 'pattern' => $key, 'route' => $rule, ); + if (($pos = strpos($key, ' ')) !== false) { + $verbs = substr($key, 0, $pos); + if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { + $rule['verb'] = explode(',', $verbs); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = substr($key, $pos + 1); + } + } + $rule['pattern'] = $key; } $rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule)); } diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index 0f08790..d0e8775 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -248,4 +248,6 @@ class UrlManagerTest extends TestCase $result = $manager->parseRequest($request); $this->assertFalse($result); } + + // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' } From 103621ad053123b86fff31d4d40f903cdfc128a3 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 30 May 2013 16:53:57 +0200 Subject: [PATCH 04/91] Savepoint: WIP implementing columns --- framework/yii/db/pgsql/PDO.php | 35 ++++++++- framework/yii/db/pgsql/Schema.php | 161 +++++++++++++++++++++++++++++++++++++- tests/unit/data/postgres.sql | 14 +++- 3 files changed, 203 insertions(+), 7 deletions(-) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 3ff9d01..75c20a3 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -18,16 +18,33 @@ namespace yii\db\pgsql; */ class PDO extends \PDO { + const OPT_SEARCH_PATH = 'search_path'; + const OPT_DEFAULT_SCHEMA = 'default_schema'; + const DEFAULT_SCHEMA = 'public'; + + private $_currentDatabase = null; + /** * Here we override the default PDO constructor in order to * find and set the default schema search path. */ public function __construct($dsn, $username, $passwd, $options) { $searchPath = null; - if (is_array($options) && isset($options['search_path'])) { - $matches = null; - if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options['search_path'], $matches) === 1) { - $searchPath = $matches[0]; + if (is_array($options)) { + if (isset($options[self::OPT_SEARCH_PATH])) { + $matches = null; + if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options[self::OPT_SEARCH_PATH], $matches) === 1) { + $searchPath = $matches[0]; + } + } + if (isset($options[self::OPT_DEFAULT_SCHEMA])) { + $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); + if ($schema !== '') { + Schema::$DEFAULT_SCHEMA = $schema; + } + } + if (Schema::$DEFAULT_SCHEMA === null || Schema::$DEFAULT_SCHEMA === '') { + Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; } } parent::__construct($dsn, $username, $passwd, $options); @@ -37,6 +54,16 @@ class PDO extends \PDO { } /** + * Returns the name of the current (connected) database + * @return string + */ + public function getCurrentDatabase() { + if (is_null($this->_currentDatabase)) { + return $this->query('select current_database()')->fetchColumn(); + } + } + + /** * Sets the schema search path of the current users session. * The syntax of the path is a comma separated string with * your custom search path at the beginning and the "public" diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 32044ee..f7329bd 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -12,7 +12,8 @@ use yii\db\TableSchema; use yii\db\ColumnSchema; /** - * Schema is the class for retrieving metadata from a PostgreSQL database (version 9.x and above). + * Schema is the class for retrieving metadata from a PostgreSQL database + * (version 9.x and above). * * @author Gevik Babakhani * @since 2.0 @@ -20,6 +21,85 @@ use yii\db\ColumnSchema; class Schema extends \yii\db\Schema { /** + * The default schema used for the current session. This value is + * automatically set to "public" by the PDO driver. + * @var string + */ + public static $DEFAULT_SCHEMA; + + /** + * @var array mapping from physical column types (keys) to abstract + * column types (values) + */ + public $typeMap = array( + 'abstime' => self::TYPE_TIMESTAMP, + //'aclitem' => self::TYPE_STRING, + 'bit' => self::TYPE_STRING, + 'boolean' => self::TYPE_BOOLEAN, + 'box' => self::TYPE_STRING, + 'character' => self::TYPE_STRING, + 'bytea' => self::TYPE_BINARY, + 'char' => self::TYPE_STRING, + //'cid' => self::TYPE_STRING, + 'cidr' => self::TYPE_STRING, + 'circle' => self::TYPE_STRING, + 'date' => self::TYPE_DATE, + //'daterange' => self::TYPE_STRING, + 'real' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_DECIMAL, + //'gtsvector' => self::TYPE_STRING, + 'inet' => self::TYPE_STRING, + 'smallint' => self::TYPE_SMALLINT, + 'integer' => self::TYPE_INTEGER, + //'int4range' => self::TYPE_STRING, //unknown + 'bigint' => self::TYPE_BIGINT, + //'int8range' => self::TYPE_STRING, // unknown + 'interval' => self::TYPE_STRING, + 'json' => self::TYPE_STRING, + 'line' => self::TYPE_STRING, + //'lseg' => self::TYPE_STRING, + 'macaddr' => self::TYPE_STRING, + 'money' => self::TYPE_MONEY, + 'name' => self::TYPE_STRING, + 'numeric' => self::TYPE_STRING, + 'numrange' => self::TYPE_DECIMAL, + 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! + 'path' => self::TYPE_STRING, + //'pg_node_tree' => self::TYPE_STRING, + 'point' => self::TYPE_STRING, + 'polygon' => self::TYPE_STRING, + //'refcursor' => self::TYPE_STRING, + //'regclass' => self::TYPE_STRING, + //'regconfig' => self::TYPE_STRING, + //'regdictionary' => self::TYPE_STRING, + //'regoper' => self::TYPE_STRING, + //'regoperator' => self::TYPE_STRING, + //'regproc' => self::TYPE_STRING, + //'regprocedure' => self::TYPE_STRING, + //'regtype' => self::TYPE_STRING, + //'reltime' => self::TYPE_STRING, + //'smgr' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + //'tid' => self::TYPE_STRING, + 'time without time zone' => self::TYPE_TIME, + 'timestamp without time zone' => self::TYPE_TIMESTAMP, + 'timestamp with time zone' => self::TYPE_TIMESTAMP, + 'time with time zone' => self::TYPE_TIMESTAMP, + //'tinterval' => self::TYPE_STRING, + //'tsquery' => self::TYPE_STRING, + //'tsrange' => self::TYPE_STRING, + //'tstzrange' => self::TYPE_STRING, + //'tsvector' => self::TYPE_STRING, + //'txid_snapshot' => self::TYPE_STRING, + 'unknown' => self::TYPE_STRING, + 'uuid' => self::TYPE_STRING, + 'bit varying' => self::TYPE_STRING, + 'character varying' => self::TYPE_STRING, + //'xid' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING + ); + + /** * Resolves the table name and schema name (if any). * @param TableSchema $table the table metadata object * @param string $name the table name @@ -32,6 +112,9 @@ class Schema extends \yii\db\Schema { } else { $table->name = $parts[0]; } + if ($table->schemaName === null) { + $table->schemaName = self::$DEFAULT_SCHEMA; + } } /** @@ -52,11 +135,85 @@ class Schema extends \yii\db\Schema { public function loadTableSchema($name) { $table = new TableSchema(); $this->resolveTableNames($table, $name); - $this->findPrimaryKeys($table); if ($this->findColumns($table)) { $this->findForeignKeys($table); return $table; } } + /** + * Collects the metadata of table columns. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) { + $dbname = $this->db->quoteValue($this->db->pdo->getCurrentDatabase()); + $tableName = $this->db->quoteValue($table->name); + $schemaName = $this->db->quoteValue($table->schemaName); + $sql = << 0 + and c.relname = {$tableName} + and d.nspname = {$schemaName} + and current_database() = {$dbname} +ORDER BY + a.attnum; +SQL; + + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($columns as $column) { + $column = $this->loadColumnSchema($column); + if ($column->name == 'numbers') + print_r($column); + } + die(); + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) { + $column = new ColumnSchema(); + $column->allowNull = $info['is_nullable']; + $column->autoIncrement = $info['is_autoinc']; + $column->comment = $info['column_comment']; + $column->dbType = $info['data_type']; + $column->defaultValue = $info['column_default']; + $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values'])); +//$column->isPrimaryKey + $column->name = $info['column_name']; + //$column->phpType +//$column->precision +//$column->scale +//$column->size; +//$column->type +//$column->unsigned + return $column; + } + } \ No newline at end of file diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index ce75206..f158e58 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -11,14 +11,26 @@ DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; +drop type if exists fullname cascade; +create type fullname as (firstname varchar,lastname varchar); + +drop type if exists mood cascade; +create type mood as enum ('sad','ok','happy',E'own\'s',E'\"quoted\"'); + + CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, address text, - status integer DEFAULT 0 + status integer DEFAULT 0, + fullname fullname, + mood mood, + numbers integer[] ); +comment on column public.tbl_customer.email is 'someone@example.com'; + CREATE TABLE tbl_category ( id serial not null primary key, name varchar(128) NOT NULL From 97270a389ad78f83190d8ff4cb595a696e6559a0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Fri, 31 May 2013 14:25:56 +0200 Subject: [PATCH 05/91] Savepoint, implementing fkeys. --- framework/yii/db/pgsql/Schema.php | 76 ++++++++++++++++++++++++++++++++------- tests/unit/data/postgres.sql | 5 ++- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index f7329bd..f42c132 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -136,12 +136,30 @@ class Schema extends \yii\db\Schema { $table = new TableSchema(); $this->resolveTableNames($table, $name); if ($this->findColumns($table)) { - $this->findForeignKeys($table); + $this->findConstraints($table); return $table; } } /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findConstraints($table) { + + try { + $constraints = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($constraints as $constraint) { + $column = $this->loadColumnSchema($column); + $table->columns[$column->name] = $column; + } + return true; + } + + /** * Collects the metadata of table columns. * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database @@ -163,13 +181,41 @@ SELECT a.attnotnull = false AS is_nullable, CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default, coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc, - array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values + array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values, + CASE atttypid + WHEN 21 /*int2*/ THEN 16 + WHEN 23 /*int4*/ THEN 32 + WHEN 20 /*int8*/ THEN 64 + WHEN 1700 /*numeric*/ THEN + CASE WHEN atttypmod = -1 + THEN null + ELSE ((atttypmod - 4) >> 16) & 65535 + END + WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/ + WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/ + ELSE null + END AS numeric_precision, + CASE + WHEN atttypid IN (21, 23, 20) THEN 0 + WHEN atttypid IN (1700) THEN + CASE + WHEN atttypmod = -1 THEN null + ELSE (atttypmod - 4) & 65535 + END + ELSE null + END AS numeric_scale, + CAST( + information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) + AS numeric + ) AS size, + a.attnum = any (ct.conkey) as is_pkey FROM pg_class c LEFT JOIN pg_attribute a ON a.attrelid = c.oid LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum LEFT JOIN pg_type t ON a.atttypid = t.oid - LEFT JOIN pg_namespace d ON d.oid = c.relnamespace + LEFT JOIN pg_namespace d ON d.oid = c.relnamespace + LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' WHERE a.attnum > 0 and c.relname = {$tableName} @@ -186,10 +232,9 @@ SQL; } foreach ($columns as $column) { $column = $this->loadColumnSchema($column); - if ($column->name == 'numbers') - print_r($column); + $table->columns[$column->name] = $column; } - die(); + return true; } /** @@ -205,14 +250,19 @@ SQL; $column->dbType = $info['data_type']; $column->defaultValue = $info['column_default']; $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values'])); -//$column->isPrimaryKey + $column->unsigned = false; // has no meanining in PG + $column->isPrimaryKey = $info['is_pkey']; $column->name = $info['column_name']; - //$column->phpType -//$column->precision -//$column->scale -//$column->size; -//$column->type -//$column->unsigned + $column->precision = $info['numeric_precision']; + $column->scale = $info['numeric_scale']; + $column->size = $info['size']; + + if (isset($this->typeMap[$column->dbType])) { + $column->type = $this->typeMap[$column->dbType]; + } else { + $column->type = self::TYPE_STRING; + } + $column->phpType = $this->getColumnPhpType($column); return $column; } diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index f158e58..6a02691 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -22,11 +22,14 @@ CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, + age numeric(3), + zipcode varchar(6), address text, status integer DEFAULT 0, fullname fullname, mood mood, - numbers integer[] + numbers integer[], + amount numeric(6,4) ); comment on column public.tbl_customer.email is 'someone@example.com'; From 04ebd12277112879bfe7c2047fdb2e2c9d08534b Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:36:19 +0200 Subject: [PATCH 06/91] Added last insert value to pgsql PDO --- framework/yii/db/pgsql/PDO.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 75c20a3..67387dd 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -25,6 +25,20 @@ class PDO extends \PDO { private $_currentDatabase = null; /** + * Returns value of the last inserted ID. + * @param string|null $sequence the sequence name. Defaults to null. + * @return integer last inserted ID value. + */ + public function lastInsertId($sequence = null) { + if ($sequence !== null) { + $sequence = $this->quote($sequence); + return $this->query("SELECT currval({$sequence})")->fetchColumn(); + } else { + return null; + } + } + + /** * Here we override the default PDO constructor in order to * find and set the default schema search path. */ From cbde22af55c5458b854e788da2eea31d3a47e0f9 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:36:55 +0200 Subject: [PATCH 07/91] Savepoint WIP QueryBuilder! --- framework/yii/db/pgsql/QueryBuilder.php | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 framework/yii/db/pgsql/QueryBuilder.php diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php new file mode 100644 index 0000000..c0d5089 --- /dev/null +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -0,0 +1,48 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder { + + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array( + Schema::TYPE_PK => 'bigserial not null primary key', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'integer', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'real', + Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DATETIME => 'timestamp', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'bytea', + Schema::TYPE_BOOLEAN => 'boolean', + Schema::TYPE_MONEY => 'numeric(19,4)', + ); + + public function insert($table, $columns, &$params) { + $sql = parent::insert($table, $columns, $params); + return $sql . ' RETURNING *'; + } + +} From 5a380360c72e7f9c88e2881ded45d6af77c82bf6 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:37:30 +0200 Subject: [PATCH 08/91] Added table based sequence. --- framework/yii/db/pgsql/Schema.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index f42c132..5203ceb 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -100,6 +100,14 @@ class Schema extends \yii\db\Schema { ); /** + * Creates a query builder for the MySQL database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() { + return new QueryBuilder($this->db); + } + + /** * Resolves the table name and schema name (if any). * @param TableSchema $table the table metadata object * @param string $name the table name @@ -146,7 +154,7 @@ class Schema extends \yii\db\Schema { * @param TableSchema $table the table metadata */ protected function findConstraints($table) { - + try { $constraints = $this->db->createCommand($sql)->queryAll(); } catch (\Exception $e) { @@ -233,6 +241,12 @@ SQL; foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; + if ($column->isPrimaryKey === true) { + $table->primaryKey[] = $column->name; + if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { + $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue); + } + } } return true; } From 18218c41328dd70d1bff3e1202f20729125b2548 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:37:47 +0200 Subject: [PATCH 09/91] Removed unused columns. --- tests/unit/data/postgres.sql | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index 6a02691..52fad0f 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -11,25 +11,12 @@ DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; -drop type if exists fullname cascade; -create type fullname as (firstname varchar,lastname varchar); - -drop type if exists mood cascade; -create type mood as enum ('sad','ok','happy',E'own\'s',E'\"quoted\"'); - - CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, - age numeric(3), - zipcode varchar(6), address text, - status integer DEFAULT 0, - fullname fullname, - mood mood, - numbers integer[], - amount numeric(6,4) + status integer DEFAULT 0 ); comment on column public.tbl_customer.email is 'someone@example.com'; From 8ae946358949938680cf3b06be93e6f7519e8c77 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 1 Jun 2013 12:45:32 -0400 Subject: [PATCH 10/91] Fixes issue #472: doc fix. --- framework/yii/bootstrap/Nav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 548fe19..029f178 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -27,7 +27,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * 'items' => array( + * 'dropdown' => array( * array( * 'label' => 'DropdownA', * 'url' => '#', From bb0e2e84872bff1f668e54ffb737635d606f9a92 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 00:37:53 +0200 Subject: [PATCH 11/91] updated composer script event for setting permissions --- apps/advanced/composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 95b3b13..cc3ec2d 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -19,10 +19,7 @@ "yiisoft/yii2-composer": "dev-master" }, "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ + "post-create-project-cmd": [ "yii\\composer\\InstallHandler::setPermissions" ] }, From 607f3a59873167f18e8f9fb74455982c899d6dd0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 07:11:27 -0400 Subject: [PATCH 12/91] Fixes issue #476 --- framework/yii/caching/FileCache.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 0c6d119..1dbcb09 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -25,8 +25,9 @@ class FileCache extends Cache { /** * @var string the directory to store cache files. You may use path alias here. + * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath = '@app/runtime/cache'; + public $cachePath; /** * @var string cache file suffix. Defaults to '.bin'. */ @@ -51,7 +52,11 @@ class FileCache extends Cache public function init() { parent::init(); - $this->cachePath = Yii::getAlias($this->cachePath); + if ($this->cachePath === null) { + $this->cachePath = Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'cache'; + } else { + $this->cachePath = Yii::getAlias($this->cachePath); + } if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); } From 36bbfd54caaa4a43c6634475b51f776c7d9af9d7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 07:29:29 -0400 Subject: [PATCH 13/91] Implemented "@app/runtime" alias. --- framework/yii/base/Application.php | 46 ++++++++++++++++++++++++++----------- framework/yii/caching/FileCache.php | 8 ++----- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index d38f6a9..95993a4 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -67,35 +67,53 @@ class Application extends Module * Constructor. * @param array $config name-value pairs that will be used to initialize the object properties. * Note that the configuration must contain both [[id]] and [[basePath]]. - * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ public function __construct($config = array()) { Yii::$app = $this; + $this->preInit($config); + + $this->registerErrorHandlers(); + $this->registerCoreComponents(); + + Component::__construct($config); + } + + /** + * Pre-initializes the application. + * This method is called at the beginning of the application constructor. + * When this method is called, none of the application properties are initialized yet. + * The default implementation will initialize a few important properties + * that may be referenced during the initialization of the rest of the properties. + * @param array $config the application configuration + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. + */ + public function preInit($config) + { if (!isset($config['id'])) { throw new InvalidConfigException('The "id" configuration is required.'); } - - if (isset($config['basePath'])) { - $this->setBasePath($config['basePath']); - Yii::setAlias('@app', $this->getBasePath()); - unset($config['basePath']); - } else { + if (!isset($config['basePath'])) { throw new InvalidConfigException('The "basePath" configuration is required.'); } - + + $this->setBasePath($config['basePath']); + Yii::setAlias('@app', $this->getBasePath()); + unset($config['basePath']); + + if (isset($config['runtime'])) { + $this->setRuntimePath($config['runtime']); + unset($config['runtime']); + } + Yii::setAlias('@app/runtime', $this->getRuntimePath()); + if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); unset($config['timeZone']); } elseif (!ini_get('date.timezone')) { $this->setTimeZone('UTC'); - } - - $this->registerErrorHandlers(); - $this->registerCoreComponents(); - - Component::__construct($config); + } } /** diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 1dbcb09..8d6a704 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -27,7 +27,7 @@ class FileCache extends Cache * @var string the directory to store cache files. You may use path alias here. * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath; + public $cachePath = '@app/runtime/cache'; /** * @var string cache file suffix. Defaults to '.bin'. */ @@ -52,11 +52,7 @@ class FileCache extends Cache public function init() { parent::init(); - if ($this->cachePath === null) { - $this->cachePath = Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'cache'; - } else { - $this->cachePath = Yii::getAlias($this->cachePath); - } + $this->cachePath = Yii::getAlias($this->cachePath); if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); } From 5c56c0fc02e54bf1ab73c172ce0e0577ccab57a8 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 16:05:11 +0200 Subject: [PATCH 14/91] Better flexibility for abstract db types Feature was requested in yiisoft/yii#765 --- framework/yii/db/QueryBuilder.php | 6 ++ framework/yii/db/mysql/QueryBuilder.php | 2 +- tests/unit/framework/db/QueryBuilderTest.php | 109 +++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 tests/unit/framework/db/QueryBuilderTest.php diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index c0b4223..3e7f95a 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -464,6 +464,8 @@ class QueryBuilder extends \yii\base\Object * the first part will be converted, and the rest of the parts will be appended to the converted result. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. * + // TODO documentation + * * If a type cannot be found in [[typeMap]], it will be returned without any change. * @param string $type abstract column type * @return string physical column type. @@ -472,6 +474,10 @@ class QueryBuilder extends \yii\base\Object { if (isset($this->typeMap[$type])) { return $this->typeMap[$type]; + } elseif ((preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches))) { + if (isset($this->typeMap[$matches[1]])) { + return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3]; + } } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { if (isset($this->typeMap[$matches[1]])) { return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type); diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 70c6d64..4b35e24 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -29,7 +29,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'int(11)', Schema::TYPE_BIGINT => 'bigint(20)', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php new file mode 100644 index 0000000..355f080 --- /dev/null +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -0,0 +1,109 @@ +driverName) + { + case 'mysql': + return new MysqlQueryBuilder($this->getConnection()); + case 'sqlite': + return new SqliteQueryBuilder($this->getConnection()); + case 'mssql': + return new MssqlQueryBuilder($this->getConnection()); + } + throw new \Exception('Test is not implemented for ' . $this->driverName); + } + + /** + * this is not used as a dataprovider for testGetColumnType to speed up the test + * when used as dataprovider every single line will cause a reconnect with the database which is not needed here + */ + public function columnTypes() + { + return array( + array(Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'), + array(Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'), + array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'text'), + array(Schema::TYPE_TEXT . '(255)', 'text'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint(6)'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'), + array(Schema::TYPE_INTEGER, 'int(11)'), + array(Schema::TYPE_INTEGER . '(8)', 'int(8)'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint(20)'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint(8)'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'), + array(Schema::TYPE_FLOAT, 'float'), + array(Schema::TYPE_FLOAT . '(16,5)', 'float'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'tinyint(1)'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } + + /** + * + */ + public function testGetColumnType() + { + $qb = $this->getQueryBuilder(); + foreach($this->columnTypes() as $item) { + list ($column, $expected) = $item; + $this->assertEquals($expected, $qb->getColumnType($column)); + } + } +} From 22357befadbb1ab920f67c9557dff6c0b295e2f0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 15:19:29 -0400 Subject: [PATCH 15/91] Added "@runtime" and "@vendor" aliases. --- extensions/smarty/yii/smarty/ViewRenderer.php | 4 ++-- extensions/twig/yii/twig/ViewRenderer.php | 2 +- framework/yii/base/Application.php | 8 +++++++- framework/yii/caching/FileCache.php | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/extensions/smarty/yii/smarty/ViewRenderer.php b/extensions/smarty/yii/smarty/ViewRenderer.php index d8c5d30..164ae8c 100644 --- a/extensions/smarty/yii/smarty/ViewRenderer.php +++ b/extensions/smarty/yii/smarty/ViewRenderer.php @@ -26,12 +26,12 @@ class ViewRenderer extends BaseViewRenderer /** * @var string the directory or path alias pointing to where Smarty cache will be stored. */ - public $cachePath = '@app/runtime/Smarty/cache'; + public $cachePath = '@runtime/Smarty/cache'; /** * @var string the directory or path alias pointing to where Smarty compiled templates will be stored. */ - public $compilePath = '@app/runtime/Smarty/compile'; + public $compilePath = '@runtime/Smarty/compile'; /** * @var Smarty diff --git a/extensions/twig/yii/twig/ViewRenderer.php b/extensions/twig/yii/twig/ViewRenderer.php index 7498d86..76bee95 100644 --- a/extensions/twig/yii/twig/ViewRenderer.php +++ b/extensions/twig/yii/twig/ViewRenderer.php @@ -25,7 +25,7 @@ class ViewRenderer extends BaseViewRenderer /** * @var string the directory or path alias pointing to where Twig cache will be stored. */ - public $cachePath = '@app/runtime/Twig/cache'; + public $cachePath = '@runtime/Twig/cache'; /** * @var array Twig options diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 95993a4..495c1f8 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -102,11 +102,17 @@ class Application extends Module Yii::setAlias('@app', $this->getBasePath()); unset($config['basePath']); + if (isset($config['vendor'])) { + $this->setVendorPath($config['vendor']); + unset($config['vendorPath']); + } + Yii::setAlias('@vendor', $this->getVendorPath()); + if (isset($config['runtime'])) { $this->setRuntimePath($config['runtime']); unset($config['runtime']); } - Yii::setAlias('@app/runtime', $this->getRuntimePath()); + Yii::setAlias('@runtime', $this->getRuntimePath()); if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 8d6a704..a15751e 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -27,7 +27,7 @@ class FileCache extends Cache * @var string the directory to store cache files. You may use path alias here. * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath = '@app/runtime/cache'; + public $cachePath = '@runtime/cache'; /** * @var string cache file suffix. Defaults to '.bin'. */ From c28b0183a5188adf1cbb574e4dbb33bb62817ca4 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 22:12:37 +0200 Subject: [PATCH 16/91] whitespace --- tests/unit/framework/db/QueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 355f080..7dc4731 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -96,7 +96,7 @@ class QueryBuilderTest extends DatabaseTestCase } /** - * + * */ public function testGetColumnType() { From 7117a6752b6927038b62e2366d9034b282b3ced0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 22:58:42 +0200 Subject: [PATCH 17/91] Added phpdoc about precision constraints for abstract db types --- framework/yii/db/QueryBuilder.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 3e7f95a..46457c6 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -464,7 +464,11 @@ class QueryBuilder extends \yii\base\Object * the first part will be converted, and the rest of the parts will be appended to the converted result. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. * - // TODO documentation + * For some of the abstract types you can also specify a length or precision constraint + * by prepending it in round brackets directly to the type. + * For example `string(32)` will be converted into "varchar(32)" on a MySQL database. + * If the underlying DBMS does not support these kind of constraints for a type it will + * be ignored. * * If a type cannot be found in [[typeMap]], it will be returned without any change. * @param string $type abstract column type From 64e5911b5e1ea27d14334da60aa82e00718b47f8 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 23:42:11 +0200 Subject: [PATCH 18/91] added updated composer script event for setting permissions --- apps/basic/composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/basic/composer.json b/apps/basic/composer.json index 29b05d1..b01c56c 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -19,10 +19,7 @@ "yiisoft/yii2-composer": "dev-master" }, "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ + "post-create-project-cmd": [ "yii\\composer\\InstallHandler::setPermissions" ] }, From 5615afab2195975261d26926e19ab35fa67e206e Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 23:42:31 +0200 Subject: [PATCH 19/91] updated deps --- apps/advanced/composer.lock | 10 +++++----- apps/basic/composer.lock | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/advanced/composer.lock b/apps/advanced/composer.lock index 516ae32..3022597 100644 --- a/apps/advanced/composer.lock +++ b/apps/advanced/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "05f7bcd0e99931b52415eaeb62d54daf", + "hash": "2d1053fbaaf2044054f273a71d0ccde0", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989", + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-29 02:55:14" + "time": "2013-06-02 19:19:29" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock index fe87399..a1cb48d 100644 --- a/apps/basic/composer.lock +++ b/apps/basic/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "0411dbbd774aa1c89256c77c68023940", + "hash": "91ba258de768b93025f86071f3bb4b84", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989", + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-29 02:55:14" + "time": "2013-06-02 19:19:29" }, { "name": "yiisoft/yii2-composer", From af68ee9d2745bf7b3fd27ea786c0d9add221e17b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 00:24:39 +0200 Subject: [PATCH 20/91] Sqlite unit test for abstract db types --- framework/yii/db/sqlite/QueryBuilder.php | 4 +- .../framework/db/sqlite/SqliteQueryBuilderTest.php | 74 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 72d48f4..52c101b 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -30,13 +30,13 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'integer', Schema::TYPE_BIGINT => 'bigint', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', Schema::TYPE_DATE => 'date', Schema::TYPE_BINARY => 'blob', - Schema::TYPE_BOOLEAN => 'tinyint(1)', + Schema::TYPE_BOOLEAN => 'boolean', Schema::TYPE_MONEY => 'decimal(19,4)', ); diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php new file mode 100644 index 0000000..c36628f --- /dev/null +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -0,0 +1,74 @@ + 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'text'), + array(Schema::TYPE_TEXT . '(255)', 'text'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), + array(Schema::TYPE_INTEGER, 'integer'), + array(Schema::TYPE_INTEGER . '(8)', 'integer'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), + array(Schema::TYPE_FLOAT, 'float'), + array(Schema::TYPE_FLOAT . '(16,5)', 'float'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'boolean'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } +} \ No newline at end of file From 9b6df54c7df061e08cce580dc9cba4e7c2c80e4f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 00:28:32 +0200 Subject: [PATCH 21/91] Adjusted type map in MSSQL Querybuilder MSSQL seems to support limits for decimal type, so we add a default limit. --- framework/yii/db/mssql/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index 45a7507..e7f8f80 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -28,7 +28,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'int(11)', Schema::TYPE_BIGINT => 'bigint(20)', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', From 8435285c1944675a47cdfb4af7f41843d1393227 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:19:38 -0400 Subject: [PATCH 22/91] doc fix. --- framework/yii/base/Module.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index ec3001e..060d321 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -409,11 +409,11 @@ abstract class Module extends Component * ~~~ * array( * 'comment' => array( - * 'class' => 'app\modules\CommentModule', + * 'class' => 'app\modules\comment\CommentModule', * 'db' => 'db', * ), * 'booking' => array( - * 'class' => 'app\modules\BookingModule', + * 'class' => 'app\modules\booking\BookingModule', * ), * ) * ~~~ From 21160338d7a2dbad04228028b00172ae3fb6602a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:31:17 -0400 Subject: [PATCH 23/91] Implemented default controller namespace. --- apps/basic/config/main.php | 1 - framework/yii/base/Module.php | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/basic/config/main.php b/apps/basic/config/main.php index 9adfba6..054259e 100644 --- a/apps/basic/config/main.php +++ b/apps/basic/config/main.php @@ -4,7 +4,6 @@ return array( 'id' => 'bootstrap', 'basePath' => dirname(__DIR__), 'preload' => array('log'), - 'controllerNamespace' => 'app\controllers', 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 060d321..fac4164 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -88,7 +88,11 @@ abstract class Module extends Component */ public $controllerMap = array(); /** - * @var string the namespace that controller classes are in. Default is to use global namespace. + * @var string the namespace that controller classes are in. If not set, + * it will use the "controllers" sub-namespace under the namespace of this module. + * For example, if the namespace of this module is "foo\bar", then the default + * controller namespace would be "foo\bar\controllers". + * If the module is an application, it will default to "app\controllers". */ public $controllerNamespace; /** @@ -178,6 +182,16 @@ abstract class Module extends Component public function init() { $this->preloadComponents(); + if ($this->controllerNamespace === null) { + if ($this instanceof Application) { + $this->controllerNamespace = 'app\\controllers'; + } else { + $class = get_class($this); + if (($pos = strrpos($class, '\\')) !== false) { + $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; + } + } + } } /** From cf47a71db7ffa997478d461330850f71f565f73a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:47:02 -0400 Subject: [PATCH 24/91] Fixes issue #478: Improved the generation of secret key --- framework/yii/helpers/base/SecurityHelper.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/framework/yii/helpers/base/SecurityHelper.php b/framework/yii/helpers/base/SecurityHelper.php index 3f69fee..f646a24 100644 --- a/framework/yii/helpers/base/SecurityHelper.php +++ b/framework/yii/helpers/base/SecurityHelper.php @@ -131,15 +131,30 @@ class SecurityHelper $keys = is_file($keyFile) ? require($keyFile) : array(); } if (!isset($keys[$name])) { - // generate a 32-char random key - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length); + $keys[$name] = static::generateRandomKey($length); file_put_contents($keyFile, " Date: Mon, 3 Jun 2013 08:28:14 +0200 Subject: [PATCH 25/91] removed unneccessary brackets --- framework/yii/db/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 46457c6..04f1969 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -478,7 +478,7 @@ class QueryBuilder extends \yii\base\Object { if (isset($this->typeMap[$type])) { return $this->typeMap[$type]; - } elseif ((preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches))) { + } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) { if (isset($this->typeMap[$matches[1]])) { return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3]; } From fbb93d0b63f8fa91ee9068dc563f27ddae4a530c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:42:50 +0200 Subject: [PATCH 26/91] doc enhancements for #461 --- framework/yii/web/UrlManager.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index a6ef0cf..d7135c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,15 +43,18 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * - * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] - * the rule should apply for by prepending it to the pattern, separated by a blank. + * For RESTful routing the mentioned shortcut format also allows you to specify the + * [[UrlRule::verb|HTTP verb]] that the rule should apply for. + * You can do that by prepending it to the pattern, separated by a space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. + * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * * Here is an example configuration for RESTful CRUD controller: + * * ~~~php * array( * 'dashboard' => 'site/index', From 11923064810be47ed24888efcd53a36674e83fd2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:59:41 +0200 Subject: [PATCH 27/91] improved UrlManager RESTful syntax regex --- framework/yii/web/UrlManager.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index d7135c5..44c63c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -45,11 +45,11 @@ class UrlManager extends Component * * For RESTful routing the mentioned shortcut format also allows you to specify the * [[UrlRule::verb|HTTP verb]] that the rule should apply for. - * You can do that by prepending it to the pattern, separated by a space. + * You can do that by prepending it to the pattern, separated by space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. - * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. + * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * @@ -142,13 +142,10 @@ class UrlManager extends Component $rule = array( 'route' => $rule, ); - if (($pos = strpos($key, ' ')) !== false) { - $verbs = substr($key, 0, $pos); - if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { - $rule['verb'] = explode(',', $verbs); - $rule['mode'] = UrlRule::PARSING_ONLY; - $key = substr($key, $pos + 1); - } + if (preg_match('/^((?:(GET|HEAD|POST|PUT|DELETE),)*(GET|HEAD|POST|PUT|DELETE))\s+(.*)$/', $key, $matches)) { + $rule['verb'] = explode(',', $matches[1]); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = $matches[4]; } $rule['pattern'] = $key; } From e94cc6bb9ea56886975827ee41087569438d2bb9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 09:32:14 +0200 Subject: [PATCH 28/91] unit test for UrlManager RESTful routing syntax removed test param 'appClass' as it does not make sense to configure application class for all tests, some explicitly need webapp where some might explicitly need console app. --- tests/unit/TestCase.php | 3 +- tests/unit/data/config.php | 2 -- tests/unit/framework/web/UrlManagerTest.php | 54 ++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index 479f85d..efcedf0 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -38,14 +38,13 @@ abstract class TestCase extends \yii\test\TestCase * The application will be destroyed on tearDown() automatically. * @param array $config The application configuration, if needed */ - protected function mockApplication($config=array()) + protected function mockApplication($config = array(), $appClass = '\yii\console\Application') { static $defaultConfig = array( 'id' => 'testapp', 'basePath' => __DIR__, ); - $appClass = $this->getParam( 'appClass', '\yii\web\Application' ); new $appClass(array_merge($defaultConfig,$config)); } diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 88c8127..a2cc445 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,8 +1,6 @@ '\yii\web\Application', - 'appClass' => '\yii\console\Application', 'databases' => array( 'mysql' => array( 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index d0e8775..7da8f34 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -249,5 +249,57 @@ class UrlManagerTest extends TestCase $this->assertFalse($result); } - // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' + public function testParseRESTRequest() + { + $manager = new UrlManager(array( + 'cache' => null, + )); + $request = new Request; + + // pretty URL rules + $manager = new UrlManager(array( + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => array( + 'PUT,POST post//' => 'post/create', + 'DELETE post/<id>' => 'post/delete', + 'post/<id>/<title>' => 'post/view', + 'POST/GET' => 'post/get', + ), + )); + // matching pathinfo GET request + $_SERVER['REQUEST_METHOD'] = 'GET'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + // matching pathinfo PUT/POST request + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + + // no wrong matching + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'POST/GET'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/get', array()), $result); + + // createUrl should ignore REST rules + $this->mockApplication(array( + 'components' => array( + 'request' => array( + 'hostInfo' => 'http://localhost/', + 'baseUrl' => '/app' + ) + ) + ), \yii\web\Application::className()); + $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123))); + $this->destroyApplication(); + + unset($_SERVER['REQUEST_METHOD']); + } } From 8157ea58048a080fa3755627aafe78015435db12 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Mon, 3 Jun 2013 09:45:41 -0400 Subject: [PATCH 29/91] Fixes issue #481. --- framework/yii/validators/DateValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/validators/DateValidator.php b/framework/yii/validators/DateValidator.php index 7370b78..2f3ce2d 100644 --- a/framework/yii/validators/DateValidator.php +++ b/framework/yii/validators/DateValidator.php @@ -58,7 +58,7 @@ class DateValidator extends Validator $date = DateTime::createFromFormat($this->format, $value); if ($date === false) { $this->addError($object, $attribute, $this->message); - } elseif ($this->timestampAttribute !== false) { + } elseif ($this->timestampAttribute !== null) { $object->{$this->timestampAttribute} = $date->getTimestamp(); } } From 5596e50bf3378be11867777c6e87922d3d44ac05 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Mon, 3 Jun 2013 10:40:09 -0400 Subject: [PATCH 30/91] Fixes issue #472. --- framework/yii/bootstrap/Nav.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 029f178..6c091a4 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -128,8 +128,10 @@ class Nav extends Widget $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); if (is_array($dropdown)) { - $dropdown['clientOptions'] = false; - $dropdown = Dropdown::widget($dropdown); + $dropdown = Dropdown::widget(array( + 'items' => $dropdown, + 'clientOptions' => false, + )); } } From 8a25953f1c5405621f66eb17b2562a999dea5834 Mon Sep 17 00:00:00 2001 From: Tobias Munk <schmunk@usrbin.de> Date: Mon, 3 Jun 2013 18:22:59 +0200 Subject: [PATCH 31/91] updated docs added installation from dev repo section --- apps/advanced/README.md | 11 +++++++++++ apps/basic/README.md | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index c443c90..cca2451 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -79,6 +79,17 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a This is not currently available. We will provide it when Yii 2 is formally released. +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/advanced +php composer.phar create-project +~~~ + + GETTING STARTED --------------- diff --git a/apps/basic/README.md b/apps/basic/README.md index 5300448..ebe2bfe 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -59,3 +59,14 @@ assuming `yii-basic` is directly under the document root of your Web server. ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + + +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/basic +php composer.phar create-project +~~~ \ No newline at end of file From a2bfdcac187bf6cf3ab3c05d0b62bd471d6a9d99 Mon Sep 17 00:00:00 2001 From: Tobias Munk <schmunk@usrbin.de> Date: Mon, 3 Jun 2013 18:22:59 +0200 Subject: [PATCH 32/91] updated docs added installation from dev repo section --- apps/advanced/README.md | 15 +++++++++++++++ apps/basic/README.md | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index c443c90..f021f81 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -79,6 +79,21 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a This is not currently available. We will provide it when Yii 2 is formally released. +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/advanced +php composer.phar create-project +~~~ + +*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run +`php composer.phar self-update` to obtain an updated version of composer which supports creating projects +from local packages.* + + GETTING STARTED --------------- diff --git a/apps/basic/README.md b/apps/basic/README.md index 5300448..f6f7f45 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -59,3 +59,18 @@ assuming `yii-basic` is directly under the document root of your Web server. ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + + +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/basic +php composer.phar create-project +~~~ + +*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run +`php composer.phar self-update` to obtain an updated version of composer which supports creating projects +from local packages.* \ No newline at end of file From 6c179ef3a7fc0dfc787dfc5318ed554cd5124791 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 20:47:37 +0300 Subject: [PATCH 33/91] File creation checks at "AssetController" have been updated to use Exceptions. --- .../yii/console/controllers/AssetController.php | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 8e3de29..0d09570 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -159,7 +159,7 @@ class AssetController extends Controller } } - $this->getAssetManager(); // check asset manager configuration + $this->getAssetManager(); // check asset manager configuration is correct } /** @@ -420,7 +420,7 @@ class AssetController extends Controller } $array = var_export($array, true); $version = date('Y-m-d H:i:s', time()); - $bytesWritten = file_put_contents($bundleFile, <<<EOD + $bundleFileContent = <<<EOD <?php /** * This file is generated by the "yii script" command. @@ -428,9 +428,8 @@ class AssetController extends Controller * @version $version */ return $array; -EOD - ); - if ($bytesWritten <= 0) { +EOD; + if (!file_put_contents($bundleFile, $bundleFileContent)) { throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'."); } echo "Output bundle configuration created at '{$bundleFile}'.\n"; @@ -498,6 +497,7 @@ EOD * Combines Java Script files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. */ public function combineJsFiles($inputFiles, $outputFile) { @@ -507,13 +507,16 @@ EOD . file_get_contents($file) . "/*** END FILE: $file ***/\n"; } - file_put_contents($outputFile, $content); + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output Java Script file '{$outputFile}'."); + } } /** * Combines CSS files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. */ public function combineCssFiles($inputFiles, $outputFile) { @@ -523,7 +526,9 @@ EOD . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile)) . "/*** END FILE: $file ***/\n"; } - file_put_contents($outputFile, $content); + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output CSS file '{$outputFile}'."); + } } /** @@ -590,6 +595,7 @@ EOD /** * Creates template of configuration file for [[actionCompress]]. * @param string $configFile output file name. + * @throws \yii\console\Exception on failure. */ public function actionTemplate($configFile) { @@ -622,9 +628,8 @@ EOD; return; } } - $bytesWritten = file_put_contents($configFile, $template); - if ($bytesWritten<=0) { - echo "Error: unable to write file '{$configFile}'!\n\n"; + if (!file_put_contents($configFile, $template)) { + throw new Exception("Unable to write template file '{$configFile}'."); } else { echo "Configuration file template created at '{$configFile}'.\n\n"; } From 5172dbd4c4748f2913b08768e651522499bc5ccb Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:25:04 +0300 Subject: [PATCH 34/91] Additional comments have been added to the "AssetController::actionTemplate" output. --- framework/yii/console/controllers/AssetController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 0d09570..d794b30 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -601,13 +601,17 @@ EOD; { $template = <<<EOD <?php - +/** + * Configuration file for the "yii asset" console command. + * Note: in the console environment some path aliases like "@wwwroot" and "@www" may not exist, + * so corresponding paths should be specified directly. + */ return array( - // + // The list of asset bundles, which files should be included to compression: 'bundles' => require('path/to/bundles.php'), - // + // The list of extensions, which asset files should be included to compression: 'extensions' => require('path/to/namespaces.php'), - // + // Compression result asset bundle: 'targets' => array( 'all' => array( 'basePath' => __DIR__, @@ -616,7 +620,7 @@ return array( 'css' => 'all-{ts}.css', ), ), - + // Asset manager configuration: 'assetManager' => array( 'basePath' => __DIR__, 'baseUrl' => '/test', From 9b8b285eb1b3b639324994f72c6c64d47a5aa09e Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:43:54 +0300 Subject: [PATCH 35/91] Doc comments at "AssetController" have been extended. --- framework/yii/console/controllers/AssetController.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index d794b30..dab7e90 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -14,6 +14,20 @@ use yii\console\Controller; /** * This command allows you to combine and compress your JavaScript and CSS files. * + * Usage: + * 1. Create a configuration file using 'template' action: + * yii asset/template /path/to/myapp/config.php + * 2. Edit the created config file, adjusting it for your web application needs. + * 3. Run the 'compress' action, using created config: + * yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php + * 4. Adjust your web application config to use compressed assets. + * + * Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist, + * so corresponding paths inside the configuration should be specified directly. + * + * Note: by default this command relies on an external tools to perform actual files compression, + * check [[jsCompressor]] and [[cssCompressor]] for more details. + * * @property array|\yii\web\AssetManager $assetManager asset manager, which will be used for assets processing. * * @author Qiang Xue <qiang.xue@gmail.com> From 1f68becb61767fb83cc1310404572ff99d244b3e Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:46:56 +0300 Subject: [PATCH 36/91] Doc comments at "AssetController" have been updated. --- framework/yii/console/controllers/AssetController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index dab7e90..61e54e9 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -57,7 +57,7 @@ class AssetController extends Controller * ~~~ * 'all' => array( * 'css' => 'all.css', - * 'js' => 'js.css', + * 'js' => 'all.js', * 'depends' => array( ... ), * ) * ~~~ @@ -322,7 +322,7 @@ class AssetController extends Controller /** * Builds output asset bundle. * @param \yii\web\AssetBundle $target output asset bundle - * @param string $type either "js" or "css". + * @param string $type either 'js' or 'css'. * @param \yii\web\AssetBundle[] $bundles source asset bundles. * @param integer $timestamp current timestamp. * @throws Exception on failure. @@ -617,7 +617,7 @@ EOD; <?php /** * Configuration file for the "yii asset" console command. - * Note: in the console environment some path aliases like "@wwwroot" and "@www" may not exist, + * Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist, * so corresponding paths should be specified directly. */ return array( From dfe2d00785d0bc86a1ac92a8f9bbb7a7f8aaea4f Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:53:07 +0300 Subject: [PATCH 37/91] Output of "AssetController::actionCompress" have been adjusted to hold the action id. --- framework/yii/console/controllers/AssetController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 61e54e9..e525ef1 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -437,11 +437,11 @@ class AssetController extends Controller $bundleFileContent = <<<EOD <?php /** - * This file is generated by the "yii script" command. + * This file is generated by the "yii {$this->id}" command. * DO NOT MODIFY THIS FILE DIRECTLY. - * @version $version + * @version {$version} */ -return $array; +return {$array}; EOD; if (!file_put_contents($bundleFile, $bundleFileContent)) { throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'."); From ec8fecb8b079b3eec4a66df67e91cc3412fc77e5 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:56:03 +0300 Subject: [PATCH 38/91] Check output bundle file format has been added to "AssetControllerTest::testActionCompress()" unit test. --- tests/unit/framework/console/controllers/AssetControllerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index db6d2a7..9d7dd28 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -239,6 +239,7 @@ class AssetControllerTest extends TestCase // Then : $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); + $this->assertTrue(is_array(require($bundleFile)), 'Output bundle file has incorrect format!'); $compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css'; $this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!'); From 152e5a0e15f1f6edf9106adf61dbaa1e0423d366 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 22:34:15 +0300 Subject: [PATCH 39/91] Doc comments and descriptive output at"AssetController" has been adjusted. --- framework/yii/console/controllers/AssetController.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index e525ef1..4c759de 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -71,7 +71,7 @@ class AssetController extends Controller */ private $_assetManager = array(); /** - * @var string|callback Java Script file compressor. + * @var string|callback JavaScript file compressor. * If a string, it is treated as shell command template, which should contain * placeholders {from} - source file name - and {to} - output file name. * Otherwise, it is treated as PHP callback, which should perform the compression. @@ -173,7 +173,7 @@ class AssetController extends Controller } } - $this->getAssetManager(); // check asset manager configuration is correct + $this->getAssetManager(); // check if asset manager configuration is correct } /** @@ -450,7 +450,7 @@ EOD; } /** - * Compresses given Java Script files and combines them into the single one. + * Compresses given JavaScript files and combines them into the single one. * @param array $inputFiles list of source file names. * @param string $outputFile output file name. * @throws \yii\console\Exception on failure @@ -508,7 +508,7 @@ EOD; } /** - * Combines Java Script files into a single one. + * Combines JavaScript files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. * @throws \yii\console\Exception on failure. @@ -522,7 +522,7 @@ EOD; . "/*** END FILE: $file ***/\n"; } if (!file_put_contents($outputFile, $content)) { - throw new Exception("Unable to write output Java Script file '{$outputFile}'."); + throw new Exception("Unable to write output JavaScript file '{$outputFile}'."); } } @@ -621,11 +621,11 @@ EOD; * so corresponding paths should be specified directly. */ return array( - // The list of asset bundles, which files should be included to compression: + // The list of asset bundles to compress: 'bundles' => require('path/to/bundles.php'), - // The list of extensions, which asset files should be included to compression: + // The list of extensions to compress: 'extensions' => require('path/to/namespaces.php'), - // Compression result asset bundle: + // Asset bundle for compression output: 'targets' => array( 'all' => array( 'basePath' => __DIR__, From 084d709e44f8e0b4985e52ac3aa1f4db319d849e Mon Sep 17 00:00:00 2001 From: Alexander Makarov <sam@rmcreative.ru> Date: Mon, 3 Jun 2013 23:39:08 +0400 Subject: [PATCH 40/91] fixed typo in app readmes --- apps/advanced/README.md | 2 +- apps/basic/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index f021f81..d8c1e17 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -81,7 +81,7 @@ This is not currently available. We will provide it when Yii 2 is formally relea ### Install from development repository -If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you can bootstrap your application with: ~~~ diff --git a/apps/basic/README.md b/apps/basic/README.md index c7f6abe..2f8f1e8 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -63,7 +63,7 @@ This is not currently available. We will provide it when Yii 2 is formally relea ### Install from development repository -If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you can bootstrap your application with: ~~~ From 5ecb5e3bd0617b0c291d9ce3b3c59eebd6c3704f Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 17:08:26 -0400 Subject: [PATCH 41/91] Fixed Formatter doc. --- framework/yii/i18n/Formatter.php | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index d688a15..4a93397 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -30,19 +30,28 @@ class Formatter extends \yii\base\Formatter */ public $locale; /** - * @var string the default format string to be used to format a date using PHP date() function. + * @var string the default format string to be used to format a date. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $dateFormat = 'short'; /** - * @var string the default format string to be used to format a time using PHP date() function. + * @var string the default format string to be used to format a time. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $timeFormat = 'short'; /** - * @var string the default format string to be used to format a date and time using PHP date() function. + * @var string the default format string to be used to format a date and time. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $datetimeFormat = 'short'; /** * @var array the options to be set for the NumberFormatter objects. Please refer to + * [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute) + * for the possible options. This property is used by [[createNumberFormatter]] when + * creating a new number formatter to format decimals, currencies, etc. */ public $numberFormatOptions = array(); @@ -81,8 +90,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see dateFormat */ @@ -111,8 +123,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see timeFormat */ @@ -141,8 +156,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see datetimeFormat */ @@ -213,7 +231,7 @@ class Formatter extends \yii\base\Formatter /** * Creates a number formatter based on the given type and format. * @param integer $type the type of the number formatter - * @param string $format the format to be used + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) * @return NumberFormatter the created formatter instance */ protected function createNumberFormatter($type, $format) From cbf642d93147442daffdd5eb4e8b184480ab1313 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 17:21:40 -0400 Subject: [PATCH 42/91] Added support for locale-dependent decimal and grouping separators. --- framework/yii/base/Formatter.php | 18 +++++++++++++----- framework/yii/i18n/Formatter.php | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index d15e5f2..e62039e 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -39,17 +39,19 @@ class Formatter extends Component public $datetimeFormat = 'Y/m/d h:i:s A'; /** * @var array the text to be displayed when formatting a boolean value. The first element corresponds - * to the text display for false, the second element for true. Defaults to <code>array('No', 'Yes')</code>. + * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`. */ public $booleanFormat; /** * @var string the character displayed as the decimal point when formatting a number. + * If not set, "." will be used. */ - public $decimalSeparator = '.'; + public $decimalSeparator; /** * @var string the character displayed as the thousands separator character when formatting a number. + * If not set, "," will be used. */ - public $thousandSeparator = ','; + public $thousandSeparator; /** @@ -273,7 +275,11 @@ class Formatter extends Component */ public function asDouble($value, $decimals = 2) { - return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); + if ($this->decimalSeparator === null) { + return sprintf("%.{$decimals}f", $value); + } else { + return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); + } } /** @@ -287,6 +293,8 @@ class Formatter extends Component */ public function asNumber($value, $decimals = 0) { - return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); + $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.'; + $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ','; + return number_format($value, $decimals, $ds, $ts); } } diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index 4a93397..8fa29dd 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -54,6 +54,16 @@ class Formatter extends \yii\base\Formatter * creating a new number formatter to format decimals, currencies, etc. */ public $numberFormatOptions = array(); + /** + * @var string the character displayed as the decimal point when formatting a number. + * If not set, the decimal separator corresponding to [[locale]] will be used. + */ + public $decimalSeparator; + /** + * @var string the character displayed as the thousands separator character when formatting a number. + * If not set, the thousand separator corresponding to [[locale]] will be used. + */ + public $thousandSeparator; /** @@ -70,6 +80,16 @@ class Formatter extends \yii\base\Formatter if ($this->locale === null) { $this->locale = Yii::$app->language; } + if ($this->decimalSeparator === null || $this->thousandSeparator === null) { + $formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL); + if ($this->decimalSeparator === null) { + $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + } + if ($this->thousandSeparator === null) { + $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + } + } + parent::init(); } From 23767304527a989c2e664f2522c0acacc857b615 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 18:11:50 -0400 Subject: [PATCH 43/91] better check of existence of tables. --- framework/yii/db/ActiveRecord.php | 8 +++++++- framework/yii/db/mysql/Schema.php | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 58411f0..6faebbf 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -275,10 +275,16 @@ class ActiveRecord extends Model /** * Returns the schema information of the DB table associated with this AR class. * @return TableSchema the schema information of the DB table associated with this AR class. + * @throws InvalidConfigException if the table for the AR class does not exist. */ public static function getTableSchema() { - return static::getDb()->getTableSchema(static::tableName()); + $schema = static::getDb()->getTableSchema(static::tableName()); + if ($schema !== null) { + return $schema; + } else { + throw new InvalidConfigException("The table does not exist: " . static::tableName()); + } } /** diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index 501149a..e7b34e0 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -181,12 +181,12 @@ class Schema extends \yii\db\Schema */ protected function findColumns($table) { - $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); - try { - $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { + $rows = $this->db->createCommand("SHOW TABLES LIKE " . $this->quoteValue($table->name))->queryAll(); + if (count($rows) === 0) { return false; } + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); + $columns = $this->db->createCommand($sql)->queryAll(); foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; From b311b6ecf3b0168024dbe0241faa6c5dea9184fd Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 18:13:32 -0400 Subject: [PATCH 44/91] typo fix. --- framework/yii/i18n/Formatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index 8fa29dd..948e277 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -83,10 +83,10 @@ class Formatter extends \yii\base\Formatter if ($this->decimalSeparator === null || $this->thousandSeparator === null) { $formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL); if ($this->decimalSeparator === null) { - $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); } if ($this->thousandSeparator === null) { - $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); } } From 9d6f11d5d524afeacd9cb702612d8667c15a6e3c Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 19:34:51 -0400 Subject: [PATCH 45/91] new way of detecting if table exists. --- framework/yii/db/mysql/Schema.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index e7b34e0..b42ef15 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -178,15 +178,21 @@ class Schema extends \yii\db\Schema * Collects the metadata of table columns. * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database + * @throws \Exception if DB query fails */ protected function findColumns($table) { - $rows = $this->db->createCommand("SHOW TABLES LIKE " . $this->quoteValue($table->name))->queryAll(); - if (count($rows) === 0) { - return false; - } $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); - $columns = $this->db->createCommand($sql)->queryAll(); + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + $previous = $e->getPrevious(); + if ($previous instanceof \PDOException && $previous->getCode() == '42S02') { + // table does not exist + return false; + } + throw $e; + } foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; From 7e111e92e2865a94359962fe323b5b759441b4f6 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Wed, 5 Jun 2013 06:15:31 -0400 Subject: [PATCH 46/91] Fixes issue #496 --- framework/yii/logging/Target.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/framework/yii/logging/Target.php b/framework/yii/logging/Target.php index fac8b53..7be7001 100644 --- a/framework/yii/logging/Target.php +++ b/framework/yii/logging/Target.php @@ -204,11 +204,9 @@ abstract class Target extends Component if ($matched) { foreach ($this->except as $category) { $prefix = rtrim($category, '*'); - foreach ($messages as $i => $message) { - if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { - $matched = false; - break; - } + if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { + $matched = false; + break; } } } From b7c1f949776bb9f4b55b0ac2d55429e8ab3a681a Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Wed, 5 Jun 2013 08:28:36 -0400 Subject: [PATCH 47/91] Fixed bug about setting default path aliases. --- framework/yii/base/Application.php | 62 +++++++++++++++++--------------------- framework/yii/base/Module.php | 3 ++ 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 495c1f8..09951bd 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -67,10 +67,20 @@ class Application extends Module * Constructor. * @param array $config name-value pairs that will be used to initialize the object properties. * Note that the configuration must contain both [[id]] and [[basePath]]. + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ public function __construct($config = array()) { Yii::$app = $this; + if (!isset($config['id'])) { + throw new InvalidConfigException('The "id" configuration is required.'); + } + if (isset($config['basePath'])) { + $this->setBasePath($config['basePath']); + unset($config['basePath']); + } else { + throw new InvalidConfigException('The "basePath" configuration is required.'); + } $this->preInit($config); @@ -83,37 +93,24 @@ class Application extends Module /** * Pre-initializes the application. * This method is called at the beginning of the application constructor. - * When this method is called, none of the application properties are initialized yet. - * The default implementation will initialize a few important properties - * that may be referenced during the initialization of the rest of the properties. * @param array $config the application configuration - * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ - public function preInit($config) + public function preInit(&$config) { - if (!isset($config['id'])) { - throw new InvalidConfigException('The "id" configuration is required.'); - } - if (!isset($config['basePath'])) { - throw new InvalidConfigException('The "basePath" configuration is required.'); - } - - $this->setBasePath($config['basePath']); - Yii::setAlias('@app', $this->getBasePath()); - unset($config['basePath']); - - if (isset($config['vendor'])) { - $this->setVendorPath($config['vendor']); + if (isset($config['vendorPath'])) { + $this->setVendorPath($config['vendorPath']); unset($config['vendorPath']); + } else { + // set "@vendor" + $this->getVendorPath(); } - Yii::setAlias('@vendor', $this->getVendorPath()); - - if (isset($config['runtime'])) { - $this->setRuntimePath($config['runtime']); - unset($config['runtime']); + if (isset($config['runtimePath'])) { + $this->setRuntimePath($config['runtimePath']); + unset($config['runtimePath']); + } else { + // set "@runtime" + $this->getRuntimePath(); } - Yii::setAlias('@runtime', $this->getRuntimePath()); - if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); unset($config['timeZone']); @@ -202,7 +199,8 @@ class Application extends Module /** * Returns the directory that stores runtime files. - * @return string the directory that stores runtime files. Defaults to 'protected/runtime'. + * @return string the directory that stores runtime files. + * Defaults to the "runtime" subdirectory under [[basePath]]. */ public function getRuntimePath() { @@ -215,16 +213,11 @@ class Application extends Module /** * Sets the directory that stores runtime files. * @param string $path the directory that stores runtime files. - * @throws InvalidConfigException if the directory does not exist or is not writable */ public function setRuntimePath($path) { - $path = Yii::getAlias($path); - if (is_dir($path) && is_writable($path)) { - $this->_runtimePath = $path; - } else { - throw new InvalidConfigException("Runtime path must be a directory writable by the Web server process: $path"); - } + $this->_runtimePath = Yii::getAlias($path); + Yii::setAlias('@runtime', $this->_runtimePath); } private $_vendorPath; @@ -232,7 +225,7 @@ class Application extends Module /** * Returns the directory that stores vendor files. * @return string the directory that stores vendor files. - * Defaults to 'vendor' directory under applications [[basePath]]. + * Defaults to "vendor" directory under [[basePath]]. */ public function getVendorPath() { @@ -249,6 +242,7 @@ class Application extends Module public function setVendorPath($path) { $this->_vendorPath = Yii::getAlias($path); + Yii::setAlias('@vendor', $this->_vendorPath); } /** diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index fac4164..5f5c376 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -236,6 +236,9 @@ abstract class Module extends Component $p = realpath($path); if ($p !== false && is_dir($p)) { $this->_basePath = $p; + if ($this instanceof Application) { + Yii::setAlias('@app', $p); + } } else { throw new InvalidParamException("The directory does not exist: $path"); } From 67d677b21e5693cf0f96c600d3035aba398f5381 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Wed, 5 Jun 2013 13:26:01 -0400 Subject: [PATCH 48/91] Renamed Request::getRequestMethod() to Request::getMethod(). --- framework/yii/web/AccessRule.php | 2 +- framework/yii/web/HttpCache.php | 2 +- framework/yii/web/Request.php | 14 +++++++------- framework/yii/web/UrlRule.php | 2 +- framework/yii/web/VerbFilter.php | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/framework/yii/web/AccessRule.php b/framework/yii/web/AccessRule.php index 3897769..9bd52ce 100644 --- a/framework/yii/web/AccessRule.php +++ b/framework/yii/web/AccessRule.php @@ -99,7 +99,7 @@ class AccessRule extends Component if ($this->matchAction($action) && $this->matchRole($user) && $this->matchIP($request->getUserIP()) - && $this->matchVerb($request->getRequestMethod()) + && $this->matchVerb($request->getMethod()) && $this->matchController($action->controller) && $this->matchCustom($action) ) { diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php index 0a3bb86..5b7682d 100644 --- a/framework/yii/web/HttpCache.php +++ b/framework/yii/web/HttpCache.php @@ -60,7 +60,7 @@ class HttpCache extends ActionFilter */ public function beforeAction($action) { - $verb = Yii::$app->request->getRequestMethod(); + $verb = Yii::$app->request->getMethod(); if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) { return true; } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index a857926..9b1e91c 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -49,7 +49,7 @@ class Request extends \yii\base\Request /** * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE * request tunneled through POST. Default to '_method'. - * @see getRequestMethod + * @see getMethod * @see getRestParams */ public $restVar = '_method'; @@ -81,7 +81,7 @@ class Request extends \yii\base\Request * @return string request method, such as GET, POST, HEAD, PUT, DELETE. * The value returned is turned into upper case. */ - public function getRequestMethod() + public function getMethod() { if (isset($_POST[$this->restVar])) { return strtoupper($_POST[$this->restVar]); @@ -96,7 +96,7 @@ class Request extends \yii\base\Request */ public function getIsPostRequest() { - return $this->getRequestMethod() === 'POST'; + return $this->getMethod() === 'POST'; } /** @@ -105,7 +105,7 @@ class Request extends \yii\base\Request */ public function getIsDeleteRequest() { - return $this->getRequestMethod() === 'DELETE'; + return $this->getMethod() === 'DELETE'; } /** @@ -114,7 +114,7 @@ class Request extends \yii\base\Request */ public function getIsPutRequest() { - return $this->getRequestMethod() === 'PUT'; + return $this->getMethod() === 'PUT'; } /** @@ -141,7 +141,7 @@ class Request extends \yii\base\Request /** * Returns the request parameters for the RESTful request. * @return array the RESTful request parameters - * @see getRequestMethod + * @see getMethod */ public function getRestParams() { @@ -772,7 +772,7 @@ class Request extends \yii\base\Request if (!$this->enableCsrfValidation) { return; } - $method = $this->getRequestMethod(); + $method = $this->getMethod(); if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE') { $cookies = $this->getCookies(); switch ($method) { diff --git a/framework/yii/web/UrlRule.php b/framework/yii/web/UrlRule.php index b1e74da..fc19ea3 100644 --- a/framework/yii/web/UrlRule.php +++ b/framework/yii/web/UrlRule.php @@ -171,7 +171,7 @@ class UrlRule extends Object return false; } - if ($this->verb !== null && !in_array($request->getRequestMethod(), $this->verb, true)) { + if ($this->verb !== null && !in_array($request->getMethod(), $this->verb, true)) { return false; } diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php index 9b475e3..ca6d47d 100644 --- a/framework/yii/web/VerbFilter.php +++ b/framework/yii/web/VerbFilter.php @@ -76,7 +76,7 @@ class VerbFilter extends Behavior { $action = $event->action->id; if (isset($this->actions[$action])) { - $verb = Yii::$app->getRequest()->getRequestMethod(); + $verb = Yii::$app->getRequest()->getMethod(); $allowed = array_map('strtoupper', $this->actions[$action]); if (!in_array($verb, $allowed)) { $event->isValid = false; From 51c1e313086f34c5f8a88466833f9e58aaaf6909 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Wed, 5 Jun 2013 17:36:21 -0400 Subject: [PATCH 49/91] moved Pagination and Sort to data. --- framework/yii/data/Pagination.php | 209 ++++++++++++++++++++++ framework/yii/data/Sort.php | 337 ++++++++++++++++++++++++++++++++++++ framework/yii/web/Pagination.php | 208 ---------------------- framework/yii/web/Sort.php | 336 ----------------------------------- framework/yii/widgets/LinkPager.php | 4 +- framework/yii/widgets/ListPager.php | 4 +- 6 files changed, 550 insertions(+), 548 deletions(-) create mode 100644 framework/yii/data/Pagination.php create mode 100644 framework/yii/data/Sort.php delete mode 100644 framework/yii/web/Pagination.php delete mode 100644 framework/yii/web/Sort.php diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php new file mode 100644 index 0000000..4e25809 --- /dev/null +++ b/framework/yii/data/Pagination.php @@ -0,0 +1,209 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\data; + +use Yii; +use yii\base\Object; + +/** + * Pagination represents information relevant to pagination of data items. + * + * When data needs to be rendered in multiple pages, Pagination can be used to + * represent information such as [[itemCount|total item count]], [[pageSize|page size]], + * [[page|current page]], etc. These information can be passed to [[yii\widgets\Pager|pagers]] + * to render pagination buttons or links. + * + * The following example shows how to create a pagination object and feed it + * to a pager. + * + * Controller action: + * + * ~~~ + * function actionIndex() + * { + * $query = Article::find()->where(array('status' => 1)); + * $countQuery = clone $query; + * $pages = new Pagination($countQuery->count()); + * $models = $query->offset($pages->offset) + * ->limit($pages->limit) + * ->all(); + * + * $this->render('index', array( + * 'models' => $models, + * 'pages' => $pages, + * )); + * } + * ~~~ + * + * View: + * + * ~~~ + * foreach ($models as $model) { + * // display $model here + * } + * + * // display pagination + * LinkPager::widget(array( + * 'pagination' => $pages, + * )); + * ~~~ + * + * @property integer $pageCount Number of pages. + * @property integer $page The zero-based index of the current page. + * @property integer $offset The offset of the data. This may be used to set the + * OFFSET value for a SQL statement for fetching the current page of data. + * @property integer $limit The limit of the data. This may be used to set the + * LIMIT value for a SQL statement for fetching the current page of data. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Pagination extends Object +{ + /** + * @var string name of the parameter storing the current page index. Defaults to 'page'. + * @see params + */ + public $pageVar = 'page'; + /** + * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]]. + * If false and [[page]] is 0, the page parameter will not be put in the URL. + */ + public $forcePageVar = true; + /** + * @var string the route of the controller action for displaying the paged contents. + * If not set, it means using the currently requested route. + */ + public $route; + /** + * @var array parameters (name => value) that should be used to obtain the current page number + * and to create new pagination URLs. If not set, $_GET will be used instead. + * + * The array element indexed by [[pageVar]] is considered to be the current page number. + * If the element does not exist, the current page number is considered 0. + */ + public $params; + /** + * @var boolean whether to check if [[page]] is within valid range. + * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1). + * Because [[pageCount]] relies on the correct value of [[itemCount]] which may not be available + * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page + * number validation. By doing so, [[page]] will return the value indexed by [[pageVar]] in [[params]]. + */ + public $validatePage = true; + /** + * @var integer number of items on each page. Defaults to 10. + * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. + */ + public $pageSize = 10; + /** + * @var integer total number of items. + */ + public $itemCount; + + /** + * Constructor. + * @param integer $itemCount total number of items. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($itemCount, $config = array()) + { + $this->itemCount = $itemCount; + parent::__construct($config); + } + + /** + * @return integer number of pages + */ + public function getPageCount() + { + if ($this->pageSize < 1) { + return $this->itemCount > 0 ? 1 : 0; + } else { + $itemCount = $this->itemCount < 0 ? 0 : (int)$this->itemCount; + return (int)(($itemCount + $this->pageSize - 1) / $this->pageSize); + } + } + + private $_page; + + /** + * Returns the zero-based current page number. + * @param boolean $recalculate whether to recalculate the current page based on the page size and item count. + * @return integer the zero-based current page number. + */ + public function getPage($recalculate = false) + { + if ($this->_page === null || $recalculate) { + $params = $this->params === null ? $_GET : $this->params; + if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) { + $this->_page = (int)$params[$this->pageVar] - 1; + if ($this->validatePage) { + $pageCount = $this->getPageCount(); + if ($this->_page >= $pageCount) { + $this->_page = $pageCount - 1; + } + } + if ($this->_page < 0) { + $this->_page = 0; + } + } else { + $this->_page = 0; + } + } + return $this->_page; + } + + /** + * Sets the current page number. + * @param integer $value the zero-based index of the current page. + */ + public function setPage($value) + { + $this->_page = $value; + } + + /** + * Creates the URL suitable for pagination with the specified page number. + * This method is mainly called by pagers when creating URLs used to perform pagination. + * @param integer $page the zero-based page number that the URL should point to. + * @return string the created URL + * @see params + * @see forcePageVar + */ + public function createUrl($page) + { + $params = $this->params === null ? $_GET : $this->params; + if ($page > 0 || $page >= 0 && $this->forcePageVar) { + $params[$this->pageVar] = $page + 1; + } else { + unset($params[$this->pageVar]); + } + $route = $this->route === null ? Yii::$app->controller->route : $this->route; + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + + /** + * @return integer the offset of the data. This may be used to set the + * OFFSET value for a SQL statement for fetching the current page of data. + */ + public function getOffset() + { + return $this->pageSize < 1 ? 0 : $this->getPage() * $this->pageSize; + } + + /** + * @return integer the limit of the data. This may be used to set the + * LIMIT value for a SQL statement for fetching the current page of data. + * Note that if the page size is infinite, a value -1 will be returned. + */ + public function getLimit() + { + return $this->pageSize < 1 ? -1 : $this->pageSize; + } +} diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php new file mode 100644 index 0000000..a46d0ce --- /dev/null +++ b/framework/yii/data/Sort.php @@ -0,0 +1,337 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\data; + +use Yii; +use yii\base\Object; +use yii\helpers\Html; + +/** + * Sort represents information relevant to sorting. + * + * When data needs to be sorted according to one or several attributes, + * we can use Sort to represent the sorting information and generate + * appropriate hyperlinks that can lead to sort actions. + * + * A typical usage example is as follows, + * + * ~~~ + * function actionIndex() + * { + * $sort = new Sort(array( + * 'attributes' => array( + * 'age', + * 'name' => array( + * 'asc' => array('last_name', 'first_name'), + * 'desc' => array('last_name' => true, 'first_name' => true), + * ), + * ), + * )); + * + * $models = Article::find() + * ->where(array('status' => 1)) + * ->orderBy($sort->orders) + * ->all(); + * + * $this->render('index', array( + * 'models' => $models, + * 'sort' => $sort, + * )); + * } + * ~~~ + * + * View: + * + * ~~~ + * // display links leading to sort actions + * echo $sort->link('name', 'Name') . ' | ' . $sort->link('age', 'Age'); + * + * foreach ($models as $model) { + * // display $model here + * } + * ~~~ + * + * In the above, we declare two [[attributes]] that support sorting: name and age. + * We pass the sort information to the Article query so that the query results are + * sorted by the orders specified by the Sort object. In the view, we show two hyperlinks + * that can lead to pages with the data sorted by the corresponding attributes. + * + * @property array $orders Sort directions indexed by column names. The sort direction + * can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. + * @property array $attributeOrders Sort directions indexed by attribute names. The sort + * direction can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Sort extends Object +{ + /** + * Sort ascending + */ + const ASC = false; + + /** + * Sort descending + */ + const DESC = true; + + /** + * @var boolean whether the sorting can be applied to multiple attributes simultaneously. + * Defaults to false, which means each time the data can only be sorted by one attribute. + */ + public $enableMultiSort = false; + + /** + * @var array list of attributes that are allowed to be sorted. Its syntax can be + * described using the following example: + * + * ~~~ + * array( + * 'age', + * 'user' => array( + * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), + * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), + * 'default' => 'desc', + * ), + * ) + * ~~~ + * + * In the above, two attributes are declared: "age" and "user". The "age" attribute is + * a simple attribute which is equivalent to the following: + * + * ~~~ + * 'age' => array( + * 'asc' => array('age' => Sort::ASC), + * 'desc' => array('age' => Sort::DESC), + * ) + * ~~~ + * + * The "user" attribute is a composite attribute: + * + * - The "user" key represents the attribute name which will appear in the URLs leading + * to sort actions. Attribute names cannot contain characters listed in [[separators]]. + * - The "asc" and "desc" elements specify how to sort by the attribute in ascending + * and descending orders, respectively. Their values represent the actual columns and + * the directions by which the data should be sorted by. + * - And the "default" element specifies if the attribute is not sorted currently, + * in which direction it should be sorted (the default value is ascending order). + */ + public $attributes = array(); + /** + * @var string the name of the parameter that specifies which attributes to be sorted + * in which direction. Defaults to 'sort'. + * @see params + */ + public $sortVar = 'sort'; + /** + * @var string the tag appeared in the [[sortVar]] parameter that indicates the attribute should be sorted + * in descending order. Defaults to 'desc'. + */ + public $descTag = 'desc'; + /** + * @var array the order that should be used when the current request does not specify any order. + * The array keys are attribute names and the array values are the corresponding sort directions. For example, + * + * ~~~ + * array( + * 'name' => Sort::ASC, + * 'create_time' => Sort::DESC, + * ) + * ~~~ + * + * @see attributeOrders + */ + public $defaults; + /** + * @var string the route of the controller action for displaying the sorted contents. + * If not set, it means using the currently requested route. + */ + public $route; + /** + * @var array separators used in the generated URL. This must be an array consisting of + * two elements. The first element specifies the character separating different + * attributes, while the second element specifies the character separating attribute name + * and the corresponding sort direction. Defaults to `array('-', '.')`. + */ + public $separators = array('-', '.'); + /** + * @var array parameters (name => value) that should be used to obtain the current sort directions + * and to create new sort URLs. If not set, $_GET will be used instead. + * + * The array element indexed by [[sortVar]] is considered to be the current sort directions. + * If the element does not exist, the [[defaults|default order]] will be used. + * + * @see sortVar + * @see defaults + */ + public $params; + + /** + * Returns the columns and their corresponding sort directions. + * @return array the columns (keys) and their corresponding sort directions (values). + * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query. + */ + public function getOrders() + { + $attributeOrders = $this->getAttributeOrders(); + $orders = array(); + foreach ($attributeOrders as $attribute => $direction) { + $definition = $this->getAttribute($attribute); + $columns = $definition[$direction === self::ASC ? 'asc' : 'desc']; + foreach ($columns as $name => $dir) { + $orders[$name] = $dir; + } + } + return $orders; + } + + /** + * Generates a hyperlink that links to the sort action to sort by the specified attribute. + * Based on the sort direction, the CSS class of the generated hyperlink will be appended + * with "asc" or "desc". + * @param string $attribute the attribute name by which the data should be sorted by. + * @param string $label the link label. Note that the label will not be HTML-encoded. + * @param array $htmlOptions additional HTML attributes for the hyperlink tag + * @return string the generated hyperlink + */ + public function link($attribute, $label, $htmlOptions = array()) + { + if (($definition = $this->getAttribute($attribute)) === false) { + return $label; + } + + if (($direction = $this->getAttributeOrder($attribute)) !== null) { + $class = $direction ? 'desc' : 'asc'; + if (isset($htmlOptions['class'])) { + $htmlOptions['class'] .= ' ' . $class; + } else { + $htmlOptions['class'] = $class; + } + } + + $url = $this->createUrl($attribute); + + return Html::a($label, $url, $htmlOptions); + } + + private $_attributeOrders; + + /** + * Returns the currently requested sort information. + * @param boolean $recalculate whether to recalculate the sort directions + * @return array sort directions indexed by attribute names. + * Sort direction can be either [[Sort::ASC]] for ascending order or + * [[Sort::DESC]] for descending order. + */ + public function getAttributeOrders($recalculate = false) + { + if ($this->_attributeOrders === null || $recalculate) { + $this->_attributeOrders = array(); + $params = $this->params === null ? $_GET : $this->params; + if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) { + $attributes = explode($this->separators[0], $params[$this->sortVar]); + foreach ($attributes as $attribute) { + $descending = false; + if (($pos = strrpos($attribute, $this->separators[1])) !== false) { + if ($descending = (substr($attribute, $pos + 1) === $this->descTag)) { + $attribute = substr($attribute, 0, $pos); + } + } + + if (($this->getAttribute($attribute)) !== false) { + $this->_attributeOrders[$attribute] = $descending; + if (!$this->enableMultiSort) { + return $this->_attributeOrders; + } + } + } + } + if (empty($this->_attributeOrders) && is_array($this->defaults)) { + $this->_attributeOrders = $this->defaults; + } + } + return $this->_attributeOrders; + } + + /** + * Returns the sort direction of the specified attribute in the current request. + * @param string $attribute the attribute name + * @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]] + * for ascending order or [[Sort::DESC]] for descending order. Null is returned + * if the attribute is invalid or does not need to be sorted. + */ + public function getAttributeOrder($attribute) + { + $this->getAttributeOrders(); + return isset($this->_attributeOrders[$attribute]) ? $this->_attributeOrders[$attribute] : null; + } + + /** + * Creates a URL for sorting the data by the specified attribute. + * This method will consider the current sorting status given by [[attributeOrders]]. + * For example, if the current page already sorts the data by the specified attribute in ascending order, + * then the URL created will lead to a page that sorts the data by the specified attribute in descending order. + * @param string $attribute the attribute name + * @return string|boolean the URL for sorting. False if the attribute is invalid. + * @see attributeOrders + * @see params + */ + public function createUrl($attribute) + { + if (($definition = $this->getAttribute($attribute)) === false) { + return false; + } + $directions = $this->getAttributeOrders(); + if (isset($directions[$attribute])) { + $descending = !$directions[$attribute]; + unset($directions[$attribute]); + } elseif (isset($definition['default'])) { + $descending = $definition['default'] === 'desc'; + } else { + $descending = false; + } + + if ($this->enableMultiSort) { + $directions = array_merge(array($attribute => $descending), $directions); + } else { + $directions = array($attribute => $descending); + } + + $sorts = array(); + foreach ($directions as $attribute => $descending) { + $sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute; + } + $params = $this->params === null ? $_GET : $this->params; + $params[$this->sortVar] = implode($this->separators[0], $sorts); + $route = $this->route === null ? Yii::$app->controller->route : $this->route; + + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + + /** + * Returns the attribute definition of the specified name. + * @param string $name the attribute name + * @return array|boolean the sort definition (column names => sort directions). + * False is returned if the attribute cannot be sorted. + * @see attributes + */ + public function getAttribute($name) + { + if (isset($this->attributes[$name])) { + return $this->attributes[$name]; + } elseif (in_array($name, $this->attributes, true)) { + return array( + 'asc' => array($name => self::ASC), + 'desc' => array($name => self::DESC), + ); + } else { + return false; + } + } +} diff --git a/framework/yii/web/Pagination.php b/framework/yii/web/Pagination.php deleted file mode 100644 index c4a8106..0000000 --- a/framework/yii/web/Pagination.php +++ /dev/null @@ -1,208 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\web; - -use Yii; - -/** - * Pagination represents information relevant to pagination of data items. - * - * When data needs to be rendered in multiple pages, Pagination can be used to - * represent information such as [[itemCount|total item count]], [[pageSize|page size]], - * [[page|current page]], etc. These information can be passed to [[yii\widgets\Pager|pagers]] - * to render pagination buttons or links. - * - * The following example shows how to create a pagination object and feed it - * to a pager. - * - * Controller action: - * - * ~~~ - * function actionIndex() - * { - * $query = Article::find()->where(array('status' => 1)); - * $countQuery = clone $query; - * $pages = new Pagination($countQuery->count()); - * $models = $query->offset($pages->offset) - * ->limit($pages->limit) - * ->all(); - * - * $this->render('index', array( - * 'models' => $models, - * 'pages' => $pages, - * )); - * } - * ~~~ - * - * View: - * - * ~~~ - * foreach ($models as $model) { - * // display $model here - * } - * - * // display pagination - * LinkPager::widget(array( - * 'pagination' => $pages, - * )); - * ~~~ - * - * @property integer $pageCount Number of pages. - * @property integer $page The zero-based index of the current page. - * @property integer $offset The offset of the data. This may be used to set the - * OFFSET value for a SQL statement for fetching the current page of data. - * @property integer $limit The limit of the data. This may be used to set the - * LIMIT value for a SQL statement for fetching the current page of data. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class Pagination extends \yii\base\Object -{ - /** - * @var string name of the parameter storing the current page index. Defaults to 'page'. - * @see params - */ - public $pageVar = 'page'; - /** - * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]]. - * If false and [[page]] is 0, the page parameter will not be put in the URL. - */ - public $forcePageVar = true; - /** - * @var string the route of the controller action for displaying the paged contents. - * If not set, it means using the currently requested route. - */ - public $route; - /** - * @var array parameters (name => value) that should be used to obtain the current page number - * and to create new pagination URLs. If not set, $_GET will be used instead. - * - * The array element indexed by [[pageVar]] is considered to be the current page number. - * If the element does not exist, the current page number is considered 0. - */ - public $params; - /** - * @var boolean whether to check if [[page]] is within valid range. - * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1). - * Because [[pageCount]] relies on the correct value of [[itemCount]] which may not be available - * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page - * number validation. By doing so, [[page]] will return the value indexed by [[pageVar]] in [[params]]. - */ - public $validatePage = true; - /** - * @var integer number of items on each page. Defaults to 10. - * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. - */ - public $pageSize = 10; - /** - * @var integer total number of items. - */ - public $itemCount; - - /** - * Constructor. - * @param integer $itemCount total number of items. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($itemCount, $config = array()) - { - $this->itemCount = $itemCount; - parent::__construct($config); - } - - /** - * @return integer number of pages - */ - public function getPageCount() - { - if ($this->pageSize < 1) { - return $this->itemCount > 0 ? 1 : 0; - } else { - $itemCount = $this->itemCount < 0 ? 0 : (int)$this->itemCount; - return (int)(($itemCount + $this->pageSize - 1) / $this->pageSize); - } - } - - private $_page; - - /** - * Returns the zero-based current page number. - * @param boolean $recalculate whether to recalculate the current page based on the page size and item count. - * @return integer the zero-based current page number. - */ - public function getPage($recalculate = false) - { - if ($this->_page === null || $recalculate) { - $params = $this->params === null ? $_GET : $this->params; - if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) { - $this->_page = (int)$params[$this->pageVar] - 1; - if ($this->validatePage) { - $pageCount = $this->getPageCount(); - if ($this->_page >= $pageCount) { - $this->_page = $pageCount - 1; - } - } - if ($this->_page < 0) { - $this->_page = 0; - } - } else { - $this->_page = 0; - } - } - return $this->_page; - } - - /** - * Sets the current page number. - * @param integer $value the zero-based index of the current page. - */ - public function setPage($value) - { - $this->_page = $value; - } - - /** - * Creates the URL suitable for pagination with the specified page number. - * This method is mainly called by pagers when creating URLs used to perform pagination. - * @param integer $page the zero-based page number that the URL should point to. - * @return string the created URL - * @see params - * @see forcePageVar - */ - public function createUrl($page) - { - $params = $this->params === null ? $_GET : $this->params; - if ($page > 0 || $page >= 0 && $this->forcePageVar) { - $params[$this->pageVar] = $page + 1; - } else { - unset($params[$this->pageVar]); - } - $route = $this->route === null ? Yii::$app->controller->route : $this->route; - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - - /** - * @return integer the offset of the data. This may be used to set the - * OFFSET value for a SQL statement for fetching the current page of data. - */ - public function getOffset() - { - return $this->pageSize < 1 ? 0 : $this->getPage() * $this->pageSize; - } - - /** - * @return integer the limit of the data. This may be used to set the - * LIMIT value for a SQL statement for fetching the current page of data. - * Note that if the page size is infinite, a value -1 will be returned. - */ - public function getLimit() - { - return $this->pageSize < 1 ? -1 : $this->pageSize; - } -} diff --git a/framework/yii/web/Sort.php b/framework/yii/web/Sort.php deleted file mode 100644 index 6014a3e..0000000 --- a/framework/yii/web/Sort.php +++ /dev/null @@ -1,336 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\web; - -use Yii; -use yii\helpers\Html; - -/** - * Sort represents information relevant to sorting. - * - * When data needs to be sorted according to one or several attributes, - * we can use Sort to represent the sorting information and generate - * appropriate hyperlinks that can lead to sort actions. - * - * A typical usage example is as follows, - * - * ~~~ - * function actionIndex() - * { - * $sort = new Sort(array( - * 'attributes' => array( - * 'age', - * 'name' => array( - * 'asc' => array('last_name', 'first_name'), - * 'desc' => array('last_name' => true, 'first_name' => true), - * ), - * ), - * )); - * - * $models = Article::find() - * ->where(array('status' => 1)) - * ->orderBy($sort->orders) - * ->all(); - * - * $this->render('index', array( - * 'models' => $models, - * 'sort' => $sort, - * )); - * } - * ~~~ - * - * View: - * - * ~~~ - * // display links leading to sort actions - * echo $sort->link('name', 'Name') . ' | ' . $sort->link('age', 'Age'); - * - * foreach ($models as $model) { - * // display $model here - * } - * ~~~ - * - * In the above, we declare two [[attributes]] that support sorting: name and age. - * We pass the sort information to the Article query so that the query results are - * sorted by the orders specified by the Sort object. In the view, we show two hyperlinks - * that can lead to pages with the data sorted by the corresponding attributes. - * - * @property array $orders Sort directions indexed by column names. The sort direction - * can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. - * @property array $attributeOrders Sort directions indexed by attribute names. The sort - * direction can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class Sort extends \yii\base\Object -{ - /** - * Sort ascending - */ - const ASC = false; - - /** - * Sort descending - */ - const DESC = true; - - /** - * @var boolean whether the sorting can be applied to multiple attributes simultaneously. - * Defaults to false, which means each time the data can only be sorted by one attribute. - */ - public $enableMultiSort = false; - - /** - * @var array list of attributes that are allowed to be sorted. Its syntax can be - * described using the following example: - * - * ~~~ - * array( - * 'age', - * 'user' => array( - * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - * 'default' => 'desc', - * ), - * ) - * ~~~ - * - * In the above, two attributes are declared: "age" and "user". The "age" attribute is - * a simple attribute which is equivalent to the following: - * - * ~~~ - * 'age' => array( - * 'asc' => array('age' => Sort::ASC), - * 'desc' => array('age' => Sort::DESC), - * ) - * ~~~ - * - * The "user" attribute is a composite attribute: - * - * - The "user" key represents the attribute name which will appear in the URLs leading - * to sort actions. Attribute names cannot contain characters listed in [[separators]]. - * - The "asc" and "desc" elements specify how to sort by the attribute in ascending - * and descending orders, respectively. Their values represent the actual columns and - * the directions by which the data should be sorted by. - * - And the "default" element specifies if the attribute is not sorted currently, - * in which direction it should be sorted (the default value is ascending order). - */ - public $attributes = array(); - /** - * @var string the name of the parameter that specifies which attributes to be sorted - * in which direction. Defaults to 'sort'. - * @see params - */ - public $sortVar = 'sort'; - /** - * @var string the tag appeared in the [[sortVar]] parameter that indicates the attribute should be sorted - * in descending order. Defaults to 'desc'. - */ - public $descTag = 'desc'; - /** - * @var array the order that should be used when the current request does not specify any order. - * The array keys are attribute names and the array values are the corresponding sort directions. For example, - * - * ~~~ - * array( - * 'name' => Sort::ASC, - * 'create_time' => Sort::DESC, - * ) - * ~~~ - * - * @see attributeOrders - */ - public $defaults; - /** - * @var string the route of the controller action for displaying the sorted contents. - * If not set, it means using the currently requested route. - */ - public $route; - /** - * @var array separators used in the generated URL. This must be an array consisting of - * two elements. The first element specifies the character separating different - * attributes, while the second element specifies the character separating attribute name - * and the corresponding sort direction. Defaults to `array('-', '.')`. - */ - public $separators = array('-', '.'); - /** - * @var array parameters (name => value) that should be used to obtain the current sort directions - * and to create new sort URLs. If not set, $_GET will be used instead. - * - * The array element indexed by [[sortVar]] is considered to be the current sort directions. - * If the element does not exist, the [[defaults|default order]] will be used. - * - * @see sortVar - * @see defaults - */ - public $params; - - /** - * Returns the columns and their corresponding sort directions. - * @return array the columns (keys) and their corresponding sort directions (values). - * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query. - */ - public function getOrders() - { - $attributeOrders = $this->getAttributeOrders(); - $orders = array(); - foreach ($attributeOrders as $attribute => $direction) { - $definition = $this->getAttribute($attribute); - $columns = $definition[$direction === self::ASC ? 'asc' : 'desc']; - foreach ($columns as $name => $dir) { - $orders[$name] = $dir; - } - } - return $orders; - } - - /** - * Generates a hyperlink that links to the sort action to sort by the specified attribute. - * Based on the sort direction, the CSS class of the generated hyperlink will be appended - * with "asc" or "desc". - * @param string $attribute the attribute name by which the data should be sorted by. - * @param string $label the link label. Note that the label will not be HTML-encoded. - * @param array $htmlOptions additional HTML attributes for the hyperlink tag - * @return string the generated hyperlink - */ - public function link($attribute, $label, $htmlOptions = array()) - { - if (($definition = $this->getAttribute($attribute)) === false) { - return $label; - } - - if (($direction = $this->getAttributeOrder($attribute)) !== null) { - $class = $direction ? 'desc' : 'asc'; - if (isset($htmlOptions['class'])) { - $htmlOptions['class'] .= ' ' . $class; - } else { - $htmlOptions['class'] = $class; - } - } - - $url = $this->createUrl($attribute); - - return Html::a($label, $url, $htmlOptions); - } - - private $_attributeOrders; - - /** - * Returns the currently requested sort information. - * @param boolean $recalculate whether to recalculate the sort directions - * @return array sort directions indexed by attribute names. - * Sort direction can be either [[Sort::ASC]] for ascending order or - * [[Sort::DESC]] for descending order. - */ - public function getAttributeOrders($recalculate = false) - { - if ($this->_attributeOrders === null || $recalculate) { - $this->_attributeOrders = array(); - $params = $this->params === null ? $_GET : $this->params; - if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) { - $attributes = explode($this->separators[0], $params[$this->sortVar]); - foreach ($attributes as $attribute) { - $descending = false; - if (($pos = strrpos($attribute, $this->separators[1])) !== false) { - if ($descending = (substr($attribute, $pos + 1) === $this->descTag)) { - $attribute = substr($attribute, 0, $pos); - } - } - - if (($this->getAttribute($attribute)) !== false) { - $this->_attributeOrders[$attribute] = $descending; - if (!$this->enableMultiSort) { - return $this->_attributeOrders; - } - } - } - } - if (empty($this->_attributeOrders) && is_array($this->defaults)) { - $this->_attributeOrders = $this->defaults; - } - } - return $this->_attributeOrders; - } - - /** - * Returns the sort direction of the specified attribute in the current request. - * @param string $attribute the attribute name - * @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]] - * for ascending order or [[Sort::DESC]] for descending order. Null is returned - * if the attribute is invalid or does not need to be sorted. - */ - public function getAttributeOrder($attribute) - { - $this->getAttributeOrders(); - return isset($this->_attributeOrders[$attribute]) ? $this->_attributeOrders[$attribute] : null; - } - - /** - * Creates a URL for sorting the data by the specified attribute. - * This method will consider the current sorting status given by [[attributeOrders]]. - * For example, if the current page already sorts the data by the specified attribute in ascending order, - * then the URL created will lead to a page that sorts the data by the specified attribute in descending order. - * @param string $attribute the attribute name - * @return string|boolean the URL for sorting. False if the attribute is invalid. - * @see attributeOrders - * @see params - */ - public function createUrl($attribute) - { - if (($definition = $this->getAttribute($attribute)) === false) { - return false; - } - $directions = $this->getAttributeOrders(); - if (isset($directions[$attribute])) { - $descending = !$directions[$attribute]; - unset($directions[$attribute]); - } elseif (isset($definition['default'])) { - $descending = $definition['default'] === 'desc'; - } else { - $descending = false; - } - - if ($this->enableMultiSort) { - $directions = array_merge(array($attribute => $descending), $directions); - } else { - $directions = array($attribute => $descending); - } - - $sorts = array(); - foreach ($directions as $attribute => $descending) { - $sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute; - } - $params = $this->params === null ? $_GET : $this->params; - $params[$this->sortVar] = implode($this->separators[0], $sorts); - $route = $this->route === null ? Yii::$app->controller->route : $this->route; - - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - - /** - * Returns the attribute definition of the specified name. - * @param string $name the attribute name - * @return array|boolean the sort definition (column names => sort directions). - * False is returned if the attribute cannot be sorted. - * @see attributes - */ - public function getAttribute($name) - { - if (isset($this->attributes[$name])) { - return $this->attributes[$name]; - } elseif (in_array($name, $this->attributes, true)) { - return array( - 'asc' => array($name => self::ASC), - 'desc' => array($name => self::DESC), - ); - } else { - return false; - } - } -} diff --git a/framework/yii/widgets/LinkPager.php b/framework/yii/widgets/LinkPager.php index 2510579..62d99f6 100644 --- a/framework/yii/widgets/LinkPager.php +++ b/framework/yii/widgets/LinkPager.php @@ -11,7 +11,7 @@ use Yii; use yii\base\InvalidConfigException; use yii\helpers\Html; use yii\base\Widget; -use yii\web\Pagination; +use yii\data\Pagination; /** * LinkPager displays a list of hyperlinks that lead to different pages of target. @@ -198,4 +198,4 @@ class LinkPager extends Widget } return array($beginPage, $endPage); } -} \ No newline at end of file +} diff --git a/framework/yii/widgets/ListPager.php b/framework/yii/widgets/ListPager.php index 7b16f7d..699126d 100644 --- a/framework/yii/widgets/ListPager.php +++ b/framework/yii/widgets/ListPager.php @@ -10,7 +10,7 @@ namespace yii\widgets; use yii\base\InvalidConfigException; use yii\helpers\Html; use yii\base\Widget; -use yii\web\Pagination; +use yii\data\Pagination; /** * ListPager displays a drop-down list that contains options leading to different pages. @@ -92,4 +92,4 @@ class ListPager extends Widget )); } -} \ No newline at end of file +} From 70e973129da2beaa4608175e08526e7b96952853 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@ext4yii.com> Date: Thu, 6 Jun 2013 11:29:28 +0200 Subject: [PATCH 50/91] Added foreign keys and cleanup internal pg types --- framework/yii/db/pgsql/QueryBuilder.php | 10 +--- framework/yii/db/pgsql/Schema.php | 99 +++++++++++++++++++++++---------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index c0d5089..0cd9d94 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -8,9 +8,6 @@ namespace yii\db\pgsql; -use yii\db\Exception; -use yii\base\InvalidParamException; - /** * QueryBuilder is the query builder for PostgreSQL databases. * @@ -24,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder { */ public $typeMap = array( Schema::TYPE_PK => 'bigserial not null primary key', - Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_STRING => 'varchar', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', Schema::TYPE_INTEGER => 'integer', @@ -40,9 +37,4 @@ class QueryBuilder extends \yii\db\QueryBuilder { Schema::TYPE_MONEY => 'numeric(19,4)', ); - public function insert($table, $columns, &$params) { - $sql = parent::insert($table, $columns, $params); - return $sql . ' RETURNING *'; - } - } diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 5203ceb..1e19383 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -33,31 +33,24 @@ class Schema extends \yii\db\Schema { */ public $typeMap = array( 'abstime' => self::TYPE_TIMESTAMP, - //'aclitem' => self::TYPE_STRING, 'bit' => self::TYPE_STRING, 'boolean' => self::TYPE_BOOLEAN, 'box' => self::TYPE_STRING, 'character' => self::TYPE_STRING, 'bytea' => self::TYPE_BINARY, 'char' => self::TYPE_STRING, - //'cid' => self::TYPE_STRING, 'cidr' => self::TYPE_STRING, 'circle' => self::TYPE_STRING, 'date' => self::TYPE_DATE, - //'daterange' => self::TYPE_STRING, 'real' => self::TYPE_FLOAT, 'double precision' => self::TYPE_DECIMAL, - //'gtsvector' => self::TYPE_STRING, 'inet' => self::TYPE_STRING, 'smallint' => self::TYPE_SMALLINT, 'integer' => self::TYPE_INTEGER, - //'int4range' => self::TYPE_STRING, //unknown 'bigint' => self::TYPE_BIGINT, - //'int8range' => self::TYPE_STRING, // unknown 'interval' => self::TYPE_STRING, 'json' => self::TYPE_STRING, 'line' => self::TYPE_STRING, - //'lseg' => self::TYPE_STRING, 'macaddr' => self::TYPE_STRING, 'money' => self::TYPE_MONEY, 'name' => self::TYPE_STRING, @@ -65,38 +58,49 @@ class Schema extends \yii\db\Schema { 'numrange' => self::TYPE_DECIMAL, 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! 'path' => self::TYPE_STRING, - //'pg_node_tree' => self::TYPE_STRING, 'point' => self::TYPE_STRING, 'polygon' => self::TYPE_STRING, - //'refcursor' => self::TYPE_STRING, - //'regclass' => self::TYPE_STRING, - //'regconfig' => self::TYPE_STRING, - //'regdictionary' => self::TYPE_STRING, - //'regoper' => self::TYPE_STRING, - //'regoperator' => self::TYPE_STRING, - //'regproc' => self::TYPE_STRING, - //'regprocedure' => self::TYPE_STRING, - //'regtype' => self::TYPE_STRING, - //'reltime' => self::TYPE_STRING, - //'smgr' => self::TYPE_STRING, 'text' => self::TYPE_TEXT, - //'tid' => self::TYPE_STRING, 'time without time zone' => self::TYPE_TIME, 'timestamp without time zone' => self::TYPE_TIMESTAMP, 'timestamp with time zone' => self::TYPE_TIMESTAMP, 'time with time zone' => self::TYPE_TIMESTAMP, - //'tinterval' => self::TYPE_STRING, - //'tsquery' => self::TYPE_STRING, - //'tsrange' => self::TYPE_STRING, - //'tstzrange' => self::TYPE_STRING, - //'tsvector' => self::TYPE_STRING, - //'txid_snapshot' => self::TYPE_STRING, 'unknown' => self::TYPE_STRING, 'uuid' => self::TYPE_STRING, 'bit varying' => self::TYPE_STRING, 'character varying' => self::TYPE_STRING, - //'xid' => self::TYPE_STRING, 'xml' => self::TYPE_STRING + + /* + * internal PG types + * 'aclitem' => self::TYPE_STRING, + * 'cid' => self::TYPE_STRING, + * 'daterange' => self::TYPE_STRING, + * 'gtsvector' => self::TYPE_STRING, + * 'int4range' => self::TYPE_STRING, //unknown + * 'lseg' => self::TYPE_STRING, + * 'int8range' => self::TYPE_STRING, // unknown + * 'pg_node_tree' => self::TYPE_STRING, + * 'refcursor' => self::TYPE_STRING, + * 'regclass' => self::TYPE_STRING, + * 'regconfig' => self::TYPE_STRING, + * 'regdictionary' => self::TYPE_STRING, + * 'regoper' => self::TYPE_STRING, + * 'regoperator' => self::TYPE_STRING, + * 'regproc' => self::TYPE_STRING, + * 'regprocedure' => self::TYPE_STRING, + * 'regtype' => self::TYPE_STRING, + * 'reltime' => self::TYPE_STRING, + * 'smgr' => self::TYPE_STRING, + * 'tid' => self::TYPE_STRING, + * 'xid' => self::TYPE_STRING, + * 'tinterval' => self::TYPE_STRING, + * 'tsquery' => self::TYPE_STRING, + * 'tsrange' => self::TYPE_STRING, + * 'tstzrange' => self::TYPE_STRING, + * 'tsvector' => self::TYPE_STRING, + * 'txid_snapshot' => self::TYPE_STRING + */ ); /** @@ -155,14 +159,51 @@ class Schema extends \yii\db\Schema { */ protected function findConstraints($table) { + $tableName = $this->quoteValue($table->name); + $tableSchema = $this->quoteValue($table->schemaName); + $database = $this->quoteValue($this->db->pdo->getCurrentDatabase()); + + //We need to extract the constraints de hard way since: + //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us + + $sql = <<<SQL +select + ct.conname as containst, + c.relname as table_name, + ns.nspname as table_schema, + current_database() as table_catalog, + (select string_agg(attname,',') attname from pg_attribute where attrelid=ct.conrelid and attnum = any(ct.conkey)) as columns, + fc.relname as foreign_table_name, + fns.nspname as foreign_table_schema, + current_database() as foreign_table_catalog, + (select string_agg(attname,',') attname from pg_attribute where attrelid=ct.confrelid and attnum = any(ct.confkey)) as foreign_columns +from + pg_constraint ct + inner join pg_class c on c.oid=ct.conrelid + inner join pg_namespace ns on c.relnamespace=ns.oid + left join pg_class fc on fc.oid=ct.confrelid + left join pg_namespace fns on fc.relnamespace=fns.oid + +where + ct.contype='f' + and c.relname={$tableName} + and ns.nspname={$tableSchema} + and current_database() = {$database} +SQL; + try { $constraints = $this->db->createCommand($sql)->queryAll(); } catch (\Exception $e) { return false; } foreach ($constraints as $constraint) { - $column = $this->loadColumnSchema($column); - $table->columns[$column->name] = $column; + $columns = explode(',', $constraint['columns']); + $fcolumns = explode(',', $constraint['foreign_columns']); + $citem = array($constraint['foreign_table_name']); + foreach ($columns as $idx => $column) { + $citem[] = array($fcolumns[$idx] => $column); + } + $table->foreignKeys[] = $citem; } return true; } From 9b65934638ce6a04efefc9b0571e3a1a1436361b Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 07:23:06 -0400 Subject: [PATCH 51/91] Added HeaderCollection. --- framework/yii/web/CookieCollection.php | 12 +- framework/yii/web/HeaderCollection.php | 198 +++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 framework/yii/web/HeaderCollection.php diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php index 97726c7..fc9375e 100644 --- a/framework/yii/web/CookieCollection.php +++ b/framework/yii/web/CookieCollection.php @@ -101,6 +101,16 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ } /** + * Returns whether there is a cookie with the specified name. + * @param string $name the cookie name + * @return boolean whether the named cookie exists + */ + public function has($name) + { + return isset($this->_cookies[$name]); + } + + /** * Adds a cookie to the collection. * If there is already a cookie with the same name in the collection, it will be removed first. * @param Cookie $cookie the cookie to be added @@ -172,7 +182,7 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ */ public function offsetExists($name) { - return isset($this->_cookies[$name]); + return $this->has($name); } /** diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php new file mode 100644 index 0000000..7a24854 --- /dev/null +++ b/framework/yii/web/HeaderCollection.php @@ -0,0 +1,198 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +use Yii; +use yii\base\Object; +use ArrayIterator; + + +/** + * HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable +{ + /** + * @var array the headers in this collection (indexed by the header names) + */ + private $_headers = array(); + + /** + * Returns an iterator for traversing the headers in the collection. + * This method is required by the SPL interface `IteratorAggregate`. + * It will be implicitly called when you use `foreach` to traverse the collection. + * @return ArrayIterator an iterator for traversing the headers in the collection. + */ + public function getIterator() + { + return new ArrayIterator($this->_headers); + } + + /** + * Returns the number of headers in the collection. + * This method is required by the SPL `Countable` interface. + * It will be implicitly called when you use `count($collection)`. + * @return integer the number of headers in the collection. + */ + public function count() + { + return $this->getCount(); + } + + /** + * Returns the number of headers in the collection. + * @return integer the number of headers in the collection. + */ + public function getCount() + { + return count($this->_headers); + } + + /** + * Returns the named header(s). + * @param string $name the name of the header to return + * @param mixed $default the value to return in case the named header does not exist + * @param boolean $first whether to only return the first header of the specified name. + * If false, all headers of the specified name will be returned. + * @return string|array the named header(s). If `$first` is true, a string will be returned; + * If `$first` is false, an array will be returned. + */ + public function get($name, $default = null, $first = true) + { + $name = strtolower($name); + if (isset($this->_headers[$name])) { + return $first ? reset($this->_headers[$name]) : $this->_headers[$name]; + } else { + return $default; + } + } + + /** + * Adds a new header. + * If there is already a header with the same name, it will be replaced. + * @param string $name the name of the header + * @param string $value the value of the header + */ + public function set($name, $value) + { + $name = strtolower($name); + $this->_headers[$name] = (array)$value; + } + + /** + * Adds a new header. + * If there is already a header with the same name, the new one will + * be appended to it instead of replacing it. + * @param string $name the name of the header + * @param string $value the value of the header + */ + public function add($name, $value) + { + $name = strtolower($name); + $this->_headers[$name][] = $value; + } + + /** + * Returns a value indicating whether the named header exists. + * @param string $name the name of the header + * @return boolean whether the named header exists + */ + public function has($name) + { + $name = strtolower($name); + return isset($this->_headers[$name]); + } + + /** + * Removes a header. + * @param string $name the name of the header to be removed. + * @return string the value of the removed header. Null is returned if the header does not exist. + */ + public function remove($name) + { + $name = strtolower($name); + if (isset($this->_headers[$name])) { + $value = $this->_headers[$name]; + unset($this->_headers[$name]); + return $value; + } else { + return null; + } + } + + /** + * Removes all headers. + */ + public function removeAll() + { + $this->_headers = array(); + } + + /** + * Returns the collection as a PHP array. + * @return array the array representation of the collection. + * The array keys are header names, and the array values are the corresponding header values. + */ + public function toArray() + { + return $this->_headers; + } + + /** + * Returns whether there is a header with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `isset($collection[$name])`. + * @param string $name the header name + * @return boolean whether the named header exists + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Returns the header with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$header = $collection[$name];`. + * This is equivalent to [[get()]]. + * @param string $name the header name + * @return string the header value with the specified name, null if the named header does not exist. + */ + public function offsetGet($name) + { + return $this->get($name); + } + + /** + * Adds the header to the collection. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$collection[$name] = $header;`. + * This is equivalent to [[add()]]. + * @param string $name the header name + * @param string $value the header value to be added + */ + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + /** + * Removes the named header. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `unset($collection[$name])`. + * This is equivalent to [[remove()]]. + * @param string $name the header name + */ + public function offsetUnset($name) + { + $this->remove($name); + } +} From 6d999ab3dc10ab5a8841b6337dd0ba6ac604e1ee Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 07:34:25 -0400 Subject: [PATCH 52/91] Response WIP --- framework/yii/web/Response.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index d37c66a..6801191 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -28,6 +28,21 @@ class Response extends \yii\base\Response */ public $ajaxRedirectCode = 278; + private $_headers; + + /** + * Returns the header collection. + * The header collection contains the currently registered HTTP headers. + * @return HeaderCollection the header collection + */ + public function getHeaders() + { + if ($this->_headers === null) { + $this->_headers = new HeaderCollection; + } + return $this->_headers; + } + /** * Sends a file to user. * @param string $fileName file name @@ -51,7 +66,7 @@ class Response extends \yii\base\Response if (isset($_SERVER['HTTP_RANGE'])) { // client sent us a multibyte range, can not hold this one for now - if (strpos($_SERVER['HTTP_RANGE'],',') !== false) { + if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) { header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); throw new HttpException(416, 'Requested Range Not Satisfiable'); } @@ -75,12 +90,12 @@ class Response extends \yii\base\Response * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ // End bytes can not be larger than $end. - $contentEnd = ($contentEnd > $fileSize) ? $fileSize -1 : $contentEnd; + $contentEnd = ($contentEnd > $fileSize) ? $fileSize - 1 : $contentEnd; // Validate the requested range and return an error if it's not correct. $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); - if ($wrongContentStart) { + if ($wrongContentStart) { header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); throw new HttpException(416, 'Requested Range Not Satisfiable'); } From 13eca9054a0e84d87f0ed7337842d2ea82a3e727 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 07:37:23 -0400 Subject: [PATCH 53/91] refactoring Request. --- framework/yii/web/Request.php | 16 ++++++++-------- framework/yii/web/UrlManager.php | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 9b1e91c..e73c3b1 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -94,7 +94,7 @@ class Request extends \yii\base\Request * Returns whether this is a POST request. * @return boolean whether this is a POST request. */ - public function getIsPostRequest() + public function getIsPost() { return $this->getMethod() === 'POST'; } @@ -103,7 +103,7 @@ class Request extends \yii\base\Request * Returns whether this is a DELETE request. * @return boolean whether this is a DELETE request. */ - public function getIsDeleteRequest() + public function getIsDelete() { return $this->getMethod() === 'DELETE'; } @@ -112,7 +112,7 @@ class Request extends \yii\base\Request * Returns whether this is a PUT request. * @return boolean whether this is a PUT request. */ - public function getIsPutRequest() + public function getIsPut() { return $this->getMethod() === 'PUT'; } @@ -121,7 +121,7 @@ class Request extends \yii\base\Request * Returns whether this is an AJAX (XMLHttpRequest) request. * @return boolean whether this is an AJAX (XMLHttpRequest) request. */ - public function getIsAjaxRequest() + public function getIsAjax() { return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'; } @@ -130,7 +130,7 @@ class Request extends \yii\base\Request * Returns whether this is an Adobe Flash or Flex request. * @return boolean whether this is an Adobe Flash or Adobe Flex request. */ - public function getIsFlashRequest() + public function getIsFlash() { return isset($_SERVER['HTTP_USER_AGENT']) && (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false); @@ -203,7 +203,7 @@ class Request extends \yii\base\Request * @return mixed the GET parameter value * @see getPost */ - public function getParam($name, $defaultValue = null) + public function get($name, $defaultValue = null) { return isset($_GET[$name]) ? $_GET[$name] : $defaultValue; } @@ -229,7 +229,7 @@ class Request extends \yii\base\Request */ public function getDelete($name, $defaultValue = null) { - return $this->getIsDeleteRequest() ? $this->getRestParam($name, $defaultValue) : null; + return $this->getIsDelete() ? $this->getRestParam($name, $defaultValue) : null; } /** @@ -240,7 +240,7 @@ class Request extends \yii\base\Request */ public function getPut($name, $defaultValue = null) { - return $this->getIsPutRequest() ? $this->getRestParam($name, $defaultValue) : null; + return $this->getIsPut() ? $this->getRestParam($name, $defaultValue) : null; } private $_hostInfo; diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index 44c63c5..4478a6c 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -196,7 +196,7 @@ class UrlManager extends Component return array($pathInfo, array()); } else { - $route = $request->getParam($this->routeVar); + $route = $request->get($this->routeVar); if (is_array($route)) { $route = ''; } From 0a9fffba8f4e45da23a4625fbc7c3f82729993ca Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 07:39:35 -0400 Subject: [PATCH 54/91] fixed test break. --- tests/unit/framework/helpers/ArrayHelperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index 84277d6..cfda9ae 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/tests/unit/framework/helpers/ArrayHelperTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\helpers\ArrayHelper; use yii\test\TestCase; -use yii\web\Sort; +use yii\data\Sort; class ArrayHelperTest extends TestCase { From bade6f7eefa3df69ae2c09998ef9eba0c2e399aa Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 07:51:55 -0400 Subject: [PATCH 55/91] minor fix about composer installer. --- extensions/composer/yii/composer/InstallHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/composer/yii/composer/InstallHandler.php b/extensions/composer/yii/composer/InstallHandler.php index 9e36a35..be4037b 100644 --- a/extensions/composer/yii/composer/InstallHandler.php +++ b/extensions/composer/yii/composer/InstallHandler.php @@ -82,7 +82,7 @@ class InstallHandler throw new Exception("Config file does not exist: $configFile"); } - require(__DIR__ . '/../../../yii2/yii/Yii.php'); + require_once(__DIR__ . '/../../../yii2/yii/Yii.php'); $application = new Application(require($configFile)); $request = $application->getRequest(); From 5a587d16f0b70d01b42bbb6d40a1528a50bb6eaa Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@ext4yii.com> Date: Thu, 6 Jun 2013 16:29:17 +0200 Subject: [PATCH 56/91] Fixed several formatting issues. Refactored == null to is_null == '' to empty(...) --- framework/yii/db/pgsql/PDO.php | 9 +++++---- framework/yii/db/pgsql/QueryBuilder.php | 3 ++- framework/yii/db/pgsql/Schema.php | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 67387dd..3f5996a 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -16,13 +16,14 @@ namespace yii\db\pgsql; * @author Gevik babakhani <gevikb@gmail.com> * @since 2.0 */ -class PDO extends \PDO { +class PDO extends \PDO +{ const OPT_SEARCH_PATH = 'search_path'; const OPT_DEFAULT_SCHEMA = 'default_schema'; const DEFAULT_SCHEMA = 'public'; - private $_currentDatabase = null; + private $_currentDatabase; /** * Returns value of the last inserted ID. @@ -53,11 +54,11 @@ class PDO extends \PDO { } if (isset($options[self::OPT_DEFAULT_SCHEMA])) { $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); - if ($schema !== '') { + if (!empty($schema)) { Schema::$DEFAULT_SCHEMA = $schema; } } - if (Schema::$DEFAULT_SCHEMA === null || Schema::$DEFAULT_SCHEMA === '') { + if (is_null(Schema::$DEFAULT_SCHEMA) || empty(Schema::$DEFAULT_SCHEMA)) { Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; } } diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 0cd9d94..f45c753 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -14,7 +14,8 @@ namespace yii\db\pgsql; * @author Gevik Babakhani <gevikb@gmail.com> * @since 2.0 */ -class QueryBuilder extends \yii\db\QueryBuilder { +class QueryBuilder extends \yii\db\QueryBuilder +{ /** * @var array mapping from abstract column types (keys) to physical column types (values). diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 1e19383..07238c4 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -18,7 +18,8 @@ use yii\db\ColumnSchema; * @author Gevik Babakhani <gevikb@gmail.com> * @since 2.0 */ -class Schema extends \yii\db\Schema { +class Schema extends \yii\db\Schema +{ /** * The default schema used for the current session. This value is From e5055664461b327c710ac3e4249deada2713ee01 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 19:51:22 +0200 Subject: [PATCH 57/91] Refactored types to be compatible with 1.1 --- framework/yii/db/pgsql/QueryBuilder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index f45c753..3417ad9 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -21,14 +21,14 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'bigserial not null primary key', + Schema::TYPE_PK => 'serial not null primary key', Schema::TYPE_STRING => 'varchar', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', Schema::TYPE_INTEGER => 'integer', Schema::TYPE_BIGINT => 'bigint', - Schema::TYPE_FLOAT => 'real', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_FLOAT => 'double precision', + Schema::TYPE_DECIMAL => 'numeric', Schema::TYPE_DATETIME => 'timestamp', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', From 2239dd733426146ff313bb03fb79539097cb8a83 Mon Sep 17 00:00:00 2001 From: Revin Roman <xgismox@gmail.com> Date: Thu, 6 Jun 2013 23:13:54 +0400 Subject: [PATCH 58/91] Fix new method name "getIsAjax" and fix web application class name in logger --- framework/yii/logging/ProfileTarget.php | 2 +- framework/yii/web/Response.php | 2 +- framework/yii/web/User.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/logging/ProfileTarget.php b/framework/yii/logging/ProfileTarget.php index f4522ac..be1d73d 100644 --- a/framework/yii/logging/ProfileTarget.php +++ b/framework/yii/logging/ProfileTarget.php @@ -70,7 +70,7 @@ class CProfileLogRoute extends CWebLogRoute public function processLogs($logs) { $app = \Yii::$app; - if (!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest()) + if (!($app instanceof \yii\web\Application) || $app->getRequest()->getIsAjax()) return; if ($this->getReport() === 'summary') diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 6801191..a8cdee7 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -258,7 +258,7 @@ class Response extends \yii\base\Response if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } - if (Yii::$app->getRequest()->getIsAjaxRequest()) { + if (Yii::$app->getRequest()->getIsAjax()) { $statusCode = $this->ajaxRedirectCode; } header('Location: ' . $url, true, $statusCode); diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 79665ae..5522bbb 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -280,7 +280,7 @@ class User extends Component public function loginRequired() { $request = Yii::$app->getRequest(); - if (!$request->getIsAjaxRequest()) { + if (!$request->getIsAjax()) { $this->setReturnUrl($request->getUrl()); } if ($this->loginUrl !== null) { From 9982d93369624ef06ad946bcf8d45409b9cbae3f Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 21:14:24 +0200 Subject: [PATCH 59/91] Removed commented and unused pgsql datatype mappings. --- framework/yii/db/pgsql/Schema.php | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 07238c4..97a3ef4 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -71,37 +71,6 @@ class Schema extends \yii\db\Schema 'bit varying' => self::TYPE_STRING, 'character varying' => self::TYPE_STRING, 'xml' => self::TYPE_STRING - - /* - * internal PG types - * 'aclitem' => self::TYPE_STRING, - * 'cid' => self::TYPE_STRING, - * 'daterange' => self::TYPE_STRING, - * 'gtsvector' => self::TYPE_STRING, - * 'int4range' => self::TYPE_STRING, //unknown - * 'lseg' => self::TYPE_STRING, - * 'int8range' => self::TYPE_STRING, // unknown - * 'pg_node_tree' => self::TYPE_STRING, - * 'refcursor' => self::TYPE_STRING, - * 'regclass' => self::TYPE_STRING, - * 'regconfig' => self::TYPE_STRING, - * 'regdictionary' => self::TYPE_STRING, - * 'regoper' => self::TYPE_STRING, - * 'regoperator' => self::TYPE_STRING, - * 'regproc' => self::TYPE_STRING, - * 'regprocedure' => self::TYPE_STRING, - * 'regtype' => self::TYPE_STRING, - * 'reltime' => self::TYPE_STRING, - * 'smgr' => self::TYPE_STRING, - * 'tid' => self::TYPE_STRING, - * 'xid' => self::TYPE_STRING, - * 'tinterval' => self::TYPE_STRING, - * 'tsquery' => self::TYPE_STRING, - * 'tsrange' => self::TYPE_STRING, - * 'tstzrange' => self::TYPE_STRING, - * 'tsvector' => self::TYPE_STRING, - * 'txid_snapshot' => self::TYPE_STRING - */ ); /** From 0fd390cac555b00149d1c000aea7796edd47d0f6 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 21:19:17 +0200 Subject: [PATCH 60/91] Replaces spaces with tabs. --- tests/unit/data/config.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index f1c7bc0..1b40513 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -18,14 +18,14 @@ return array( 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', ), - 'pgsql' => array( + 'pgsql' => array( 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', 'attributes' => array( - 'search_path' => 'master,hello' + 'search_path' => 'master,hello' ), 'fixture' => __DIR__ . '/postgres.sql', - ) + ) ) ); From 4ed28a9aee0d26a499d22722e8ddedea8a1fe725 Mon Sep 17 00:00:00 2001 From: gsd <sergey.gonimar@gmail.com> Date: Fri, 7 Jun 2013 01:54:20 +0600 Subject: [PATCH 61/91] Update CompareValidator.php --- framework/yii/validators/CompareValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/validators/CompareValidator.php b/framework/yii/validators/CompareValidator.php index b8e8a50..c94577c 100644 --- a/framework/yii/validators/CompareValidator.php +++ b/framework/yii/validators/CompareValidator.php @@ -58,7 +58,7 @@ class CompareValidator extends Validator * - `<`: 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 or equal to the value being compared with. */ - public $operator = '='; + public $operator = '=='; /** * @var string the user-defined error message. It may contain the following placeholders which * will be replaced accordingly by the validator: From b5513c84a0d353c1b1067f51ad3de674967854b3 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 22:19:03 +0200 Subject: [PATCH 62/91] Added db creation for travis CI This needs to be done in two seperate command since postgres does not accept db creation in multiline statement. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e4b8278..b7ea5ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,10 @@ php: env: - DB=mysql + - DB=postgres before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'drop database if exists yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database yiitest;'; fi" script: phpunit \ No newline at end of file From 8c36cd08db587724c9568bddfac1f2ef4f13cb9f Mon Sep 17 00:00:00 2001 From: yiidevelop <yiidevelop@hotmail.com> Date: Fri, 7 Jun 2013 03:26:36 +0700 Subject: [PATCH 63/91] Fix use incorrect namespace of Html helpers --- framework/yii/web/UploadedFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/UploadedFile.php b/framework/yii/web/UploadedFile.php index 6e685a3..a1cd735 100644 --- a/framework/yii/web/UploadedFile.php +++ b/framework/yii/web/UploadedFile.php @@ -7,7 +7,7 @@ namespace yii\web; -use yii\widgets\Html; +use yii\helpers\Html; /** * @author Qiang Xue <qiang.xue@gmail.com> From dbe84acfddb16e3fee5290204cd4ea0fdf6e2ca0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 22:26:43 +0200 Subject: [PATCH 64/91] Added missing postgres user to the travis config. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7ea5ce..8dff4a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,6 @@ env: before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'drop database if exists yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'drop database if exists yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'create database yiitest;'; fi" script: phpunit \ No newline at end of file From 1c92e4c3e57a164f2a24315c4ad0ab146b649dd9 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 22:45:30 +0200 Subject: [PATCH 65/91] Force travis to create a postgresql test database --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dff4a3..da09923 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,6 @@ env: before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'drop database if exists yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'create database yiitest;'; fi" + - psql -U postgres -c 'drop database if exists yiitest;'; + - psql -U postgres -c 'create database yiitest;'; script: phpunit \ No newline at end of file From e45e061cc53c326cd3abc9fc8929e5f83bb31159 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 22:52:28 +0200 Subject: [PATCH 66/91] Removed conditional pgsql env --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index da09923..01abd50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ php: env: - DB=mysql - - DB=postgres before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" From 8b61ab7f71a4c355540cfa680a19b77e10076761 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Thu, 6 Jun 2013 22:59:46 +0200 Subject: [PATCH 67/91] Force travis to create the pgsql test db --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dff4a3..01abd50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,9 @@ php: env: - DB=mysql - - DB=postgres before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'drop database if exists yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'create database yiitest;'; fi" + - psql -U postgres -c 'drop database if exists yiitest;'; + - psql -U postgres -c 'create database yiitest;'; script: phpunit \ No newline at end of file From bf05ee2942f3e8ab4f736d5b25bfc189a3b3e666 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 6 Jun 2013 17:10:22 -0400 Subject: [PATCH 68/91] Response WIP. --- framework/yii/logging/ProfileTarget.php | 2 +- framework/yii/web/Response.php | 147 +++++++++++++++++++++++++++++++- framework/yii/web/User.php | 2 +- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/framework/yii/logging/ProfileTarget.php b/framework/yii/logging/ProfileTarget.php index f4522ac..d67762d 100644 --- a/framework/yii/logging/ProfileTarget.php +++ b/framework/yii/logging/ProfileTarget.php @@ -70,7 +70,7 @@ class CProfileLogRoute extends CWebLogRoute public function processLogs($logs) { $app = \Yii::$app; - if (!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest()) + if (!($app instanceof CWebApplication) || $app->getRequest()->getIsAjax()) return; if ($this->getReport() === 'summary') diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 6801191..ea1d57e 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -8,9 +8,11 @@ namespace yii\web; use Yii; +use XMLWriter; use yii\base\HttpException; use yii\helpers\FileHelper; use yii\helpers\Html; +use yii\helpers\Json; use yii\helpers\StringHelper; /** @@ -27,7 +29,13 @@ class Response extends \yii\base\Response * @see redirect */ public $ajaxRedirectCode = 278; - + /** + * @var string + */ + public $content; + /** + * @var HeaderCollection + */ private $_headers; /** @@ -44,6 +52,141 @@ class Response extends \yii\base\Response } /** + * Returns the current cache control setting as a string like sent in a header. + * @return string the cache control setting, or null if there is no such header specified + */ + public function getCacheControl() + { + return $this->getHeaders()->get('Cache-Control'); + } + + /** + * Sets the current cache control setting to be sent + * @param string $value the cache control header value + */ + public function setCacheControl($value) + { + $this->getHeaders()->set('Cache-Control', $value); + } + + /** + * Gets the ETag header to be sent + * @return string the ETag header, or false if none is set + */ + public function getEtag() + { + return $this->getHeaders()->get("ETag"); + } + + /** + * Sets the ETag header to be sent + * @param string $value the ETag header + */ + public function setEtag($value) + { + $this->getHeaders()->set("ETag", $value); + } + + /** + * Gets the last modified header to send + * @return string the last modified header, or null if none is set + */ + public function getLastModified() + { + return $this->getHeaders()->get("Last-Modified"); + } + + /** + * Sets the last modified header to send + * @param integer $value the unix time of the last modified date + */ + public function setLastModified($value) + { + $this->getHeaders()->set("Last-Modified", $value); + } + + /** + * Gets the content type header to send + * @return string the content type header, or null if none is set + */ + public function getContentType() + { + return $this->getHeaders()->get("Content-type"); + } + + /** + * Sets the content type header to send + * @param string $value the content type header + */ + public function setContentType($value) + { + $this->getHeaders()->set("Content-type", $value); + } + + /** + * Gets the content disposition header to send + * @return string the content disposition, or null if none is set + */ + public function getContentDisposition() + { + return $this->getHeaders()->get("Content-Disposition"); + } + + /** + * Sets the content disposition header to send + * @param string $contentDisposition the content disposition header + */ + public function setContentDisposition($contentDisposition) + { + $this->getHeaders()->set("Content-Disposition", $contentDisposition); + } + + public function renderJson($data) + { + $this->setContentType("application/json"); + $this->content = Json::encode($data); + } + + public function renderJsonp($callbackName, $data) + { + $this->setContentType("application/json"); + $data = Json::encode($data); + $this->content = "$callbackName($data)"; + } + + /** + * Sends the response to the client. + * @return boolean true if the response was sent + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + } + + /** + * Sends the response headers to the client + */ + protected function sendHeaders() + { + foreach ($this->_headers as $name => $values) { + foreach ($values as $value) { + header("$name: $value"); + } + } + $this->_headers->removeAll(); + } + + /** + * Sends the response content to the client + */ + protected function sendContent() + { + echo $this->content; + $this->content = null; + } + + /** * Sends a file to user. * @param string $fileName file name * @param string $content content to be set. @@ -258,7 +401,7 @@ class Response extends \yii\base\Response if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } - if (Yii::$app->getRequest()->getIsAjaxRequest()) { + if (Yii::$app->getRequest()->getIsAjax()) { $statusCode = $this->ajaxRedirectCode; } header('Location: ' . $url, true, $statusCode); diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 79665ae..5522bbb 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -280,7 +280,7 @@ class User extends Component public function loginRequired() { $request = Yii::$app->getRequest(); - if (!$request->getIsAjaxRequest()) { + if (!$request->getIsAjax()) { $this->setReturnUrl($request->getUrl()); } if ($this->loginUrl !== null) { From ba0da059da109c26a8ef7ca965ac2a7b9db13af1 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 08:00:47 -0400 Subject: [PATCH 69/91] response WIP --- framework/yii/base/HttpException.php | 65 +---------- framework/yii/web/Response.php | 216 ++++++++++++++++++++++++++++++++--- 2 files changed, 207 insertions(+), 74 deletions(-) diff --git a/framework/yii/base/HttpException.php b/framework/yii/base/HttpException.php index 4d63764..b1478b0 100644 --- a/framework/yii/base/HttpException.php +++ b/framework/yii/base/HttpException.php @@ -7,6 +7,9 @@ namespace yii\base; +use yii\web\Response; + + /** * HttpException represents an exception caused by an improper request of the end-user. * @@ -42,66 +45,8 @@ class HttpException extends UserException */ public function getName() { - static $httpCodes = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - 118 => 'Connection timed out', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 207 => 'Multi-Status', - 210 => 'Content Different', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - 310 => 'Too many Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Time-out', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested range unsatisfiable', - 417 => 'Expectation failed', - 418 => 'I’m a teapot', - 422 => 'Unprocessable entity', - 423 => 'Locked', - 424 => 'Method failure', - 425 => 'Unordered Collection', - 426 => 'Upgrade Required', - 449 => 'Retry With', - 450 => 'Blocked by Windows Parental Controls', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway ou Proxy Error', - 503 => 'Service Unavailable', - 504 => 'Gateway Time-out', - 505 => 'HTTP Version not supported', - 507 => 'Insufficient storage', - 509 => 'Bandwidth Limit Exceeded', - ); - - if (isset($httpCodes[$this->statusCode])) { - return $httpCodes[$this->statusCode]; + if (isset(Response::$statusTexts[$this->statusCode])) { + return Response::$statusTexts[$this->statusCode]; } else { return 'Error'; } diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index ea1d57e..93c031e 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -8,8 +8,8 @@ namespace yii\web; use Yii; -use XMLWriter; use yii\base\HttpException; +use yii\base\InvalidParamException; use yii\helpers\FileHelper; use yii\helpers\Html; use yii\helpers\Json; @@ -34,10 +34,117 @@ class Response extends \yii\base\Response */ public $content; /** + * @var string + */ + public $statusText; + /** + * @var string the charset to use. If not set, [[\yii\base\Application::charset]] will be used. + */ + public $charset; + /** + * @var string the version of the HTTP protocol to use + */ + public $version = '1.0'; + + /** + * @var array list of HTTP status codes and the corresponding texts + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 118 => 'Connection timed out', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 210 => 'Content Different', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 310 => 'Too many Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested range unsatisfiable', + 417 => 'Expectation failed', + 418 => 'I’m a teapot', + 422 => 'Unprocessable entity', + 423 => 'Locked', + 424 => 'Method failure', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 449 => 'Retry With', + 450 => 'Blocked by Windows Parental Controls', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway ou Proxy Error', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 507 => 'Insufficient storage', + 508 => 'Loop Detected', + 509 => 'Bandwidth Limit Exceeded', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + private $_statusCode = 200; + /** * @var HeaderCollection */ private $_headers; + + public function init() + { + if ($this->charset === null) { + $this->charset = Yii::$app->charset; + } + } + + public function getStatusCode() + { + return $this->_statusCode; + } + + public function setStatusCode($value) + { + $this->_statusCode = (int)$value; + if ($this->isInvalid()) { + throw new InvalidParamException("The HTTP status code is invalid: $value"); + } + $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : 'Error'; + } + /** * Returns the header collection. * The header collection contains the currently registered HTTP headers. @@ -75,7 +182,7 @@ class Response extends \yii\base\Response */ public function getEtag() { - return $this->getHeaders()->get("ETag"); + return $this->getHeaders()->get('ETag'); } /** @@ -84,7 +191,7 @@ class Response extends \yii\base\Response */ public function setEtag($value) { - $this->getHeaders()->set("ETag", $value); + $this->getHeaders()->set('ETag', $value); } /** @@ -93,7 +200,7 @@ class Response extends \yii\base\Response */ public function getLastModified() { - return $this->getHeaders()->get("Last-Modified"); + return $this->getHeaders()->get('Last-Modified'); } /** @@ -102,7 +209,7 @@ class Response extends \yii\base\Response */ public function setLastModified($value) { - $this->getHeaders()->set("Last-Modified", $value); + $this->getHeaders()->set('Last-Modified', $value); } /** @@ -111,7 +218,7 @@ class Response extends \yii\base\Response */ public function getContentType() { - return $this->getHeaders()->get("Content-type"); + return $this->getHeaders()->get('Content-type'); } /** @@ -120,7 +227,7 @@ class Response extends \yii\base\Response */ public function setContentType($value) { - $this->getHeaders()->set("Content-type", $value); + $this->getHeaders()->set('Content-type', $value); } /** @@ -129,29 +236,29 @@ class Response extends \yii\base\Response */ public function getContentDisposition() { - return $this->getHeaders()->get("Content-Disposition"); + return $this->getHeaders()->get('Content-Disposition'); } /** * Sets the content disposition header to send - * @param string $contentDisposition the content disposition header + * @param string $value the content disposition header */ - public function setContentDisposition($contentDisposition) + public function setContentDisposition($value) { - $this->getHeaders()->set("Content-Disposition", $contentDisposition); + $this->getHeaders()->set('Content-Disposition', $value); } public function renderJson($data) { - $this->setContentType("application/json"); + $this->setContentType('application/json'); $this->content = Json::encode($data); } public function renderJsonp($callbackName, $data) { - $this->setContentType("application/json"); + $this->setContentType('text/javascript'); $data = Json::encode($data); - $this->content = "$callbackName($data)"; + $this->content = "$callbackName($data);"; } /** @@ -169,6 +276,7 @@ class Response extends \yii\base\Response */ protected function sendHeaders() { + header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}"); foreach ($this->_headers as $name => $values) { foreach ($values as $value) { header("$name: $value"); @@ -446,4 +554,84 @@ class Response extends \yii\base\Response { return Yii::$app->getRequest()->getCookies(); } + + /** + * @return boolean whether this response has a valid [[statusCode]]. + */ + public function isInvalid() + { + return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600; + } + + /** + * @return boolean whether this response is informational + */ + public function isInformational() + { + return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; + } + + /** + * @return boolean whether this response is successfully + */ + public function isSuccessful() + { + return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; + } + + /** + * @return boolean whether this response is a redirection + */ + public function isRedirection() + { + return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; + } + + /** + * @return boolean whether this response indicates a client error + */ + public function isClientError() + { + return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; + } + + /** + * @return boolean whether this response indicates a server error + */ + public function isServerError() + { + return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; + } + + /** + * @return boolean whether this response is OK + */ + public function isOk() + { + return 200 === $this->getStatusCode(); + } + + /** + * @return boolean whether this response indicates the current request is forbidden + */ + public function isForbidden() + { + return 403 === $this->getStatusCode(); + } + + /** + * @return boolean whether this response indicates the currently requested resource is not found + */ + public function isNotFound() + { + return 404 === $this->getStatusCode(); + } + + /** + * @return boolean whether this response is empty + */ + public function isEmpty() + { + return in_array($this->getStatusCode(), array(201, 204, 304)); + } } From b455f2c97afc405387bff929ddcb972d6c48a366 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 08:08:40 -0400 Subject: [PATCH 70/91] Fixed test break. --- framework/yii/base/HttpException.php | 6 ++---- tests/unit/framework/web/ResponseTest.php | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/yii/base/HttpException.php b/framework/yii/base/HttpException.php index b1478b0..cce0bb0 100644 --- a/framework/yii/base/HttpException.php +++ b/framework/yii/base/HttpException.php @@ -7,8 +7,6 @@ namespace yii\base; -use yii\web\Response; - /** * HttpException represents an exception caused by an improper request of the end-user. @@ -45,8 +43,8 @@ class HttpException extends UserException */ public function getName() { - if (isset(Response::$statusTexts[$this->statusCode])) { - return Response::$statusTexts[$this->statusCode]; + if (isset(\yii\web\Response::$statusTexts[$this->statusCode])) { + return \yii\web\Response::$statusTexts[$this->statusCode]; } else { return 'Error'; } diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php index 2fde63d..5da9b8c 100644 --- a/tests/unit/framework/web/ResponseTest.php +++ b/tests/unit/framework/web/ResponseTest.php @@ -32,6 +32,7 @@ class ResponseTest extends \yiiunit\TestCase protected function setUp() { parent::setUp(); + $this->mockApplication(); $this->reset(); } From b14bb52cddec33cf4f1f8bc8fc2ae387689f6ba1 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 08:47:22 -0400 Subject: [PATCH 71/91] Fixed status text. --- framework/yii/web/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 93c031e..12c3f34 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -142,7 +142,7 @@ class Response extends \yii\base\Response if ($this->isInvalid()) { throw new InvalidParamException("The HTTP status code is invalid: $value"); } - $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : 'Error'; + $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : ''; } /** From a08399bcc5cc98743aae26540307efd35059555a Mon Sep 17 00:00:00 2001 From: resurtm <resurtm@gmail.com> Date: Fri, 7 Jun 2013 20:56:02 +0600 Subject: [PATCH 72/91] Yii::t() minor fix. --- framework/yii/YiiBase.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php index 4fe0e77..c8eaf7a 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/YiiBase.php @@ -606,7 +606,10 @@ class YiiBase public static function t($category, $message, $params = array(), $language = null) { if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language); + if ($language === null) { + $language = self::$app->language; + } + return self::$app->getI18N()->translate($category, $message, $language); } else { return is_array($params) ? strtr($message, $params) : $message; } From f4cc4a8bd72ca8b3ff3d2c43c79f28b2243edd75 Mon Sep 17 00:00:00 2001 From: resurtm <resurtm@gmail.com> Date: Fri, 7 Jun 2013 20:58:58 +0600 Subject: [PATCH 73/91] Fixes #13. Implement DB message source. --- framework/yii/i18n/DbMessageSource.php | 151 +++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 framework/yii/i18n/DbMessageSource.php diff --git a/framework/yii/i18n/DbMessageSource.php b/framework/yii/i18n/DbMessageSource.php new file mode 100644 index 0000000..831cabb --- /dev/null +++ b/framework/yii/i18n/DbMessageSource.php @@ -0,0 +1,151 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\i18n; + +use Yii; +use yii\base\InvalidConfigException; +use yii\helpers\ArrayHelper; +use yii\caching\Cache; +use yii\db\Connection; +use yii\db\Query; + +/** + * DbMessageSource extends [[MessageSource]] and represents a message source that stores translated + * messages in database. + * + * The database must contain the following two tables: + * + * ~~~ + * CREATE TABLE tbl_source_message ( + * id INTEGER PRIMARY KEY, + * category VARCHAR(32), + * message TEXT + * ); + * + * CREATE TABLE tbl_message ( + * id INTEGER, + * language VARCHAR(16), + * translation TEXT, + * PRIMARY KEY (id, language), + * CONSTRAINT fk_message_source_message FOREIGN KEY (id) + * REFERENCES tbl_source_message (id) ON DELETE CASCADE ON UPDATE RESTRICT + * ); + * ~~~ + * + * The `tbl_source_message` table stores the messages to be translated, and the `tbl_message` table stores + * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]] + * and [[messageTable]], respectively. + * + * When [[cachingDuration]] is set as a positive number, message translations will be cached. + * + * @author resurtm <resurtm@gmail.com> + * @since 2.0 + */ +class DbMessageSource extends MessageSource +{ + /** + * Prefix which would be used when generating cache key. + */ + const CACHE_KEY_PREFIX = 'DbMessageSource'; + + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbMessageSource object is created, if you want to change this property, you should only assign + * it with a DB connection object. + */ + public $db = 'db'; + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * The messages data will be cached using this cache object. Note, this property has meaning only + * in case [[cachingDuration]] set to non-zero value. + * After the DbMessageSource object is created, if you want to change this property, you should only assign + * it with a cache object. + */ + public $cache = 'cache'; + /** + * @var string the name of the source message table. Defaults to `{{%source_message}}`. + */ + public $sourceMessageTable = '{{%source_message}}'; + /** + * @var string the name of the translated message table. Defaults to `{{%message}}`. + */ + public $messageTable = '{{%message}}'; + /** + * @var integer the time in seconds that the messages can remain valid in cache. + * Defaults to 0, meaning the caching is disabled. + */ + public $cachingDuration = 0; + + /** + * Initializes the DbMessageSource component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * Configured [[cache]] component would also be initialized. + * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid. + */ + public function init() + { + parent::init(); + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("DbMessageSource::db must be either a DB connection instance or the application component ID of a DB connection."); + } + if ($this->cachingDuration > 0) { + if (is_string($this->cache)) { + $this->cache = Yii::$app->getComponent($this->cache); + } + if (!$this->cache instanceof Cache) { + throw new InvalidConfigException("DbMessageSource::cache must be either a cache object or the application component ID of the cache object."); + } + } + } + + /** + * Loads the message translation for the specified language and category. + * Child classes should override this method to return the message translations of + * the specified language and category. + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages. The keys are original messages, and the values + * are translated messages. + */ + protected function loadMessages($category, $language) + { + if ($this->cachingDuration === 0) { + return $this->retrieveMessages($category, $language); + } else { + $key = static::CACHE_KEY_PREFIX . ".messages.{$category}.{$language}"; + $messages = $this->cache->get($key); + if ($messages === false) { + $messages = $this->retrieveMessages($category, $language); + $this->cache->set($key, $messages, $this->cachingDuration); + } + return $messages; + } + } + + /** + * Loads the messages from database. + * You may override this method to customize the message storage in the database. + * @param string $category the message category. + * @param string $language the target language. + * @return array the messages loaded from database. + */ + protected function retrieveMessages($category, $language) + { + $query = new Query(); + $messages = $query->select(array('t1.message message', 't2.translation translation')) + ->from(array($this->sourceMessageTable . ' t1', $this->messageTable . ' t2')) + ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :language') + ->params(array(':category' => $category, ':language' => $language)) + ->createCommand($this->db) + ->queryAll(); + return ArrayHelper::map($messages, 'message', 'translation'); + } +} From 29ac3aeea6ace6a1947f0e0226b9d751ed157c5f Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 11:02:26 -0400 Subject: [PATCH 74/91] Added Jsonable support. --- framework/yii/YiiBase.php | 25 ++++++++++++++++++++++ framework/yii/base/Jsonable.php | 22 +++++++++++++++++++ framework/yii/base/Model.php | 13 ++++++++++- framework/yii/base/Object.php | 19 +++++++++++++--- .../yii/console/controllers/AssetController.php | 1 - framework/yii/helpers/base/Json.php | 20 ++++++++++------- 6 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 framework/yii/base/Jsonable.php diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php index 4fe0e77..6114ced 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/YiiBase.php @@ -611,6 +611,31 @@ class YiiBase return is_array($params) ? strtr($message, $params) : $message; } } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } } YiiBase::$aliases = array( diff --git a/framework/yii/base/Jsonable.php b/framework/yii/base/Jsonable.php new file mode 100644 index 0000000..4a3f7d9 --- /dev/null +++ b/framework/yii/base/Jsonable.php @@ -0,0 +1,22 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\base; + +/** + * Jsonable should be implemented by classes that need to be represented in JSON format. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +interface Jsonable +{ + /** + * @return string the JSON representation of this object + */ + function toJson(); +} diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index c7432f5..b9b7d2d 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -10,6 +10,7 @@ namespace yii\base; use ArrayObject; use ArrayIterator; use yii\helpers\Inflector; +use yii\helpers\Json; use yii\validators\RequiredValidator; use yii\validators\Validator; @@ -41,7 +42,7 @@ use yii\validators\Validator; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Model extends Component implements \IteratorAggregate, \ArrayAccess +class Model extends Component implements \IteratorAggregate, \ArrayAccess, Jsonable { /** * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set @@ -638,6 +639,16 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess } /** + * Returns the JSON representation of this object. + * The default implementation will return [[attributes]]. + * @return string the JSON representation of this object. + */ + public function toJson() + { + return Json::encode($this->getAttributes()); + } + + /** * Returns an iterator for traversing the attributes in the model. * This method is required by the interface IteratorAggregate. * @return ArrayIterator an iterator for traversing the items in the list. diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index a90a231..79c1f7b 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -7,12 +7,15 @@ namespace yii\base; +use Yii; +use yii\helpers\Json; + /** * @include @yii/base/Object.md * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Object +class Object implements Jsonable { /** * @return string the fully qualified name of this class. @@ -38,8 +41,8 @@ class Object */ public function __construct($config = array()) { - foreach ($config as $name => $value) { - $this->$name = $value; + if (!empty($config)) { + Yii::configure($this, $config); } $this->init(); } @@ -216,4 +219,14 @@ class Object { return method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name); } + + /** + * Returns the JSON representation of this object. + * The default implementation will return all public member variables. + * @return string the JSON representation of this object. + */ + public function toJson() + { + return Json::encode(Yii::getObjectVars($this)); + } } diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 4c759de..cd8710e 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -164,7 +164,6 @@ class AssetController extends Controller protected function loadConfiguration($configFile) { echo "Loading configuration from '{$configFile}'...\n"; - foreach (require($configFile) as $name => $value) { if (property_exists($this, $name) || $this->canSetProperty($name)) { $this->$name = $value; diff --git a/framework/yii/helpers/base/Json.php b/framework/yii/helpers/base/Json.php index 8de55f9..41d5bf0 100644 --- a/framework/yii/helpers/base/Json.php +++ b/framework/yii/helpers/base/Json.php @@ -8,6 +8,7 @@ namespace yii\helpers\base; use yii\base\InvalidParamException; +use yii\base\Jsonable; use yii\web\JsExpression; /** @@ -90,16 +91,19 @@ class Json $token = '!{[' . count($expressions) . ']}!'; $expressions['"' . $token . '"'] = $data->expression; return $token; - } - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions); - } else { - $result[$key] = $value; + } elseif ($data instanceof Jsonable) { + return $data->toJson(); + } else { + $result = array(); + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $result[$key] = static::processData($value, $expressions); + } else { + $result[$key] = $value; + } } + return $result; } - return $result; } else { return $data; } From 6c7672c562b39d3a6cabe45655e67f35c196284a Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 11:08:15 -0400 Subject: [PATCH 75/91] Minor refactoring of t(). --- framework/yii/YiiBase.php | 5 +---- framework/yii/i18n/I18N.php | 9 ++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php index d7b2bd6..ee75822 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/YiiBase.php @@ -606,10 +606,7 @@ class YiiBase public static function t($category, $message, $params = array(), $language = null) { if (self::$app !== null) { - if ($language === null) { - $language = self::$app->language; - } - return self::$app->getI18N()->translate($category, $message, $language); + return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); } else { return is_array($params) ? strtr($message, $params) : $message; } diff --git a/framework/yii/i18n/I18N.php b/framework/yii/i18n/I18N.php index a358683..b929f49 100644 --- a/framework/yii/i18n/I18N.php +++ b/framework/yii/i18n/I18N.php @@ -78,16 +78,11 @@ class I18N extends Component * @param string $category the message category. * @param string $message the message to be translated. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current - * [[\yii\base\Application::language|application language]] will be used. + * @param string $language the language code (e.g. `en_US`, `en`). * @return string the translated message. */ - public function translate($category, $message, $params = array(), $language = null) + public function translate($category, $message, $params, $language) { - if ($language === null) { - $language = Yii::$app->language; - } - $message = $this->getMessageSource($category)->translate($category, $message, $language); if (!is_array($params)) { From 3833d62f00f0e0b5809056eb4349bfc65e18c4a2 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 11:31:31 -0400 Subject: [PATCH 76/91] Response WIP --- framework/yii/web/Response.php | 98 ++---------------------------------------- 1 file changed, 4 insertions(+), 94 deletions(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 12c3f34..86978d5 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -158,105 +158,15 @@ class Response extends \yii\base\Response return $this->_headers; } - /** - * Returns the current cache control setting as a string like sent in a header. - * @return string the cache control setting, or null if there is no such header specified - */ - public function getCacheControl() - { - return $this->getHeaders()->get('Cache-Control'); - } - - /** - * Sets the current cache control setting to be sent - * @param string $value the cache control header value - */ - public function setCacheControl($value) - { - $this->getHeaders()->set('Cache-Control', $value); - } - - /** - * Gets the ETag header to be sent - * @return string the ETag header, or false if none is set - */ - public function getEtag() - { - return $this->getHeaders()->get('ETag'); - } - - /** - * Sets the ETag header to be sent - * @param string $value the ETag header - */ - public function setEtag($value) - { - $this->getHeaders()->set('ETag', $value); - } - - /** - * Gets the last modified header to send - * @return string the last modified header, or null if none is set - */ - public function getLastModified() - { - return $this->getHeaders()->get('Last-Modified'); - } - - /** - * Sets the last modified header to send - * @param integer $value the unix time of the last modified date - */ - public function setLastModified($value) - { - $this->getHeaders()->set('Last-Modified', $value); - } - - /** - * Gets the content type header to send - * @return string the content type header, or null if none is set - */ - public function getContentType() - { - return $this->getHeaders()->get('Content-type'); - } - - /** - * Sets the content type header to send - * @param string $value the content type header - */ - public function setContentType($value) - { - $this->getHeaders()->set('Content-type', $value); - } - - /** - * Gets the content disposition header to send - * @return string the content disposition, or null if none is set - */ - public function getContentDisposition() - { - return $this->getHeaders()->get('Content-Disposition'); - } - - /** - * Sets the content disposition header to send - * @param string $value the content disposition header - */ - public function setContentDisposition($value) - { - $this->getHeaders()->set('Content-Disposition', $value); - } - public function renderJson($data) { - $this->setContentType('application/json'); + $this->getHeaders()->set('content-type', 'application/json'); $this->content = Json::encode($data); } - public function renderJsonp($callbackName, $data) + public function renderJsonp($data, $callbackName) { - $this->setContentType('text/javascript'); + $this->getHeaders()->set('content-type', 'text/javascript'); $data = Json::encode($data); $this->content = "$callbackName($data);"; } @@ -452,7 +362,7 @@ class Response extends \yii\base\Response } if (!isset($options['mimeType'])) { - if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) { + if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath)) === null) { $options['mimeType'] = 'text/plain'; } } From d48ef4198ebd5e5107335091e94f4910384c3af0 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Fri, 7 Jun 2013 11:47:50 -0400 Subject: [PATCH 77/91] Minor refactoring of DbMessageSource. --- framework/yii/i18n/DbMessageSource.php | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/framework/yii/i18n/DbMessageSource.php b/framework/yii/i18n/DbMessageSource.php index 831cabb..14cf0b9 100644 --- a/framework/yii/i18n/DbMessageSource.php +++ b/framework/yii/i18n/DbMessageSource.php @@ -41,8 +41,6 @@ use yii\db\Query; * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]] * and [[messageTable]], respectively. * - * When [[cachingDuration]] is set as a positive number, message translations will be cached. - * * @author resurtm <resurtm@gmail.com> * @since 2.0 */ @@ -68,18 +66,23 @@ class DbMessageSource extends MessageSource */ public $cache = 'cache'; /** - * @var string the name of the source message table. Defaults to `{{%source_message}}`. + * @var string the name of the source message table. */ - public $sourceMessageTable = '{{%source_message}}'; + public $sourceMessageTable = 'tbl_source_message'; /** - * @var string the name of the translated message table. Defaults to `{{%message}}`. + * @var string the name of the translated message table. */ - public $messageTable = '{{%message}}'; + public $messageTable = 'tbl_message'; /** * @var integer the time in seconds that the messages can remain valid in cache. - * Defaults to 0, meaning the caching is disabled. + * Use 0 to indicate that the cached data will never expire. + * @see enableCaching */ public $cachingDuration = 0; + /** + * @var boolean whether to enable caching translated messages + */ + public $enableCaching = false; /** * Initializes the DbMessageSource component. @@ -96,7 +99,7 @@ class DbMessageSource extends MessageSource if (!$this->db instanceof Connection) { throw new InvalidConfigException("DbMessageSource::db must be either a DB connection instance or the application component ID of a DB connection."); } - if ($this->cachingDuration > 0) { + if ($this->enableCaching) { if (is_string($this->cache)) { $this->cache = Yii::$app->getComponent($this->cache); } @@ -117,16 +120,20 @@ class DbMessageSource extends MessageSource */ protected function loadMessages($category, $language) { - if ($this->cachingDuration === 0) { - return $this->retrieveMessages($category, $language); - } else { - $key = static::CACHE_KEY_PREFIX . ".messages.{$category}.{$language}"; + if ($this->enableCaching) { + $key = array( + __CLASS__, + $category, + $language, + ); $messages = $this->cache->get($key); if ($messages === false) { - $messages = $this->retrieveMessages($category, $language); + $messages = $this->loadMessagesFromDb($category, $language); $this->cache->set($key, $messages, $this->cachingDuration); } return $messages; + } else { + return $this->loadMessagesFromDb($category, $language); } } @@ -137,7 +144,7 @@ class DbMessageSource extends MessageSource * @param string $language the target language. * @return array the messages loaded from database. */ - protected function retrieveMessages($category, $language) + protected function loadMessagesFromDb($category, $language) { $query = new Query(); $messages = $query->select(array('t1.message message', 't2.translation translation')) From 7a7d2a9c06c099de8064729ca3fc95fb24241b75 Mon Sep 17 00:00:00 2001 From: resurtm <resurtm@gmail.com> Date: Fri, 7 Jun 2013 22:41:11 +0600 Subject: [PATCH 78/91] CS fixes. --- framework/yii/base/ErrorHandler.php | 2 +- framework/yii/base/Formatter.php | 1 - framework/yii/base/InvalidCallException.php | 1 - framework/yii/base/InvalidConfigException.php | 1 - framework/yii/base/InvalidParamException.php | 1 - framework/yii/base/InvalidRouteException.php | 1 - framework/yii/base/Jsonable.php | 2 +- framework/yii/base/Module.php | 1334 ++++++++++---------- framework/yii/base/NotSupportedException.php | 1 - framework/yii/base/Theme.php | 2 +- framework/yii/base/UnknownClassException.php | 1 - framework/yii/base/UnknownMethodException.php | 1 - framework/yii/base/UnknownPropertyException.php | 1 - framework/yii/bootstrap/Button.php | 3 +- framework/yii/bootstrap/ButtonDropdown.php | 2 +- framework/yii/bootstrap/ButtonGroup.php | 1 - framework/yii/bootstrap/Collapse.php | 2 +- framework/yii/bootstrap/Dropdown.php | 1 - framework/yii/bootstrap/Nav.php | 2 +- framework/yii/bootstrap/Progress.php | 1 - framework/yii/bootstrap/Widget.php | 1 - framework/yii/caching/DbCache.php | 2 +- framework/yii/caching/XCache.php | 1 - framework/yii/console/Exception.php | 1 - .../yii/console/controllers/AssetController.php | 5 +- .../yii/console/controllers/MessageController.php | 3 +- .../yii/console/controllers/MigrateController.php | 1270 +++++++++---------- framework/yii/db/Connection.php | 5 +- framework/yii/debug/Module.php | 2 +- .../yii/debug/controllers/DefaultController.php | 2 +- framework/yii/helpers/Json.php | 1 - framework/yii/helpers/base/Console.php | 5 +- framework/yii/helpers/base/Html.php | 1 - framework/yii/helpers/base/Inflector.php | 6 +- framework/yii/i18n/MessageSource.php | 1 - framework/yii/jui/Accordion.php | 2 +- framework/yii/jui/Menu.php | 1 - framework/yii/jui/Widget.php | 1 - framework/yii/rbac/DbManager.php | 3 +- framework/yii/rbac/PhpManager.php | 2 +- framework/yii/requirements/requirements.php | 2 +- framework/yii/validators/CaptchaValidator.php | 1 - framework/yii/validators/DateValidator.php | 1 - framework/yii/validators/DefaultValueValidator.php | 1 - framework/yii/validators/ExistValidator.php | 1 - framework/yii/validators/FilterValidator.php | 1 + framework/yii/validators/RangeValidator.php | 2 +- .../yii/validators/RegularExpressionValidator.php | 2 +- framework/yii/validators/StringValidator.php | 1 - framework/yii/validators/UrlValidator.php | 2 +- framework/yii/validators/Validator.php | 2 +- framework/yii/web/HeaderCollection.php | 1 - framework/yii/web/PageCache.php | 206 +-- framework/yii/web/Request.php | 1 - framework/yii/web/User.php | 2 +- framework/yii/widgets/FragmentCache.php | 348 ++--- framework/yii/widgets/ListPager.php | 1 - 57 files changed, 1611 insertions(+), 1640 deletions(-) diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 8dc3fce..7bf9e7e 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -255,7 +255,7 @@ class ErrorHandler extends Component if (isset($_SERVER['SERVER_SOFTWARE'])) { foreach ($serverUrls as $url => $keywords) { foreach ($keywords as $keyword) { - if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false ) { + if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) { return '<a href="' . $url . '" target="_blank">' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '</a>'; } } diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index e62039e..545f570 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -12,7 +12,6 @@ use DateTime; use yii\helpers\HtmlPurifier; use yii\helpers\Html; - /** * Formatter provides a set of commonly used data formatting methods. * diff --git a/framework/yii/base/InvalidCallException.php b/framework/yii/base/InvalidCallException.php index 9a146d4..73cb4b9 100644 --- a/framework/yii/base/InvalidCallException.php +++ b/framework/yii/base/InvalidCallException.php @@ -23,4 +23,3 @@ class InvalidCallException extends Exception return \Yii::t('yii', 'Invalid Call'); } } - diff --git a/framework/yii/base/InvalidConfigException.php b/framework/yii/base/InvalidConfigException.php index c617381..0a6b4c5 100644 --- a/framework/yii/base/InvalidConfigException.php +++ b/framework/yii/base/InvalidConfigException.php @@ -23,4 +23,3 @@ class InvalidConfigException extends Exception return \Yii::t('yii', 'Invalid Configuration'); } } - diff --git a/framework/yii/base/InvalidParamException.php b/framework/yii/base/InvalidParamException.php index 0262051..44430a8 100644 --- a/framework/yii/base/InvalidParamException.php +++ b/framework/yii/base/InvalidParamException.php @@ -23,4 +23,3 @@ class InvalidParamException extends Exception return \Yii::t('yii', 'Invalid Parameter'); } } - diff --git a/framework/yii/base/InvalidRouteException.php b/framework/yii/base/InvalidRouteException.php index a573636..fbcc087 100644 --- a/framework/yii/base/InvalidRouteException.php +++ b/framework/yii/base/InvalidRouteException.php @@ -23,4 +23,3 @@ class InvalidRouteException extends UserException return \Yii::t('yii', 'Invalid Route'); } } - diff --git a/framework/yii/base/Jsonable.php b/framework/yii/base/Jsonable.php index 4a3f7d9..e9425a6 100644 --- a/framework/yii/base/Jsonable.php +++ b/framework/yii/base/Jsonable.php @@ -18,5 +18,5 @@ interface Jsonable /** * @return string the JSON representation of this object */ - function toJson(); + public function toJson(); } diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 5f5c376..cc7c849 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -1,667 +1,667 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\base; - -use Yii; - -/** - * Module is the base class for module and application classes. - * - * A module represents a sub-application which contains MVC elements by itself, such as - * models, views, controllers, etc. - * - * A module may consist of [[modules|sub-modules]]. - * - * [[components|Components]] may be registered with the module so that they are globally - * accessible within the module. - * - * @property string $uniqueId An ID that uniquely identifies this module among all modules within - * the current application. - * @property string $basePath The root directory of the module. Defaults to the directory containing the module class. - * @property string $controllerPath The directory containing the controller classes. Defaults to "[[basePath]]/controllers". - * @property string $viewPath The directory containing the view files within this module. Defaults to "[[basePath]]/views". - * @property string $layoutPath The directory containing the layout view files within this module. Defaults to "[[viewPath]]/layouts". - * @property array $modules The configuration of the currently installed modules (module ID => configuration). - * @property array $components The components (indexed by their IDs) registered within this module. - * @property array $import List of aliases to be imported. This property is write-only. - * @property array $aliases List of aliases to be defined. This property is write-only. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -abstract class Module extends Component -{ - /** - * @event ActionEvent an event raised before executing a controller action. - * You may set [[ActionEvent::isValid]] to be false to cancel the action execution. - */ - const EVENT_BEFORE_ACTION = 'beforeAction'; - /** - * @event ActionEvent an event raised after executing a controller action. - */ - const EVENT_AFTER_ACTION = 'afterAction'; - /** - * @var array custom module parameters (name => value). - */ - public $params = array(); - /** - * @var array the IDs of the components that should be preloaded when this module is created. - */ - public $preload = array(); - /** - * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]]. - */ - public $id; - /** - * @var Module the parent module of this module. Null if this module does not have a parent. - */ - public $module; - /** - * @var string|boolean the layout that should be applied for views within this module. This refers to a view name - * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]] - * will be taken. If this is false, layout will be disabled within this module. - */ - public $layout; - /** - * @var array mapping from controller ID to controller configurations. - * Each name-value pair specifies the configuration of a single controller. - * A controller configuration can be either a string or an array. - * If the former, the string should be the class name or path alias of the controller. - * If the latter, the array must contain a 'class' element which specifies - * the controller's class name or path alias, and the rest of the name-value pairs - * in the array are used to initialize the corresponding controller properties. For example, - * - * ~~~ - * array( - * 'account' => '@app/controllers/UserController', - * 'article' => array( - * 'class' => '@app/controllers/PostController', - * 'pageTitle' => 'something new', - * ), - * ) - * ~~~ - */ - public $controllerMap = array(); - /** - * @var string the namespace that controller classes are in. If not set, - * it will use the "controllers" sub-namespace under the namespace of this module. - * For example, if the namespace of this module is "foo\bar", then the default - * controller namespace would be "foo\bar\controllers". - * If the module is an application, it will default to "app\controllers". - */ - public $controllerNamespace; - /** - * @return string the default route of this module. Defaults to 'default'. - * The route may consist of child module ID, controller ID, and/or action ID. - * For example, `help`, `post/create`, `admin/post/create`. - * If action ID is not given, it will take the default value as specified in - * [[Controller::defaultAction]]. - */ - public $defaultRoute = 'default'; - /** - * @var string the root directory of the module. - */ - private $_basePath; - /** - * @var string the root directory that contains view files for this module - */ - private $_viewPath; - /** - * @var string the root directory that contains layout view files for this module. - */ - private $_layoutPath; - /** - * @var string the directory containing controller classes in the module. - */ - private $_controllerPath; - /** - * @var array child modules of this module - */ - private $_modules = array(); - /** - * @var array components registered under this module - */ - private $_components = array(); - - /** - * Constructor. - * @param string $id the ID of this module - * @param Module $parent the parent module (if any) - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($id, $parent = null, $config = array()) - { - $this->id = $id; - $this->module = $parent; - parent::__construct($config); - } - - /** - * Getter magic method. - * This method is overridden to support accessing components - * like reading module properties. - * @param string $name component or property name - * @return mixed the named property value - */ - public function __get($name) - { - if ($this->hasComponent($name)) { - return $this->getComponent($name); - } else { - return parent::__get($name); - } - } - - /** - * Checks if a property value is null. - * This method overrides the parent implementation by checking - * if the named component is loaded. - * @param string $name the property name or the event name - * @return boolean whether the property value is null - */ - public function __isset($name) - { - if ($this->hasComponent($name)) { - return $this->getComponent($name) !== null; - } else { - return parent::__isset($name); - } - } - - /** - * Initializes the module. - * This method is called after the module is created and initialized with property values - * given in configuration. The default implement will create a path alias using the module [[id]] - * and then call [[preloadComponents()]] to load components that are declared in [[preload]]. - */ - public function init() - { - $this->preloadComponents(); - if ($this->controllerNamespace === null) { - if ($this instanceof Application) { - $this->controllerNamespace = 'app\\controllers'; - } else { - $class = get_class($this); - if (($pos = strrpos($class, '\\')) !== false) { - $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; - } - } - } - } - - /** - * Returns an ID that uniquely identifies this module among all modules within the current application. - * Note that if the module is an application, an empty string will be returned. - * @return string the unique ID of the module. - */ - public function getUniqueId() - { - if ($this instanceof Application) { - return ''; - } elseif ($this->module) { - return $this->module->getUniqueId() . '/' . $this->id; - } else { - return $this->id; - } - } - - /** - * Returns the root directory of the module. - * It defaults to the directory containing the module class file. - * @return string the root directory of the module. - */ - public function getBasePath() - { - if ($this->_basePath === null) { - $class = new \ReflectionClass($this); - $this->_basePath = dirname($class->getFileName()); - } - return $this->_basePath; - } - - /** - * Sets the root directory of the module. - * This method can only be invoked at the beginning of the constructor. - * @param string $path the root directory of the module. This can be either a directory name or a path alias. - * @throws InvalidParamException if the directory does not exist. - */ - public function setBasePath($path) - { - $path = Yii::getAlias($path); - $p = realpath($path); - if ($p !== false && is_dir($p)) { - $this->_basePath = $p; - if ($this instanceof Application) { - Yii::setAlias('@app', $p); - } - } else { - throw new InvalidParamException("The directory does not exist: $path"); - } - } - - /** - * Returns the directory that contains the controller classes. - * Defaults to "[[basePath]]/controllers". - * @return string the directory that contains the controller classes. - */ - public function getControllerPath() - { - if ($this->_controllerPath !== null) { - return $this->_controllerPath; - } else { - return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers'; - } - } - - /** - * Sets the directory that contains the controller classes. - * @param string $path the directory that contains the controller classes. - * This can be either a directory name or a path alias. - * @throws Exception if the directory is invalid - */ - public function setControllerPath($path) - { - $this->_controllerPath = Yii::getAlias($path); - } - - /** - * Returns the directory that contains the view files for this module. - * @return string the root directory of view files. Defaults to "[[basePath]]/view". - */ - public function getViewPath() - { - if ($this->_viewPath !== null) { - return $this->_viewPath; - } else { - return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; - } - } - - /** - * Sets the directory that contains the view files. - * @param string $path the root directory of view files. - * @throws Exception if the directory is invalid - */ - public function setViewPath($path) - { - $this->_viewPath = Yii::getAlias($path); - } - - /** - * Returns the directory that contains layout view files for this module. - * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts". - */ - public function getLayoutPath() - { - if ($this->_layoutPath !== null) { - return $this->_layoutPath; - } else { - return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; - } - } - - /** - * Sets the directory that contains the layout files. - * @param string $path the root directory of layout files. - * @throws Exception if the directory is invalid - */ - public function setLayoutPath($path) - { - $this->_layoutPath = Yii::getAlias($path); - } - - /** - * Defines path aliases. - * This method calls [[Yii::setAlias()]] to register the path aliases. - * This method is provided so that you can define path aliases when configuring a module. - * @param array $aliases list of path aliases to be defined. The array keys are alias names - * (must start with '@') and the array values are the corresponding paths or aliases. - * For example, - * - * ~~~ - * array( - * '@models' => '@app/models', // an existing alias - * '@backend' => __DIR__ . '/../backend', // a directory - * ) - * ~~~ - */ - public function setAliases($aliases) - { - foreach ($aliases as $name => $alias) { - Yii::setAlias($name, $alias); - } - } - - /** - * Checks whether the named module exists. - * @param string $id module ID - * @return boolean whether the named module exists. Both loaded and unloaded modules - * are considered. - */ - public function hasModule($id) - { - return isset($this->_modules[$id]); - } - - /** - * Retrieves the named module. - * @param string $id module ID (case-sensitive) - * @param boolean $load whether to load the module if it is not yet loaded. - * @return Module|null the module instance, null if the module - * does not exist. - * @see hasModule() - */ - public function getModule($id, $load = true) - { - if (isset($this->_modules[$id])) { - if ($this->_modules[$id] instanceof Module) { - return $this->_modules[$id]; - } elseif ($load) { - Yii::trace("Loading module: $id", __METHOD__); - return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this); - } - } - return null; - } - - /** - * Adds a sub-module to this module. - * @param string $id module ID - * @param Module|array|null $module the sub-module to be added to this module. This can - * be one of the followings: - * - * - a [[Module]] object - * - a configuration array: when [[getModule()]] is called initially, the array - * will be used to instantiate the sub-module - * - null: the named sub-module will be removed from this module - */ - public function setModule($id, $module) - { - if ($module === null) { - unset($this->_modules[$id]); - } else { - $this->_modules[$id] = $module; - } - } - - /** - * Returns the sub-modules in this module. - * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false, - * then all sub-modules registered in this module will be returned, whether they are loaded or not. - * Loaded modules will be returned as objects, while unloaded modules as configuration arrays. - * @return array the modules (indexed by their IDs) - */ - public function getModules($loadedOnly = false) - { - if ($loadedOnly) { - $modules = array(); - foreach ($this->_modules as $module) { - if ($module instanceof Module) { - $modules[] = $module; - } - } - return $modules; - } else { - return $this->_modules; - } - } - - /** - * Registers sub-modules in the current module. - * - * Each sub-module should be specified as a name-value pair, where - * name refers to the ID of the module and value the module or a configuration - * array that can be used to create the module. In the latter case, [[Yii::createObject()]] - * will be used to create the module. - * - * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently. - * - * The following is an example for registering two sub-modules: - * - * ~~~ - * array( - * 'comment' => array( - * 'class' => 'app\modules\comment\CommentModule', - * 'db' => 'db', - * ), - * 'booking' => array( - * 'class' => 'app\modules\booking\BookingModule', - * ), - * ) - * ~~~ - * - * @param array $modules modules (id => module configuration or instances) - */ - public function setModules($modules) - { - foreach ($modules as $id => $module) { - $this->_modules[$id] = $module; - } - } - - /** - * Checks whether the named component exists. - * @param string $id component ID - * @return boolean whether the named component exists. Both loaded and unloaded components - * are considered. - */ - public function hasComponent($id) - { - return isset($this->_components[$id]); - } - - /** - * Retrieves the named component. - * @param string $id component ID (case-sensitive) - * @param boolean $load whether to load the component if it is not yet loaded. - * @return Component|null the component instance, null if the component does not exist. - * @see hasComponent() - */ - public function getComponent($id, $load = true) - { - if (isset($this->_components[$id])) { - if ($this->_components[$id] instanceof Object) { - return $this->_components[$id]; - } elseif ($load) { - Yii::trace("Loading component: $id", __METHOD__); - return $this->_components[$id] = Yii::createObject($this->_components[$id]); - } - } - return null; - } - - /** - * Registers a component with this module. - * @param string $id component ID - * @param Component|array|null $component the component to be registered with the module. This can - * be one of the followings: - * - * - a [[Component]] object - * - a configuration array: when [[getComponent()]] is called initially for this component, the array - * will be used to instantiate the component via [[Yii::createObject()]]. - * - null: the named component will be removed from the module - */ - public function setComponent($id, $component) - { - if ($component === null) { - unset($this->_components[$id]); - } else { - $this->_components[$id] = $component; - } - } - - /** - * Returns the registered components. - * @param boolean $loadedOnly whether to return the loaded components only. If this is set false, - * then all components specified in the configuration will be returned, whether they are loaded or not. - * Loaded components will be returned as objects, while unloaded components as configuration arrays. - * @return array the components (indexed by their IDs) - */ - public function getComponents($loadedOnly = false) - { - if ($loadedOnly) { - $components = array(); - foreach ($this->_components as $component) { - if ($component instanceof Component) { - $components[] = $component; - } - } - return $components; - } else { - return $this->_components; - } - } - - /** - * Registers a set of components in this module. - * - * Each component should be specified as a name-value pair, where - * name refers to the ID of the component and value the component or a configuration - * array that can be used to create the component. In the latter case, [[Yii::createObject()]] - * will be used to create the component. - * - * If a new component has the same ID as an existing one, the existing one will be overwritten silently. - * - * The following is an example for setting two components: - * - * ~~~ - * array( - * 'db' => array( - * 'class' => 'yii\db\Connection', - * 'dsn' => 'sqlite:path/to/file.db', - * ), - * 'cache' => array( - * 'class' => 'yii\caching\DbCache', - * 'db' => 'db', - * ), - * ) - * ~~~ - * - * @param array $components components (id => component configuration or instance) - */ - public function setComponents($components) - { - foreach ($components as $id => $component) { - if (isset($this->_components[$id]['class']) && !isset($component['class'])) { - $component['class'] = $this->_components[$id]['class']; - } - $this->_components[$id] = $component; - } - } - - /** - * Loads components that are declared in [[preload]]. - */ - public function preloadComponents() - { - foreach ($this->preload as $id) { - $this->getComponent($id); - } - } - - /** - * Runs a controller action specified by a route. - * This method parses the specified route and creates the corresponding child module(s), controller and action - * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. - * If the route is empty, the method will use [[defaultRoute]]. - * @param string $route the route that specifies the action. - * @param array $params the parameters to be passed to the action - * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. - * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully - */ - public function runAction($route, $params = array()) - { - $result = $this->createController($route); - if (is_array($result)) { - /** @var $controller Controller */ - list($controller, $actionID) = $result; - $oldController = Yii::$app->controller; - Yii::$app->controller = $controller; - $status = $controller->runAction($actionID, $params); - Yii::$app->controller = $oldController; - return $status; - } else { - throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".'); - } - } - - /** - * Creates a controller instance based on the controller ID. - * - * The controller is created within this module. The method first attempts to - * create the controller based on the [[controllerMap]] of the module. If not available, - * it will look for the controller class under the [[controllerPath]] and create an - * instance of it. - * - * @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 - * 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) - { - if ($route === '') { - $route = $this->defaultRoute; - } - $route = trim($route, '/'); - if (($pos = strpos($route, '/')) !== false) { - $id = substr($route, 0, $pos); - $route = substr($route, $pos + 1); - } else { - $id = $route; - $route = ''; - } - - $module = $this->getModule($id); - if ($module !== null) { - return $module->createController($route); - } - - if (isset($this->controllerMap[$id])) { - $controller = Yii::createObject($this->controllerMap[$id], $id, $this); - } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { - $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller'; - $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; - if (!is_file($classFile)) { - return false; - } - $className = ltrim($this->controllerNamespace . '\\' . $className, '\\'); - Yii::$classMap[$className] = $classFile; - if (is_subclass_of($className, 'yii\base\Controller')) { - $controller = new $className($id, $this); - } elseif (YII_DEBUG) { - throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); - } - } - - return isset($controller) ? array($controller, $route) : false; - } - - /** - * This method is invoked right before an action is to be executed (after all possible filters.) - * You may override this method to do last-minute preparation for the action. - * @param Action $action the action to be executed. - * @return boolean whether the action should continue to be executed. - */ - public function beforeAction($action) - { - $event = new ActionEvent($action); - $this->trigger(self::EVENT_BEFORE_ACTION, $event); - return $event->isValid; - } - - /** - * This method is invoked right after an action is executed. - * You may override this method to do some postprocessing for the action. - * @param Action $action the action just executed. - */ - public function afterAction($action) - { - $this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action)); - } -} +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\base; + +use Yii; + +/** + * Module is the base class for module and application classes. + * + * A module represents a sub-application which contains MVC elements by itself, such as + * models, views, controllers, etc. + * + * A module may consist of [[modules|sub-modules]]. + * + * [[components|Components]] may be registered with the module so that they are globally + * accessible within the module. + * + * @property string $uniqueId An ID that uniquely identifies this module among all modules within + * the current application. + * @property string $basePath The root directory of the module. Defaults to the directory containing the module class. + * @property string $controllerPath The directory containing the controller classes. Defaults to "[[basePath]]/controllers". + * @property string $viewPath The directory containing the view files within this module. Defaults to "[[basePath]]/views". + * @property string $layoutPath The directory containing the layout view files within this module. Defaults to "[[viewPath]]/layouts". + * @property array $modules The configuration of the currently installed modules (module ID => configuration). + * @property array $components The components (indexed by their IDs) registered within this module. + * @property array $import List of aliases to be imported. This property is write-only. + * @property array $aliases List of aliases to be defined. This property is write-only. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +abstract class Module extends Component +{ + /** + * @event ActionEvent an event raised before executing a controller action. + * You may set [[ActionEvent::isValid]] to be false to cancel the action execution. + */ + const EVENT_BEFORE_ACTION = 'beforeAction'; + /** + * @event ActionEvent an event raised after executing a controller action. + */ + const EVENT_AFTER_ACTION = 'afterAction'; + /** + * @var array custom module parameters (name => value). + */ + public $params = array(); + /** + * @var array the IDs of the components that should be preloaded when this module is created. + */ + public $preload = array(); + /** + * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]]. + */ + public $id; + /** + * @var Module the parent module of this module. Null if this module does not have a parent. + */ + public $module; + /** + * @var string|boolean the layout that should be applied for views within this module. This refers to a view name + * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]] + * will be taken. If this is false, layout will be disabled within this module. + */ + public $layout; + /** + * @var array mapping from controller ID to controller configurations. + * Each name-value pair specifies the configuration of a single controller. + * A controller configuration can be either a string or an array. + * If the former, the string should be the class name or path alias of the controller. + * If the latter, the array must contain a 'class' element which specifies + * the controller's class name or path alias, and the rest of the name-value pairs + * in the array are used to initialize the corresponding controller properties. For example, + * + * ~~~ + * array( + * 'account' => '@app/controllers/UserController', + * 'article' => array( + * 'class' => '@app/controllers/PostController', + * 'pageTitle' => 'something new', + * ), + * ) + * ~~~ + */ + public $controllerMap = array(); + /** + * @var string the namespace that controller classes are in. If not set, + * it will use the "controllers" sub-namespace under the namespace of this module. + * For example, if the namespace of this module is "foo\bar", then the default + * controller namespace would be "foo\bar\controllers". + * If the module is an application, it will default to "app\controllers". + */ + public $controllerNamespace; + /** + * @return string the default route of this module. Defaults to 'default'. + * The route may consist of child module ID, controller ID, and/or action ID. + * For example, `help`, `post/create`, `admin/post/create`. + * If action ID is not given, it will take the default value as specified in + * [[Controller::defaultAction]]. + */ + public $defaultRoute = 'default'; + /** + * @var string the root directory of the module. + */ + private $_basePath; + /** + * @var string the root directory that contains view files for this module + */ + private $_viewPath; + /** + * @var string the root directory that contains layout view files for this module. + */ + private $_layoutPath; + /** + * @var string the directory containing controller classes in the module. + */ + private $_controllerPath; + /** + * @var array child modules of this module + */ + private $_modules = array(); + /** + * @var array components registered under this module + */ + private $_components = array(); + + /** + * Constructor. + * @param string $id the ID of this module + * @param Module $parent the parent module (if any) + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($id, $parent = null, $config = array()) + { + $this->id = $id; + $this->module = $parent; + parent::__construct($config); + } + + /** + * Getter magic method. + * This method is overridden to support accessing components + * like reading module properties. + * @param string $name component or property name + * @return mixed the named property value + */ + public function __get($name) + { + if ($this->hasComponent($name)) { + return $this->getComponent($name); + } else { + return parent::__get($name); + } + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking + * if the named component is loaded. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + if ($this->hasComponent($name)) { + return $this->getComponent($name) !== null; + } else { + return parent::__isset($name); + } + } + + /** + * Initializes the module. + * This method is called after the module is created and initialized with property values + * given in configuration. The default implement will create a path alias using the module [[id]] + * and then call [[preloadComponents()]] to load components that are declared in [[preload]]. + */ + public function init() + { + $this->preloadComponents(); + if ($this->controllerNamespace === null) { + if ($this instanceof Application) { + $this->controllerNamespace = 'app\\controllers'; + } else { + $class = get_class($this); + if (($pos = strrpos($class, '\\')) !== false) { + $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; + } + } + } + } + + /** + * Returns an ID that uniquely identifies this module among all modules within the current application. + * Note that if the module is an application, an empty string will be returned. + * @return string the unique ID of the module. + */ + public function getUniqueId() + { + if ($this instanceof Application) { + return ''; + } elseif ($this->module) { + return $this->module->getUniqueId() . '/' . $this->id; + } else { + return $this->id; + } + } + + /** + * Returns the root directory of the module. + * It defaults to the directory containing the module class file. + * @return string the root directory of the module. + */ + public function getBasePath() + { + if ($this->_basePath === null) { + $class = new \ReflectionClass($this); + $this->_basePath = dirname($class->getFileName()); + } + return $this->_basePath; + } + + /** + * Sets the root directory of the module. + * This method can only be invoked at the beginning of the constructor. + * @param string $path the root directory of the module. This can be either a directory name or a path alias. + * @throws InvalidParamException if the directory does not exist. + */ + public function setBasePath($path) + { + $path = Yii::getAlias($path); + $p = realpath($path); + if ($p !== false && is_dir($p)) { + $this->_basePath = $p; + if ($this instanceof Application) { + Yii::setAlias('@app', $p); + } + } else { + throw new InvalidParamException("The directory does not exist: $path"); + } + } + + /** + * Returns the directory that contains the controller classes. + * Defaults to "[[basePath]]/controllers". + * @return string the directory that contains the controller classes. + */ + public function getControllerPath() + { + if ($this->_controllerPath !== null) { + return $this->_controllerPath; + } else { + return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers'; + } + } + + /** + * Sets the directory that contains the controller classes. + * @param string $path the directory that contains the controller classes. + * This can be either a directory name or a path alias. + * @throws Exception if the directory is invalid + */ + public function setControllerPath($path) + { + $this->_controllerPath = Yii::getAlias($path); + } + + /** + * Returns the directory that contains the view files for this module. + * @return string the root directory of view files. Defaults to "[[basePath]]/view". + */ + public function getViewPath() + { + if ($this->_viewPath !== null) { + return $this->_viewPath; + } else { + return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; + } + } + + /** + * Sets the directory that contains the view files. + * @param string $path the root directory of view files. + * @throws Exception if the directory is invalid + */ + public function setViewPath($path) + { + $this->_viewPath = Yii::getAlias($path); + } + + /** + * Returns the directory that contains layout view files for this module. + * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts". + */ + public function getLayoutPath() + { + if ($this->_layoutPath !== null) { + return $this->_layoutPath; + } else { + return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; + } + } + + /** + * Sets the directory that contains the layout files. + * @param string $path the root directory of layout files. + * @throws Exception if the directory is invalid + */ + public function setLayoutPath($path) + { + $this->_layoutPath = Yii::getAlias($path); + } + + /** + * Defines path aliases. + * This method calls [[Yii::setAlias()]] to register the path aliases. + * This method is provided so that you can define path aliases when configuring a module. + * @param array $aliases list of path aliases to be defined. The array keys are alias names + * (must start with '@') and the array values are the corresponding paths or aliases. + * For example, + * + * ~~~ + * array( + * '@models' => '@app/models', // an existing alias + * '@backend' => __DIR__ . '/../backend', // a directory + * ) + * ~~~ + */ + public function setAliases($aliases) + { + foreach ($aliases as $name => $alias) { + Yii::setAlias($name, $alias); + } + } + + /** + * Checks whether the named module exists. + * @param string $id module ID + * @return boolean whether the named module exists. Both loaded and unloaded modules + * are considered. + */ + public function hasModule($id) + { + return isset($this->_modules[$id]); + } + + /** + * Retrieves the named module. + * @param string $id module ID (case-sensitive) + * @param boolean $load whether to load the module if it is not yet loaded. + * @return Module|null the module instance, null if the module + * does not exist. + * @see hasModule() + */ + public function getModule($id, $load = true) + { + if (isset($this->_modules[$id])) { + if ($this->_modules[$id] instanceof Module) { + return $this->_modules[$id]; + } elseif ($load) { + Yii::trace("Loading module: $id", __METHOD__); + return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this); + } + } + return null; + } + + /** + * Adds a sub-module to this module. + * @param string $id module ID + * @param Module|array|null $module the sub-module to be added to this module. This can + * be one of the followings: + * + * - a [[Module]] object + * - a configuration array: when [[getModule()]] is called initially, the array + * will be used to instantiate the sub-module + * - null: the named sub-module will be removed from this module + */ + public function setModule($id, $module) + { + if ($module === null) { + unset($this->_modules[$id]); + } else { + $this->_modules[$id] = $module; + } + } + + /** + * Returns the sub-modules in this module. + * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false, + * then all sub-modules registered in this module will be returned, whether they are loaded or not. + * Loaded modules will be returned as objects, while unloaded modules as configuration arrays. + * @return array the modules (indexed by their IDs) + */ + public function getModules($loadedOnly = false) + { + if ($loadedOnly) { + $modules = array(); + foreach ($this->_modules as $module) { + if ($module instanceof Module) { + $modules[] = $module; + } + } + return $modules; + } else { + return $this->_modules; + } + } + + /** + * Registers sub-modules in the current module. + * + * Each sub-module should be specified as a name-value pair, where + * name refers to the ID of the module and value the module or a configuration + * array that can be used to create the module. In the latter case, [[Yii::createObject()]] + * will be used to create the module. + * + * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently. + * + * The following is an example for registering two sub-modules: + * + * ~~~ + * array( + * 'comment' => array( + * 'class' => 'app\modules\comment\CommentModule', + * 'db' => 'db', + * ), + * 'booking' => array( + * 'class' => 'app\modules\booking\BookingModule', + * ), + * ) + * ~~~ + * + * @param array $modules modules (id => module configuration or instances) + */ + public function setModules($modules) + { + foreach ($modules as $id => $module) { + $this->_modules[$id] = $module; + } + } + + /** + * Checks whether the named component exists. + * @param string $id component ID + * @return boolean whether the named component exists. Both loaded and unloaded components + * are considered. + */ + public function hasComponent($id) + { + return isset($this->_components[$id]); + } + + /** + * Retrieves the named component. + * @param string $id component ID (case-sensitive) + * @param boolean $load whether to load the component if it is not yet loaded. + * @return Component|null the component instance, null if the component does not exist. + * @see hasComponent() + */ + public function getComponent($id, $load = true) + { + if (isset($this->_components[$id])) { + if ($this->_components[$id] instanceof Object) { + return $this->_components[$id]; + } elseif ($load) { + Yii::trace("Loading component: $id", __METHOD__); + return $this->_components[$id] = Yii::createObject($this->_components[$id]); + } + } + return null; + } + + /** + * Registers a component with this module. + * @param string $id component ID + * @param Component|array|null $component the component to be registered with the module. This can + * be one of the followings: + * + * - a [[Component]] object + * - a configuration array: when [[getComponent()]] is called initially for this component, the array + * will be used to instantiate the component via [[Yii::createObject()]]. + * - null: the named component will be removed from the module + */ + public function setComponent($id, $component) + { + if ($component === null) { + unset($this->_components[$id]); + } else { + $this->_components[$id] = $component; + } + } + + /** + * Returns the registered components. + * @param boolean $loadedOnly whether to return the loaded components only. If this is set false, + * then all components specified in the configuration will be returned, whether they are loaded or not. + * Loaded components will be returned as objects, while unloaded components as configuration arrays. + * @return array the components (indexed by their IDs) + */ + public function getComponents($loadedOnly = false) + { + if ($loadedOnly) { + $components = array(); + foreach ($this->_components as $component) { + if ($component instanceof Component) { + $components[] = $component; + } + } + return $components; + } else { + return $this->_components; + } + } + + /** + * Registers a set of components in this module. + * + * Each component should be specified as a name-value pair, where + * name refers to the ID of the component and value the component or a configuration + * array that can be used to create the component. In the latter case, [[Yii::createObject()]] + * will be used to create the component. + * + * If a new component has the same ID as an existing one, the existing one will be overwritten silently. + * + * The following is an example for setting two components: + * + * ~~~ + * array( + * 'db' => array( + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ), + * 'cache' => array( + * 'class' => 'yii\caching\DbCache', + * 'db' => 'db', + * ), + * ) + * ~~~ + * + * @param array $components components (id => component configuration or instance) + */ + public function setComponents($components) + { + foreach ($components as $id => $component) { + if (isset($this->_components[$id]['class']) && !isset($component['class'])) { + $component['class'] = $this->_components[$id]['class']; + } + $this->_components[$id] = $component; + } + } + + /** + * Loads components that are declared in [[preload]]. + */ + public function preloadComponents() + { + foreach ($this->preload as $id) { + $this->getComponent($id); + } + } + + /** + * Runs a controller action specified by a route. + * This method parses the specified route and creates the corresponding child module(s), controller and action + * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. + * If the route is empty, the method will use [[defaultRoute]]. + * @param string $route the route that specifies the action. + * @param array $params the parameters to be passed to the action + * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. + * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully + */ + public function runAction($route, $params = array()) + { + $result = $this->createController($route); + if (is_array($result)) { + /** @var $controller Controller */ + list($controller, $actionID) = $result; + $oldController = Yii::$app->controller; + Yii::$app->controller = $controller; + $status = $controller->runAction($actionID, $params); + Yii::$app->controller = $oldController; + return $status; + } else { + throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".'); + } + } + + /** + * Creates a controller instance based on the controller ID. + * + * The controller is created within this module. The method first attempts to + * create the controller based on the [[controllerMap]] of the module. If not available, + * it will look for the controller class under the [[controllerPath]] and create an + * instance of it. + * + * @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 + * 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) + { + if ($route === '') { + $route = $this->defaultRoute; + } + $route = trim($route, '/'); + if (($pos = strpos($route, '/')) !== false) { + $id = substr($route, 0, $pos); + $route = substr($route, $pos + 1); + } else { + $id = $route; + $route = ''; + } + + $module = $this->getModule($id); + if ($module !== null) { + return $module->createController($route); + } + + if (isset($this->controllerMap[$id])) { + $controller = Yii::createObject($this->controllerMap[$id], $id, $this); + } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { + $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller'; + $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; + if (!is_file($classFile)) { + return false; + } + $className = ltrim($this->controllerNamespace . '\\' . $className, '\\'); + Yii::$classMap[$className] = $classFile; + if (is_subclass_of($className, 'yii\base\Controller')) { + $controller = new $className($id, $this); + } elseif (YII_DEBUG) { + throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); + } + } + + return isset($controller) ? array($controller, $route) : false; + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + $event = new ActionEvent($action); + $this->trigger(self::EVENT_BEFORE_ACTION, $event); + return $event->isValid; + } + + /** + * This method is invoked right after an action is executed. + * You may override this method to do some postprocessing for the action. + * @param Action $action the action just executed. + */ + public function afterAction($action) + { + $this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action)); + } +} diff --git a/framework/yii/base/NotSupportedException.php b/framework/yii/base/NotSupportedException.php index 8a93e14..33f936f 100644 --- a/framework/yii/base/NotSupportedException.php +++ b/framework/yii/base/NotSupportedException.php @@ -23,4 +23,3 @@ class NotSupportedException extends Exception return \Yii::t('yii', 'Not Supported'); } } - diff --git a/framework/yii/base/Theme.php b/framework/yii/base/Theme.php index ca1efcd..91c32dc 100644 --- a/framework/yii/base/Theme.php +++ b/framework/yii/base/Theme.php @@ -73,7 +73,7 @@ class Theme extends Component */ public function init() { - parent::init(); + parent::init(); if (empty($this->pathMap)) { if ($this->basePath !== null) { $this->basePath = Yii::getAlias($this->basePath); diff --git a/framework/yii/base/UnknownClassException.php b/framework/yii/base/UnknownClassException.php index e4a682a..7b893d4 100644 --- a/framework/yii/base/UnknownClassException.php +++ b/framework/yii/base/UnknownClassException.php @@ -23,4 +23,3 @@ class UnknownClassException extends Exception return \Yii::t('yii', 'Unknown Class'); } } - diff --git a/framework/yii/base/UnknownMethodException.php b/framework/yii/base/UnknownMethodException.php index d8cea34..3b33659 100644 --- a/framework/yii/base/UnknownMethodException.php +++ b/framework/yii/base/UnknownMethodException.php @@ -23,4 +23,3 @@ class UnknownMethodException extends Exception return \Yii::t('yii', 'Unknown Method'); } } - diff --git a/framework/yii/base/UnknownPropertyException.php b/framework/yii/base/UnknownPropertyException.php index b8e93c5..682fdfa 100644 --- a/framework/yii/base/UnknownPropertyException.php +++ b/framework/yii/base/UnknownPropertyException.php @@ -23,4 +23,3 @@ class UnknownPropertyException extends Exception return \Yii::t('yii', 'Unknown Property'); } } - diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index 104e700..856c420 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -6,9 +6,8 @@ */ namespace yii\bootstrap; -use yii\base\InvalidConfigException; -use yii\helpers\Html; +use yii\helpers\Html; /** * Button renders a bootstrap button. diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php index 4168bd5..fec042e 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -6,8 +6,8 @@ */ namespace yii\bootstrap; -use yii\helpers\Html; +use yii\helpers\Html; /** * ButtonDropdown renders a group or split button dropdown bootstrap component. diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php index f50d6a8..e5bf4e9 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -10,7 +10,6 @@ namespace yii\bootstrap; use yii\helpers\base\ArrayHelper; use yii\helpers\Html; - /** * ButtonGroup renders a button group bootstrap component. * diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php index a7929e3..fdcaae1 100644 --- a/framework/yii/bootstrap/Collapse.php +++ b/framework/yii/bootstrap/Collapse.php @@ -130,4 +130,4 @@ class Collapse extends Widget return implode("\n", $group); } -} \ No newline at end of file +} diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index 2bee0ff..827e6cc 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -11,7 +11,6 @@ use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; use yii\helpers\Html; - /** * Dropdown renders a Bootstrap dropdown menu component. * diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 6c091a4..8e35010 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -118,7 +118,7 @@ class Nav extends Widget $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); - if(ArrayHelper::getValue($item, 'active')) { + if (ArrayHelper::getValue($item, 'active')) { $this->addCssClass($options, 'active'); } diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php index 708c0fe..7c0473e 100644 --- a/framework/yii/bootstrap/Progress.php +++ b/framework/yii/bootstrap/Progress.php @@ -11,7 +11,6 @@ use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; use yii\helpers\Html; - /** * Progress renders a bootstrap progress bar component. * diff --git a/framework/yii/bootstrap/Widget.php b/framework/yii/bootstrap/Widget.php index a2e6d77..48b0331 100644 --- a/framework/yii/bootstrap/Widget.php +++ b/framework/yii/bootstrap/Widget.php @@ -11,7 +11,6 @@ use Yii; use yii\base\View; use yii\helpers\Json; - /** * \yii\bootstrap\Widget is the base class for all bootstrap widgets. * diff --git a/framework/yii/caching/DbCache.php b/framework/yii/caching/DbCache.php index 7e5f12d..7571a42 100644 --- a/framework/yii/caching/DbCache.php +++ b/framework/yii/caching/DbCache.php @@ -170,7 +170,7 @@ class DbCache extends Cache } else { return $this->addValue($key, $value, $expire); } - } + } /** * Stores a value identified by a key into cache if the cache does not contain this key. diff --git a/framework/yii/caching/XCache.php b/framework/yii/caching/XCache.php index 91f483b..1f12f23 100644 --- a/framework/yii/caching/XCache.php +++ b/framework/yii/caching/XCache.php @@ -86,4 +86,3 @@ class XCache extends Cache return true; } } - diff --git a/framework/yii/console/Exception.php b/framework/yii/console/Exception.php index 9e9003e..f272bde 100644 --- a/framework/yii/console/Exception.php +++ b/framework/yii/console/Exception.php @@ -25,4 +25,3 @@ class Exception extends UserException return \Yii::t('yii', 'Error'); } } - diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index cd8710e..dcd1667 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -220,7 +220,8 @@ class AssetController extends Controller * @param array $result already loaded bundles list. * @throws \yii\console\Exception on failure. */ - protected function loadBundleDependency($name, $bundle, &$result) { + protected function loadBundleDependency($name, $bundle, &$result) + { if (!empty($bundle->depends)) { $assetManager = $this->getAssetManager(); foreach ($bundle->depends as $dependencyName) { @@ -572,7 +573,7 @@ EOD; $inputFileRelativePathParts = explode('/', $inputFileRelativePath); $outputFileRelativePathParts = explode('/', $outputFileRelativePath); - $callback = function($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) { + $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) { $fullMatch = $matches[0]; $inputUrl = $matches[1]; diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php index 715fb5c..44cf1b1 100644 --- a/framework/yii/console/controllers/MessageController.php +++ b/framework/yii/console/controllers/MessageController.php @@ -179,8 +179,7 @@ class MessageController extends Controller } ksort($translated); foreach ($translated as $message => $translation) { - if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) - { + if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) { if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { $todo[$message]=$translation; } else { diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php index d3eb257..eca7787 100644 --- a/framework/yii/console/controllers/MigrateController.php +++ b/framework/yii/console/controllers/MigrateController.php @@ -1,635 +1,635 @@ -<?php -/** - * @author Qiang Xue <qiang.xue@gmail.com> - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\console\controllers; - -use Yii; -use yii\console\Exception; -use yii\console\Controller; -use yii\db\Connection; -use yii\db\Query; -use yii\helpers\ArrayHelper; - -/** - * This command manages application migrations. - * - * A migration means a set of persistent changes to the application environment - * that is shared among different developers. For example, in an application - * backed by a database, a migration may refer to a set of changes to - * the database, such as creating a new table, adding a new table column. - * - * This command provides support for tracking the migration history, upgrading - * or downloading with migrations, and creating new migration skeletons. - * - * The migration history is stored in a database table named - * as [[migrationTable]]. The table will be automatically created the first time - * this command is executed, if it does not exist. You may also manually - * create it as follows: - * - * ~~~ - * CREATE TABLE tbl_migration ( - * version varchar(255) PRIMARY KEY, - * apply_time integer - * ) - * ~~~ - * - * Below are some common usages of this command: - * - * ~~~ - * # creates a new migration named 'create_user_table' - * yii migrate/create create_user_table - * - * # applies ALL new migrations - * yii migrate - * - * # reverts the last applied migration - * yii migrate/down - * ~~~ - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class MigrateController extends Controller -{ - /** - * The name of the dummy migration that marks the beginning of the whole migration history. - */ - const BASE_MIGRATION = 'm000000_000000_base'; - - /** - * @var string the default command action. - */ - public $defaultAction = 'up'; - /** - * @var string the directory storing the migration classes. This can be either - * a path alias or a directory. - */ - public $migrationPath = '@app/migrations'; - /** - * @var string the name of the table for keeping applied migration information. - */ - public $migrationTable = 'tbl_migration'; - /** - * @var string the template file for generating new migrations. - * This can be either a path alias (e.g. "@app/migrations/template.php") - * or a file path. - */ - public $templateFile = '@yii/views/migration.php'; - /** - * @var boolean whether to execute the migration in an interactive mode. - */ - public $interactive = true; - /** - * @var Connection|string the DB connection object or the application - * component ID of the DB connection. - */ - public $db = 'db'; - - /** - * Returns the names of the global options for this command. - * @return array the names of the global options for this command. - */ - public function globalOptions() - { - return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive'); - } - - /** - * This method is invoked right before an action is to be executed (after all possible filters.) - * It checks the existence of the [[migrationPath]]. - * @param \yii\base\Action $action the action to be executed. - * @return boolean whether the action should continue to be executed. - * @throws Exception if the migration directory does not exist. - */ - public function beforeAction($action) - { - if (parent::beforeAction($action)) { - $path = Yii::getAlias($this->migrationPath); - if (!is_dir($path)) { - throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist."); - } - $this->migrationPath = $path; - - if($action->id!=='create') { - if (is_string($this->db)) { - $this->db = Yii::$app->getComponent($this->db); - } - if (!$this->db instanceof Connection) { - throw new Exception("The 'db' option must refer to the application component ID of a DB connection."); - } - } - - $version = Yii::getVersion(); - echo "Yii Migration Tool (based on Yii v{$version})\n\n"; - return true; - } else { - return false; - } - } - - /** - * Upgrades the application by applying new migrations. - * For example, - * - * ~~~ - * yii migrate # apply all new migrations - * yii migrate 3 # apply the first 3 new migrations - * ~~~ - * - * @param integer $limit the number of new migrations to be applied. If 0, it means - * applying all available new migrations. - */ - public function actionUp($limit = 0) - { - $migrations = $this->getNewMigrations(); - if (empty($migrations)) { - echo "No new migration found. Your system is up-to-date.\n"; - Yii::$app->end(); - } - - $total = count($migrations); - $limit = (int)$limit; - if ($limit > 0) { - $migrations = array_slice($migrations, 0, $limit); - } - - $n = count($migrations); - if ($n === $total) { - echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n"; - } else { - echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n"; - } - - foreach ($migrations as $migration) { - echo " $migration\n"; - } - echo "\n"; - - if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { - foreach ($migrations as $migration) { - if (!$this->migrateUp($migration)) { - echo "\nMigration failed. The rest of the migrations are canceled.\n"; - return; - } - } - echo "\nMigrated up successfully.\n"; - } - } - - /** - * Downgrades the application by reverting old migrations. - * For example, - * - * ~~~ - * yii migrate/down # revert the last migration - * yii migrate/down 3 # revert the last 3 migrations - * ~~~ - * - * @param integer $limit the number of migrations to be reverted. Defaults to 1, - * meaning the last applied migration will be reverted. - * @throws Exception if the number of the steps specified is less than 1. - */ - public function actionDown($limit = 1) - { - $limit = (int)$limit; - if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); - } - - $migrations = $this->getMigrationHistory($limit); - if (empty($migrations)) { - echo "No migration has been done before.\n"; - return; - } - $migrations = array_keys($migrations); - - $n = count($migrations); - echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n"; - foreach ($migrations as $migration) { - echo " $migration\n"; - } - echo "\n"; - - if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { - foreach ($migrations as $migration) { - if (!$this->migrateDown($migration)) { - echo "\nMigration failed. The rest of the migrations are canceled.\n"; - return; - } - } - echo "\nMigrated down successfully.\n"; - } - } - - /** - * Redoes the last few migrations. - * - * This command will first revert the specified migrations, and then apply - * them again. For example, - * - * ~~~ - * yii migrate/redo # redo the last applied migration - * yii migrate/redo 3 # redo the last 3 applied migrations - * ~~~ - * - * @param integer $limit the number of migrations to be redone. Defaults to 1, - * meaning the last applied migration will be redone. - * @throws Exception if the number of the steps specified is less than 1. - */ - public function actionRedo($limit = 1) - { - $limit = (int)$limit; - if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); - } - - $migrations = $this->getMigrationHistory($limit); - if (empty($migrations)) { - echo "No migration has been done before.\n"; - return; - } - $migrations = array_keys($migrations); - - $n = count($migrations); - echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n"; - foreach ($migrations as $migration) { - echo " $migration\n"; - } - echo "\n"; - - if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { - foreach ($migrations as $migration) { - if (!$this->migrateDown($migration)) { - echo "\nMigration failed. The rest of the migrations are canceled.\n"; - return; - } - } - foreach (array_reverse($migrations) as $migration) { - if (!$this->migrateUp($migration)) { - echo "\nMigration failed. The rest of the migrations migrations are canceled.\n"; - return; - } - } - echo "\nMigration redone successfully.\n"; - } - } - - /** - * Upgrades or downgrades till the specified version. - * - * This command will first revert the specified migrations, and then apply - * them again. For example, - * - * ~~~ - * yii migrate/to 101129_185401 # using timestamp - * yii migrate/to m101129_185401_create_user_table # using full name - * ~~~ - * - * @param string $version the version name that the application should be migrated to. - * This can be either the timestamp or the full name of the migration. - * @throws Exception if the version argument is invalid - */ - public function actionTo($version) - { - $originalVersion = $version; - if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { - $version = 'm' . $matches[1]; - } else { - throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); - } - - // try migrate up - $migrations = $this->getNewMigrations(); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - $this->actionUp($i + 1); - return; - } - } - - // try migrate down - $migrations = array_keys($this->getMigrationHistory(-1)); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - if ($i === 0) { - echo "Already at '$originalVersion'. Nothing needs to be done.\n"; - } else { - $this->actionDown($i); - } - return; - } - } - - throw new Exception("Unable to find the version '$originalVersion'."); - } - - /** - * Modifies the migration history to the specified version. - * - * No actual migration will be performed. - * - * ~~~ - * yii migrate/mark 101129_185401 # using timestamp - * yii migrate/mark m101129_185401_create_user_table # using full name - * ~~~ - * - * @param string $version the version at which the migration history should be marked. - * This can be either the timestamp or the full name of the migration. - * @throws Exception if the version argument is invalid or the version cannot be found. - */ - public function actionMark($version) - { - $originalVersion = $version; - if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { - $version = 'm' . $matches[1]; - } else { - throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); - } - - // try mark up - $migrations = $this->getNewMigrations(); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - if ($this->confirm("Set migration history at $originalVersion?")) { - $command = $this->db->createCommand(); - for ($j = 0; $j <= $i; ++$j) { - $command->insert($this->migrationTable, array( - 'version' => $migrations[$j], - 'apply_time' => time(), - ))->execute(); - } - echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; - } - return; - } - } - - // try mark down - $migrations = array_keys($this->getMigrationHistory(-1)); - foreach ($migrations as $i => $migration) { - if (strpos($migration, $version . '_') === 0) { - if ($i === 0) { - echo "Already at '$originalVersion'. Nothing needs to be done.\n"; - } else { - if ($this->confirm("Set migration history at $originalVersion?")) { - $command = $this->db->createCommand(); - for ($j = 0; $j < $i; ++$j) { - $command->delete($this->migrationTable, array( - 'version' => $migrations[$j], - ))->execute(); - } - echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; - } - } - return; - } - } - - throw new Exception("Unable to find the version '$originalVersion'."); - } - - /** - * Displays the migration history. - * - * This command will show the list of migrations that have been applied - * so far. For example, - * - * ~~~ - * yii migrate/history # showing the last 10 migrations - * yii migrate/history 5 # showing the last 5 migrations - * yii migrate/history 0 # showing the whole history - * ~~~ - * - * @param integer $limit the maximum number of migrations to be displayed. - * If it is 0, the whole migration history will be displayed. - */ - public function actionHistory($limit = 10) - { - $limit = (int)$limit; - $migrations = $this->getMigrationHistory($limit); - if (empty($migrations)) { - echo "No migration has been done before.\n"; - } else { - $n = count($migrations); - if ($limit > 0) { - echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; - } else { - echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n"; - } - foreach ($migrations as $version => $time) { - echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n"; - } - } - } - - /** - * Displays the un-applied new migrations. - * - * This command will show the new migrations that have not been applied. - * For example, - * - * ~~~ - * yii migrate/new # showing the first 10 new migrations - * yii migrate/new 5 # showing the first 5 new migrations - * yii migrate/new 0 # showing all new migrations - * ~~~ - * - * @param integer $limit the maximum number of new migrations to be displayed. - * If it is 0, all available new migrations will be displayed. - */ - public function actionNew($limit = 10) - { - $limit = (int)$limit; - $migrations = $this->getNewMigrations(); - if (empty($migrations)) { - echo "No new migrations found. Your system is up-to-date.\n"; - } else { - $n = count($migrations); - if ($limit > 0 && $n > $limit) { - $migrations = array_slice($migrations, 0, $limit); - echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; - } else { - echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; - } - - foreach ($migrations as $migration) { - echo " " . $migration . "\n"; - } - } - } - - /** - * Creates a new migration. - * - * This command creates a new migration using the available migration template. - * After using this command, developers should modify the created migration - * skeleton by filling up the actual migration logic. - * - * ~~~ - * yii migrate/create create_user_table - * ~~~ - * - * @param string $name the name of the new migration. This should only contain - * letters, digits and/or underscores. - * @throws Exception if the name argument is invalid. - */ - public function actionCreate($name) - { - if (!preg_match('/^\w+$/', $name)) { - throw new Exception("The migration name should contain letters, digits and/or underscore characters only."); - } - - $name = 'm' . gmdate('ymd_His') . '_' . $name; - $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php'; - - if ($this->confirm("Create new migration '$file'?")) { - $content = $this->renderFile(Yii::getAlias($this->templateFile), array( - 'className' => $name, - )); - file_put_contents($file, $content); - echo "New migration created successfully.\n"; - } - } - - /** - * Upgrades with the specified migration class. - * @param string $class the migration class name - * @return boolean whether the migration is successful - */ - protected function migrateUp($class) - { - if ($class === self::BASE_MIGRATION) { - return true; - } - - echo "*** applying $class\n"; - $start = microtime(true); - $migration = $this->createMigration($class); - if ($migration->up() !== false) { - $this->db->createCommand()->insert($this->migrationTable, array( - 'version' => $class, - 'apply_time' => time(), - ))->execute(); - $time = microtime(true) - $start; - echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; - return true; - } else { - $time = microtime(true) - $start; - echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; - return false; - } - } - - /** - * Downgrades with the specified migration class. - * @param string $class the migration class name - * @return boolean whether the migration is successful - */ - protected function migrateDown($class) - { - if ($class === self::BASE_MIGRATION) { - return true; - } - - echo "*** reverting $class\n"; - $start = microtime(true); - $migration = $this->createMigration($class); - if ($migration->down() !== false) { - $this->db->createCommand()->delete($this->migrationTable, array( - 'version' => $class, - ))->execute(); - $time = microtime(true) - $start; - echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; - return true; - } else { - $time = microtime(true) - $start; - echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; - return false; - } - } - - /** - * Creates a new migration instance. - * @param string $class the migration class name - * @return \yii\db\Migration the migration instance - */ - protected function createMigration($class) - { - $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; - require_once($file); - return new $class(array( - 'db' => $this->db, - )); - } - - /** - * Returns the migration history. - * @param integer $limit the maximum number of records in the history to be returned - * @return array the migration history - */ - protected function getMigrationHistory($limit) - { - if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) { - $this->createMigrationHistoryTable(); - } - $query = new Query; - $rows = $query->select(array('version', 'apply_time')) - ->from($this->migrationTable) - ->orderBy('version DESC') - ->limit($limit) - ->createCommand() - ->queryAll(); - $history = ArrayHelper::map($rows, 'version', 'apply_time'); - unset($history[self::BASE_MIGRATION]); - return $history; - } - - /** - * Creates the migration history table. - */ - protected function createMigrationHistoryTable() - { - echo 'Creating migration history table "' . $this->migrationTable . '"...'; - $this->db->createCommand()->createTable($this->migrationTable, array( - 'version' => 'varchar(255) NOT NULL PRIMARY KEY', - 'apply_time' => 'integer', - ))->execute(); - $this->db->createCommand()->insert($this->migrationTable, array( - 'version' => self::BASE_MIGRATION, - 'apply_time' => time(), - ))->execute(); - echo "done.\n"; - } - - /** - * Returns the migrations that are not applied. - * @return array list of new migrations - */ - protected function getNewMigrations() - { - $applied = array(); - foreach ($this->getMigrationHistory(-1) as $version => $time) { - $applied[substr($version, 1, 13)] = true; - } - - $migrations = array(); - $handle = opendir($this->migrationPath); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file; - if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) { - $migrations[] = $matches[1]; - } - } - closedir($handle); - sort($migrations); - return $migrations; - } -} +<?php +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\console\controllers; + +use Yii; +use yii\console\Exception; +use yii\console\Controller; +use yii\db\Connection; +use yii\db\Query; +use yii\helpers\ArrayHelper; + +/** + * This command manages application migrations. + * + * A migration means a set of persistent changes to the application environment + * that is shared among different developers. For example, in an application + * backed by a database, a migration may refer to a set of changes to + * the database, such as creating a new table, adding a new table column. + * + * This command provides support for tracking the migration history, upgrading + * or downloading with migrations, and creating new migration skeletons. + * + * The migration history is stored in a database table named + * as [[migrationTable]]. The table will be automatically created the first time + * this command is executed, if it does not exist. You may also manually + * create it as follows: + * + * ~~~ + * CREATE TABLE tbl_migration ( + * version varchar(255) PRIMARY KEY, + * apply_time integer + * ) + * ~~~ + * + * Below are some common usages of this command: + * + * ~~~ + * # creates a new migration named 'create_user_table' + * yii migrate/create create_user_table + * + * # applies ALL new migrations + * yii migrate + * + * # reverts the last applied migration + * yii migrate/down + * ~~~ + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class MigrateController extends Controller +{ + /** + * The name of the dummy migration that marks the beginning of the whole migration history. + */ + const BASE_MIGRATION = 'm000000_000000_base'; + + /** + * @var string the default command action. + */ + public $defaultAction = 'up'; + /** + * @var string the directory storing the migration classes. This can be either + * a path alias or a directory. + */ + public $migrationPath = '@app/migrations'; + /** + * @var string the name of the table for keeping applied migration information. + */ + public $migrationTable = 'tbl_migration'; + /** + * @var string the template file for generating new migrations. + * This can be either a path alias (e.g. "@app/migrations/template.php") + * or a file path. + */ + public $templateFile = '@yii/views/migration.php'; + /** + * @var boolean whether to execute the migration in an interactive mode. + */ + public $interactive = true; + /** + * @var Connection|string the DB connection object or the application + * component ID of the DB connection. + */ + public $db = 'db'; + + /** + * Returns the names of the global options for this command. + * @return array the names of the global options for this command. + */ + public function globalOptions() + { + return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive'); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * It checks the existence of the [[migrationPath]]. + * @param \yii\base\Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + * @throws Exception if the migration directory does not exist. + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + $path = Yii::getAlias($this->migrationPath); + if (!is_dir($path)) { + throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist."); + } + $this->migrationPath = $path; + + if ($action->id !== 'create') { + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new Exception("The 'db' option must refer to the application component ID of a DB connection."); + } + } + + $version = Yii::getVersion(); + echo "Yii Migration Tool (based on Yii v{$version})\n\n"; + return true; + } else { + return false; + } + } + + /** + * Upgrades the application by applying new migrations. + * For example, + * + * ~~~ + * yii migrate # apply all new migrations + * yii migrate 3 # apply the first 3 new migrations + * ~~~ + * + * @param integer $limit the number of new migrations to be applied. If 0, it means + * applying all available new migrations. + */ + public function actionUp($limit = 0) + { + $migrations = $this->getNewMigrations(); + if (empty($migrations)) { + echo "No new migration found. Your system is up-to-date.\n"; + Yii::$app->end(); + } + + $total = count($migrations); + $limit = (int)$limit; + if ($limit > 0) { + $migrations = array_slice($migrations, 0, $limit); + } + + $n = count($migrations); + if ($n === $total) { + echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n"; + } else { + echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n"; + } + + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateUp($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + return; + } + } + echo "\nMigrated up successfully.\n"; + } + } + + /** + * Downgrades the application by reverting old migrations. + * For example, + * + * ~~~ + * yii migrate/down # revert the last migration + * yii migrate/down 3 # revert the last 3 migrations + * ~~~ + * + * @param integer $limit the number of migrations to be reverted. Defaults to 1, + * meaning the last applied migration will be reverted. + * @throws Exception if the number of the steps specified is less than 1. + */ + public function actionDown($limit = 1) + { + $limit = (int)$limit; + if ($limit < 1) { + throw new Exception("The step argument must be greater than 0."); + } + + $migrations = $this->getMigrationHistory($limit); + if (empty($migrations)) { + echo "No migration has been done before.\n"; + return; + } + $migrations = array_keys($migrations); + + $n = count($migrations); + echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n"; + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateDown($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + return; + } + } + echo "\nMigrated down successfully.\n"; + } + } + + /** + * Redoes the last few migrations. + * + * This command will first revert the specified migrations, and then apply + * them again. For example, + * + * ~~~ + * yii migrate/redo # redo the last applied migration + * yii migrate/redo 3 # redo the last 3 applied migrations + * ~~~ + * + * @param integer $limit the number of migrations to be redone. Defaults to 1, + * meaning the last applied migration will be redone. + * @throws Exception if the number of the steps specified is less than 1. + */ + public function actionRedo($limit = 1) + { + $limit = (int)$limit; + if ($limit < 1) { + throw new Exception("The step argument must be greater than 0."); + } + + $migrations = $this->getMigrationHistory($limit); + if (empty($migrations)) { + echo "No migration has been done before.\n"; + return; + } + $migrations = array_keys($migrations); + + $n = count($migrations); + echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n"; + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateDown($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + return; + } + } + foreach (array_reverse($migrations) as $migration) { + if (!$this->migrateUp($migration)) { + echo "\nMigration failed. The rest of the migrations migrations are canceled.\n"; + return; + } + } + echo "\nMigration redone successfully.\n"; + } + } + + /** + * Upgrades or downgrades till the specified version. + * + * This command will first revert the specified migrations, and then apply + * them again. For example, + * + * ~~~ + * yii migrate/to 101129_185401 # using timestamp + * yii migrate/to m101129_185401_create_user_table # using full name + * ~~~ + * + * @param string $version the version name that the application should be migrated to. + * This can be either the timestamp or the full name of the migration. + * @throws Exception if the version argument is invalid + */ + public function actionTo($version) + { + $originalVersion = $version; + if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { + $version = 'm' . $matches[1]; + } else { + throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); + } + + // try migrate up + $migrations = $this->getNewMigrations(); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + $this->actionUp($i + 1); + return; + } + } + + // try migrate down + $migrations = array_keys($this->getMigrationHistory(-1)); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($i === 0) { + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + } else { + $this->actionDown($i); + } + return; + } + } + + throw new Exception("Unable to find the version '$originalVersion'."); + } + + /** + * Modifies the migration history to the specified version. + * + * No actual migration will be performed. + * + * ~~~ + * yii migrate/mark 101129_185401 # using timestamp + * yii migrate/mark m101129_185401_create_user_table # using full name + * ~~~ + * + * @param string $version the version at which the migration history should be marked. + * This can be either the timestamp or the full name of the migration. + * @throws Exception if the version argument is invalid or the version cannot be found. + */ + public function actionMark($version) + { + $originalVersion = $version; + if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { + $version = 'm' . $matches[1]; + } else { + throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); + } + + // try mark up + $migrations = $this->getNewMigrations(); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($this->confirm("Set migration history at $originalVersion?")) { + $command = $this->db->createCommand(); + for ($j = 0; $j <= $i; ++$j) { + $command->insert($this->migrationTable, array( + 'version' => $migrations[$j], + 'apply_time' => time(), + ))->execute(); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + return; + } + } + + // try mark down + $migrations = array_keys($this->getMigrationHistory(-1)); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($i === 0) { + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + } else { + if ($this->confirm("Set migration history at $originalVersion?")) { + $command = $this->db->createCommand(); + for ($j = 0; $j < $i; ++$j) { + $command->delete($this->migrationTable, array( + 'version' => $migrations[$j], + ))->execute(); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + } + return; + } + } + + throw new Exception("Unable to find the version '$originalVersion'."); + } + + /** + * Displays the migration history. + * + * This command will show the list of migrations that have been applied + * so far. For example, + * + * ~~~ + * yii migrate/history # showing the last 10 migrations + * yii migrate/history 5 # showing the last 5 migrations + * yii migrate/history 0 # showing the whole history + * ~~~ + * + * @param integer $limit the maximum number of migrations to be displayed. + * If it is 0, the whole migration history will be displayed. + */ + public function actionHistory($limit = 10) + { + $limit = (int)$limit; + $migrations = $this->getMigrationHistory($limit); + if (empty($migrations)) { + echo "No migration has been done before.\n"; + } else { + $n = count($migrations); + if ($limit > 0) { + echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } else { + echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n"; + } + foreach ($migrations as $version => $time) { + echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n"; + } + } + } + + /** + * Displays the un-applied new migrations. + * + * This command will show the new migrations that have not been applied. + * For example, + * + * ~~~ + * yii migrate/new # showing the first 10 new migrations + * yii migrate/new 5 # showing the first 5 new migrations + * yii migrate/new 0 # showing all new migrations + * ~~~ + * + * @param integer $limit the maximum number of new migrations to be displayed. + * If it is 0, all available new migrations will be displayed. + */ + public function actionNew($limit = 10) + { + $limit = (int)$limit; + $migrations = $this->getNewMigrations(); + if (empty($migrations)) { + echo "No new migrations found. Your system is up-to-date.\n"; + } else { + $n = count($migrations); + if ($limit > 0 && $n > $limit) { + $migrations = array_slice($migrations, 0, $limit); + echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } else { + echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } + + foreach ($migrations as $migration) { + echo " " . $migration . "\n"; + } + } + } + + /** + * Creates a new migration. + * + * This command creates a new migration using the available migration template. + * After using this command, developers should modify the created migration + * skeleton by filling up the actual migration logic. + * + * ~~~ + * yii migrate/create create_user_table + * ~~~ + * + * @param string $name the name of the new migration. This should only contain + * letters, digits and/or underscores. + * @throws Exception if the name argument is invalid. + */ + public function actionCreate($name) + { + if (!preg_match('/^\w+$/', $name)) { + throw new Exception("The migration name should contain letters, digits and/or underscore characters only."); + } + + $name = 'm' . gmdate('ymd_His') . '_' . $name; + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php'; + + if ($this->confirm("Create new migration '$file'?")) { + $content = $this->renderFile(Yii::getAlias($this->templateFile), array( + 'className' => $name, + )); + file_put_contents($file, $content); + echo "New migration created successfully.\n"; + } + } + + /** + * Upgrades with the specified migration class. + * @param string $class the migration class name + * @return boolean whether the migration is successful + */ + protected function migrateUp($class) + { + if ($class === self::BASE_MIGRATION) { + return true; + } + + echo "*** applying $class\n"; + $start = microtime(true); + $migration = $this->createMigration($class); + if ($migration->up() !== false) { + $this->db->createCommand()->insert($this->migrationTable, array( + 'version' => $class, + 'apply_time' => time(), + ))->execute(); + $time = microtime(true) - $start; + echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + return true; + } else { + $time = microtime(true) - $start; + echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + return false; + } + } + + /** + * Downgrades with the specified migration class. + * @param string $class the migration class name + * @return boolean whether the migration is successful + */ + protected function migrateDown($class) + { + if ($class === self::BASE_MIGRATION) { + return true; + } + + echo "*** reverting $class\n"; + $start = microtime(true); + $migration = $this->createMigration($class); + if ($migration->down() !== false) { + $this->db->createCommand()->delete($this->migrationTable, array( + 'version' => $class, + ))->execute(); + $time = microtime(true) - $start; + echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + return true; + } else { + $time = microtime(true) - $start; + echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + return false; + } + } + + /** + * Creates a new migration instance. + * @param string $class the migration class name + * @return \yii\db\Migration the migration instance + */ + protected function createMigration($class) + { + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; + require_once($file); + return new $class(array( + 'db' => $this->db, + )); + } + + /** + * Returns the migration history. + * @param integer $limit the maximum number of records in the history to be returned + * @return array the migration history + */ + protected function getMigrationHistory($limit) + { + if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) { + $this->createMigrationHistoryTable(); + } + $query = new Query; + $rows = $query->select(array('version', 'apply_time')) + ->from($this->migrationTable) + ->orderBy('version DESC') + ->limit($limit) + ->createCommand() + ->queryAll(); + $history = ArrayHelper::map($rows, 'version', 'apply_time'); + unset($history[self::BASE_MIGRATION]); + return $history; + } + + /** + * Creates the migration history table. + */ + protected function createMigrationHistoryTable() + { + echo 'Creating migration history table "' . $this->migrationTable . '"...'; + $this->db->createCommand()->createTable($this->migrationTable, array( + 'version' => 'varchar(255) NOT NULL PRIMARY KEY', + 'apply_time' => 'integer', + ))->execute(); + $this->db->createCommand()->insert($this->migrationTable, array( + 'version' => self::BASE_MIGRATION, + 'apply_time' => time(), + ))->execute(); + echo "done.\n"; + } + + /** + * Returns the migrations that are not applied. + * @return array list of new migrations + */ + protected function getNewMigrations() + { + $applied = array(); + foreach ($this->getMigrationHistory(-1) as $version => $time) { + $applied[substr($version, 1, 13)] = true; + } + + $migrations = array(); + $handle = opendir($this->migrationPath); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) { + $migrations[] = $matches[1]; + } + } + closedir($handle); + sort($migrations); + return $migrations; + } +} diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 3a4d0ad..0dd47d8 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -305,8 +305,7 @@ class Connection extends Component $this->pdo = $this->createPdoInstance(); $this->initConnection(); Yii::endProfile($token, __METHOD__); - } - catch (\PDOException $e) { + } catch (\PDOException $e) { Yii::endProfile($token, __METHOD__); Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__); $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; @@ -508,7 +507,7 @@ class Connection extends Component { $db = $this; return preg_replace_callback('/(\\{\\{([%\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/', - function($matches) use($db) { + function ($matches) use ($db) { if (isset($matches[3])) { return $db->quoteColumnName($matches[3]); } else { diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php index 3421d95..a680f53 100644 --- a/framework/yii/debug/Module.php +++ b/framework/yii/debug/Module.php @@ -14,4 +14,4 @@ namespace yii\debug; class Module extends \yii\base\Module { public $controllerNamespace = 'yii\debug\controllers'; -} \ No newline at end of file +} diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php index 4d686ee..f1160b1 100644 --- a/framework/yii/debug/controllers/DefaultController.php +++ b/framework/yii/debug/controllers/DefaultController.php @@ -31,4 +31,4 @@ class DefaultController extends Controller echo "Unable to find debug data tagged with '$tag'."; } } -} \ No newline at end of file +} diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 5e77c3f..117db1f 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,5 +14,4 @@ namespace yii\helpers; */ class Json extends base\Json { - } diff --git a/framework/yii/helpers/base/Console.php b/framework/yii/helpers/base/Console.php index 6ad0b7b..e3acbd9 100644 --- a/framework/yii/helpers/base/Console.php +++ b/framework/yii/helpers/base/Console.php @@ -286,7 +286,7 @@ class Console * You can pass any of the FG_*, BG_* and TEXT_* constants and also [[xtermFgColor]] and [[xtermBgColor]]. * @return string */ - public static function ansiFormat($string, $format=array()) + public static function ansiFormat($string, $format = array()) { $code = implode(';', $format); return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; @@ -589,11 +589,10 @@ class Console if (static::isRunningOnWindows()) { $output = array(); exec('mode con', $output); - if(isset($output) && strpos($output[1], 'CON')!==false) { + if (isset($output) && strpos($output[1], 'CON') !== false) { return $size = array((int)preg_replace('~[^0-9]~', '', $output[3]), (int)preg_replace('~[^0-9]~', '', $output[4])); } } else { - // try stty if available $stty = array(); if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) { diff --git a/framework/yii/helpers/base/Html.php b/framework/yii/helpers/base/Html.php index 9a0001c..47385e2 100644 --- a/framework/yii/helpers/base/Html.php +++ b/framework/yii/helpers/base/Html.php @@ -1479,5 +1479,4 @@ class Html $name = strtolower(static::getInputName($model, $attribute)); return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); } - } diff --git a/framework/yii/helpers/base/Inflector.php b/framework/yii/helpers/base/Inflector.php index cc5d33f..f500c4c 100644 --- a/framework/yii/helpers/base/Inflector.php +++ b/framework/yii/helpers/base/Inflector.php @@ -50,7 +50,7 @@ class Inflector '/(ax|cris|test)is$/i' => '\1es', '/s$/' => 's', '/^$/' => '', - '/$/' => 's', + '/$/' => 's', ); /** * @var array the rules for converting a word into its singular form. @@ -94,7 +94,7 @@ class Inflector '/(n)ews$/i' => '\1\2ews', '/eaus$/' => 'eau', '/^(.*us)$/' => '\\1', - '/s$/i' => '', + '/s$/i' => '', ); /** * @var array the special rules for converting a word between its plural form and singular form. @@ -468,7 +468,7 @@ class Inflector if (in_array(($number % 100), range(11, 13))) { return $number . 'th'; } - switch (($number % 10)) { + switch ($number % 10) { case 1: return $number . 'st'; case 2: return $number . 'nd'; case 3: return $number . 'rd'; diff --git a/framework/yii/i18n/MessageSource.php b/framework/yii/i18n/MessageSource.php index cf23338..90adbfb 100644 --- a/framework/yii/i18n/MessageSource.php +++ b/framework/yii/i18n/MessageSource.php @@ -118,4 +118,3 @@ class MessageSource extends Component } } } - diff --git a/framework/yii/jui/Accordion.php b/framework/yii/jui/Accordion.php index f36c981..898649e 100644 --- a/framework/yii/jui/Accordion.php +++ b/framework/yii/jui/Accordion.php @@ -125,7 +125,7 @@ class Accordion extends Widget $items[] = Html::tag($headerTag, $item['header'], $headerOptions); $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); $tag = ArrayHelper::remove($options, 'tag', 'div'); - $items[] = Html::tag($tag, $item['content'], $options);; + $items[] = Html::tag($tag, $item['content'], $options); } return implode("\n", $items); diff --git a/framework/yii/jui/Menu.php b/framework/yii/jui/Menu.php index d4e390c..83523e7 100644 --- a/framework/yii/jui/Menu.php +++ b/framework/yii/jui/Menu.php @@ -10,7 +10,6 @@ namespace yii\jui; use Yii; use yii\helpers\Json; - /** * Menu renders a menu jQuery UI widget. * diff --git a/framework/yii/jui/Widget.php b/framework/yii/jui/Widget.php index d34a8bd..5724919 100644 --- a/framework/yii/jui/Widget.php +++ b/framework/yii/jui/Widget.php @@ -10,7 +10,6 @@ namespace yii\jui; use Yii; use yii\helpers\Json; - /** * \yii\jui\Widget is the base class for all jQuery UI widgets. * diff --git a/framework/yii/rbac/DbManager.php b/framework/yii/rbac/DbManager.php index b7a5d4e..8d3bea2 100644 --- a/framework/yii/rbac/DbManager.php +++ b/framework/yii/rbac/DbManager.php @@ -493,8 +493,9 @@ class DbManager extends Manager 'bizRule' => $row['biz_rule'], 'data' => $data, )); - } else + } else { return null; + } } /** diff --git a/framework/yii/rbac/PhpManager.php b/framework/yii/rbac/PhpManager.php index 7a476e0..8ecc75c 100644 --- a/framework/yii/rbac/PhpManager.php +++ b/framework/yii/rbac/PhpManager.php @@ -468,7 +468,7 @@ class PhpManager extends Manager 'bizRule' => $assignment['bizRule'], 'data' => $assignment['data'], )); - } + } } } } diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 63aa70d..670544d 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -45,4 +45,4 @@ return array( 'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.' ), -); \ No newline at end of file +); diff --git a/framework/yii/validators/CaptchaValidator.php b/framework/yii/validators/CaptchaValidator.php index dbc263e..01870d3 100644 --- a/framework/yii/validators/CaptchaValidator.php +++ b/framework/yii/validators/CaptchaValidator.php @@ -117,4 +117,3 @@ class CaptchaValidator extends Validator return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; } } - diff --git a/framework/yii/validators/DateValidator.php b/framework/yii/validators/DateValidator.php index 2f3ce2d..2f9e18b 100644 --- a/framework/yii/validators/DateValidator.php +++ b/framework/yii/validators/DateValidator.php @@ -73,4 +73,3 @@ class DateValidator extends Validator return DateTime::createFromFormat($this->format, $value) !== false; } } - diff --git a/framework/yii/validators/DefaultValueValidator.php b/framework/yii/validators/DefaultValueValidator.php index 185dbd4..20df5fd 100644 --- a/framework/yii/validators/DefaultValueValidator.php +++ b/framework/yii/validators/DefaultValueValidator.php @@ -40,4 +40,3 @@ class DefaultValueValidator extends Validator } } } - diff --git a/framework/yii/validators/ExistValidator.php b/framework/yii/validators/ExistValidator.php index 7c45491..9c74890 100644 --- a/framework/yii/validators/ExistValidator.php +++ b/framework/yii/validators/ExistValidator.php @@ -99,4 +99,3 @@ class ExistValidator extends Validator return $query->exists(); } } - diff --git a/framework/yii/validators/FilterValidator.php b/framework/yii/validators/FilterValidator.php index 72a9a9d..560feb1 100644 --- a/framework/yii/validators/FilterValidator.php +++ b/framework/yii/validators/FilterValidator.php @@ -6,6 +6,7 @@ */ namespace yii\validators; + use yii\base\InvalidConfigException; /** diff --git a/framework/yii/validators/RangeValidator.php b/framework/yii/validators/RangeValidator.php index a915275..90256df 100644 --- a/framework/yii/validators/RangeValidator.php +++ b/framework/yii/validators/RangeValidator.php @@ -35,7 +35,7 @@ class RangeValidator extends Validator * @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]]. **/ - public $not = false; + public $not = false; /** * Initializes the validator. diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php index 417f2bc..72a5a74 100644 --- a/framework/yii/validators/RegularExpressionValidator.php +++ b/framework/yii/validators/RegularExpressionValidator.php @@ -32,7 +32,7 @@ class RegularExpressionValidator extends Validator * 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. diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php index abe4634..e06354b 100644 --- a/framework/yii/validators/StringValidator.php +++ b/framework/yii/validators/StringValidator.php @@ -174,4 +174,3 @@ class StringValidator extends Validator return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; } } - diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php index 6cf12c1..18f2f45 100644 --- a/framework/yii/validators/UrlValidator.php +++ b/framework/yii/validators/UrlValidator.php @@ -99,7 +99,7 @@ class UrlValidator extends Validator } if ($this->enableIDN) { - $value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) { + $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) { return '://' . idn_to_ascii($matches[1]); }, $value); } diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php index 6b103bf..2629002 100644 --- a/framework/yii/validators/Validator.php +++ b/framework/yii/validators/Validator.php @@ -179,7 +179,7 @@ abstract class Validator extends Component } foreach ($attributes as $attribute) { $skip = $this->skipOnError && $object->hasErrors($attribute) - || $this->skipOnEmpty && $this->isEmpty($object->$attribute); + || $this->skipOnEmpty && $this->isEmpty($object->$attribute); if (!$skip) { $this->validateAttribute($object, $attribute); } diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php index 7a24854..c7e1462 100644 --- a/framework/yii/web/HeaderCollection.php +++ b/framework/yii/web/HeaderCollection.php @@ -11,7 +11,6 @@ use Yii; use yii\base\Object; use ArrayIterator; - /** * HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers. * diff --git a/framework/yii/web/PageCache.php b/framework/yii/web/PageCache.php index 2fe36b3..8b28e62 100644 --- a/framework/yii/web/PageCache.php +++ b/framework/yii/web/PageCache.php @@ -1,104 +1,104 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\web; - -use Yii; -use yii\base\ActionFilter; -use yii\base\Action; -use yii\base\View; -use yii\caching\Dependency; - -/** - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class PageCache extends ActionFilter -{ - /** - * @var boolean whether the content being cached should be differentiated according to the route. - * A route consists of the requested controller ID and action ID. Defaults to true. - */ - public $varyByRoute = true; - /** - * @var string the application component ID of the [[\yii\caching\Cache|cache]] object. - */ - public $cache = 'cache'; - /** - * @var integer number of seconds that the data can remain valid in cache. - * Use 0 to indicate that the cached data will never expire. - */ - public $duration = 60; - /** - * @var array|Dependency the dependency that the cached content depends on. - * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. - * For example, - * - * ~~~ - * array( - * 'class' => 'yii\caching\DbDependency', - * 'sql' => 'SELECT MAX(lastModified) FROM Post', - * ) - * ~~~ - * - * would make the output cache depends on the last modified time of all posts. - * If any post has its modification time changed, the cached content would be invalidated. - */ - public $dependency; - /** - * @var array list of factors that would cause the variation of the content being cached. - * Each factor is a string representing a variation (e.g. the language, a GET parameter). - * The following variation setting will cause the content to be cached in different versions - * according to the current application language: - * - * ~~~ - * array( - * Yii::$app->language, - * ) - */ - public $variations; - /** - * @var boolean whether to enable the fragment cache. You may use this property to turn on and off - * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). - */ - public $enabled = true; - - - public function init() - { - parent::init(); - if ($this->view === null) { - $this->view = Yii::$app->getView(); - } - } - - /** - * This method is invoked right before an action is to be executed (after all possible filters.) - * You may override this method to do last-minute preparation for the action. - * @param Action $action the action to be executed. - * @return boolean whether the action should continue to be executed. - */ - public function beforeAction($action) - { - $properties = array(); - foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) { - $properties[$name] = $this->$name; - } - $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__; - return $this->view->beginCache($id, $properties); - } - - /** - * This method is invoked right after an action is executed. - * You may override this method to do some postprocessing for the action. - * @param Action $action the action just executed. - */ - public function afterAction($action) - { - $this->view->endCache(); - } +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +use Yii; +use yii\base\ActionFilter; +use yii\base\Action; +use yii\base\View; +use yii\caching\Dependency; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class PageCache extends ActionFilter +{ + /** + * @var boolean whether the content being cached should be differentiated according to the route. + * A route consists of the requested controller ID and action ID. Defaults to true. + */ + public $varyByRoute = true; + /** + * @var string the application component ID of the [[\yii\caching\Cache|cache]] object. + */ + public $cache = 'cache'; + /** + * @var integer number of seconds that the data can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + */ + public $duration = 60; + /** + * @var array|Dependency the dependency that the cached content depends on. + * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. + * For example, + * + * ~~~ + * array( + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT MAX(lastModified) FROM Post', + * ) + * ~~~ + * + * would make the output cache depends on the last modified time of all posts. + * If any post has its modification time changed, the cached content would be invalidated. + */ + public $dependency; + /** + * @var array list of factors that would cause the variation of the content being cached. + * Each factor is a string representing a variation (e.g. the language, a GET parameter). + * The following variation setting will cause the content to be cached in different versions + * according to the current application language: + * + * ~~~ + * array( + * Yii::$app->language, + * ) + */ + public $variations; + /** + * @var boolean whether to enable the fragment cache. You may use this property to turn on and off + * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). + */ + public $enabled = true; + + + public function init() + { + parent::init(); + if ($this->view === null) { + $this->view = Yii::$app->getView(); + } + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + $properties = array(); + foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) { + $properties[$name] = $this->$name; + } + $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__; + return $this->view->beginCache($id, $properties); + } + + /** + * This method is invoked right after an action is executed. + * You may override this method to do some postprocessing for the action. + * @param Action $action the action just executed. + */ + public function afterAction($action) + { + $this->view->endCache(); + } } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index e73c3b1..7cec044 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -792,4 +792,3 @@ class Request extends \yii\base\Request } } } - diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 5522bbb..005f987 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -221,7 +221,7 @@ class User extends Component if ($destroySession) { Yii::$app->getSession()->destroy(); } - $this->afterLogout($identity); + $this->afterLogout($identity); } } diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php index 8445955..0fd8646 100644 --- a/framework/yii/widgets/FragmentCache.php +++ b/framework/yii/widgets/FragmentCache.php @@ -1,174 +1,174 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\widgets; - -use Yii; -use yii\base\Widget; -use yii\caching\Cache; -use yii\caching\Dependency; - -/** - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class FragmentCache extends Widget -{ - /** - * @var Cache|string the cache object or the application component ID of the cache object. - * After the FragmentCache object is created, if you want to change this property, - * you should only assign it with a cache object. - */ - public $cache = 'cache'; - /** - * @var integer number of seconds that the data can remain valid in cache. - * Use 0 to indicate that the cached data will never expire. - */ - public $duration = 60; - /** - * @var array|Dependency the dependency that the cached content depends on. - * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. - * For example, - * - * ~~~ - * array( - * 'class' => 'yii\caching\DbDependency', - * 'sql' => 'SELECT MAX(lastModified) FROM Post', - * ) - * ~~~ - * - * would make the output cache depends on the last modified time of all posts. - * If any post has its modification time changed, the cached content would be invalidated. - */ - public $dependency; - /** - * @var array list of factors that would cause the variation of the content being cached. - * Each factor is a string representing a variation (e.g. the language, a GET parameter). - * The following variation setting will cause the content to be cached in different versions - * according to the current application language: - * - * ~~~ - * array( - * Yii::$app->language, - * ) - */ - public $variations; - /** - * @var boolean whether to enable the fragment cache. You may use this property to turn on and off - * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). - */ - public $enabled = true; - /** - * @var array a list of placeholders for embedding dynamic contents. This property - * is used internally to implement the content caching feature. Do not modify it. - */ - public $dynamicPlaceholders; - - /** - * Initializes the FragmentCache object. - */ - public function init() - { - parent::init(); - - if (!$this->enabled) { - $this->cache = null; - } elseif (is_string($this->cache)) { - $this->cache = Yii::$app->getComponent($this->cache); - } - - if ($this->getCachedContent() === false) { - $this->view->cacheStack[] = $this; - ob_start(); - ob_implicit_flush(false); - } - } - - /** - * Marks the end of content to be cached. - * Content displayed before this method call and after {@link init()} - * will be captured and saved in cache. - * This method does nothing if valid content is already found in cache. - */ - public function run() - { - if (($content = $this->getCachedContent()) !== false) { - echo $content; - } elseif ($this->cache instanceof Cache) { - $content = ob_get_clean(); - array_pop($this->view->cacheStack); - if (is_array($this->dependency)) { - $this->dependency = Yii::createObject($this->dependency); - } - $data = array($content, $this->dynamicPlaceholders); - $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency); - - if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) { - $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders); - } - echo $content; - } - } - - /** - * @var string|boolean the cached content. False if the content is not cached. - */ - private $_content; - - /** - * Returns the cached content if available. - * @return string|boolean the cached content. False is returned if valid content is not found in the cache. - */ - public function getCachedContent() - { - if ($this->_content === null) { - $this->_content = false; - if ($this->cache instanceof Cache) { - $key = $this->calculateKey(); - $data = $this->cache->get($key); - if (is_array($data) && count($data) === 2) { - list ($content, $placeholders) = $data; - if (is_array($placeholders) && count($placeholders) > 0) { - if (empty($this->view->cacheStack)) { - // outermost cache: replace placeholder with dynamic content - $content = $this->updateDynamicContent($content, $placeholders); - } - foreach ($placeholders as $name => $statements) { - $this->view->addDynamicPlaceholder($name, $statements); - } - } - $this->_content = $content; - } - } - } - return $this->_content; - } - - protected function updateDynamicContent($content, $placeholders) - { - foreach ($placeholders as $name => $statements) { - $placeholders[$name] = $this->view->evaluateDynamicContent($statements); - } - return strtr($content, $placeholders); - } - - /** - * Generates a unique key used for storing the content in cache. - * The key generated depends on both [[id]] and [[variations]]. - * @return mixed a valid cache key - */ - protected function calculateKey() - { - $factors = array(__CLASS__, $this->getId()); - if (is_array($this->variations)) { - foreach ($this->variations as $factor) { - $factors[] = $factor; - } - } - return $factors; - } -} +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\widgets; + +use Yii; +use yii\base\Widget; +use yii\caching\Cache; +use yii\caching\Dependency; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class FragmentCache extends Widget +{ + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * After the FragmentCache object is created, if you want to change this property, + * you should only assign it with a cache object. + */ + public $cache = 'cache'; + /** + * @var integer number of seconds that the data can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + */ + public $duration = 60; + /** + * @var array|Dependency the dependency that the cached content depends on. + * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. + * For example, + * + * ~~~ + * array( + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT MAX(lastModified) FROM Post', + * ) + * ~~~ + * + * would make the output cache depends on the last modified time of all posts. + * If any post has its modification time changed, the cached content would be invalidated. + */ + public $dependency; + /** + * @var array list of factors that would cause the variation of the content being cached. + * Each factor is a string representing a variation (e.g. the language, a GET parameter). + * The following variation setting will cause the content to be cached in different versions + * according to the current application language: + * + * ~~~ + * array( + * Yii::$app->language, + * ) + */ + public $variations; + /** + * @var boolean whether to enable the fragment cache. You may use this property to turn on and off + * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). + */ + public $enabled = true; + /** + * @var array a list of placeholders for embedding dynamic contents. This property + * is used internally to implement the content caching feature. Do not modify it. + */ + public $dynamicPlaceholders; + + /** + * Initializes the FragmentCache object. + */ + public function init() + { + parent::init(); + + if (!$this->enabled) { + $this->cache = null; + } elseif (is_string($this->cache)) { + $this->cache = Yii::$app->getComponent($this->cache); + } + + if ($this->getCachedContent() === false) { + $this->view->cacheStack[] = $this; + ob_start(); + ob_implicit_flush(false); + } + } + + /** + * Marks the end of content to be cached. + * Content displayed before this method call and after {@link init()} + * will be captured and saved in cache. + * This method does nothing if valid content is already found in cache. + */ + public function run() + { + if (($content = $this->getCachedContent()) !== false) { + echo $content; + } elseif ($this->cache instanceof Cache) { + $content = ob_get_clean(); + array_pop($this->view->cacheStack); + if (is_array($this->dependency)) { + $this->dependency = Yii::createObject($this->dependency); + } + $data = array($content, $this->dynamicPlaceholders); + $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency); + + if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) { + $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders); + } + echo $content; + } + } + + /** + * @var string|boolean the cached content. False if the content is not cached. + */ + private $_content; + + /** + * Returns the cached content if available. + * @return string|boolean the cached content. False is returned if valid content is not found in the cache. + */ + public function getCachedContent() + { + if ($this->_content === null) { + $this->_content = false; + if ($this->cache instanceof Cache) { + $key = $this->calculateKey(); + $data = $this->cache->get($key); + if (is_array($data) && count($data) === 2) { + list ($content, $placeholders) = $data; + if (is_array($placeholders) && count($placeholders) > 0) { + if (empty($this->view->cacheStack)) { + // outermost cache: replace placeholder with dynamic content + $content = $this->updateDynamicContent($content, $placeholders); + } + foreach ($placeholders as $name => $statements) { + $this->view->addDynamicPlaceholder($name, $statements); + } + } + $this->_content = $content; + } + } + } + return $this->_content; + } + + protected function updateDynamicContent($content, $placeholders) + { + foreach ($placeholders as $name => $statements) { + $placeholders[$name] = $this->view->evaluateDynamicContent($statements); + } + return strtr($content, $placeholders); + } + + /** + * Generates a unique key used for storing the content in cache. + * The key generated depends on both [[id]] and [[variations]]. + * @return mixed a valid cache key + */ + protected function calculateKey() + { + $factors = array(__CLASS__, $this->getId()); + if (is_array($this->variations)) { + foreach ($this->variations as $factor) { + $factors[] = $factor; + } + } + return $factors; + } +} diff --git a/framework/yii/widgets/ListPager.php b/framework/yii/widgets/ListPager.php index 699126d..30371d3 100644 --- a/framework/yii/widgets/ListPager.php +++ b/framework/yii/widgets/ListPager.php @@ -91,5 +91,4 @@ class ListPager extends Widget '{page}' => $page + 1, )); } - } From 879b494acbf4463781f37437f16b92d6c10dced0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani <gevik@xs4all.nl> Date: Fri, 7 Jun 2013 21:28:58 +0200 Subject: [PATCH 79/91] Removed custom pgsql PDO and added defaultSchema as public property. --- framework/yii/db/Connection.php | 2 - framework/yii/db/pgsql/PDO.php | 103 -------------------------------------- framework/yii/db/pgsql/Schema.php | 15 ++---- 3 files changed, 5 insertions(+), 115 deletions(-) delete mode 100644 framework/yii/db/pgsql/PDO.php diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 6a252bf..3a4d0ad 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -343,8 +343,6 @@ class Connection extends Component $driver = strtolower(substr($this->dsn, 0, $pos)); if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { $pdoClass = 'yii\db\mssql\PDO'; - } else if ($driver === 'pgsql') { - $pdoClass = 'yii\db\pgsql\PDO'; } } return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php deleted file mode 100644 index 3f5996a..0000000 --- a/framework/yii/db/pgsql/PDO.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php - -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\db\pgsql; - -/** - * This is an extension of the default PDO class for PostgreSQL drivers. - * It provides additional low level functionality for setting database - * configuration parameters. - * - * @author Gevik babakhani <gevikb@gmail.com> - * @since 2.0 - */ -class PDO extends \PDO -{ - - const OPT_SEARCH_PATH = 'search_path'; - const OPT_DEFAULT_SCHEMA = 'default_schema'; - const DEFAULT_SCHEMA = 'public'; - - private $_currentDatabase; - - /** - * Returns value of the last inserted ID. - * @param string|null $sequence the sequence name. Defaults to null. - * @return integer last inserted ID value. - */ - public function lastInsertId($sequence = null) { - if ($sequence !== null) { - $sequence = $this->quote($sequence); - return $this->query("SELECT currval({$sequence})")->fetchColumn(); - } else { - return null; - } - } - - /** - * Here we override the default PDO constructor in order to - * find and set the default schema search path. - */ - public function __construct($dsn, $username, $passwd, $options) { - $searchPath = null; - if (is_array($options)) { - if (isset($options[self::OPT_SEARCH_PATH])) { - $matches = null; - if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options[self::OPT_SEARCH_PATH], $matches) === 1) { - $searchPath = $matches[0]; - } - } - if (isset($options[self::OPT_DEFAULT_SCHEMA])) { - $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); - if (!empty($schema)) { - Schema::$DEFAULT_SCHEMA = $schema; - } - } - if (is_null(Schema::$DEFAULT_SCHEMA) || empty(Schema::$DEFAULT_SCHEMA)) { - Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; - } - } - parent::__construct($dsn, $username, $passwd, $options); - if (!is_null($searchPath)) { - $this->setSchemaSearchPath($searchPath); - } - } - - /** - * Returns the name of the current (connected) database - * @return string - */ - public function getCurrentDatabase() { - if (is_null($this->_currentDatabase)) { - return $this->query('select current_database()')->fetchColumn(); - } - } - - /** - * Sets the schema search path of the current users session. - * The syntax of the path is a comma separated string with - * your custom search path at the beginning and the "public" - * schema at the end. - * - * This method automatically adds the "public" schema at the - * end of the search path if it is not provied. - * @param string custom schema search path. defaults to public - */ - public function setSchemaSearchPath($searchPath = 'public') { - $schemas = explode(',', str_replace(' ', '', $searchPath)); - if (end($schemas) !== 'public') { - $schemas[] = 'public'; - } - foreach ($schemas as $k => $item) { - $schemas[$k] = '"' . str_replace(array('"', "'", ';'), '', $item) . '"'; - } - $path = implode(', ', $schemas); - $this->exec('SET search_path TO ' . $path); - } - -} diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 97a3ef4..80a7e33 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -22,11 +22,10 @@ class Schema extends \yii\db\Schema { /** - * The default schema used for the current session. This value is - * automatically set to "public" by the PDO driver. + * The default schema used for the current session. * @var string */ - public static $DEFAULT_SCHEMA; + public $defaultSchema = 'public'; /** * @var array mapping from physical column types (keys) to abstract @@ -95,10 +94,10 @@ class Schema extends \yii\db\Schema $table->name = $parts[0]; } if ($table->schemaName === null) { - $table->schemaName = self::$DEFAULT_SCHEMA; + $table->schemaName = $this->defaultSchema; } } - + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. @@ -122,7 +121,7 @@ class Schema extends \yii\db\Schema return $table; } } - + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata @@ -131,7 +130,6 @@ class Schema extends \yii\db\Schema $tableName = $this->quoteValue($table->name); $tableSchema = $this->quoteValue($table->schemaName); - $database = $this->quoteValue($this->db->pdo->getCurrentDatabase()); //We need to extract the constraints de hard way since: //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us @@ -158,7 +156,6 @@ where ct.contype='f' and c.relname={$tableName} and ns.nspname={$tableSchema} - and current_database() = {$database} SQL; try { @@ -184,7 +181,6 @@ SQL; * @return boolean whether the table exists in the database */ protected function findColumns($table) { - $dbname = $this->db->quoteValue($this->db->pdo->getCurrentDatabase()); $tableName = $this->db->quoteValue($table->name); $schemaName = $this->db->quoteValue($table->schemaName); $sql = <<<SQL @@ -239,7 +235,6 @@ WHERE a.attnum > 0 and c.relname = {$tableName} and d.nspname = {$schemaName} - and current_database() = {$dbname} ORDER BY a.attnum; SQL; From d07fd39f7ee2fefa2164348eaa8e15339bcdf3ab Mon Sep 17 00:00:00 2001 From: gevik <gevikb@gmail.com> Date: Fri, 7 Jun 2013 22:54:04 +0200 Subject: [PATCH 80/91] Removed false exception catching. --- framework/yii/db/pgsql/Schema.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 80a7e33..50d01aa 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -97,7 +97,7 @@ class Schema extends \yii\db\Schema $table->schemaName = $this->defaultSchema; } } - + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. @@ -121,7 +121,7 @@ class Schema extends \yii\db\Schema return $table; } } - + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata @@ -158,11 +158,7 @@ where and ns.nspname={$tableSchema} SQL; - try { - $constraints = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { - return false; - } + $constraints = $this->db->createCommand($sql)->queryAll(); foreach ($constraints as $constraint) { $columns = explode(',', $constraint['columns']); $fcolumns = explode(',', $constraint['foreign_columns']); @@ -172,7 +168,6 @@ SQL; } $table->foreignKeys[] = $citem; } - return true; } /** From b6d5a9002b96f36b627164217f9a9c7d7120563d Mon Sep 17 00:00:00 2001 From: gevik <gevikb@gmail.com> Date: Fri, 7 Jun 2013 23:01:01 +0200 Subject: [PATCH 81/91] Removed the config setting that should not have been commited. --- tests/unit/data/config.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 1b40513..036624b 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -22,9 +22,6 @@ return array( 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', - 'attributes' => array( - 'search_path' => 'master,hello' - ), 'fixture' => __DIR__ . '/postgres.sql', ) ) From cf1e12ad118a2a322e2cbc9a845955791af136c7 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Sat, 8 Jun 2013 12:37:39 -0400 Subject: [PATCH 82/91] Fixes issue #514. --- framework/yii/db/pgsql/Schema.php | 114 ++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 50d01aa..bec1803 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -12,7 +12,7 @@ use yii\db\TableSchema; use yii\db\ColumnSchema; /** - * Schema is the class for retrieving metadata from a PostgreSQL database + * Schema is the class for retrieving metadata from a PostgreSQL database * (version 9.x and above). * * @author Gevik Babakhani <gevikb@gmail.com> @@ -23,60 +23,61 @@ class Schema extends \yii\db\Schema /** * The default schema used for the current session. - * @var string + * @var string */ public $defaultSchema = 'public'; /** - * @var array mapping from physical column types (keys) to abstract + * @var array mapping from physical column types (keys) to abstract * column types (values) */ public $typeMap = array( - 'abstime' => self::TYPE_TIMESTAMP, - 'bit' => self::TYPE_STRING, - 'boolean' => self::TYPE_BOOLEAN, - 'box' => self::TYPE_STRING, - 'character' => self::TYPE_STRING, - 'bytea' => self::TYPE_BINARY, - 'char' => self::TYPE_STRING, - 'cidr' => self::TYPE_STRING, - 'circle' => self::TYPE_STRING, - 'date' => self::TYPE_DATE, - 'real' => self::TYPE_FLOAT, - 'double precision' => self::TYPE_DECIMAL, - 'inet' => self::TYPE_STRING, - 'smallint' => self::TYPE_SMALLINT, - 'integer' => self::TYPE_INTEGER, - 'bigint' => self::TYPE_BIGINT, - 'interval' => self::TYPE_STRING, - 'json' => self::TYPE_STRING, - 'line' => self::TYPE_STRING, - 'macaddr' => self::TYPE_STRING, - 'money' => self::TYPE_MONEY, - 'name' => self::TYPE_STRING, - 'numeric' => self::TYPE_STRING, - 'numrange' => self::TYPE_DECIMAL, - 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! - 'path' => self::TYPE_STRING, - 'point' => self::TYPE_STRING, - 'polygon' => self::TYPE_STRING, - 'text' => self::TYPE_TEXT, - 'time without time zone' => self::TYPE_TIME, - 'timestamp without time zone' => self::TYPE_TIMESTAMP, - 'timestamp with time zone' => self::TYPE_TIMESTAMP, - 'time with time zone' => self::TYPE_TIMESTAMP, - 'unknown' => self::TYPE_STRING, - 'uuid' => self::TYPE_STRING, - 'bit varying' => self::TYPE_STRING, - 'character varying' => self::TYPE_STRING, - 'xml' => self::TYPE_STRING + 'abstime' => self::TYPE_TIMESTAMP, + 'bit' => self::TYPE_STRING, + 'boolean' => self::TYPE_BOOLEAN, + 'box' => self::TYPE_STRING, + 'character' => self::TYPE_STRING, + 'bytea' => self::TYPE_BINARY, + 'char' => self::TYPE_STRING, + 'cidr' => self::TYPE_STRING, + 'circle' => self::TYPE_STRING, + 'date' => self::TYPE_DATE, + 'real' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_DECIMAL, + 'inet' => self::TYPE_STRING, + 'smallint' => self::TYPE_SMALLINT, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'interval' => self::TYPE_STRING, + 'json' => self::TYPE_STRING, + 'line' => self::TYPE_STRING, + 'macaddr' => self::TYPE_STRING, + 'money' => self::TYPE_MONEY, + 'name' => self::TYPE_STRING, + 'numeric' => self::TYPE_STRING, + 'numrange' => self::TYPE_DECIMAL, + 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! + 'path' => self::TYPE_STRING, + 'point' => self::TYPE_STRING, + 'polygon' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + 'time without time zone' => self::TYPE_TIME, + 'timestamp without time zone' => self::TYPE_TIMESTAMP, + 'timestamp with time zone' => self::TYPE_TIMESTAMP, + 'time with time zone' => self::TYPE_TIMESTAMP, + 'unknown' => self::TYPE_STRING, + 'uuid' => self::TYPE_STRING, + 'bit varying' => self::TYPE_STRING, + 'character varying' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING ); /** * Creates a query builder for the MySQL database. * @return QueryBuilder query builder instance */ - public function createQueryBuilder() { + public function createQueryBuilder() + { return new QueryBuilder($this->db); } @@ -85,7 +86,8 @@ class Schema extends \yii\db\Schema * @param TableSchema $table the table metadata object * @param string $name the table name */ - protected function resolveTableNames($table, $name) { + protected function resolveTableNames($table, $name) + { $parts = explode('.', str_replace('"', '', $name)); if (isset($parts[1])) { $table->schemaName = $parts[0]; @@ -104,7 +106,8 @@ class Schema extends \yii\db\Schema * @param string $name table name * @return string the properly quoted table name */ - public function quoteSimpleTableName($name) { + public function quoteSimpleTableName($name) + { return strpos($name, '"') !== false ? $name : '"' . $name . '"'; } @@ -113,12 +116,15 @@ class Schema extends \yii\db\Schema * @param string $name table name * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. */ - public function loadTableSchema($name) { + public function loadTableSchema($name) + { $table = new TableSchema(); $this->resolveTableNames($table, $name); if ($this->findColumns($table)) { $this->findConstraints($table); return $table; + } else { + return null; } } @@ -126,7 +132,8 @@ class Schema extends \yii\db\Schema * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata */ - protected function findConstraints($table) { + protected function findConstraints($table) + { $tableName = $this->quoteValue($table->name); $tableSchema = $this->quoteValue($table->schemaName); @@ -175,7 +182,8 @@ SQL; * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database */ - protected function findColumns($table) { + protected function findColumns($table) + { $tableName = $this->db->quoteValue($table->name); $schemaName = $this->db->quoteValue($table->schemaName); $sql = <<<SQL @@ -234,11 +242,11 @@ ORDER BY a.attnum; SQL; - try { - $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { + $columns = $this->db->createCommand($sql)->queryAll(); + if (empty($columns)) { return false; } + foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; @@ -257,7 +265,8 @@ SQL; * @param array $info column information * @return ColumnSchema the column schema object */ - protected function loadColumnSchema($info) { + protected function loadColumnSchema($info) + { $column = new ColumnSchema(); $column->allowNull = $info['is_nullable']; $column->autoIncrement = $info['is_autoinc']; @@ -280,5 +289,4 @@ SQL; $column->phpType = $this->getColumnPhpType($column); return $column; } - -} \ No newline at end of file +} From 95b1636a9815da870020403a36f2115785556e41 Mon Sep 17 00:00:00 2001 From: Mojtaba Salehi <me@mojtaba.info> Date: Sat, 8 Jun 2013 23:26:21 +0430 Subject: [PATCH 83/91] Multilevel Items --- framework/yii/bootstrap/Nav.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 8e35010..8069699 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -27,13 +27,19 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * 'dropdown' => array( + * 'items' => array( * array( - * 'label' => 'DropdownA', + * 'label' => 'Level 1 -DropdownA', * 'url' => '#', + * 'items' => array( + * array( + * 'label' => 'Level 2 -DropdownA', + * 'url' => '#', + * ), + * ), * ), * array( - * 'label' => 'DropdownB', + * 'label' => 'Level 1 -DropdownB', * 'url' => '#', * ), * ), @@ -114,7 +120,7 @@ class Nav extends Widget } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; $options = ArrayHelper::getValue($item, 'options', array()); - $dropdown = ArrayHelper::getValue($item, 'dropdown'); + $items = ArrayHelper::getValue($item, 'items'); $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); @@ -122,19 +128,19 @@ class Nav extends Widget $this->addCssClass($options, 'active'); } - if ($dropdown !== null) { + if ($items !== null) { $linkOptions['data-toggle'] = 'dropdown'; $this->addCssClass($options, 'dropdown'); $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); - if (is_array($dropdown)) { - $dropdown = Dropdown::widget(array( - 'items' => $dropdown, + if (is_array($items)) { + $items = Dropdown::widget(array( + 'items' => $items, 'clientOptions' => false, )); } } - return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options); + return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options); } } From 19793f7af056e6f3e5fec31ddf9dc252ce3d6b86 Mon Sep 17 00:00:00 2001 From: gevik <gevikb@gmail.com> Date: Sat, 8 Jun 2013 21:51:15 +0200 Subject: [PATCH 84/91] Removed unused columsn from find constraint sql. Fixed typo. Added extra schema check for when a foreign table is not in the same schema. Updated indentation to conform to other classes. --- framework/yii/db/pgsql/Schema.php | 53 +++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index bec1803..7223a3b 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -73,7 +73,7 @@ class Schema extends \yii\db\Schema ); /** - * Creates a query builder for the MySQL database. + * Creates a query builder for the PostgreSQL database. * @return QueryBuilder query builder instance */ public function createQueryBuilder() @@ -89,13 +89,16 @@ class Schema extends \yii\db\Schema protected function resolveTableNames($table, $name) { $parts = explode('.', str_replace('"', '', $name)); - if (isset($parts[1])) { + if (isset($parts[1])) + { $table->schemaName = $parts[0]; $table->name = $parts[1]; - } else { + } else + { $table->name = $parts[0]; } - if ($table->schemaName === null) { + if ($table->schemaName === null) + { $table->schemaName = $this->defaultSchema; } } @@ -120,11 +123,10 @@ class Schema extends \yii\db\Schema { $table = new TableSchema(); $this->resolveTableNames($table, $name); - if ($this->findColumns($table)) { + if ($this->findColumns($table)) + { $this->findConstraints($table); return $table; - } else { - return null; } } @@ -143,14 +145,9 @@ class Schema extends \yii\db\Schema $sql = <<<SQL select - ct.conname as containst, - c.relname as table_name, - ns.nspname as table_schema, - current_database() as table_catalog, (select string_agg(attname,',') attname from pg_attribute where attrelid=ct.conrelid and attnum = any(ct.conkey)) as columns, fc.relname as foreign_table_name, fns.nspname as foreign_table_schema, - current_database() as foreign_table_catalog, (select string_agg(attname,',') attname from pg_attribute where attrelid=ct.confrelid and attnum = any(ct.confkey)) as foreign_columns from pg_constraint ct @@ -169,7 +166,14 @@ SQL; foreach ($constraints as $constraint) { $columns = explode(',', $constraint['columns']); $fcolumns = explode(',', $constraint['foreign_columns']); - $citem = array($constraint['foreign_table_name']); + if ($constraint['foreign_table_schema'] !== $this->defaultSchema) + { + $foreign_table = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; + } else + { + $foreign_table = $constraint['foreign_table_name']; + } + $citem = array($foreign_table); foreach ($columns as $idx => $column) { $citem[] = array($fcolumns[$idx] => $column); } @@ -242,17 +246,21 @@ ORDER BY a.attnum; SQL; - $columns = $this->db->createCommand($sql)->queryAll(); - if (empty($columns)) { + try + { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) + { return false; } - foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; - if ($column->isPrimaryKey === true) { + if ($column->isPrimaryKey === true) + { $table->primaryKey[] = $column->name; - if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { + if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) + { $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue); } } @@ -281,12 +289,15 @@ SQL; $column->scale = $info['numeric_scale']; $column->size = $info['size']; - if (isset($this->typeMap[$column->dbType])) { + if (isset($this->typeMap[$column->dbType])) + { $column->type = $this->typeMap[$column->dbType]; - } else { + } else + { $column->type = self::TYPE_STRING; } $column->phpType = $this->getColumnPhpType($column); return $column; } -} + +} \ No newline at end of file From 6fd74f5c27cb37eaacb82b48e3847eaa63c6273d Mon Sep 17 00:00:00 2001 From: gevik <gevikb@gmail.com> Date: Sat, 8 Jun 2013 23:47:24 +0200 Subject: [PATCH 85/91] Updated code style. braces on the same line for control statements. --- framework/yii/db/pgsql/Schema.php | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 7223a3b..66f0876 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -89,16 +89,13 @@ class Schema extends \yii\db\Schema protected function resolveTableNames($table, $name) { $parts = explode('.', str_replace('"', '', $name)); - if (isset($parts[1])) - { + if (isset($parts[1])) { $table->schemaName = $parts[0]; $table->name = $parts[1]; - } else - { + } else { $table->name = $parts[0]; } - if ($table->schemaName === null) - { + if ($table->schemaName === null) { $table->schemaName = $this->defaultSchema; } } @@ -123,8 +120,7 @@ class Schema extends \yii\db\Schema { $table = new TableSchema(); $this->resolveTableNames($table, $name); - if ($this->findColumns($table)) - { + if ($this->findColumns($table)) { $this->findConstraints($table); return $table; } @@ -166,11 +162,9 @@ SQL; foreach ($constraints as $constraint) { $columns = explode(',', $constraint['columns']); $fcolumns = explode(',', $constraint['foreign_columns']); - if ($constraint['foreign_table_schema'] !== $this->defaultSchema) - { + if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { $foreign_table = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; - } else - { + } else { $foreign_table = $constraint['foreign_table_name']; } $citem = array($foreign_table); @@ -246,21 +240,17 @@ ORDER BY a.attnum; SQL; - try - { + try { $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) - { + } catch (\Exception $e) { return false; } foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; - if ($column->isPrimaryKey === true) - { + if ($column->isPrimaryKey === true) { $table->primaryKey[] = $column->name; - if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) - { + if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue); } } @@ -289,11 +279,9 @@ SQL; $column->scale = $info['numeric_scale']; $column->size = $info['size']; - if (isset($this->typeMap[$column->dbType])) - { + if (isset($this->typeMap[$column->dbType])) { $column->type = $this->typeMap[$column->dbType]; - } else - { + } else { $column->type = self::TYPE_STRING; } $column->phpType = $this->getColumnPhpType($column); From ec9dab11fad43c869c19cd3119a2ce516d7ae886 Mon Sep 17 00:00:00 2001 From: gevik <gevikb@gmail.com> Date: Sun, 9 Jun 2013 00:04:37 +0200 Subject: [PATCH 86/91] [1] Redone missing code. [2] Added empty line at the end of file [3] Removed exception. --- framework/yii/db/pgsql/Schema.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 66f0876..8cfb535 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -123,6 +123,8 @@ class Schema extends \yii\db\Schema if ($this->findColumns($table)) { $this->findConstraints($table); return $table; + } else { + return null; } } @@ -240,11 +242,7 @@ ORDER BY a.attnum; SQL; - try { - $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { - return false; - } + $columns = $this->db->createCommand($sql)->queryAll(); foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; @@ -288,4 +286,4 @@ SQL; return $column; } -} \ No newline at end of file +} From 864bf936794f8bd7e60be07a31011c0b29908317 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Sun, 9 Jun 2013 09:41:32 -0300 Subject: [PATCH 87/91] coding style fix. --- framework/yii/db/pgsql/Schema.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 8cfb535..94f845f 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -165,11 +165,11 @@ SQL; $columns = explode(',', $constraint['columns']); $fcolumns = explode(',', $constraint['foreign_columns']); if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { - $foreign_table = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; + $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; } else { - $foreign_table = $constraint['foreign_table_name']; + $foreignTable = $constraint['foreign_table_name']; } - $citem = array($foreign_table); + $citem = array($foreignTable); foreach ($columns as $idx => $column) { $citem[] = array($fcolumns[$idx] => $column); } @@ -285,5 +285,4 @@ SQL; $column->phpType = $this->getColumnPhpType($column); return $column; } - } From f2d477dce6ab0d5f215e8a4e285e8eda646eb673 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Sun, 9 Jun 2013 09:47:41 -0400 Subject: [PATCH 88/91] Response WIP --- apps/basic/controllers/SiteController.php | 1 + framework/yii/base/Application.php | 24 +++++-- framework/yii/base/ErrorHandler.php | 13 ++-- framework/yii/base/Response.php | 13 ++++ framework/yii/console/Response.php | 17 +++++ framework/yii/web/CaptchaAction.php | 28 +++++--- framework/yii/web/Cookie.php | 2 +- framework/yii/web/CookieCollection.php | 99 ++++++++++----------------- framework/yii/web/HeaderCollection.php | 6 +- framework/yii/web/HttpCache.php | 16 +++-- framework/yii/web/Request.php | 63 ++++++++++++++--- framework/yii/web/Response.php | 108 +++++++++++++++++++++++------- framework/yii/web/Session.php | 19 ++++-- framework/yii/web/User.php | 2 +- framework/yii/web/VerbFilter.php | 2 +- 15 files changed, 274 insertions(+), 139 deletions(-) create mode 100644 framework/yii/console/Response.php diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index ff3b8b4..9d1922b 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -20,6 +20,7 @@ class SiteController extends Controller public function actionIndex() { + Yii::$app->end(0, false); echo $this->render('index'); } diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 09951bd..f5f3d6a 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -128,6 +128,11 @@ class Application extends Module ini_set('display_errors', 0); set_exception_handler(array($this, 'handleException')); set_error_handler(array($this, 'handleError'), error_reporting()); + // Allocating twice more than required to display memory exhausted error + // in case of trying to allocate last 1 byte while all memory is taken. + $this->_memoryReserve = str_repeat('x', 1024 * 256); + register_shutdown_function(array($this, 'end'), 0, false); + register_shutdown_function(array($this, 'handleFatalError')); } } @@ -142,11 +147,10 @@ class Application extends Module { if (!$this->_ended) { $this->_ended = true; + $this->getResponse()->end(); $this->afterRequest(); } - $this->handleFatalError(); - if ($exit) { exit($status); } @@ -160,11 +164,10 @@ class Application extends Module public function run() { $this->beforeRequest(); - // Allocating twice more than required to display memory exhausted error - // in case of trying to allocate last 1 byte while all memory is taken. - $this->_memoryReserve = str_repeat('x', 1024 * 256); - register_shutdown_function(array($this, 'end'), 0, false); + $response = $this->getResponse(); + $response->begin(); $status = $this->processRequest(); + $response->end(); $this->afterRequest(); return $status; } @@ -315,6 +318,15 @@ class Application extends Module } /** + * Returns the response component. + * @return \yii\web\Response|\yii\console\Response the response component + */ + public function getResponse() + { + return $this->getComponent('response'); + } + + /** * Returns the view object. * @return View the view object that is used to render various view files. */ diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 7bf9e7e..4e3e92a 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -82,11 +82,12 @@ class ErrorHandler extends Component } elseif (!(Yii::$app instanceof \yii\web\Application)) { Yii::$app->renderException($exception); } else { + $response = Yii::$app->getResponse(); if (!headers_sent()) { if ($exception instanceof HttpException) { - header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName()); + $response->setStatusCode($exception->statusCode); } else { - header('HTTP/1.0 500 ' . get_class($exception)); + $response->setStatusCode(500); } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { @@ -100,13 +101,13 @@ class ErrorHandler extends Component $view = new View(); $request = ''; - foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) { - if (!empty($GLOBALS['_' . $name])) { - $request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n"; + foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) { + if (!empty($GLOBALS[$name])) { + $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n"; } } $request = rtrim($request, "\n\n"); - echo $view->renderFile($this->mainView, array( + $response->content = $view->renderFile($this->mainView, array( 'exception' => $exception, 'request' => $request, ), $this); diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php index 396b073..b89b537 100644 --- a/framework/yii/base/Response.php +++ b/framework/yii/base/Response.php @@ -13,6 +13,9 @@ namespace yii\base; */ class Response extends Component { + const EVENT_BEGIN_RESPONSE = 'beginResponse'; + const EVENT_END_RESPONSE = 'endResponse'; + /** * Starts output buffering */ @@ -56,4 +59,14 @@ class Response extends Component ob_end_clean(); } } + + public function begin() + { + $this->trigger(self::EVENT_BEGIN_RESPONSE); + } + + public function end() + { + $this->trigger(self::EVENT_END_RESPONSE); + } } diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php new file mode 100644 index 0000000..34f105d --- /dev/null +++ b/framework/yii/console/Response.php @@ -0,0 +1,17 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\console; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Response extends \yii\base\Response +{ + +} diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/web/CaptchaAction.php index cff2314..1ed1fb0 100644 --- a/framework/yii/web/CaptchaAction.php +++ b/framework/yii/web/CaptchaAction.php @@ -277,11 +277,8 @@ class CaptchaAction extends Action imagecolordeallocate($image, $foreColor); - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Content-Transfer-Encoding: binary'); - header("Content-type: image/png"); + $this->sendHttpHeaders(); + imagepng($image); imagedestroy($image); } @@ -319,12 +316,21 @@ class CaptchaAction extends Action $x += (int)($fontMetrics['textWidth']) + $this->offset; } - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Content-Transfer-Encoding: binary'); - header("Content-type: image/png"); $image->setImageFormat('png'); - echo $image; + Yii::$app->getResponse()->content = (string)$image; + $this->sendHttpHeaders(); + } + + /** + * Sends the HTTP headers needed by image response. + */ + protected function sendHttpHeaders() + { + Yii::$app->getResponse()->getHeaders() + ->set('Pragma', 'public') + ->set('Expires', '0') + ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->set('Content-Transfer-Encoding', 'binary') + ->set('Content-type', 'image/png'); } } diff --git a/framework/yii/web/Cookie.php b/framework/yii/web/Cookie.php index 610e5aa..8cbb412 100644 --- a/framework/yii/web/Cookie.php +++ b/framework/yii/web/Cookie.php @@ -45,7 +45,7 @@ class Cookie extends \yii\base\Object * By setting this property to true, the cookie will not be accessible by scripting languages, * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks. */ - public $httponly = false; + public $httpOnly = false; /** * Magic method to turn a cookie object into a string without having to explicitly access [[value]]. diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php index fc9375e..3e22092 100644 --- a/framework/yii/web/CookieCollection.php +++ b/framework/yii/web/CookieCollection.php @@ -9,7 +9,8 @@ namespace yii\web; use Yii; use ArrayIterator; -use yii\helpers\SecurityHelper; +use yii\base\InvalidCallException; +use yii\base\Object; /** * CookieCollection maintains the cookies available in the current request. @@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable +class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable { /** - * @var boolean whether to enable cookie validation. By setting this property to true, - * if a cookie is tampered on the client side, it will be ignored when received on the server side. + * @var boolean whether this collection is read only. */ - public $enableValidation = true; - /** - * @var string the secret key used for cookie validation. If not set, a random key will be generated and used. - */ - public $validationKey; + public $readOnly = false; /** * @var Cookie[] the cookies in this collection (indexed by the cookie names) @@ -38,12 +34,14 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ /** * Constructor. + * @param array $cookies the cookies that this collection initially contains. This should be + * an array of name-value pairs.s * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($config = array()) + public function __construct($cookies = array(), $config = array()) { + $this->_cookies = $cookies; parent::__construct($config); - $this->_cookies = $this->loadCookies(); } /** @@ -114,50 +112,53 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ * Adds a cookie to the collection. * If there is already a cookie with the same name in the collection, it will be removed first. * @param Cookie $cookie the cookie to be added + * @throws InvalidCallException if the cookie collection is read only */ public function add($cookie) { - if (isset($this->_cookies[$cookie->name])) { - $c = $this->_cookies[$cookie->name]; - setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly); - } - - $value = $cookie->value; - if ($this->enableValidation) { - if ($this->validationKey === null) { - $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id); - } else { - $key = $this->validationKey; - } - $value = SecurityHelper::hashData(serialize($value), $key); + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } - - setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly); $this->_cookies[$cookie->name] = $cookie; } /** - * Removes a cookie from the collection. + * Removes a cookie. + * If `$removeFromBrowser` is true, the cookie will be removed from the browser. + * In this case, a cookie with outdated expiry will be added to the collection. * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed. + * @param boolean $removeFromBrowser whether to remove the cookie from browser + * @throws InvalidCallException if the cookie collection is read only */ - public function remove($cookie) + public function remove($cookie, $removeFromBrowser = true) { - if (is_string($cookie) && isset($this->_cookies[$cookie])) { - $cookie = $this->_cookies[$cookie]; + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } if ($cookie instanceof Cookie) { - setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly); + $cookie->expire = 1; + $cookie->value = ''; + } else { + $cookie = new Cookie(array( + 'name' => $cookie, + 'expire' => 1, + )); + } + if ($removeFromBrowser) { + $this->_cookies[$cookie->name] = $cookie; + } else { unset($this->_cookies[$cookie->name]); } } /** * Removes all cookies. + * @throws InvalidCallException if the cookie collection is read only */ public function removeAll() { - foreach ($this->_cookies as $cookie) { - setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly); + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } $this->_cookies = array(); } @@ -222,36 +223,4 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ { $this->remove($name); } - - /** - * Returns the current cookies in terms of [[Cookie]] objects. - * @return Cookie[] list of current cookies - */ - protected function loadCookies() - { - $cookies = array(); - if ($this->enableValidation) { - if ($this->validationKey === null) { - $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id); - } else { - $key = $this->validationKey; - } - foreach ($_COOKIE as $name => $value) { - if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) { - $cookies[$name] = new Cookie(array( - 'name' => $name, - 'value' => @unserialize($value), - )); - } - } - } else { - foreach ($_COOKIE as $name => $value) { - $cookies[$name] = new Cookie(array( - 'name' => $name, - 'value' => $value, - )); - } - } - return $cookies; - } } diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php index c7e1462..ed9ec6f 100644 --- a/framework/yii/web/HeaderCollection.php +++ b/framework/yii/web/HeaderCollection.php @@ -79,11 +79,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * If there is already a header with the same name, it will be replaced. * @param string $name the name of the header * @param string $value the value of the header + * @return HeaderCollection the collection object itself */ - public function set($name, $value) + public function set($name, $value = '') { $name = strtolower($name); $this->_headers[$name] = (array)$value; + return $this; } /** @@ -92,11 +94,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * be appended to it instead of replacing it. * @param string $name the name of the header * @param string $value the value of the header + * @return HeaderCollection the collection object itself */ public function add($name, $value) { $name = strtolower($name); $this->_headers[$name][] = $value; + return $this; } /** diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php index 5b7682d..cc9e6ed 100644 --- a/framework/yii/web/HttpCache.php +++ b/framework/yii/web/HttpCache.php @@ -50,7 +50,7 @@ class HttpCache extends ActionFilter /** * @var string HTTP cache control header. If null, the header will not be sent. */ - public $cacheControlHeader = 'Cache-Control: max-age=3600, public'; + public $cacheControlHeader = 'max-age=3600, public'; /** * This method is invoked right before an action is to be executed (after all possible filters.) @@ -60,7 +60,7 @@ class HttpCache extends ActionFilter */ public function beforeAction($action) { - $verb = Yii::$app->request->getMethod(); + $verb = Yii::$app->getRequest()->getMethod(); if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) { return true; } @@ -75,17 +75,18 @@ class HttpCache extends ActionFilter } $this->sendCacheControlHeader(); + $response = Yii::$app->getResponse(); if ($etag !== null) { - header("ETag: $etag"); + $response->getHeaders()->set('Etag', $etag); } if ($this->validateCache($lastModified, $etag)) { - header('HTTP/1.1 304 Not Modified'); + $response->setStatusCode(304); return false; } if ($lastModified !== null) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); + $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); } return true; } @@ -113,9 +114,10 @@ class HttpCache extends ActionFilter protected function sendCacheControlHeader() { session_cache_limiter('public'); - header('Pragma:', true); + $headers = Yii::$app->getResponse()->getHeaders(); + $headers->set('Pragma'); if ($this->cacheControlHeader !== null) { - header($this->cacheControlHeader, true); + $headers->set('Cache-Control', $this->cacheControlHeader); } } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 7cec044..6f5cdb5 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -10,6 +10,7 @@ namespace yii\web; use Yii; use yii\base\HttpException; use yii\base\InvalidConfigException; +use yii\helpers\SecurityHelper; /** * @author Qiang Xue <qiang.xue@gmail.com> @@ -37,16 +38,12 @@ class Request extends \yii\base\Request * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true. * @see Cookie */ - public $csrfCookie = array('httponly' => true); + public $csrfCookie = array('httpOnly' => true); /** * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true. */ public $enableCookieValidation = true; /** - * @var string the secret key used for cookie validation. If not set, a random key will be generated and used. - */ - public $cookieValidationKey; - /** * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE * request tunneled through POST. Default to '_method'. * @see getMethod @@ -717,14 +714,64 @@ class Request extends \yii\base\Request public function getCookies() { if ($this->_cookies === null) { - $this->_cookies = new CookieCollection(array( - 'enableValidation' => $this->enableCookieValidation, - 'validationKey' => $this->cookieValidationKey, + $this->_cookies = new CookieCollection($this->loadCookies(), array( + 'readOnly' => true, )); } return $this->_cookies; } + /** + * Converts `$_COOKIE` into an array of [[Cookie]]. + * @return array the cookies obtained from request + */ + protected function loadCookies() + { + $cookies = array(); + if ($this->enableCookieValidation) { + $key = $this->getCookieValidationKey(); + foreach ($_COOKIE as $name => $value) { + if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) { + $cookies[$name] = new Cookie(array( + 'name' => $name, + 'value' => @unserialize($value), + )); + } + } + } else { + foreach ($_COOKIE as $name => $value) { + $cookies[$name] = new Cookie(array( + 'name' => $name, + 'value' => $value, + )); + } + } + return $cookies; + } + + private $_cookieValidationKey; + + /** + * @return string the secret key used for cookie validation. If it was set previously, + * a random key will be generated and used. + */ + public function getCookieValidationKey() + { + if ($this->_cookieValidationKey === null) { + $this->_cookieValidationKey = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id); + } + return $this->_cookieValidationKey; + } + + /** + * Sets the secret key used for cookie validation. + * @param string $value the secret key used for cookie validation. + */ + public function setCookieValidationKey($value) + { + $this->_cookieValidationKey = $value; + } + private $_csrfToken; /** diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 86978d5..a16d04d 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -13,6 +13,7 @@ use yii\base\InvalidParamException; use yii\helpers\FileHelper; use yii\helpers\Html; use yii\helpers\Json; +use yii\helpers\SecurityHelper; use yii\helpers\StringHelper; /** @@ -131,18 +132,35 @@ class Response extends \yii\base\Response } } + public function begin() + { + parent::begin(); + $this->beginOutput(); + } + + public function end() + { + $this->content .= $this->endOutput(); + $this->send(); + parent::end(); + } + public function getStatusCode() { return $this->_statusCode; } - public function setStatusCode($value) + public function setStatusCode($value, $text = null) { $this->_statusCode = (int)$value; if ($this->isInvalid()) { throw new InvalidParamException("The HTTP status code is invalid: $value"); } - $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : ''; + if ($text === null) { + $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : ''; + } else { + $this->statusText = $text; + } } /** @@ -186,13 +204,42 @@ class Response extends \yii\base\Response */ protected function sendHeaders() { + if (headers_sent()) { + return; + } header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}"); - foreach ($this->_headers as $name => $values) { - foreach ($values as $value) { - header("$name: $value"); + if ($this->_headers) { + $headers = $this->getHeaders(); + foreach ($headers as $name => $values) { + foreach ($values as $value) { + header("$name: $value", false); + } + } + $headers->removeAll(); + } + $this->sendCookies(); + } + + /** + * Sends the cookies to the client. + */ + protected function sendCookies() + { + if ($this->_cookies === null) { + return; + } + $request = Yii::$app->getRequest(); + if ($request->enableCookieValidation) { + $validationKey = $request->getCookieValidationKey(); + } + foreach ($this->getCookies() as $cookie) { + $value = $cookie->value; + if ($cookie->expire != 1 && isset($validationKey)) { + $value = SecurityHelper::hashData(serialize($value), $validationKey); } + setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); } - $this->_headers->removeAll(); + $this->getCookies()->removeAll(); } /** @@ -222,13 +269,15 @@ class Response extends \yii\base\Response $contentStart = 0; $contentEnd = $fileSize - 1; + $headers = $this->getHeaders(); + // tell the client that we accept range requests - header('Accept-Ranges: bytes'); + $headers->set('Accept-Ranges', 'bytes'); if (isset($_SERVER['HTTP_RANGE'])) { // client sent us a multibyte range, can not hold this one for now if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) { - header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); throw new HttpException(416, 'Requested Range Not Satisfiable'); } @@ -257,25 +306,26 @@ class Response extends \yii\base\Response $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); if ($wrongContentStart) { - header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); throw new HttpException(416, 'Requested Range Not Satisfiable'); } - header('HTTP/1.1 206 Partial Content'); - header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + $this->setStatusCode(206); + $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); } else { - header('HTTP/1.1 200 OK'); + $this->setStatusCode(200); } $length = $contentEnd - $contentStart + 1; // Calculate new content length - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Content-Type: ' . $mimeType); - header('Content-Length: ' . $length); - header('Content-Disposition: attachment; filename="' . $fileName . '"'); - header('Content-Transfer-Encoding: binary'); + $headers->set('Pragma', 'public') + ->set('Expires', '0') + ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->set('Content-Type', $mimeType) + ->set('Content-Length', $length) + ->set('Content-Disposition', "attachment; filename=\"$fileName\"") + ->set('Content-Transfer-Encoding', 'binary'); + $content = StringHelper::substr($content, $contentStart, $length); if ($terminate) { @@ -371,16 +421,18 @@ class Response extends \yii\base\Response $options['xHeader'] = 'X-Sendfile'; } + $headers = $this->getHeaders(); + if ($options['mimeType'] !== null) { - header('Content-type: ' . $options['mimeType']); + $headers->set('Content-Type', $options['mimeType']); } - header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"'); + $headers->set('Content-Disposition', "$disposition; filename=\"{$options['saveName']}\""); if (isset($options['addHeaders'])) { foreach ($options['addHeaders'] as $header => $value) { - header($header . ': ' . $value); + $headers->set($header, $value); } } - header(trim($options['xHeader']) . ': ' . $filePath); + $headers->set(trim($options['xHeader']), $filePath); if (!isset($options['terminate']) || $options['terminate']) { Yii::$app->end(); @@ -422,7 +474,8 @@ class Response extends \yii\base\Response if (Yii::$app->getRequest()->getIsAjax()) { $statusCode = $this->ajaxRedirectCode; } - header('Location: ' . $url, true, $statusCode); + $this->getHeaders()->set('Location', $url); + $this->setStatusCode($statusCode); if ($terminate) { Yii::$app->end(); } @@ -441,6 +494,8 @@ class Response extends \yii\base\Response $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate); } + private $_cookies; + /** * Returns the cookie collection. * Through the returned cookie collection, you add or remove cookies as follows, @@ -462,7 +517,10 @@ class Response extends \yii\base\Response */ public function getCookies() { - return Yii::$app->getRequest()->getCookies(); + if ($this->_cookies === null) { + $this->_cookies = new CookieCollection; + } + return $this->_cookies; } /** diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index 1b48433..cf1fa21 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * @var array parameter-value pairs to override default session cookie parameters */ public $cookieParams = array( - 'httponly' => true + 'httpOnly' => true ); /** @@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function getCookieParams() { - return session_get_cookie_params(); + $params = session_get_cookie_params(); + if (isset($params['httponly'])) { + $params['httpOnly'] = $params['httponly']; + unset($params['httponly']); + } + return $params; } /** * Sets the session cookie parameters. * The effect of this method only lasts for the duration of the script. * Call this method before the session starts. - * @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly. + * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`. * @throws InvalidParamException if the parameters are incomplete. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php */ public function setCookieParams($value) { - $data = session_get_cookie_params(); + $data = $this->getCookieParams(); extract($data); extract($value); - if (isset($lifetime, $path, $domain, $secure, $httponly)) { - session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); + if (isset($lifetime, $path, $domain, $secure, $httpOnly)) { + session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly); } else { - throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.'); + throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.'); } } diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 005f987..7ea561c 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -56,7 +56,7 @@ class User extends Component * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true. * @see Cookie */ - public $identityCookie = array('name' => '_identity', 'httponly' => true); + public $identityCookie = array('name' => '_identity', 'httpOnly' => true); /** * @var integer the number of seconds in which the user will be logged out automatically if he * remains inactive. If this property is not set, the user will be logged out after diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php index ca6d47d..2b7567f 100644 --- a/framework/yii/web/VerbFilter.php +++ b/framework/yii/web/VerbFilter.php @@ -81,7 +81,7 @@ class VerbFilter extends Behavior if (!in_array($verb, $allowed)) { $event->isValid = false; // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 - header('Allow: ' . implode(', ', $allowed)); + Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed)); throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed)); } } From ef5afef7b24f87bb8d2af7ba835969a7cbe86f1c Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Sun, 9 Jun 2013 10:32:29 -0400 Subject: [PATCH 89/91] Fixed test break. --- framework/yii/web/Response.php | 19 +++++++--- tests/unit/framework/web/ResponseTest.php | 60 +++++++------------------------ 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index a16d04d..f337fbb 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -118,7 +118,7 @@ class Response extends \yii\base\Response 511 => 'Network Authentication Required', ); - private $_statusCode = 200; + private $_statusCode; /** * @var HeaderCollection */ @@ -199,6 +199,14 @@ class Response extends \yii\base\Response $this->sendContent(); } + public function reset() + { + $this->_headers = null; + $this->_statusCode = null; + $this->statusText = null; + $this->content = null; + } + /** * Sends the response headers to the client */ @@ -207,7 +215,10 @@ class Response extends \yii\base\Response if (headers_sent()) { return; } - header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}"); + $statusCode = $this->getStatusCode(); + if ($statusCode !== null) { + header("HTTP/{$this->version} $statusCode {$this->statusText}"); + } if ($this->_headers) { $headers = $this->getHeaders(); foreach ($headers as $name => $values) { @@ -334,10 +345,10 @@ class Response extends \yii\base\Response ob_start(); Yii::$app->end(0, false); ob_end_clean(); - echo $content; + $this->content = $content; exit(0); } else { - echo $content; + $this->content = $content; } } diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php index 5da9b8c..74d90cf 100644 --- a/tests/unit/framework/web/ResponseTest.php +++ b/tests/unit/framework/web/ResponseTest.php @@ -1,45 +1,20 @@ <?php -namespace yii\web; - -use yiiunit\framework\web\ResponseTest; - -/** - * Mock PHP header function to check for sent headers - * @param string $string - * @param bool $replace - * @param int $httpResponseCode - */ -function header($string, $replace = true, $httpResponseCode = null) { - ResponseTest::$headers[] = $string; - // TODO implement replace - - if ($httpResponseCode !== null) { - ResponseTest::$httpResponseCode = $httpResponseCode; - } -} - namespace yiiunit\framework\web; +use Yii; use yii\helpers\StringHelper; use yii\web\Response; class ResponseTest extends \yiiunit\TestCase { - public static $headers = array(); - public static $httpResponseCode = 200; + public $response; protected function setUp() { parent::setUp(); $this->mockApplication(); - $this->reset(); - } - - protected function reset() - { - static::$headers = array(); - static::$httpResponseCode = 200; + $this->response = new Response; } public function rightRanges() @@ -60,14 +35,15 @@ class ResponseTest extends \yiiunit\TestCase { $content = $this->generateTestFileContent(); $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; - $sent = $this->runSendFile('testFile.txt', $content, null); - - $this->assertEquals($expectedFile, $sent); - $this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers)); - $this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers)); - $this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers)); - $this->assertTrue(in_array('Content-Type: text/plain', static::$headers)); - $this->assertTrue(in_array('Content-Length: ' . $length, static::$headers)); + $this->response->sendFile('testFile.txt', $content, null, false); + + $this->assertEquals($expectedFile, $this->response->content); + $this->assertEquals(206, $this->response->statusCode); + $headers = $this->response->headers; + $this->assertEquals("bytes", $headers->get('Accept-Ranges')); + $this->assertEquals("bytes " . $expectedHeader . '/' . StringHelper::strlen($content), $headers->get('Content-Range')); + $this->assertEquals('text/plain', $headers->get('Content-Type')); + $this->assertEquals("$length", $headers->get('Content-Length')); } public function wrongRanges() @@ -91,21 +67,11 @@ class ResponseTest extends \yiiunit\TestCase $content = $this->generateTestFileContent(); $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; - $this->runSendFile('testFile.txt', $content, null); + $this->response->sendFile('testFile.txt', $content, null, false); } protected function generateTestFileContent() { return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'; } - - protected function runSendFile($fileName, $content, $mimeType) - { - ob_start(); - ob_implicit_flush(false); - $response = new Response(); - $response->sendFile($fileName, $content, $mimeType, false); - $file = ob_get_clean(); - return $file; - } } From 88b05cdfebd78a03ad399b42ed7fe94a0119dbc9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov <sam@rmcreative.ru> Date: Sun, 9 Jun 2013 20:15:55 +0400 Subject: [PATCH 90/91] =?UTF-8?q?renamed=20backstage=20=E2=86=92=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/advanced/README.md | 12 ++-- apps/advanced/backend/assets/.gitkeep | 1 + apps/advanced/backend/config/.gitignore | 2 + apps/advanced/backend/config/assets.php | 18 +++++ apps/advanced/backend/config/main.php | 40 +++++++++++ apps/advanced/backend/config/params.php | 5 ++ .../backend/controllers/SiteController.php | 33 +++++++++ apps/advanced/backend/models/.gitkeep | 1 + apps/advanced/backend/runtime/.gitignore | 2 + apps/advanced/backend/views/layouts/main.php | 64 ++++++++++++++++++ apps/advanced/backend/views/site/index.php | 47 +++++++++++++ apps/advanced/backend/views/site/login.php | 24 +++++++ apps/advanced/backend/www/.gitignore | 1 + apps/advanced/backend/www/assets/.gitignore | 2 + apps/advanced/backend/www/css/site.css | 78 ++++++++++++++++++++++ apps/advanced/backstage/assets/.gitkeep | 1 - apps/advanced/backstage/config/.gitignore | 2 - apps/advanced/backstage/config/assets.php | 18 ----- apps/advanced/backstage/config/main.php | 40 ----------- apps/advanced/backstage/config/params.php | 5 -- .../backstage/controllers/SiteController.php | 33 --------- apps/advanced/backstage/models/.gitkeep | 1 - apps/advanced/backstage/runtime/.gitignore | 2 - apps/advanced/backstage/views/layouts/main.php | 64 ------------------ apps/advanced/backstage/views/site/index.php | 47 ------------- apps/advanced/backstage/views/site/login.php | 24 ------- apps/advanced/backstage/www/.gitignore | 1 - apps/advanced/backstage/www/assets/.gitignore | 2 - apps/advanced/backstage/www/css/site.css | 78 ---------------------- apps/advanced/composer.json | 4 +- 30 files changed, 326 insertions(+), 326 deletions(-) create mode 100644 apps/advanced/backend/assets/.gitkeep create mode 100644 apps/advanced/backend/config/.gitignore create mode 100644 apps/advanced/backend/config/assets.php create mode 100644 apps/advanced/backend/config/main.php create mode 100644 apps/advanced/backend/config/params.php create mode 100644 apps/advanced/backend/controllers/SiteController.php create mode 100644 apps/advanced/backend/models/.gitkeep create mode 100644 apps/advanced/backend/runtime/.gitignore create mode 100644 apps/advanced/backend/views/layouts/main.php create mode 100644 apps/advanced/backend/views/site/index.php create mode 100644 apps/advanced/backend/views/site/login.php create mode 100644 apps/advanced/backend/www/.gitignore create mode 100644 apps/advanced/backend/www/assets/.gitignore create mode 100644 apps/advanced/backend/www/css/site.css delete mode 100644 apps/advanced/backstage/assets/.gitkeep delete mode 100644 apps/advanced/backstage/config/.gitignore delete mode 100644 apps/advanced/backstage/config/assets.php delete mode 100644 apps/advanced/backstage/config/main.php delete mode 100644 apps/advanced/backstage/config/params.php delete mode 100644 apps/advanced/backstage/controllers/SiteController.php delete mode 100644 apps/advanced/backstage/models/.gitkeep delete mode 100644 apps/advanced/backstage/runtime/.gitignore delete mode 100644 apps/advanced/backstage/views/layouts/main.php delete mode 100644 apps/advanced/backstage/views/site/index.php delete mode 100644 apps/advanced/backstage/views/site/login.php delete mode 100644 apps/advanced/backstage/www/.gitignore delete mode 100644 apps/advanced/backstage/www/assets/.gitignore delete mode 100644 apps/advanced/backstage/www/css/site.css diff --git a/apps/advanced/README.md b/apps/advanced/README.md index d8c1e17..f2abc1e 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -10,7 +10,7 @@ if you have a project to be deployed for production soon. Thank you for using Yii 2 Advanced Application Template - an application template that works out-of-box and can be easily customized to fit for your needs. -Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backstage separation, +Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backend separation, deployment in different environments, configuration nesting etc. @@ -20,18 +20,18 @@ DIRECTORY STRUCTURE ``` common config/ contains shared configurations - models/ contains model classes used in both backstage and frontend + models/ contains model classes used in both backend and frontend console config/ contains console configurations controllers/ contains console controllers (commands) migrations/ contains database migrations models/ contains console-specific model classes runtime/ contains files generated during runtime -backstage +backend assets/ contains application assets such as JavaScript and CSS - config/ contains backstage configurations + config/ contains backend configurations controllers/ contains Web controller classes - models/ contains backstage-specific model classes + models/ contains backend-specific model classes runtime/ contains files generated during runtime views/ contains view files for the Web application www/ contains the entry script and Web resources @@ -107,7 +107,7 @@ the installed application. You only need to do these once for all. Now you should be able to access: - the frontend using the URL `http://localhost/yii-advanced/frontend/www/` -- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` +- the backend using the URL `http://localhost/yii-advanced/backend/www/` assuming `yii-advanced` is directly under the document root of your Web server. diff --git a/apps/advanced/backend/assets/.gitkeep b/apps/advanced/backend/assets/.gitkeep new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/apps/advanced/backend/assets/.gitkeep @@ -0,0 +1 @@ +* diff --git a/apps/advanced/backend/config/.gitignore b/apps/advanced/backend/config/.gitignore new file mode 100644 index 0000000..20da318 --- /dev/null +++ b/apps/advanced/backend/config/.gitignore @@ -0,0 +1,2 @@ +main-local.php +params-local.php \ No newline at end of file diff --git a/apps/advanced/backend/config/assets.php b/apps/advanced/backend/config/assets.php new file mode 100644 index 0000000..ee0d610 --- /dev/null +++ b/apps/advanced/backend/config/assets.php @@ -0,0 +1,18 @@ +<?php + +return array( + 'app' => array( + 'basePath' => '@wwwroot', + 'baseUrl' => '@www', + 'css' => array( + 'css/site.css', + ), + 'js' => array( + + ), + 'depends' => array( + 'yii', + 'yii/bootstrap/responsive', + ), + ), +); diff --git a/apps/advanced/backend/config/main.php b/apps/advanced/backend/config/main.php new file mode 100644 index 0000000..3140cd2 --- /dev/null +++ b/apps/advanced/backend/config/main.php @@ -0,0 +1,40 @@ +<?php +$rootDir = __DIR__ . '/../..'; + +$params = array_merge( + require($rootDir . '/common/config/params.php'), + require($rootDir . '/common/config/params-local.php'), + require(__DIR__ . '/params.php'), + require(__DIR__ . '/params-local.php') +); + +return array( + 'id' => 'app-backend', + 'basePath' => dirname(__DIR__), + 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', + 'preload' => array('log'), + 'controllerNamespace' => 'backend\controllers', + 'modules' => array( + ), + 'components' => array( + 'db' => $params['components.db'], + 'cache' => $params['components.cache'], + 'user' => array( + 'class' => 'yii\web\User', + 'identityClass' => 'common\models\User', + ), + 'assetManager' => array( + 'bundles' => require(__DIR__ . '/assets.php'), + ), + 'log' => array( + 'class' => 'yii\logging\Router', + 'targets' => array( + array( + 'class' => 'yii\logging\FileTarget', + 'levels' => array('error', 'warning'), + ), + ), + ), + ), + 'params' => $params, +); diff --git a/apps/advanced/backend/config/params.php b/apps/advanced/backend/config/params.php new file mode 100644 index 0000000..1e197d0 --- /dev/null +++ b/apps/advanced/backend/config/params.php @@ -0,0 +1,5 @@ +<?php + +return array( + 'adminEmail' => 'admin@example.com', +); \ No newline at end of file diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php new file mode 100644 index 0000000..0306c97 --- /dev/null +++ b/apps/advanced/backend/controllers/SiteController.php @@ -0,0 +1,33 @@ +<?php + +namespace backend\controllers; + +use Yii; +use yii\web\Controller; +use common\models\LoginForm; + +class SiteController extends Controller +{ + public function actionIndex() + { + echo $this->render('index'); + } + + public function actionLogin() + { + $model = new LoginForm(); + if ($this->populate($_POST, $model) && $model->login()) { + Yii::$app->response->redirect(array('site/index')); + } else { + echo $this->render('login', array( + 'model' => $model, + )); + } + } + + public function actionLogout() + { + Yii::$app->getUser()->logout(); + Yii::$app->getResponse()->redirect(array('site/index')); + } +} diff --git a/apps/advanced/backend/models/.gitkeep b/apps/advanced/backend/models/.gitkeep new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/apps/advanced/backend/models/.gitkeep @@ -0,0 +1 @@ +* diff --git a/apps/advanced/backend/runtime/.gitignore b/apps/advanced/backend/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/apps/advanced/backend/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php new file mode 100644 index 0000000..44117f4 --- /dev/null +++ b/apps/advanced/backend/views/layouts/main.php @@ -0,0 +1,64 @@ +<?php +use yii\helpers\Html; +use yii\widgets\Menu; +use yii\widgets\Breadcrumbs; +use yii\debug\Toolbar; + +/** + * @var $this \yii\base\View + * @var $content string + */ +$this->registerAssetBundle('app'); +?> +<?php $this->beginPage(); ?> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"/> + <title><?php echo Html::encode($this->title); ?> + head(); ?> + + +
+ beginBody(); ?> +
+

My Company

+ + + +
+ + isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), + )); ?> + + +
+ + + endBody(); ?> +
+ + + +endPage(); ?> diff --git a/apps/advanced/backend/views/site/index.php b/apps/advanced/backend/views/site/index.php new file mode 100644 index 0000000..158b61c --- /dev/null +++ b/apps/advanced/backend/views/site/index.php @@ -0,0 +1,47 @@ +title = 'Welcome'; +?> +
+

Welcome!

+ +

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus + commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

+ Get started with Yii +
+ +
+ + +
+
+

Heading

+ +

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris + condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. + Donec sed odio dui.

+ +

View details »

+
+
+

Heading

+ +

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris + condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. + Donec sed odio dui.

+ +

View details »

+
+
+

Heading

+ +

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta + felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum + massa.

+ +

View details »

+
+
+ diff --git a/apps/advanced/backend/views/site/login.php b/apps/advanced/backend/views/site/login.php new file mode 100644 index 0000000..f676b98 --- /dev/null +++ b/apps/advanced/backend/views/site/login.php @@ -0,0 +1,24 @@ +title = 'Login'; +$this->params['breadcrumbs'][] = $this->title; +?> +

title); ?>

+ +

Please fill out the following fields to login:

+ + array('class' => 'form-horizontal'))); ?> + field($model, 'username')->textInput(); ?> + field($model, 'password')->passwordInput(); ?> + field($model, 'rememberMe')->checkbox(); ?> +
+ 'btn btn-primary')); ?> +
+ diff --git a/apps/advanced/backend/www/.gitignore b/apps/advanced/backend/www/.gitignore new file mode 100644 index 0000000..148f2b0 --- /dev/null +++ b/apps/advanced/backend/www/.gitignore @@ -0,0 +1 @@ +/index.php \ No newline at end of file diff --git a/apps/advanced/backend/www/assets/.gitignore b/apps/advanced/backend/www/assets/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/apps/advanced/backend/www/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/apps/advanced/backend/www/css/site.css b/apps/advanced/backend/www/css/site.css new file mode 100644 index 0000000..890a953 --- /dev/null +++ b/apps/advanced/backend/www/css/site.css @@ -0,0 +1,78 @@ +body { + padding-top: 20px; + padding-bottom: 60px; +} + +/* Custom container */ +.container { + margin: 0 auto; + max-width: 1000px; +} + +.container > hr { + margin: 60px 0; +} + +/* Main marketing message and sign up button */ +.jumbotron { + margin: 80px 0; + text-align: center; +} + +.jumbotron h1 { + font-size: 100px; + line-height: 1; +} + +.jumbotron .lead { + font-size: 24px; + line-height: 1.25; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + +/* Supporting marketing content */ +.marketing { + margin: 60px 0; +} + +.marketing p + h4 { + margin-top: 28px; +} + +/* Customize the navbar links to be fill the entire space of the .navbar */ +.navbar .navbar-inner { + padding: 0; +} + +.navbar .nav { + margin: 0; + display: table; + width: 100%; +} + +.navbar .nav li { + display: table-cell; + width: 1%; + float: none; +} + +.navbar .nav li a { + font-weight: bold; + text-align: center; + border-left: 1px solid rgba(255, 255, 255, .75); + border-right: 1px solid rgba(0, 0, 0, .1); +} + +.navbar .nav li:first-child a { + border-left: 0; + border-radius: 3px 0 0 3px; +} + +.navbar .nav li:last-child a { + border-right: 0; + border-radius: 0 3px 3px 0; +} diff --git a/apps/advanced/backstage/assets/.gitkeep b/apps/advanced/backstage/assets/.gitkeep deleted file mode 100644 index 72e8ffc..0000000 --- a/apps/advanced/backstage/assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backstage/config/.gitignore b/apps/advanced/backstage/config/.gitignore deleted file mode 100644 index 20da318..0000000 --- a/apps/advanced/backstage/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/apps/advanced/backstage/config/assets.php b/apps/advanced/backstage/config/assets.php deleted file mode 100644 index ee0d610..0000000 --- a/apps/advanced/backstage/config/assets.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - 'basePath' => '@wwwroot', - 'baseUrl' => '@www', - 'css' => array( - 'css/site.css', - ), - 'js' => array( - - ), - 'depends' => array( - 'yii', - 'yii/bootstrap/responsive', - ), - ), -); diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backstage/config/main.php deleted file mode 100644 index 6e55c47..0000000 --- a/apps/advanced/backstage/config/main.php +++ /dev/null @@ -1,40 +0,0 @@ - 'app-backend', - 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'preload' => array('log'), - 'controllerNamespace' => 'backstage\controllers', - 'modules' => array( - ), - 'components' => array( - 'db' => $params['components.db'], - 'cache' => $params['components.cache'], - 'user' => array( - 'class' => 'yii\web\User', - 'identityClass' => 'common\models\User', - ), - 'assetManager' => array( - 'bundles' => require(__DIR__ . '/assets.php'), - ), - 'log' => array( - 'class' => 'yii\logging\Router', - 'targets' => array( - array( - 'class' => 'yii\logging\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), - 'params' => $params, -); diff --git a/apps/advanced/backstage/config/params.php b/apps/advanced/backstage/config/params.php deleted file mode 100644 index 1e197d0..0000000 --- a/apps/advanced/backstage/config/params.php +++ /dev/null @@ -1,5 +0,0 @@ - 'admin@example.com', -); \ No newline at end of file diff --git a/apps/advanced/backstage/controllers/SiteController.php b/apps/advanced/backstage/controllers/SiteController.php deleted file mode 100644 index d40738a..0000000 --- a/apps/advanced/backstage/controllers/SiteController.php +++ /dev/null @@ -1,33 +0,0 @@ -render('index'); - } - - public function actionLogin() - { - $model = new LoginForm(); - if ($this->populate($_POST, $model) && $model->login()) { - Yii::$app->response->redirect(array('site/index')); - } else { - echo $this->render('login', array( - 'model' => $model, - )); - } - } - - public function actionLogout() - { - Yii::$app->getUser()->logout(); - Yii::$app->getResponse()->redirect(array('site/index')); - } -} diff --git a/apps/advanced/backstage/models/.gitkeep b/apps/advanced/backstage/models/.gitkeep deleted file mode 100644 index 72e8ffc..0000000 --- a/apps/advanced/backstage/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backstage/runtime/.gitignore b/apps/advanced/backstage/runtime/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/apps/advanced/backstage/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/backstage/views/layouts/main.php b/apps/advanced/backstage/views/layouts/main.php deleted file mode 100644 index 44117f4..0000000 --- a/apps/advanced/backstage/views/layouts/main.php +++ /dev/null @@ -1,64 +0,0 @@ -registerAssetBundle('app'); -?> -beginPage(); ?> - - - - - <?php echo Html::encode($this->title); ?> - head(); ?> - - -
- beginBody(); ?> -
-

My Company

- - - -
- - isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - - -
- - - endBody(); ?> -
- - - -endPage(); ?> diff --git a/apps/advanced/backstage/views/site/index.php b/apps/advanced/backstage/views/site/index.php deleted file mode 100644 index 158b61c..0000000 --- a/apps/advanced/backstage/views/site/index.php +++ /dev/null @@ -1,47 +0,0 @@ -title = 'Welcome'; -?> -
-

Welcome!

- -

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus - commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

- Get started with Yii -
- -
- - -
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. - Donec sed odio dui.

- -

View details »

-
-
-

Heading

- -

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta - felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum - massa.

- -

View details »

-
-
- diff --git a/apps/advanced/backstage/views/site/login.php b/apps/advanced/backstage/views/site/login.php deleted file mode 100644 index f676b98..0000000 --- a/apps/advanced/backstage/views/site/login.php +++ /dev/null @@ -1,24 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> -

title); ?>

- -

Please fill out the following fields to login:

- - array('class' => 'form-horizontal'))); ?> - field($model, 'username')->textInput(); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- diff --git a/apps/advanced/backstage/www/.gitignore b/apps/advanced/backstage/www/.gitignore deleted file mode 100644 index 148f2b0..0000000 --- a/apps/advanced/backstage/www/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/index.php \ No newline at end of file diff --git a/apps/advanced/backstage/www/assets/.gitignore b/apps/advanced/backstage/www/assets/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/apps/advanced/backstage/www/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/advanced/backstage/www/css/site.css b/apps/advanced/backstage/www/css/site.css deleted file mode 100644 index 890a953..0000000 --- a/apps/advanced/backstage/www/css/site.css +++ /dev/null @@ -1,78 +0,0 @@ -body { - padding-top: 20px; - padding-bottom: 60px; -} - -/* Custom container */ -.container { - margin: 0 auto; - max-width: 1000px; -} - -.container > hr { - margin: 60px 0; -} - -/* Main marketing message and sign up button */ -.jumbotron { - margin: 80px 0; - text-align: center; -} - -.jumbotron h1 { - font-size: 100px; - line-height: 1; -} - -.jumbotron .lead { - font-size: 24px; - line-height: 1.25; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -/* Supporting marketing content */ -.marketing { - margin: 60px 0; -} - -.marketing p + h4 { - margin-top: 28px; -} - -/* Customize the navbar links to be fill the entire space of the .navbar */ -.navbar .navbar-inner { - padding: 0; -} - -.navbar .nav { - margin: 0; - display: table; - width: 100%; -} - -.navbar .nav li { - display: table-cell; - width: 1%; - float: none; -} - -.navbar .nav li a { - font-weight: bold; - text-align: center; - border-left: 1px solid rgba(255, 255, 255, .75); - border-right: 1px solid rgba(0, 0, 0, .1); -} - -.navbar .nav li:first-child a { - border-left: 0; - border-radius: 3px 0 0 3px; -} - -.navbar .nav li:last-child a { - border-right: 0; - border-radius: 0 3px 3px 0; -} diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index cc3ec2d..0e393cf 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -25,8 +25,8 @@ }, "extra": { "yii-install-writable": [ - "backstage/runtime", - "backstage/www/assets", + "backend/runtime", + "backend/www/assets", "console/runtime", "console/migrations", From d5d463f9b4b1447859cd01d3269050f9a5d4b6c0 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 9 Jun 2013 20:50:41 +0400 Subject: [PATCH 91/91] fixed init.bat paths --- apps/advanced/init.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat index dc2cd83..4fc52f7 100644 --- a/apps/advanced/init.bat +++ b/apps/advanced/init.bat @@ -1,7 +1,7 @@ @echo off rem ------------------------------------------------------------- -rem Yii command line install script for Windows. +rem Yii command line init script for Windows. rem rem @author Qiang Xue rem @link http://www.yiiframework.com/ @@ -15,6 +15,6 @@ set YII_PATH=%~dp0 if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe -"%PHP_COMMAND%" "%YII_PATH%install" %* +"%PHP_COMMAND%" "%YII_PATH%init" %* @endlocal