diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index 4f178ef..de7c9fa 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -17,9 +17,11 @@ using the following syntax: e.g. `[[name()]]`, `[[name\space\MyClass::name()]]`. ### Namespace and package names - Extension MUST use the type `yii2-extension` in `composer.json` file. -- Extension MUST NOT use `yii` or `yii2` in the composer package name or in the namespaces used in the package. +- Extension MUST NOT use `yiisoft` in the composer package name, the composer vendor name or in the namespaces used in the package. +- Additionally extensions MUST NOT use `yii` or `yii2` in their composer vendor name. +- Extension MUST NOT have a root-namespace named `\yii`, `\yii2` or `\yiisoft`. - Extension SHOULD use namespaces in this format `vendor-name\package` (all lowercase). -- Extension MAY use a `yii2-` prefix in the composer vendor name (URL). +- Extension MAY use `yii2-` in the composer package name (e.g `vendor\yii2-api-adapter` or `vendor\my-yii2-package` (URL). - Extension MAY use a `yii2-` prefix in the repository name (URL). ### Dependencies @@ -75,4 +77,4 @@ Authorization Testing your extension ---------------------- -- Extension SHOULD be testable with *PHPUnit*. \ No newline at end of file +- Extension SHOULD be testable with *PHPUnit*. diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index 3386df6..fc8b275 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -18,10 +18,10 @@ then you must [setup your GIT installation to work with GitHub](http://help.gith ### 2. Add the main Yii repository as an additional git remote called "upstream" -Change to the directory where you cloned Yii, normally, "yii". Then enter the following command: +Change to the directory where you cloned Yii, normally, "yii2". Then enter the following command: ``` -git remote add upstream git://github.com/yiisoft/yii.git +git remote add upstream git://github.com/yiisoft/yii2.git ``` ### 3. Make sure there is an issue created for the thing you are working on. diff --git a/extensions/yii/sphinx/Command.php b/extensions/yii/sphinx/Command.php index ba802f5..9197b67 100644 --- a/extensions/yii/sphinx/Command.php +++ b/extensions/yii/sphinx/Command.php @@ -319,7 +319,7 @@ class Command extends \yii\db\Command /** * @inheritdoc */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); } diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 747e59d..b7699ac 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -34,6 +34,8 @@ Yii Framework 2 Change Log - Enh #1611: Added `BaseActiveRecord::markAttributeDirty()` (qiangxue) - Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue) - Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue) +- Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo) +- Enh #1645: Added `Connection::$pdoClass` property (Ragazzo) - Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) diff --git a/framework/yii/db/ActiveQueryTrait.php b/framework/yii/db/ActiveQueryTrait.php index c8ba773..79bac78 100644 --- a/framework/yii/db/ActiveQueryTrait.php +++ b/framework/yii/db/ActiveQueryTrait.php @@ -6,6 +6,7 @@ */ namespace yii\db; +use yii\base\InvalidCallException; /** * ActiveQueryTrait implements the common methods and properties for active record query classes. @@ -42,6 +43,10 @@ trait ActiveQueryTrait public function __call($name, $params) { if (method_exists($this->modelClass, $name)) { + $method = new \ReflectionMethod($this->modelClass, $name); + if (!$method->isStatic() || !$method->isPublic()) { + throw new InvalidCallException("The scope method \"{$this->modelClass}::$name()\" must be public and static."); + } array_unshift($params, $this); call_user_func_array([$this->modelClass, $name], $params); return $this; diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index 2075b2d..76b4269 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -743,12 +743,13 @@ class Command extends \yii\base\Component * @param boolean $check whether to turn on or off the integrity check. * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current * or default schema. + * @param string $table the table name. * @return Command the command object itself * @throws NotSupportedException if this is not supported by the underlying DBMS */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { - $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema); + $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table); return $this->setSql($sql); } } diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index ef5be87..030e6c7 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -253,7 +253,10 @@ class Connection extends Component * @var Schema the database schema */ private $_schema; - + /** + * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used. + */ + public $pdoClass; /** * Returns a value indicating whether the DB connection is established. @@ -338,13 +341,17 @@ class Connection extends Component */ protected function createPdoInstance() { - $pdoClass = 'PDO'; - if (($pos = strpos($this->dsn, ':')) !== false) { - $driver = strtolower(substr($this->dsn, 0, $pos)); - if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { - $pdoClass = 'yii\db\mssql\PDO'; + $pdoClass = $this->pdoClass; + if ($pdoClass === null) { + $pdoClass = 'PDO'; + if (($pos = strpos($this->dsn, ':')) !== false) { + $driver = strtolower(substr($this->dsn, 0, $pos)); + if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { + $pdoClass = 'yii\db\mssql\PDO'; + } } } + return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); } diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index a649d8a..d5258c1 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -280,7 +280,7 @@ class Schema extends \yii\db\Schema * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. * @return array all table names in the database. The names have NO schema name prefix. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { $sql = 'SHOW TABLES'; if ($schema !== '') { diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 09a620d..f1ac032 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -8,6 +8,8 @@ namespace yii\db\pgsql; +use yii\base\InvalidParamException; + /** * QueryBuilder is the query builder for PostgreSQL databases. * @@ -62,6 +64,65 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $sequence = '"' . $table->sequenceName . '"'; + + if (strpos($sequence, '.') !== false) { + $sequence = str_replace('.', '"."', $sequence); + } + + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = "(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1"; + } else { + $value = (int)$value; + } + return "SELECT SETVAL('$sequence',$value,false)"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. + * @param string $table the table name. + * @return string the SQL statement for checking integrity + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + $enable = $check ? 'ENABLE' : 'DISABLE'; + $schema = $schema ? $schema : $this->db->schema->defaultSchema; + $tableNames = $table ? [$table] : $this->db->schema->findTableNames($schema); + $command = ''; + + foreach ($tableNames as $tableName) { + $tableName = '"' . $schema . '"."' . $tableName . '"'; + $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; + } + + #enable to have ability to alter several tables + $this->db->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true); + return $command; + } + + /** * Builds a SQL statement for changing the definition of a column. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. * @param string $column the name of the column to be changed. The name will be properly quoted by the method. diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 96889ab..59340c9 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -158,7 +158,7 @@ class Schema extends \yii\db\Schema * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. * @return array all table names in the database. The names have NO schema name prefix. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { if ($schema === '') { $schema = $this->defaultSchema; diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php index 9f410b4..6548999 100644 --- a/framework/yii/db/sqlite/Schema.php +++ b/framework/yii/db/sqlite/Schema.php @@ -87,7 +87,7 @@ class Schema extends \yii\db\Schema * If not empty, the returned table names will be prefixed with the schema name. * @return array all table names in the database. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; return $this->db->createCommand($sql)->queryColumn(); diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php index d78d28f..f657a40 100644 --- a/framework/yii/test/DbFixtureManager.php +++ b/framework/yii/test/DbFixtureManager.php @@ -146,6 +146,7 @@ class DbFixtureManager extends Component } $this->db->createCommand()->truncateTable($tableName)->execute(); + $this->db->createCommand()->resetSequence($tableName,1)->execute(); $fileName = $this->basePath . '/' . $tableName . '.php'; if (!is_file($fileName)) { diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index aae2e3c..ee232f4 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1039,7 +1039,8 @@ class Request extends \yii\base\Request if ($this->_maskedCsrfToken === null) { $token = $this->getCsrfToken(); $mask = Security::generateRandomKey(self::CSRF_MASK_LENGTH); - $this->_maskedCsrfToken = base64_encode($mask . $this->xorTokens($token, $mask)); + // The + sign may be decoded as blank space later, which will fail the validation + $this->_maskedCsrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_maskedCsrfToken; } @@ -1120,7 +1121,7 @@ class Request extends \yii\base\Request private function validateCsrfTokenInternal($token, $trueToken) { - $token = base64_decode($token); + $token = str_replace('.', '+', base64_decode($token)); $n = StringHelper::byteLength($token); if ($n <= self::CSRF_MASK_LENGTH) { return false;