diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index 8560b86..4951ce3 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -56,6 +56,10 @@ class ReleaseController extends Controller * @var bool whether to fetch latest tags. */ public $update = false; + /** + * @var string override the default version. e.g. for major or patch releases. + */ + public $version; public function options($actionID) @@ -63,6 +67,7 @@ class ReleaseController extends Controller $options = ['basePath']; if ($actionID === 'release') { $options[] = 'dryRun'; + $options[] = 'version'; } elseif ($actionID === 'info') { $options[] = 'update'; } @@ -190,7 +195,17 @@ class ReleaseController extends Controller $this->validateWhat($what); $versions = $this->getCurrentVersions($what); - $newVersions = $this->getNextVersions($versions, self::PATCH);// TODO add support for minor + + if ($this->version !== null) { + // if a version is explicitly given + $newVersions = []; + foreach($versions as $k => $v) { + $newVersions[$k] = $this->version; + } + } else { + // otherwise get next patch or minor + $newVersions = $this->getNextVersions($versions, self::PATCH); + } $this->stdout("You are about to prepare a new release for the following things:\n\n"); $this->printWhat($what, $newVersions, $versions); @@ -363,8 +378,10 @@ class ReleaseController extends Controller $this->stdout($h = "Preparing framework release version $version", Console::BOLD); $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); - $this->runGit('git checkout master', $frameworkPath); // TODO add compatibility for other release branches - $this->runGit('git pull', $frameworkPath); // TODO add compatibility for other release branches + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $frameworkPath); // checks @@ -415,8 +432,8 @@ class ReleaseController extends Controller $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); $this->runGit("git commit -a -m \"release version $version\"", $frameworkPath); - $this->runGit("git tag -a $version -m\"version $version\"", $frameworkPath); - $this->runGit("git push origin master", $frameworkPath); + $this->runGit("git tag -a $version -m \"version $version\"", $frameworkPath); + $this->runGit("git push", $frameworkPath); $this->runGit("git push --tags", $frameworkPath); $this->stdout("\n\n"); @@ -467,7 +484,7 @@ class ReleaseController extends Controller $this->runGit("git diff --color", $frameworkPath); $this->stdout("\n\n"); $this->runGit("git commit -a -m \"prepare for next release\"", $frameworkPath); - $this->runGit("git push origin master", $frameworkPath); + $this->runGit("git push", $frameworkPath); $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); @@ -492,8 +509,10 @@ class ReleaseController extends Controller $this->stdout($h = "Preparing release for application $name version $version", Console::BOLD); $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); - $this->runGit('git checkout master', $path); // TODO add compatibility for other release branches - $this->runGit('git pull', $path); // TODO add compatibility for other release branches + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); // adjustments @@ -526,8 +545,8 @@ class ReleaseController extends Controller $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); $this->runGit("git commit -a -m \"release version $version\"", $path); - $this->runGit("git tag -a $version -m\"version $version\"", $path); - $this->runGit("git push origin master", $path); + $this->runGit("git tag -a $version -m \"version $version\"", $path); + $this->runGit("git push", $path); $this->runGit("git push --tags", $path); $this->stdout("\n\n"); @@ -551,7 +570,7 @@ class ReleaseController extends Controller $this->runGit("git diff --color", $path); $this->stdout("\n\n"); $this->runGit("git commit -a -m \"prepare for next release\"", $path); - $this->runGit("git push origin master", $path); + $this->runGit("git push", $path); $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); @@ -606,8 +625,10 @@ class ReleaseController extends Controller $this->stdout($h = "Preparing release for extension $name version $version", Console::BOLD); $this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD); - $this->runGit('git checkout master', $path); // TODO add compatibility for other release branches - $this->runGit('git pull', $path); // TODO add compatibility for other release branches + if (!$this->confirm('Make sure you are on the right branch for this release and that it tracks the correct remote branch! Continue?')) { + exit(1); + } + $this->runGit('git pull', $path); // adjustments @@ -640,8 +661,8 @@ class ReleaseController extends Controller $this->stdout("\n\nHint: if you decide 'no' for any of the following, the command will not be executed. You may manually run them later if needed. E.g. try the release locally without pushing it.\n\n"); $this->runGit("git commit -a -m \"release version $version\"", $path); - $this->runGit("git tag -a $version -m\"version $version\"", $path); - $this->runGit("git push origin master", $path); + $this->runGit("git tag -a $version -m \"version $version\"", $path); + $this->runGit("git push", $path); $this->runGit("git push --tags", $path); $this->stdout("\n\n"); @@ -664,7 +685,7 @@ class ReleaseController extends Controller $this->runGit("git diff --color", $path); $this->stdout("\n\n"); $this->runGit("git commit -a -m \"prepare for next release\"", $path); - $this->runGit("git push origin master", $path); + $this->runGit("git push", $path); $this->stdout("\n\nDONE!", Console::FG_YELLOW, Console::BOLD); diff --git a/code-of-conduct.md b/code-of-conduct.md index 0082eb6..82fdef1 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,45 +1,68 @@ Yii Contributor Code of Conduct ======================= +## Our Pledge + As contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to respect all community members. -We are committed to making participation in this project a good experience for everyone. +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery +* The use of sexualized language or imagery and unwelcome sexual attention or + advances * Personal attacks -* Trolling or insulting/derogatory comments +* Trolling or insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission -* Other unethical or unprofessional conduct +* Other conduct which could reasonably be considered inappropriate in + a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in response +to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +## Scope -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +This Code of Conduct applies both within project spaces and in public spaces when +an individual is representing the project or its community. Examples of representing +a project or community include posting via an official social media account, +within project GitHub, official forum or acting as an appointed representative at +an online or offline event. -By adopting this Code of Conduct, project maintainers commit themselves to -fairly and consistently applying these principles to every aspect of managing -this project. Project maintainers who do not follow or enforce the Code of -Conduct may be permanently removed from the project team. +## Enforcement -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported +by contacting core team members. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to the reporter of +an incident. Further details of specific enforcement policies may be posted separately. -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting core team members. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. Maintainers are -obligated to maintain confidentiality with regard to the reporter of an -incident. +Project maintainers who do not follow or enforce the Code of Conduct in good faith +may face temporary or permanent repercussions as determined by other members of +the project's leadership. +## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.3.0, available at -[http://contributor-covenant.org/version/1/3/0/][version] +version 1.4.0, available at +[http://contributor-covenant.org/version/1/4/][version] [homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/3/0/ +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/docs/guide-ja/rest-controllers.md b/docs/guide-ja/rest-controllers.md index 0be01f7..140d9e6 100644 --- a/docs/guide-ja/rest-controllers.md +++ b/docs/guide-ja/rest-controllers.md @@ -72,6 +72,40 @@ public function behaviors() ``` +### CORS + +コントローラに [CORS (クロスオリジンリソース共有)](structure-filters.md#cors) フィルタを追加するのは、上記の他のフィルタを追加するのより、若干複雑になります。 +と言うのは、CORS フィルタは認証メソッドより前に適用されなければならないため、他のフィルタとは少し異なるアプローチが必要だからです。 +また、ブラウザが認証クレデンシャルを送信する必要なく、リクエストが出来るかどうかを前もって安全に判断できるように、 +[CORS プリフライトリクエスト](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests) の認証を無効にする必要もあります。 +下記のコードは、[[yii\rest\ActiveController]] を拡張した既存のコントローラに [yii\filters\Cors]] フィルタを追加するのに必要なコードを示しています。 + +```php +use yii\filters\auth\HttpBasicAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + + // 認証フィルタを削除する + $auth = $behaviors['authenticator']; + unset($behaviors['authenticator']); + + // CORS フィルタを追加する + $behaviors['corsFilter'] = [ + 'class' => \yii\filters\Cors::className(), + ]; + + // 認証フィルタを再度追加する + $behaviors['authenticator'] = $auth; + // CORS プリフライトリクエスト (HTTP OPTIONS メソッド) の認証を回避する + $behaviors['authenticator']['except'] = ['options']; + + return $behaviors; +} +``` + + ## `ActiveController` を拡張する コントローラを [[yii\rest\ActiveController]] から拡張する場合は、このコントローラを通じて提供しようとしているリソースクラスの名前を [[yii\rest\ActiveController::modelClass|modelClass]] プロパティにセットしなければなりません。 diff --git a/docs/guide-ja/security-authorization.md b/docs/guide-ja/security-authorization.md index 3f8e9de..12953e7 100644 --- a/docs/guide-ja/security-authorization.md +++ b/docs/guide-ja/security-authorization.md @@ -439,6 +439,51 @@ Jane の場合は、彼女が管理者であるため、少し簡単になりま ![アクセスチェック](images/rbac-access-check-3.png "アクセスチェック") +コントローラ内で権限付与を実装するのには、いくつかの方法があります。 +追加と削除に対するアクセス権を分離する細分化された許可が必要な場合は、それぞれのアクションに対してアクセス権をチェックする必要があります。 +各アクションメソッドの中で上記の条件を使用するか、または [[yii\filters\AccessControl]] を使います。 + +```php +public function behaviors() +{ + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['index'], + 'roles' => ['managePost'], + ], + [ + 'allow' => true, + 'actions' => ['view'], + 'roles' => ['viewPost'], + ], + [ + 'allow' => true, + 'actions' => ['create'], + 'roles' => ['createPost'], + ], + [ + 'allow' => true, + 'actions' => ['update'], + 'roles' => ['updatePost'], + ], + [ + 'allow' => true, + 'actions' => ['delete'], + 'roles' => ['deletePost'], + ], + ], + ], + ]; +} +``` + +全ての CRUD 操作がまとめて管理される場合は、`managePost` のような単一の許可を使い、 +[[yii\web\Controller::beforeAction()]] の中でそれをチェックするのが良いアイデアです。 + ### デフォルトロールを使う デフォルトロールというのは、*全て* のユーザに *黙示的* に割り当てられるロールです。 diff --git a/docs/guide-ja/start-installation.md b/docs/guide-ja/start-installation.md index c33b046..e1589c0 100644 --- a/docs/guide-ja/start-installation.md +++ b/docs/guide-ja/start-installation.md @@ -212,15 +212,20 @@ server { #} #error_page 404 /404.html; + # /assets ディレクトリの php ファイルへのアクセスを拒否する + location ~ ^/assets/.*\.php$ { + deny all; + } + location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_pass 127.0.0.1:9000; + fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; } - location ~ /\.(ht|svn|git) { + location ~* /\. { deny all; } } diff --git a/docs/guide-ja/structure-filters.md b/docs/guide-ja/structure-filters.md index 1b38846..21cbdc6 100644 --- a/docs/guide-ja/structure-filters.md +++ b/docs/guide-ja/structure-filters.md @@ -328,7 +328,9 @@ public function behaviors() } ``` -Cors のフィルタリングは `cors` プロパティを使ってチューニングすることが出来ます。 +あなたの API の [[yii\rest\ActiveController]] クラスに CORS フィルタを追加したい場合は、[REST コントローラ](rest-controllers.md#cors) の節も参照して下さい。 + +Cors のフィルタリングは [[yii\filters\Cors::$cors|$cors]] プロパティを使ってチューニングすることが出来ます。 * `cors['Origin']`: 許可される生成元を定義するのに使われる配列。 `['*']` (すべて) または `['http://www.myserver.net'、'http://www.myotherserver.com']` などが設定可能。デフォルトは `['*']`。 diff --git a/docs/guide-ja/test-environment-setup.md b/docs/guide-ja/test-environment-setup.md index 040eebd..808105c 100644 --- a/docs/guide-ja/test-environment-setup.md +++ b/docs/guide-ja/test-environment-setup.md @@ -17,7 +17,7 @@ Codeception は、特定のプロジェクトのためだけにローカルに ローカルのインストールのためには、次のコマンドを使います。 ``` -composer require "codeception/codeception=2.0.*" +composer require "codeception/codeception=2.1.*" composer require "codeception/specify=*" composer require "codeception/verify=*" ``` @@ -25,7 +25,7 @@ composer require "codeception/verify=*" グローバルのインストールのためには、`global` 命令を使う必要があります。 ``` -composer global require "codeception/codeception=2.0.*" +composer global require "codeception/codeception=2.1.*" composer global require "codeception/specify=*" composer global require "codeception/verify=*" ``` diff --git a/docs/guide-ja/tutorial-core-validators.md b/docs/guide-ja/tutorial-core-validators.md index f94b20f..e4ec7d9 100644 --- a/docs/guide-ja/tutorial-core-validators.md +++ b/docs/guide-ja/tutorial-core-validators.md @@ -88,6 +88,27 @@ public function rules() * `<=`: 検証される値が比較される値よりも小さいか等しいことを検証する。 +### 日付の値を比較する + +compare バリデータは、文字列や数値を比較するためにしか使えません。 +日付のような値を比較する必要がある場合は、二つの選択肢があります。 +日付をある固定値と比較するときは、単に [[yii\validators\DateValidator|date]] バリデータを使って、その [[yii\validators\DateValidator::$min|$min]] や [[yii\validators\DateValidator::$max|$max]] のプロパティを指定すれば良いでしょう。 +フォームに入力された二つの日付、例えば、`fromDate` と `toDate` のフィールドを比較する必要がある場合は、 +次のように、compare バリデータと date バリデータを組み合わせて使うことが出来ます。 + +```php +['fromDate', 'date', 'timestampAttribute' => 'fromDate'], +['toDate', 'date', 'timestampAttribute' => 'toDate'], +['fromDate', 'compare', 'compareAttribute' => 'toDate', 'operator' => '<', 'enableClientValidation' => false], +``` + +バリデータは指定された順序に従って実行されますので、まず最初に、`fromDate` と `toDate` に入力された値が有効な日付であることが確認されます。 +そして、有効な日付であった場合は、機械が読める形式に変換されます。 +その後に、これらの二つの値が compare バリデータによって比較されます。 +現在、date バリデータはクライアント側のバリデーションを提供していませんので、これはサーバ側でのみ動作します。 +そのため、compare バリデータについても、[[yii\validators\CompareValidator::$enableClientValidation|$enableClientValidation]] は `false` に設定されています。 + + ## [[yii\validators\DateValidator|date]] ```php diff --git a/docs/guide-ja/tutorial-i18n.md b/docs/guide-ja/tutorial-i18n.md index 169cdf1..f8e4f91 100644 --- a/docs/guide-ja/tutorial-i18n.md +++ b/docs/guide-ja/tutorial-i18n.md @@ -48,6 +48,9 @@ return [ \Yii::$app->language = 'zh-CN'; ``` +> Tip: ソース言語がコードの部分によって異なる場合は、メッセージソースごとにソース言語をオーバーライドすることが出来ます。 +> これについては、次の説で説明します。 + ## メッセージ翻訳 diff --git a/docs/guide-ja/tutorial-performance-tuning.md b/docs/guide-ja/tutorial-performance-tuning.md index d17751b..3a2b1fc 100644 --- a/docs/guide-ja/tutorial-performance-tuning.md +++ b/docs/guide-ja/tutorial-performance-tuning.md @@ -198,5 +198,10 @@ composer dumpautoload -o 次のプロファイリングツールが役に立つでしょう。 - [Yii のデバッグツールバーとデバッガ](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide-ja/README.md) -- [XDebug プロファイラ](http://xdebug.org/docs/profiler) +- [Blackfire](https://blackfire.io/) - [XHProf](http://www.php.net/manual/ja/book.xhprof.php) +- [XDebug プロファイラ](http://xdebug.org/docs/profiler) + +## Prepare application for scaling + +何をやっても助けにならないときは、あなたのアプリケーションをスケーラブルにすることを試みましょう。良い導入記事が [Configuring a Yii2 Application for an Autoscaling Stack (Yii2 アプリケーションを自動スケール環境のために構成する)](https://github.com/samdark/yii2-cookbook/blob/master/book/scaling.md) の中で提供されています。更に詳しく知りたい場合は [Web apps performance and scaling (ウェブアプリのパフォーマンスとスケーリング)](http://thehighload.com/) を参照して下さい。 diff --git a/docs/guide-ja/tutorial-shared-hosting.md b/docs/guide-ja/tutorial-shared-hosting.md index a9b65da..c3d063b 100644 --- a/docs/guide-ja/tutorial-shared-hosting.md +++ b/docs/guide-ja/tutorial-shared-hosting.md @@ -2,17 +2,39 @@ ==================== 共有ホスティング環境では、たいてい、構成やディレクトリ構造について、大きな制約があります。 -それでも、ほとんどの場合、Yii 2.0 を共有ホスティング環境で走らせることは可能です。 +それでも、ほとんどの場合、少し調整をすれば、Yii 2.0 を共有ホスティング環境で走らせることが可能です。 -ベーシックアプリケーションを配備する ------------------------------------- +## ベーシックプロジェクトテンプレートを配備する -通例、一つのウェブルートしかありませんので、ベーシックプロジェクトテンプレートを使用することを推奨します。 -[Yii をインストールする](start-installation.md) の節を参照して、プロジェクトテンプレートをローカル環境にインストールしてください。 +通例、共有ホスティング環境では、一つのウェブルートしかありませんので、可能であればベーシックプロジェクトテンプレートを使用して下さい。 +まず、[Yii をインストールする](start-installation.md) の節を参照して、プロジェクトテンプレートをローカル環境にインストールします。 +そして、ローカル環境でアプリケーションが動くようにした後で、共有ホスティング環境でホスト出来るようにいくつかの修正を行います。 + +### ウェブルートの名前を変える + +FTP またはその他の手段であなたの共有ホストに接続します。おそらく、下記のようなディレクトリが見えるでしょう。 + +``` +config +logs +www +``` + +上記で `www` はウェブサーバのウェブルートディレクトリです。 +別の名前かもしれません。よくある名前は、`www`、`htdocs`、`public_html` です。 + +私たちのベーシックプロジェクトテンプレートではウェブルートの名前は `web` になっています。 +あなたのウェブサーバにアップロードする前に、ローカルのウェブルートの名前をあなたのサーバに適合するように変更します。 +すなわち、`web` から `www` や `public_html` など、何であれ、あなたの共有ホストのウェブルートの名前に変更します。 + +### FTP ルートディレクトリは書き込み可能 + +ルートレベルのディレクトリ、すなわち、`config`、`logs`、`www` があるディレクトリに対して書き込みが出来るのであれば、 +`assets`、`commands` などをそのままルートレベルのディレクトリにアップロードします。 ### ウェブサーバのための追加設定 -使用されているウェブサーバが Apache である場合は、次の内容を持つ `.htaccess` ファイルを `web` (`index.php` があるディレクトリ) に追加する必要があります。 +使用されているウェブサーバが Apache である場合は、次の内容を持つ `.htaccess` ファイルを `web` (または `public_html` など、要するに、`index.php` があるディレクトリ) に追加する必要があります。 ``` Options +FollowSymLinks @@ -30,87 +52,17 @@ RewriteRule . index.php nginx の場合は、追加の構成ファイルは必要がない筈です。 -### ウェブルートの名前を変える - -FTP またはその他の手段であなたの共有ホストに接続したとき、下記のようなディレクトリが見えれば、あなたはきっとラッキーです。 - -``` -config -logs -www -``` - -上記で `www` はウェブサーバのディレクトリルート (すなわち、ウェブルート) です。 -別の名前かもしれません。よくある名前は、`www`、`htdocs`、`public_html` です。 -ベーシックプロジェクトテンプレートではウェブルートの名前は `web` になっていますので、アップロードする前に、これをホストのウェブルートの名前に変更する必要があります。 - -### FTP ルートディレクトリは書き込み可能 - -ルートレベルのディレクトリ、すなわち、`config`、`logs`、`www` があるディレクトリに対して書き込みが出来るのであれば、`assets`、`commands` などをそのままアップロードします。 - ### 必要条件をチェックする -Yii を走らせるためには、ホストは Yii の必要条件を満たさなければなりません。 +Yii を走らせるためには、あなたのウェブサーバは Yii の必要条件を満たさなければなりません。 最低限の必要条件は PHP 5.4 です。 -残りの条件をチェックするために、`requirements.php` をルートディレクトリからウェブルートディレクトリにコピーして、`http://example.com/requirements.php` という URL を使ってブラウザ経由で走らせます。 +必要条件をチェックするために、`requirements.php` をルートディレクトリからウェブルートディレクトリにコピーして、 +`http://example.com/requirements.php` という URL を使ってブラウザ経由で走らせます。 後でファイルを削除するのを忘れないでください。 -アドバンストアプリケーションを配備する --------------------------------------- - -アドバンストアプリケーションを共有ホストに配備するのは、ベーシックアプリケーションを配備するのに比べると、少しトリッキーになります。 -なぜなら、アドバンストアプリケーションは、共有ホストが通常は持っていない二つのウェブルートを持つからです。 -このため、構造を少し修正します。 - -### エントリスクリプトを単一のウェブルートに移動する +## アドバンストプロジェクトテンプレートを配備する -まずは、ウェブルートディレクトリが必要です。 -上記の [ウェブルートの名前を変える](#renaming-webroot) で説明したように、あなたのホストのウェブルートに合うように名前を付けてください。 -次に、以下のような構造を作成します。 - -``` -www - admin -backend -common -console -environments -frontend -... -``` - -`www` がフロントエンドディレクトリになりますので、`frontend/web` の内容をそこに移動します。 -同じように、`backend/web` の内容を `www/admin` に移動します。 -どちらの場合も、`index.php` および `index-test.php` の中のパスを修正する必要があります。 - -### セッションとクッキーを分離する - -元来は、バックエンドとフロントエンドは異なるドメインで走ることを意図されています。 -両方を同じドメインに移動すると、二つが同じクッキーを共有して干渉することになります。 -これを修正するために、バックエンドのアプリケーション構成 `backend/config/main.php` を以下のように修正します。 - -```php -'components' => [ - 'request' => [ - 'csrfParam' => '_backendCSRF', - 'csrfCookie' => [ - 'httpOnly' => true, - 'path' => '/admin', - ], - ], - 'user' => [ - 'identityCookie' => [ - 'name' => '_backendIdentity', - 'path' => '/admin', - 'httpOnly' => true, - ], - ], - 'session' => [ - 'name' => 'BACKENDSESSID', - 'cookieParams' => [ - 'path' => '/admin', - ], - ], -], -``` +アドバンストプロジェクトテンプレートを共有ホストに配備することは、ベーシックプロジェクトテンプレートを配備するのに比べると少しトリッキーにはなりますが、可能です。 +[アドバンストプロジェクトテンプレートのドキュメント](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-ja/topic-shared-hosting.md) +で説明されている指示に従って下さい。 diff --git a/docs/guide-ja/tutorial-yii-integration.md b/docs/guide-ja/tutorial-yii-integration.md index e3f419f..4bfd46a 100644 --- a/docs/guide-ja/tutorial-yii-integration.md +++ b/docs/guide-ja/tutorial-yii-integration.md @@ -144,7 +144,7 @@ require(__DIR__ . '/../components/Yii.php'); // Yii 2 アプリケーションの構成 $yii2Config = require(__DIR__ . '/../config/yii2/web.php'); -new yii\web\Application($yii2Config); // ここで run() を呼ばない +new yii\web\Application($yii2Config); // ここで run() を呼ばない。yii2 app はサービスロケータとしてのみ使用される。 // Yii 1 アプリケーションの構成 $yii1Config = require(__DIR__ . '/../config/yii1/main.php'); @@ -168,7 +168,7 @@ class Yii extends \yii\BaseYii Yii::$classMap = include($yii2path . '/classes.php'); // Yii 2 オートローダを Yii 1 によって登録 -Yii::registerAutoloader(['Yii', 'autoload']); +Yii::registerAutoloader(['yii\BaseYii', 'autoload']); // 依存注入コンテナを作成 Yii::$container = new yii\di\Container; ``` diff --git a/docs/guide-ru/concept-di-container.md b/docs/guide-ru/concept-di-container.md index 582b6ed..1018ca8 100644 --- a/docs/guide-ru/concept-di-container.md +++ b/docs/guide-ru/concept-di-container.md @@ -1,7 +1,7 @@ Контейнер внедрения зависимостей ============================== -Контейнер внедрения зависимостей - это объект, который знает, как создать и настроить экземпляр класса и зависимых от него объектов. +Контейнер внедрения зависимостей — это объект, который знает, как создать и настроить экземпляр класса и зависимых от него объектов. [Статья Мартина Фаулера](http://martinfowler.com/articles/injection.html) хорошо объясняет, почему контейнер внедрения зависимостей является полезным. Здесь, преимущественно, будет объясняться использование контейнера внедрения зависимостей, предоставляемого в Yii. diff --git a/docs/guide/security-authorization.md b/docs/guide/security-authorization.md index ce6f7b4..4fd5a4f 100644 --- a/docs/guide/security-authorization.md +++ b/docs/guide/security-authorization.md @@ -439,6 +439,50 @@ In case of Jane it is a bit simpler since she is an admin: ![Access check](images/rbac-access-check-3.png "Access check") +Inside your controller there are a few ways to implement authorization. If you want granular permissions that +separate access to adding and deleting, then you need to check access for each action. You can either use the +above condition in each action method, or use [[yii\filters\AccessControl]]: + +```php +public function behaviors() +{ + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['index'], + 'roles' => ['managePost'], + ], + [ + 'allow' => true, + 'actions' => ['view'], + 'roles' => ['viewPost'], + ], + [ + 'allow' => true, + 'actions' => ['create'], + 'roles' => ['createPost'], + ], + [ + 'allow' => true, + 'actions' => ['update'], + 'roles' => ['updatePost'], + ], + [ + 'allow' => true, + 'actions' => ['delete'], + 'roles' => ['deletePost'], + ], + ], + ], + ]; +} +``` + +If all the CRUD operations are managed together then it's a good idea to use a single permission, like `managePost`, and +check it in [[yii\web\Controller::beforeAction()]]. ### Using Default Roles diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md index d6c7359..461670f 100644 --- a/docs/guide/start-installation.md +++ b/docs/guide/start-installation.md @@ -217,15 +217,20 @@ server { #} #error_page 404 /404.html; + # deny accessing php files for the /assets directory + location ~ ^/assets/.*\.php$ { + deny all; + } + location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_pass 127.0.0.1:9000; + fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; } - location ~ /\.(ht|svn|git) { + location ~* /\. { deny all; } } diff --git a/docs/guide/tutorial-shared-hosting.md b/docs/guide/tutorial-shared-hosting.md index 9c9f33d..c231dfa 100644 --- a/docs/guide/tutorial-shared-hosting.md +++ b/docs/guide/tutorial-shared-hosting.md @@ -1,12 +1,15 @@ Shared Hosting Environment ========================== -Shared hosting environments are often quite limited about configuration and directory structure. Still in most cases you can run Yii 2.0 on a shared hosting environment with a few adjustements. +Shared hosting environments are often quite limited about configuration and directory structure. Still in most cases you +can run Yii 2.0 on a shared hosting environment with a few adjustements. -Deploying a basic application ---------------------------- +## Deploying a basic project template -Since in a shared hosting environment there's typically only one webroot, use the basic project template if you can. Refer to the [Installing Yii chapter](start-installation.md) and install the basic project template locally. After you have the application working locally, we'll make some adjustments so it can be hosted on your shared hosting server. +Since in a shared hosting environment there's typically only one webroot, use the basic project template if you can. +Refer to the [Installing Yii chapter](start-installation.md) and install the basic project template locally. +After you have the application working locally, we'll make some adjustments so it can be hosted on your shared hosting +server. ### Renaming webroot @@ -18,17 +21,22 @@ logs www ``` -In the above, `www` is your webserver webroot directory. It could be named differently. Common names are: `www`, `htdocs`, and `public_html`. +In the above, `www` is your webserver webroot directory. It could be named differently. Common names are: `www`, +`htdocs`, and `public_html`. -The webroot in our basic project template is named `web`. Before uploading the application to your webserver rename your local webroot to match your server, i.e., from `web` to `www`, `public_html` or whatever the name of your hosting webroot. +The webroot in our basic project template is named `web`. Before uploading the application to your webserver rename +your local webroot to match your server, i.e., from `web` to `www`, `public_html` or whatever the name of your hosting +webroot. ### FTP root directory is writeable -If you can write to the root level directory i.e. where `config`, `logs` and `www` are, then upload `assets`, `commands` etc. as is to the root level directory. +If you can write to the root level directory i.e. where `config`, `logs` and `www` are, then upload `assets`, `commands` +etc. as is to the root level directory. ### Add extras for webserver -If your webserver is Apache you'll need to add an `.htaccess` file with the following content to `web` (or `public_html` or whatever) (where the `index.php` file is located): +If your webserver is Apache you'll need to add an `.htaccess` file with the following content to `web` +(or `public_html` or whatever) (where the `index.php` file is located): ``` Options +FollowSymLinks @@ -48,57 +56,12 @@ In case of nginx you should not need any extra config files. ### Check requirements -In order to run Yii, your webserver must meet its requirements. The very minimum requirement is PHP 5.4. In order to check the requirements copy `requirements.php` from your root directory into the webroot directory and run it via browser using -`http://example.com/requirements.php` URL. Don't forget to delete the file afterwards. +In order to run Yii, your webserver must meet its requirements. The very minimum requirement is PHP 5.4. In order to +check the requirements copy `requirements.php` from your root directory into the webroot directory and run it via +browser using `http://example.com/requirements.php` URL. Don't forget to delete the file afterwards. -Deploying an advanced application ---------------------------------- +## Deploying an advanced project template -Deploying an advanced application to shared hosting is a bit trickier than a basic application because it has two webroots, which shared hosting webservers don't support. We will need to adjust the directory structure. - -### Move entry scripts into single webroot - -First of all we need a webroot directory. Create a new directory and name it to match your hosting webroot name as described in [Renaming webroot](#renaming-webroot) above, e.g., `www` or `public_html` or the like. Then create the following structure where `www` is the hosting webroot directory you just created: - -``` -www - admin -backend -common -console -environments -frontend -... -``` - -`www` will be our frontend directory so move the contents of `frontend/web` into it. Move the contents of `backend/web` into `www/admin`. In each case you will need to adjust the paths in `index.php` and `index-test.php`. - -### Separate sessions and cookies - -Originally the backend and frontend are intended to run at different domains. When we’re moving it all to the same domain the frontend and backend will be sharing the same cookies, creating a clash. It order to fix it, adjust backend application config -`backend/config/main.php` as follows: - -```php -'components' => [ - 'request' => [ - 'csrfParam' => '_backendCSRF', - 'csrfCookie' => [ - 'httpOnly' => true, - 'path' => '/admin', - ], - ], - 'user' => [ - 'identityCookie' => [ - 'name' => '_backendIdentity', - 'path' => '/admin', - 'httpOnly' => true, - ], - ], - 'session' => [ - 'name' => 'BACKENDSESSID', - 'cookieParams' => [ - 'path' => '/admin', - ], - ], -], -``` +Deploying an advanced application to shared hosting is a bit trickier than a basic application but it could be achieved. +Follow intructions described in +[advanced project template documentation](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/topic-shared-hosting.md). diff --git a/docs/internals/README.md b/docs/internals/README.md new file mode 100644 index 0000000..8c7e4fd --- /dev/null +++ b/docs/internals/README.md @@ -0,0 +1,43 @@ +Yii Developer Documentation +=========================== + +This directory contains documentation about Yii Framework development and release process. + +Contributor Guidelines +---------------------- + +- [How to Report an Issue](report-an-issue.md) +- [Getting started](getting-started.md) +- [Git workflow for Yii 2 contributors](git-workflow.md) - a step by step guide on how to set up your dev environment and start contributing to Yii. +- [Yii2 Core Framework Code Style](core-code-style.md) +- [Yii2 View Code Style](view-code-style.md) + + +Documentation +------------- + +- [Translation Status](translation-status.md) - which documents are ready for translation. +- [Translation teams](translation-teams.md) +- [Translation workflow](translation-workflow.md) + + +Framework Development +--------------------- + +- [Pull request quality assurance](pull-request-qa.md) +- [Automated Tasks](automation.md), like code style fixes, automatic documentation and file generation. +- [Design Decisions](design-decisions.md) - a FAQ-like list of statements about commonly debated things. + +Versioning and Release +---------------------- + +- [Project Organization](project-organization.md) +- [Yii Versioning](versions.md) +- [Releasing a new version](release.md) + +Misc +---- + +### Exception Hierarchy + +![Yii Framework Exception Hierarchy](exception_hierarchy.png) diff --git a/docs/internals/automation.md b/docs/internals/automation.md index a250cd5..539a3f8 100644 --- a/docs/internals/automation.md +++ b/docs/internals/automation.md @@ -13,3 +13,8 @@ There are some tasks that are done automatically when working on Yii: Run `./build/build php-doc/fix` to fix them. Check the changes before you commit them as there may be unwanted changes because the command is not perfect. You may use `git add -p` to review the changes. + +- Updating the Mime Type Magic file (`framework/helpers/mimeTypes.php`) from the Apache HTTPd repository. + Run `./build/build mime-type` to update the file. + +All of the above commands are included in the [release process](). They may be run between releases but it is not necessary. diff --git a/docs/internals/core-code-style.md b/docs/internals/core-code-style.md index 4f50a91..eff0b8b 100644 --- a/docs/internals/core-code-style.md +++ b/docs/internals/core-code-style.md @@ -1,4 +1,4 @@ -Yii2 Core framework code style +Yii2 Core Framework Code Style ============================== The following code style is used for Yii 2.x core and official extensions development. If you want to pull-request code diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index d6c9279..20eb216 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -29,7 +29,7 @@ Change to the directory where you cloned Yii, normally, "yii2". Then enter the f git remote add upstream git://github.com/yiisoft/yii2.git ``` -### 3. Prepare the testing environment +### 3. Prepare the testing environment The following steps are not necessary if you want to work only on translations or documentation. diff --git a/docs/internals/project-organization.md b/docs/internals/project-organization.md new file mode 100644 index 0000000..41c6910 --- /dev/null +++ b/docs/internals/project-organization.md @@ -0,0 +1,36 @@ +Project Organization +==================== + +This document describes the organization of the Yii2 development repositories. + +1. Individual Core extensions and application templates are maintained in + separate *independent* GitHub projects under the [yiisoft](https://github.com/yiisoft) Github organization. + + Extension repository names are prefixed with `yii2-`, e.g. `yii2-gii` for the `gii` extension. + The composer package name is equal to the Github repository path, e.g. `yiisoft/yii2-gii`. + + Application template repository names are prefixed with `yii2-app-`, e.g. `yii2-app-basic` for the `basic` application template. + The composer package name is equal to the Github repository path, e.g. `yiisoft/yii2-app-basic`. + + Each extension/app project will + + * maintain its tutorial doc in its "docs" folder. The API doc will be generated on-the-fly when the extension/app is being released. + * maintain its own test code in its "tests" folder. + * maintain its own message translations and all other relevant meta code. + * track issues via the corresponding GitHub project. + + Extension repositories will be released independently as needed, Application templates will be released together with the framework. + See [versioning policy](versions.md) for more details. + +2. The `yiisoft/yii2` project is the main repository for developing Yii2 framework. + This repository provides the composer package [yiisoft/yii2-dev](https://packagist.org/packages/yiisoft/yii2-dev). + It contains the core framework code, framework unit tests, the definitive guide, and a set of build tools for framework development and release. + + Core framework bugs and feature requests are tracked in the issue tracker of this Github project. + +3. The repository `yiisoft/yii2-framework` is a read-only git subsplit of the `framework` directory of the dev project repository and + provides the composer package [yiisoft/yii2](https://packagist.org/packages/yiisoft/yii2) which is the official package to be + used when installing the framework. + +4. For development the apps and extensions can be included in the dev project structure using the + [build dev/app](git-workflow.md#prepare-the-test-environment)-Command. \ No newline at end of file diff --git a/docs/internals/release.md b/docs/internals/release.md new file mode 100644 index 0000000..b163a07 --- /dev/null +++ b/docs/internals/release.md @@ -0,0 +1,74 @@ +Releasing a new version +======================= + +The list of steps needed to make a release of the framework has grown over time and became +hard to manage manually, so we have created a command line tool to ensure no step is forgotten. + +Release steps overview +---------------------- + +- ... + +The release command +------------------- + +These steps are automated in the [release console command](../../build/controllers/ReleaseController.php) +which is included in the framework development repository. + +The release command can be invoked using the Yii application contained in the `build` directory of +the framework: + + ./build/build help release # run this in framework repo root + +> Info: You can run the command with the `--dryRun` option to see what it would do. Using this option, +> no changes will be made and no commits or tags will be created or pushed. + +### Requirements + +The release command depends on the development environment introduced in +the [Git Workflow Document](git-workflow.md#extensions), i.e. the application +templates must be located under `/apps/` and extensions must be located under `/extensions/`. +This structure is preferably created using the `dev/app` command. + +### Version overview + +To get an overview over the versions of framework and extensions, you can run + + ./build/build release/info + +You may run it with `--update` to fetch tags for all repos to get the newest information. + +### Make a release + +Making a framework release includes the following commands (apps are always released together with the framework): + + ./build release framework + ./build release app-basic + ./build release app-advanced + +Making an extension release includes only one command (e.g. for redis): + + ./build release redis + +The default release command will release a new minor version from the currently checked out branch. +To release another version thatn the default, you have to specify it using the `--version` option, e.g. +`--version=2.1.0`, or `--version=2.1.0-beta`. + + +#### Release a new major version e.g. 2.1.0 + +Releasing a new major version includes a branch change as described in the +[versioning policy](versions.md). +The following describes an example of releasing version `2.1.0` which has been +developed on the `2.1` branch derived from `master`. `master` has contained the `2.0.x` versions +before. + +- create a new branch `2.0` from `master` +- ensure composer.json does not contain a branch alias on this branch anymore. +- merge necessary changes from `master` to `2.1` +- point `master` to the lastest commit on `2.1` +- adjust composer.json branch alias for master to `2.1.x-dev`. +- delete `2.1` branch + +Now check out `master` and run the release command with the `--version=2.1.0` option. + diff --git a/docs/internals/translation-status.md b/docs/internals/translation-status.md index 1e67b0d..bbcf01c 100644 --- a/docs/internals/translation-status.md +++ b/docs/internals/translation-status.md @@ -1,106 +1,4 @@ Documentation status ==================== -File | Ready for translation -------------------------------------|--------------------- -README.md | Yes -intro-yii.md | Yes -intro-upgrade-from-v1.md | Yes -start-installation.md | Yes -start-workflow.md | Yes -start-hello.md | Yes -start-forms.md | Yes -start-databases.md | Yes -start-gii.md | Yes -start-looking-ahead.md | Yes -structure-overview.md | Yes -structure-entry-scripts.md | Yes -structure-applications.md | Yes -structure-application-components.md | Yes -structure-controllers.md | Yes -structure-views.md | Yes -structure-models.md | Yes -structure-modules.md | Yes -structure-filters.md | Yes -structure-widgets.md | Yes -structure-assets.md | Yes -structure-extensions.md | Yes -runtime-overview.md | Yes -runtime-bootstrapping.md | Yes -runtime-routing.md | Yes -runtime-requests.md | Yes -runtime-responses.md | Yes -runtime-sessions-cookies.md | Yes -runtime-handling-errors.md | Yes -runtime-logging.md | Yes -concept-components.md | Yes -concept-properties.md | Yes -concept-events.md | Yes -concept-behaviors.md | Yes -concept-configurations.md | Yes -concept-aliases.md | Yes -concept-autoloading.md | Yes -concept-service-locator.md | Yes -concept-di-container.md | Yes -db-dao.md | Yes -db-query-builder.md | Yes -db-active-record.md | Yes -db-migrations.md | Yes -db-sphinx.md | -db-redis.md | -db-mongodb.md | -db-elasticsearch.md | -input-forms.md | -input-validation.md | Yes -input-file-upload.md | Yes -input-multiple-models.md | Yes -input-tabular-input.md | -output-formatting.md | Yes -output-pagination.md | Yes -output-sorting.md | Yes -output-data-providers.md | Yes -output-data-widgets.md | -output-theming.md | Yes -security-authentication.md | Yes -security-authorization.md | Yes -security-passwords.md | -security-auth-clients.md | -security-best-practices.md | -caching-overview.md | Yes -caching-data.md | Yes -caching-fragment.md | Yes -caching-page.md | Yes -caching-http.md | Yes -rest-quick-start.md | Yes -rest-resources.md | Yes -rest-controllers.md | Yes -rest-routing.md | Yes -rest-response-formatting.md | Yes -rest-authentication.md | Yes -rest-rate-limiting.md | Yes -rest-versioning.md | Yes -rest-error-handling.md | Yes -tool-debugger.md | -tool-gii.md | -tool-api-doc.md | -test-overview.md | -test-unit.md | -test-functional.md | -test-acceptance.md | -test-fixtures.md | -tutorial-advanced-app.md | -tutorial-start-from-scratch.md | -tutorial-console.md | -tutorial-i18n.md | -tutorial-mailing.md | -tutorial-performance-tuning.md | Yes -tutorial-shared-hosting.md | -tutorial-template-engines.md | -tutorial-core-validators.md | Yes -tutorial-yii-integration.md | Yes -widget-bootstrap.md | -widget-jui.md | -helper-overview.md | -helper-array.md | -helper-html.md | -helper-url.md | +Everything is ready to be translated. diff --git a/docs/internals/versions-branches.png b/docs/internals/versions-branches.png new file mode 100644 index 0000000..5192c1d Binary files /dev/null and b/docs/internals/versions-branches.png differ diff --git a/docs/internals/versions.md b/docs/internals/versions.md index 1ab39e6..2b2961f 100644 --- a/docs/internals/versions.md +++ b/docs/internals/versions.md @@ -9,41 +9,78 @@ Within the core developer team, we have emphasized several times that it is impo But this is an ideal plan. The ferver article has given out a real world example that this is hard to achieve in practice, regardless you are using semver or not. -In summary, our versioning policy is as follows: +In summary, our versioning policy for Yii 2 is as follows: -## Patch Releases `2.x.Y` +## Version numbers -Patch releases, which should be 100% BC-compatible. Ideally, we hope they contain bug fixes only so that it reduces -the chance of breaking BC. Practically, since 2.0.x is released more frequently, we are also adding minor features +Version numbers are in the format of `2.x.y.z`, where the `z` can be skipped for releases for which `z` is `0`. + +A possible Yii version 3 is not covered here as we'd expect it to be like 2.0 over 1.0. We expect that this only happens every 3 to 5 years, +depending on external technology advancement (such as PHP upgraded from 5.0 to 5.4). + +### `2.X.0`: major releases + +BC-breaking releases, which contain major features and changes that may break BC. Upgrading from earlier versions may +not be trivial, but a complete upgrade guide will be available. + +* Mainly contain new features and bug fixes +* Contain minor features and bug fixes merged from patch releases. +* May contain BC-breaking changes which are recorded in a `UPGRADE-2.X.md` file. +* Release cycle is around 12 months or more. +* Require pre-releases: `2.X.0-alpha`, `2.X.0-beta`, `2.X.0-rc`. +* Requires major news releases and marketing effort. + + +### `2.x.Y`: minor releases + +Patch releases, which should be 100% BC-compatible. Ideally, we hope they contain only changes that do not affect backwards compatibilty +however it is not always possible to keep 100% BC-compatible, so upgrade notes are recorded in `UPGRADE.md`. +Practically, since 2.0.x is released more frequently, we are also adding minor features to it so that users can enjoy them earlier. -* Maintained on a branch named `2.x` * Mainly contain bug fixes and minor feature enhancements -* No major features. -* Must be 100% backward compatible to ensure worry-free upgrade. Only exception is security issues that may require breaking BC. +* No major features or changes. +* Should be 100% backward compatible to ensure worry-free upgrade. Only a few exceptions are allowed which are documented in `UPGRADE.md`. * Release cycle is around 1 to 2 months. * No pre-releases (alpha, beta, RC) needed. * Should be merged back to master branch constantly (at least once every week manually). +* With news announcements. Project site will be updated. -## Minor Releases `2.X.0` +### `2.x.y.Z`: patch releases -BC-breaking releases, which contains major features and changes that may break BC. Upgrading from earlier versions may -not be trivial, but a complete upgrade guide or even script will be available. +Patch releases, which should be 100% BC-compatible, containing bug fixes only. +No news announcement or project site update (unless it contains major/security issue fixes). +The release process will be made mostly automatic. -* Developed on master branch -* Mainly contain new features and bug fixes -* Contain minor features and bug fixes merged from patch releases -* May contain BC-breaking changes which are recorded in `UPGRADE-2.X.md` file -* Release cycle is around 6 to 8 months -* Require pre-releases: `2.X.0-alpha`, `2.X.0-beta`, `2.X.0-rc` -* Requires major news releases and marketing effort. +* containing bug fixes only, no features included. +* Must be 100% backward compatible to ensure worry-free upgrade. Only exception is security issues that may require breaking BC. +* Release cycle is around 1 to 2 weeks. +* No pre-releases (alpha, beta, RC) needed. +* Should be merged back to master branch on release. + + +## Branching policy + +* `master` branch is the development branch for the current stable major release, currently `2.0.x` versions. +* Each new major release will be developed on a branch named after the version number, e.g. `2.1`. +* Once a new major release `2.n` is ready, create a maintenance branch named `2.(n-1).x` off `master`. + E.g. a `2.0` branch is created if version `2.1.0` is released as stable and will now be developed on `master` + (cmp. [composer branch naming schema](https://getcomposer.org/doc/02-libraries.md#branches)). +* Create tags `2.x.y.z` and `2.x.y` branch to mark patch releases. For `2.x.y.0` releases, the `0` will be skipped. +* Changes on `2.n.x` maintenance branches will be merged back to `master` constantly. + +The following image shows an illustration of the branches on changing commit history over time: + +![Branching policy](versions-branches.png) -## Major Releases `X.0.0` +## Releases -It's like 2.0 over 1.0. We expect this only happens every 3 to 5 years, depending on external technology advancement -(such as PHP upgraded from 5.0 to 5.4). +Both Yii2 Framework and official extension projects follow the above versioning and branching policies. +Framework and official extension projects are released independently of each other, i.e. version number mismatch between framework and extension is expected. +The Application Templates are always released together with the framework. -> Note: Official extensions are following the same versioning policy but could be released independently from -the framework i.e. version number mismatch between framework and extension is expected. +The release cycles described above only apply to the core framework. +Extensions are released on demand. +It is likely that an extension has no new releases for a very long time because it does not receive any bug fixes or enhancements. diff --git a/docs/internals/view-code-style.md b/docs/internals/view-code-style.md index 05cef76..8d60b40 100644 --- a/docs/internals/view-code-style.md +++ b/docs/internals/view-code-style.md @@ -1,4 +1,4 @@ -Yii2 view code style +Yii2 View Code Style ==================== The following code style is used for Yii 2.x core and official extensions view files. We aren't forcing you to use this code style for your application. Feel free to choose what suits you better. diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5ad4645..013c36c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -12,8 +12,9 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- - +- Enh #11725: Added indexes on message tables (OndrejVasicek) - Enh #10422: Added `null` method on `yii\db\ColumnSchemaBuilder` to explicitly set column nullability (nevermnd) +- Enh #9574: Implicit run `ColumnSchemaBuilder::null()` when default value is set to `null`. (rob006) - Enh #8795: Refactored `yii\web\User::loginByCookie()` in order to make it easier to override (maine-mike, silverfire) - Enh #9948: `yii\rbac\PhpManager` now invalidates script file cache performed by 'OPCache' or 'APC' on file saving (klimov-paul) - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) @@ -26,6 +27,7 @@ Yii Framework 2 Change Log - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) - Enh #11438: Configurable `yii\helpers\Markdown` default flavor (mdmunir) +- Enh #11729: Added `yii\grid\CheckboxColumn::$cssClas` property to specify a class added to checkbox input (thiagotalma) - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) - Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) @@ -43,6 +45,11 @@ Yii Framework 2 Change Log - Bug #11549: Fixed `ArrayHelper::getValue()` to work properly with float keys (zsounder, AnikanovD) - Bug #8644: Fixed trying to ENABLE/DISABLE TRIGGER ALL on a view in PostgreSQL (ricpelo) - Bug #11536: Fixed regression introduced in 2.0.8, where scalar value was not allowed in QueryBuilder `IN` condition anymore (cebe) +- Bug #11693: Handle `QueryBuilder::batchInsert()` calls with no data to insert (rob006) +- Bug #11672: Fixed `yii\validators\NumberValidator` erroring when value is an object without `__toString()` method (SamMousa) +- Bug #11561: Fixed DI container throwing exceptions for optional dependencies (SamMousa) +- Enh #11168: `yii\helpers\BaseHtml` now uses abstracted `booleanInput()` and `activeBooleanInput()` methods to render `radio()`, `checkbox()`, `activeRadio()` and `activeCheckbox()` (cesarnicola) +- Bug #11822: Fixed exception on non-string value provided as CSRF token (cebe) 2.0.8 April 28, 2016 -------------------- diff --git a/framework/assets/yii.gridView.js b/framework/assets/yii.gridView.js index c201652..039b84a 100644 --- a/framework/assets/yii.gridView.js +++ b/framework/assets/yii.gridView.js @@ -155,12 +155,13 @@ return; } var checkAll = "#" + id + " input[name='" + options.checkAll + "']"; - var inputs = "#" + id + " input[name='" + options.name + "']"; + var inputs = options.class ? "input." + options.class : "input[name='" + options.name + "']"; + var inputsEnabled = "#" + id + " " + inputs + ":enabled"; $(document).off('click.yiiGridView', checkAll).on('click.yiiGridView', checkAll, function () { - $grid.find("input[name='" + options.name + "']:enabled").prop('checked', this.checked); + $grid.find(inputs + ":enabled").prop('checked', this.checked); }); - $(document).off('click.yiiGridView', inputs + ":enabled").on('click.yiiGridView', inputs + ":enabled", function () { - var all = $grid.find("input[name='" + options.name + "']").length == $grid.find("input[name='" + options.name + "']:checked").length; + $(document).off('click.yiiGridView', inputsEnabled).on('click.yiiGridView', inputsEnabled, function () { + var all = $grid.find(inputs).length == $grid.find(inputs + ":checked").length; $grid.find("input[name='" + options.checkAll + "']").prop('checked', all); }); }, diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index 7d6fa26..b381b58 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -180,6 +180,10 @@ class ColumnSchemaBuilder extends Object */ public function defaultValue($default) { + if ($default === null) { + $this->null(); + } + $this->default = $default; return $this; } @@ -327,7 +331,7 @@ class ColumnSchemaBuilder extends Object protected function buildDefaultString() { if ($this->default === null) { - return ''; + return $this->isNotNull === false ? ' DEFAULT NULL' : ''; } $string = ' DEFAULT '; diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 73c73a8..950f497 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -182,6 +182,10 @@ class QueryBuilder extends \yii\base\Object */ public function batchInsert($table, $columns, $rows) { + if (empty($rows)) { + return ''; + } + $schema = $this->db->getSchema(); if (($tableSchema = $schema->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index 2d43483..072f3f0 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -225,6 +225,10 @@ EOD; */ public function batchInsert($table, $columns, $rows) { + if (empty($rows)) { + return ''; + } + $schema = $this->db->getSchema(); if (($tableSchema = $schema->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php index b544ee4..68289a6 100644 --- a/framework/db/pgsql/QueryBuilder.php +++ b/framework/db/pgsql/QueryBuilder.php @@ -267,6 +267,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function batchInsert($table, $columns, $rows) { + if (empty($rows)) { + return ''; + } + $schema = $this->db->getSchema(); if (($tableSchema = $schema->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 29afc6c..8081043 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -70,6 +70,10 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function batchInsert($table, $columns, $rows) { + if (empty($rows)) { + return ''; + } + // SQLite supports batch insert natively since 3.7.11 // http://www.sqlite.org/releaselog/3_7_11.html $this->db->open(); // ensure pdo is not null diff --git a/framework/di/Container.php b/framework/di/Container.php index 2c6e313..0a4464d 100644 --- a/framework/di/Container.php +++ b/framework/di/Container.php @@ -144,6 +144,7 @@ class Container extends Component * @param array $config a list of name-value pairs that will be used to initialize the object properties. * @return object an instance of the requested class. * @throws InvalidConfigException if the class cannot be recognized or correspond to an invalid definition + * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) */ public function get($class, $params = [], $config = []) { @@ -354,6 +355,7 @@ class Container extends Component * @param array $params constructor parameters * @param array $config configurations to be applied to the new instance * @return object the newly created instance of the specified class + * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) */ protected function build($class, $params, $config) { @@ -365,6 +367,9 @@ class Container extends Component } $dependencies = $this->resolveDependencies($dependencies, $reflection); + if (!$reflection->isInstantiable()) { + throw new NotInstantiableException($reflection->name); + } if (empty($config)) { return $reflection->newInstanceArgs($dependencies); } @@ -481,6 +486,7 @@ class Container extends Component * This can be either a list of parameters, or an associative array representing named function parameters. * @return mixed the callback return value. * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. + * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) * @since 2.0.7 */ public function invoke(callable $callback, $params = []) @@ -502,6 +508,7 @@ class Container extends Component * @param array $params The array of parameters for the function, can be either numeric or associative. * @return array The resolved dependencies. * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. + * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) * @since 2.0.7 */ public function resolveCallableDependencies(callable $callback, $params = []) @@ -528,7 +535,17 @@ class Container extends Component } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { $args[] = $obj; } else { - $args[] = $this->get($className); + // If the argument is optional we catch not instantiable exceptions + try { + $args[] = $this->get($className); + } catch (NotInstantiableException $e) { + if ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw $e; + } + } + } } elseif ($associative && isset($params[$name])) { $args[] = $params[$name]; diff --git a/framework/di/NotInstantiableException.php b/framework/di/NotInstantiableException.php new file mode 100644 index 0000000..70898dd --- /dev/null +++ b/framework/di/NotInstantiableException.php @@ -0,0 +1,39 @@ + + * @since 2.0.9 + */ +class NotInstantiableException extends InvalidConfigException +{ + /** + * @inheritdoc + */ + public function __construct($class, $message = null, $code = 0, \Exception $previous = null) + { + if ($message === null) { + $message = "Can not instantiate $class."; + } + parent::__construct($message, $code, $previous); + } + + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Not instantiable'; + } +} diff --git a/framework/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php index ad5f6c8..7c35b11 100644 --- a/framework/grid/CheckboxColumn.php +++ b/framework/grid/CheckboxColumn.php @@ -67,7 +67,10 @@ class CheckboxColumn extends Column * @var boolean whether it is possible to select multiple rows. Defaults to `true`. */ public $multiple = true; - + /** + * @var string the css class that will be used to find the checkboxes. + */ + public $cssClass; /** * @inheritdoc @@ -116,6 +119,10 @@ class CheckboxColumn extends Column $options['value'] = is_array($key) ? Json::encode($key) : $key; } + if ($this->cssClass !== null) { + Html::addCssClass($options, $this->cssClass); + } + return Html::checkbox($this->name, !empty($options['checked']), $options); } @@ -148,6 +155,7 @@ class CheckboxColumn extends Column $id = $this->grid->options['id']; $options = Json::encode([ 'name' => $this->name, + 'class' => $this->cssClass, 'multiple' => $this->multiple, 'checkAll' => $this->grid->showHeader ? $this->getHeaderCheckBoxName() : null, ]); diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index cc4d70e..c9975c4 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -672,48 +672,35 @@ class BaseHtml * Generates a radio button input. * @param string $name the name attribute. * @param boolean $checked whether the radio button should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. Do not set this option unless you set the "label" option. - * - * The rest of the options will be rendered as the attributes of the resulting radio button tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @param array $options the tag options in terms of name-value pairs. + * See [[booleanInput()]] for details about accepted attributes. * * @return string the generated radio button tag */ public static function radio($name, $checked = false, $options = []) { - $options['checked'] = (bool) $checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . $content; - } else { - return $hidden . static::input('radio', $name, $value, $options); - } + return static::booleanInput('radio', $name, $checked, $options); } /** * Generates a checkbox input. * @param string $name the name attribute. * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. + * See [[booleanInput()]] for details about accepted attributes. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = []) + { + return static::booleanInput('checkbox', $name, $checked, $options); + } + + /** + * Generates a boolean input. + * @param string $type the input type. This can be either `radio` or `checkbox`. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: * * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute @@ -729,8 +716,9 @@ class BaseHtml * See [[renderTagAttributes()]] for details on how attributes are being rendered. * * @return string the generated checkbox tag + * @since 2.0.9 */ - public static function checkbox($name, $checked = false, $options = []) + protected static function booleanInput($type, $name, $checked = false, $options = []) { $options['checked'] = (bool) $checked; $value = array_key_exists('value', $options) ? $options['value'] : '1'; @@ -745,10 +733,10 @@ class BaseHtml $label = $options['label']; $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + $content = static::label(static::input($type, $name, $value, $options) . ' ' . $label, null, $labelOptions); return $hidden . $content; } else { - return $hidden . static::input('checkbox', $name, $value, $options); + return $hidden . static::input($type, $name, $value, $options); } } @@ -1396,47 +1384,14 @@ class BaseHtml * @param Model $model the model object * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. If you do not want any hidden input, you should explicitly set this option as null. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * The radio button will be enclosed by the label tag. Note that if you do not specify this option, a default label - * will be used based on the attribute label declaration in the model. If you do not want any label, you should - * explicitly set this option as null. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @param array $options the tag options in terms of name-value pairs. + * See [[booleanInput()]] for details about accepted attributes. * * @return string the generated radio button tag */ public static function activeRadio($model, $attribute, $options = []) { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); - - if (!array_key_exists('value', $options)) { - $options['value'] = '1'; - } - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('label', $options)) { - $options['label'] = static::encode($model->getAttributeLabel(static::getAttributeName($attribute))); - } - - $checked = "$value" === "{$options['value']}"; - - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - - return static::radio($name, $checked, $options); + return static::activeBooleanInput('radio', $model, $attribute, $options); } /** @@ -1445,27 +1400,30 @@ class BaseHtml * @param Model $model the model object * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. If you do not want any hidden input, you should explicitly set this option as null. - * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * The checkbox will be enclosed by the label tag. Note that if you do not specify this option, a default label - * will be used based on the attribute label declaration in the model. If you do not want any label, you should - * explicitly set this option as null. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @param array $options the tag options in terms of name-value pairs. + * See [[booleanInput()]] for details about accepted attributes. * * @return string the generated checkbox tag */ public static function activeCheckbox($model, $attribute, $options = []) { + return static::activeBooleanInput('checkbox', $model, $attribute, $options); + } + + /** + * Generates a boolean input + * This method is mainly called by [[activeCheckbox()]] and [[activeRadio()]]. + * @param string $type the input type. This can be either `radio` or `checkbox`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. + * See [[booleanInput()]] for details about accepted attributes. + * @return string the generated input element + * @since 2.0.9 + */ + protected static function activeBooleanInput($type, $model, $attribute, $options = []) + { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $value = static::getAttributeValue($model, $attribute); @@ -1485,7 +1443,7 @@ class BaseHtml $options['id'] = static::getInputId($model, $attribute); } - return static::checkbox($name, $checked, $options); + return static::$type($name, $checked, $options); } /** diff --git a/framework/i18n/migrations/m150207_210500_i18n_init.php b/framework/i18n/migrations/m150207_210500_i18n_init.php index b8cff13..170e025 100644 --- a/framework/i18n/migrations/m150207_210500_i18n_init.php +++ b/framework/i18n/migrations/m150207_210500_i18n_init.php @@ -39,6 +39,8 @@ class m150207_210500_i18n_init extends Migration $this->addPrimaryKey('pk_message_id_language', '{{%message}}', ['id', 'language']); $this->addForeignKey('fk_message_source_message', '{{%message}}', 'id', '{{%source_message}}', 'id', 'CASCADE', 'RESTRICT'); + $this->createIndex('idx_source_message_category', '{{%source_message}}', 'category'); + $this->createIndex('idx_message_language', '{{%message}}', 'language'); } public function down() diff --git a/framework/i18n/migrations/schema-mssql.sql b/framework/i18n/migrations/schema-mssql.sql index e4f9715..c5982b9 100644 --- a/framework/i18n/migrations/schema-mssql.sql +++ b/framework/i18n/migrations/schema-mssql.sql @@ -27,3 +27,6 @@ CREATE TABLE [message] ALTER TABLE [message] ADD CONSTRAINT [pk_message_id_language] PRIMARY KEY ([id], [language]); ALTER TABLE [message] ADD CONSTRAINT [fk_message_source_message] FOREIGN KEY ([id]) REFERENCES [source_message] ([id]) ON UPDATE CASCADE ON DELETE NO ACTION; + +CREATE INDEX [idx_message_language] on [message] ([language]); +CREATE INDEX [idx_source_message_category] on [source_message] ([category]); \ No newline at end of file diff --git a/framework/i18n/migrations/schema-mysql.sql b/framework/i18n/migrations/schema-mysql.sql index 2fc2d9a..dbbbd93 100644 --- a/framework/i18n/migrations/schema-mysql.sql +++ b/framework/i18n/migrations/schema-mysql.sql @@ -28,3 +28,6 @@ CREATE TABLE `message` ALTER TABLE `message` ADD CONSTRAINT `pk_message_id_language` PRIMARY KEY (`id`, `language`); ALTER TABLE `message` ADD CONSTRAINT `fk_message_source_message` FOREIGN KEY (`id`) REFERENCES `source_message` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT; + +CREATE INDEX idx_message_language ON message (language); +CREATE INDEX idx_source_message_category ON source_message (category); diff --git a/framework/i18n/migrations/schema-oci.sql b/framework/i18n/migrations/schema-oci.sql index f0b526c..edd9c0f 100644 --- a/framework/i18n/migrations/schema-oci.sql +++ b/framework/i18n/migrations/schema-oci.sql @@ -28,3 +28,6 @@ CREATE TABLE "message" primary key ("id", "language"), foreign key ("id") references "source_message" ("id") on delete cascade ); + +CREATE INDEX idx_message_language ON "message"("language"); +CREATE INDEX idx_source_message_category ON "source_message"("category"); \ No newline at end of file diff --git a/framework/i18n/migrations/schema-pgsql.sql b/framework/i18n/migrations/schema-pgsql.sql index 838aca9..651ee7b 100644 --- a/framework/i18n/migrations/schema-pgsql.sql +++ b/framework/i18n/migrations/schema-pgsql.sql @@ -30,3 +30,9 @@ CREATE TABLE "message" ALTER TABLE "message" ADD CONSTRAINT "pk_message_id_language" PRIMARY KEY ("id", "language"); ALTER TABLE "message" ADD CONSTRAINT "fk_message_source_message" FOREIGN KEY ("id") REFERENCES "source_message" ("id") ON UPDATE CASCADE ON DELETE RESTRICT; + +CREATE INDEX "idx_message_language" ON "message" USING btree (language); +ALTER TABLE "message" CLUSTER ON "idx_message_language"; + +CREATE INDEX "idx_source_message_category" ON "source_message" USING btree (category); +ALTER TABLE "source_message" CLUSTER ON "idx_source_message_category"; \ No newline at end of file diff --git a/framework/i18n/migrations/schema-sqlite.sql b/framework/i18n/migrations/schema-sqlite.sql index fcc4fbe..338bf62 100644 --- a/framework/i18n/migrations/schema-sqlite.sql +++ b/framework/i18n/migrations/schema-sqlite.sql @@ -26,3 +26,5 @@ CREATE TABLE `message` PRIMARY KEY (`id`, `language`) ); +CREATE INDEX idx_message_language ON message (language); +CREATE INDEX idx_source_message_category ON source_message (category); \ No newline at end of file diff --git a/framework/messages/fa/yii.php b/framework/messages/fa/yii.php index 841db00..df46505 100644 --- a/framework/messages/fa/yii.php +++ b/framework/messages/fa/yii.php @@ -49,9 +49,9 @@ return [ 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. ارتفاع نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. عرض نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', 'The requested view "{name}" was not found.' => 'نمای درخواستی "{name}" یافت نشد.', - 'The verification code is incorrect.' => 'کد تائید اشتباه است.', + 'The verification code is incorrect.' => 'کد تأیید اشتباه است.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} مورد.', - 'Unable to verify your data submission.' => 'قادر به تائید اطلاعات ارسالی شما نمی‌باشد.', + 'Unable to verify your data submission.' => 'قادر به تأیید اطلاعات ارسالی شما نمی‌باشد.', 'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}', 'Update' => 'بروزرسانی', 'View' => 'نما', @@ -88,7 +88,7 @@ return [ '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} باید کمتر یا برابر با "{compareValueOrAttribute}" باشد.', '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{max}" باشد.', '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{min}" باشد.', - '{attribute} must not be a subnet.' => '{attribute} باید کمتر یا برابر "{compareValueOrAttribute}" باشد.', + '{attribute} must not be a subnet.' => '{attribute} نباید یک زیرشبکه باشد.', '{attribute} must not be an IPv4 address.' => '{attribute} باید آدرس IPv4 نباشد.', '{attribute} must not be an IPv6 address.' => '{attribute} باید آدرس IPv6 نباشد.', '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} باید مانند "{compareValueOrAttribute}" تکرار نشود.', diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index b0e647c..32e6abb 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -80,7 +80,7 @@ class NumberValidator extends Validator public function validateAttribute($model, $attribute) { $value = $model->$attribute; - if (is_array($value)) { + if (is_array($value) || (is_object($value) && !method_exists($value, '__toString'))) { $this->addError($model, $attribute, $this->message); return; } diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index e59c84c..60eb698 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -374,7 +374,13 @@ class Validator extends Component $params['attribute'] = $model->getAttributeLabel($attribute); if (!isset($params['value'])) { $value = $model->$attribute; - $params['value'] = is_array($value) ? 'array()' : $value; + if (is_array($value)) { + $params['value'] = 'array()'; + } elseif (is_object($value) && !method_exists($value, '__toString')) { + $params['value'] = '(object)'; + } else { + $params['value'] = $value; + } } $model->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language)); } diff --git a/framework/web/Request.php b/framework/web/Request.php index 5dfb314..1b5ab55 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -1403,6 +1403,10 @@ class Request extends \yii\base\Request */ private function validateCsrfTokenInternal($token, $trueToken) { + if (!is_string($token)) { + return false; + } + $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); if ($n <= static::CSRF_MASK_LENGTH) { diff --git a/tests/framework/db/ColumnSchemaBuilderTest.php b/tests/framework/db/ColumnSchemaBuilderTest.php index 3ba595f..807f76f 100644 --- a/tests/framework/db/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/ColumnSchemaBuilderTest.php @@ -25,7 +25,7 @@ abstract class ColumnSchemaBuilderTest extends TestCase public function typesProvider() { return [ - ['integer NULL', Schema::TYPE_INTEGER, null, [ + ['integer NULL DEFAULT NULL', Schema::TYPE_INTEGER, null, [ ['unsigned'], ['null'], ]], ['integer(10)', Schema::TYPE_INTEGER, 10, [ diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index aefd619..e3a4f6a 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -281,6 +281,15 @@ SQL; ] ); $this->assertEquals(2, $command->execute()); + + // @see https://github.com/yiisoft/yii2/issues/11693 + $command = $this->getConnection()->createCommand(); + $command->batchInsert( + '{{customer}}', + ['email', 'name', 'address'], + [] + ); + $this->assertEquals(0, $command->execute()); } public function testInsert() diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 8e9ba58..238d76e 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -877,6 +877,18 @@ abstract class QueryBuilderTest extends DatabaseTestCase ], ], [ + Schema::TYPE_TIMESTAMP . ' NULL DEFAULT NULL', + $this->timestamp()->defaultValue(null), + [ + 'mysql' => 'timestamp NULL DEFAULT NULL', + 'postgres' => 'timestamp(0) NULL DEFAULT NULL', + 'sqlite' => 'timestamp NULL DEFAULT NULL', + 'oci' => 'TIMESTAMP NULL DEFAULT NULL', + 'sqlsrv' => 'timestamp NULL DEFAULT NULL', + 'cubrid' => 'timestamp NULL DEFAULT NULL', + ], + ], + [ Schema::TYPE_UPK, $this->primaryKey()->unsigned(), [ diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php index 7f32d8f..eb2c876 100644 --- a/tests/framework/di/ContainerTest.php +++ b/tests/framework/di/ContainerTest.php @@ -13,6 +13,7 @@ use yii\di\Instance; use yiiunit\framework\di\stubs\Bar; use yiiunit\framework\di\stubs\Foo; use yiiunit\framework\di\stubs\Qux; +use yiiunit\framework\di\stubs\QuxInterface; use yiiunit\TestCase; use yii\validators\NumberValidator; @@ -168,7 +169,6 @@ class ContainerTest extends TestCase list($request, $response) = Yii::$container->invoke($myFunc); $this->assertEquals($request, Yii::$app->request); $this->assertEquals($response, Yii::$app->response); - } public function testAssociativeInvoke() @@ -213,4 +213,14 @@ class ContainerTest extends TestCase $this->assertEquals([1, 5], Yii::$container->resolveCallableDependencies($closure, ['a' => 1, 'b' => 5])); $this->assertEquals([1, 5], Yii::$container->resolveCallableDependencies($closure, [1, 5])); } + + public function testOptionalDependencies() + { + $container = new Container(); + // Test optional unresolvable dependency. + $closure = function(QuxInterface $test = null) { + return $test; + }; + $this->assertNull($container->invoke($closure)); + } } diff --git a/tests/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php index 828c783..cf04cde 100644 --- a/tests/framework/validators/NumberValidatorTest.php +++ b/tests/framework/validators/NumberValidatorTest.php @@ -150,6 +150,13 @@ class NumberValidatorTest extends TestCase $model = FakedValidationModel::createWithAttributes(['attr_num' => [1, 2, 3]]); $val->validateAttribute($model, 'attr_num'); $this->assertTrue($model->hasErrors('attr_num')); + + // @see https://github.com/yiisoft/yii2/issues/11672 + $model = new FakedValidationModel(); + $model->attr_number = new \stdClass(); + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + } public function testEnsureCustomMessageIsSetOnValidateAttribute() diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index 8ee890a..b5a59b6 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -88,7 +88,100 @@ class RequestTest extends TestCase $token = $request->getCsrfToken(); + // accept any value if CSRF validation is disabled + $request->enableCsrfValidation = false; $this->assertTrue($request->validateCsrfToken($token)); + $this->assertTrue($request->validateCsrfToken($token . 'a')); + $this->assertTrue($request->validateCsrfToken([])); + $this->assertTrue($request->validateCsrfToken([$token])); + $this->assertTrue($request->validateCsrfToken(0)); + $this->assertTrue($request->validateCsrfToken(null)); + + // enable validation + $request->enableCsrfValidation = true; + + // accept any value on GET request + foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + $_POST[$request->methodParam] = $method; + $this->assertTrue($request->validateCsrfToken($token)); + $this->assertTrue($request->validateCsrfToken($token . 'a')); + $this->assertTrue($request->validateCsrfToken([])); + $this->assertTrue($request->validateCsrfToken([$token])); + $this->assertTrue($request->validateCsrfToken(0)); + $this->assertTrue($request->validateCsrfToken(null)); + } + + // only accept valid token on POST + foreach(['POST', 'PUT', 'DELETE'] as $method) { + $_POST[$request->methodParam] = $method; + $this->assertTrue($request->validateCsrfToken($token)); + $this->assertFalse($request->validateCsrfToken($token . 'a')); + $this->assertFalse($request->validateCsrfToken([])); + $this->assertFalse($request->validateCsrfToken([$token])); + $this->assertFalse($request->validateCsrfToken(0)); + $this->assertFalse($request->validateCsrfToken(null)); + } + } + + /** + * test CSRF token validation by POST param + */ + public function testCsrfTokenPost() + { + $this->mockWebApplication(); + + $request = new Request(); + $request->enableCsrfCookie = false; + + $token = $request->getCsrfToken(); + + // accept no value on GET request + foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + $_POST[$request->methodParam] = $method; + $this->assertTrue($request->validateCsrfToken()); + } + + // only accept valid token on POST + foreach(['POST', 'PUT', 'DELETE'] as $method) { + $_POST[$request->methodParam] = $method; + $request->setBodyParams([]); + $this->assertFalse($request->validateCsrfToken()); + $request->setBodyParams([$request->csrfParam => $token]); + $this->assertTrue($request->validateCsrfToken()); + } + + } + + /** + * test CSRF token validation by POST param + */ + public function testCsrfTokenHeader() + { + $this->mockWebApplication(); + + $request = new Request(); + $request->enableCsrfCookie = false; + + $token = $request->getCsrfToken(); + + // accept no value on GET request + foreach(['GET', 'HEAD', 'OPTIONS'] as $method) { + $_POST[$request->methodParam] = $method; + $this->assertTrue($request->validateCsrfToken()); + } + + // only accept valid token on POST + foreach(['POST', 'PUT', 'DELETE'] as $method) { + $_POST[$request->methodParam] = $method; + $request->setBodyParams([]); + //$request->headers->remove(Request::CSRF_HEADER); + unset($_SERVER['HTTP_' . str_replace('-', '_', strtoupper(Request::CSRF_HEADER))]); + $this->assertFalse($request->validateCsrfToken()); + //$request->headers->add(Request::CSRF_HEADER, $token); + $_SERVER['HTTP_' . str_replace('-', '_', strtoupper(Request::CSRF_HEADER))] = $token; + $this->assertTrue($request->validateCsrfToken()); + } + } public function testResolve() diff --git a/tests/framework/web/UrlManagerTest.php b/tests/framework/web/UrlManagerTest.php index 7c650ab..0fa6622 100644 --- a/tests/framework/web/UrlManagerTest.php +++ b/tests/framework/web/UrlManagerTest.php @@ -251,6 +251,27 @@ class UrlManagerTest extends TestCase $this->assertEquals('http://www.example.com?r=post%2Fview&id=1&title=sample+post', $url); } + public function testCreateAbsoluteUrlWithSuffix() + { + $manager = new UrlManager([ + 'baseUrl' => '/', + 'scriptUrl' => '', + 'hostInfo' => 'http://app.example.com', + 'cache' => null, + + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'suffix' => '/', + 'rules' => [ + 'http://app.example.com/login' => 'site/login', + ], + ]); + $url = $manager->createAbsoluteUrl(['site/login']); + $this->assertEquals('http://app.example.com/login/', $url); + $url = $manager->createUrl(['site/login']); + $this->assertEquals('http://app.example.com/login/', $url); + } + public function testParseRequest() { $manager = new UrlManager(['cache' => null]);