Merge branch 'master' of github.com:yiisoft/yii2 into message-command

This commit is contained in:
Klimov Paul
2013-06-11 19:49:38 +03:00
54 changed files with 1311 additions and 407 deletions

View File

@@ -16,10 +16,13 @@ class ActionFilter extends Behavior
/**
* @var array list of action IDs that this filter should apply to. If this property is not set,
* then the filter applies to all actions, unless they are listed in [[except]].
* If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it.
* @see except
*/
public $only;
/**
* @var array list of action IDs that this filter should not apply to.
* @see only
*/
public $except = array();

View File

@@ -8,6 +8,7 @@
namespace yii\base;
use Yii;
use yii\web\HttpException;
/**
* Application is the base class for all application classes.
@@ -17,8 +18,14 @@ use Yii;
*/
class Application extends Module
{
const EVENT_BEFORE_REQUEST = 'beforeRequest';
const EVENT_AFTER_REQUEST = 'afterRequest';
/**
* @event Event an event that is triggered at the beginning of [[run()]].
*/
const EVENT_BEFORE_RUN = 'beforeRun';
/**
* @event Event an event that is triggered at the end of [[run()]].
*/
const EVENT_AFTER_RUN = 'afterRun';
/**
* @var string the application name.
*/
@@ -128,6 +135,10 @@ class Application extends Module
ini_set('display_errors', 0);
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
// Allocating twice more than required to display memory exhausted error
// in case of trying to allocate last 1 byte while all memory is taken.
$this->_memoryReserve = str_repeat('x', 1024 * 256);
register_shutdown_function(array($this, 'handleFatalError'));
}
}
@@ -142,11 +153,10 @@ class Application extends Module
{
if (!$this->_ended) {
$this->_ended = true;
$this->afterRequest();
$this->getResponse()->end();
$this->afterRun();
}
$this->handleFatalError();
if ($exit) {
exit($status);
}
@@ -159,30 +169,30 @@ class Application extends Module
*/
public function run()
{
$this->beforeRequest();
// Allocating twice more than required to display memory exhausted error
// in case of trying to allocate last 1 byte while all memory is taken.
$this->_memoryReserve = str_repeat('x', 1024 * 256);
$this->beforeRun();
$response = $this->getResponse();
$response->begin();
register_shutdown_function(array($this, 'end'), 0, false);
$status = $this->processRequest();
$this->afterRequest();
$response->end();
$this->afterRun();
return $status;
}
/**
* Raises the [[EVENT_BEFORE_REQUEST]] event right BEFORE the application processes the request.
* Raises the [[EVENT_BEFORE_RUN]] event right BEFORE the application processes the request.
*/
public function beforeRequest()
public function beforeRun()
{
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->trigger(self::EVENT_BEFORE_RUN);
}
/**
* Raises the [[EVENT_AFTER_REQUEST]] event right AFTER the application processes the request.
* Raises the [[EVENT_AFTER_RUN]] event right AFTER the application processes the request.
*/
public function afterRequest()
public function afterRun()
{
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->trigger(self::EVENT_AFTER_RUN);
}
/**
@@ -314,6 +324,15 @@ class Application extends Module
return $this->getComponent('request');
}
/**
* Returns the response component.
* @return \yii\web\Response|\yii\console\Response the response component
*/
public function getResponse()
{
return $this->getComponent('response');
}
/**
* Returns the view object.
* @return View the view object that is used to render various view files.

View File

@@ -8,6 +8,7 @@
namespace yii\base;
use Yii;
use yii\web\HttpException;
/**
* ErrorHandler handles uncaught PHP errors and exceptions.
@@ -82,11 +83,12 @@ class ErrorHandler extends Component
} elseif (!(Yii::$app instanceof \yii\web\Application)) {
Yii::$app->renderException($exception);
} else {
$response = Yii::$app->getResponse();
if (!headers_sent()) {
if ($exception instanceof HttpException) {
header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName());
$response->setStatusCode($exception->statusCode);
} else {
header('HTTP/1.0 500 ' . get_class($exception));
$response->setStatusCode(500);
}
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
@@ -100,13 +102,13 @@ class ErrorHandler extends Component
$view = new View();
$request = '';
foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) {
if (!empty($GLOBALS['_' . $name])) {
$request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n";
foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) {
if (!empty($GLOBALS[$name])) {
$request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
}
}
$request = rtrim($request, "\n\n");
echo $view->renderFile($this->mainView, array(
$response->content = $view->renderFile($this->mainView, array(
'exception' => $exception,
'request' => $request,
), $this);

View File

@@ -13,20 +13,29 @@ namespace yii\base;
*/
class Response extends Component
{
/**
* @event Event an event raised when the application begins to generate the response.
*/
const EVENT_BEGIN_RESPONSE = 'beginResponse';
/**
* @event Event an event raised when the generation of the response finishes.
*/
const EVENT_END_RESPONSE = 'endResponse';
/**
* Starts output buffering
*/
public function beginOutput()
public function beginBuffer()
{
ob_start();
ob_implicit_flush(false);
}
/**
* Returns contents of the output buffer and discards it
* Returns contents of the output buffer and stops the buffer.
* @return string output buffer contents
*/
public function endOutput()
public function endBuffer()
{
return ob_get_clean();
}
@@ -35,16 +44,16 @@ class Response extends Component
* Returns contents of the output buffer
* @return string output buffer contents
*/
public function getOutput()
public function getBuffer()
{
return ob_get_contents();
}
/**
* Discards the output buffer
* @param boolean $all if true recursively discards all output buffers used
* @param boolean $all if true, it will discards all output buffers.
*/
public function cleanOutput($all = true)
public function cleanBuffer($all = true)
{
if ($all) {
for ($level = ob_get_level(); $level > 0; --$level) {
@@ -56,4 +65,28 @@ class Response extends Component
ob_end_clean();
}
}
/**
* Begins generating the response.
* This method is called at the beginning of [[Application::run()]].
* The default implementation will trigger the [[EVENT_BEGIN_RESPONSE]] event.
* If you overwrite this method, make sure you call the parent implementation so that
* the event can be triggered.
*/
public function begin()
{
$this->trigger(self::EVENT_BEGIN_RESPONSE);
}
/**
* Ends generating the response.
* This method is called at the end of [[Application::run()]].
* The default implementation will trigger the [[EVENT_END_RESPONSE]] event.
* If you overwrite this method, make sure you call the parent implementation so that
* the event can be triggered.
*/
public function end()
{
$this->trigger(self::EVENT_END_RESPONSE);
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Response extends \yii\base\Response
{
}

View File

@@ -653,6 +653,32 @@ class Command extends \yii\base\Component
return $this->setSql($sql);
}
/**
* Creates a SQL command for adding a primary key constraint to an existing table.
* The method will properly quote the table and column names.
* @param string $name the name of the primary key constraint.
* @param string $table the table that the primary key constraint will be added to.
* @param string|array $columns comma separated string or array of columns that the primary key will consist of.
* @return Command the command object itself.
*/
public function addPrimaryKey($name, $table, $columns)
{
$sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
return $this->setSql($sql);
}
/**
* Creates a SQL command for removing a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return Command the command object itself
*/
public function dropPrimaryKey($name, $table)
{
$sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
return $this->setSql($sql);
}
/**
* Creates a SQL command for adding a foreign key constraint to an existing table.
* The method will properly quote the table and column names.

View File

@@ -309,6 +309,35 @@ class Migration extends \yii\base\Component
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for creating a primary key.
* The method will properly quote the table and column names.
* @param string $name the name of the primary key constraint.
* @param string $table the table that the primary key constraint will be added to.
* @param string|array $columns comma separated string or array of columns that the primary key will consist of.
*/
public function addPrimaryKey($name, $table, $columns)
{
echo " > add primary key $name on $table (".(is_array($columns) ? implode(',',$columns) : $columns).") ...";
$time = microtime(true);
$this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute();
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for dropping a primary key.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return Command the command object itself
*/
public function dropPrimaryKey($name, $table)
{
echo " > drop primary key $name ...";
$time = microtime(true);
$this->db->createCommand()->dropPrimaryKey($name, $table)->execute();
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds a SQL statement for adding a foreign key constraint to an existing table.
* The method will properly quote the table and column names.

View File

@@ -268,6 +268,41 @@ class QueryBuilder extends \yii\base\Object
{
return "DROP TABLE " . $this->db->quoteTableName($table);
}
/**
* Builds a SQL statement for adding a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint.
* @param string $table the table that the primary key constraint will be added to.
* @param string|array $columns comma separated string or array of columns that the primary key will consist of.
* @return string the SQL statement for adding a primary key constraint to an existing table.
*/
public function addPrimaryKey($name, $table, $columns)
{
if (is_string($columns)) {
$columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
}
foreach ($columns as $i=>$col) {
$columns[$i]=$this->db->quoteColumnName($col);
}
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
. implode(', ', $columns). ' )';
}
/**
* Builds a SQL statement for removing a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return string the SQL statement for removing a primary key constraint from an existing table. *
*/
public function dropPrimaryKey($name, $table)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table)
. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
}
/**
* Builds a SQL statement for truncating a DB table.

View File

@@ -88,6 +88,17 @@ class QueryBuilder extends \yii\db\QueryBuilder
. ' DROP FOREIGN KEY ' . $this->db->quoteColumnName($name);
}
/**
* Builds a SQL statement for removing a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return string the SQL statement for removing a primary key constraint from an existing table.
*/
public function dropPrimaryKey($name, $table)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY';
}
/**
* 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
@@ -113,7 +124,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
} elseif ($table === null) {
throw new InvalidParamException("Table not found: $tableName");
} else {
throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
}
}

View File

@@ -21,21 +21,20 @@ class QueryBuilder extends \yii\db\QueryBuilder
* @var array mapping from abstract column types (keys) to physical column types (values).
*/
public $typeMap = array(
Schema::TYPE_PK => 'serial not null primary key',
Schema::TYPE_STRING => 'varchar',
Schema::TYPE_TEXT => 'text',
Schema::TYPE_SMALLINT => 'smallint',
Schema::TYPE_INTEGER => 'integer',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'double precision',
Schema::TYPE_DECIMAL => 'numeric',
Schema::TYPE_DATETIME => 'timestamp',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'bytea',
Schema::TYPE_BOOLEAN => 'boolean',
Schema::TYPE_MONEY => 'numeric(19,4)',
Schema::TYPE_PK => 'serial not null primary key',
Schema::TYPE_STRING => 'varchar(255)',
Schema::TYPE_TEXT => 'text',
Schema::TYPE_SMALLINT => 'smallint',
Schema::TYPE_INTEGER => 'integer',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'double precision',
Schema::TYPE_DECIMAL => 'numeric(10,0)',
Schema::TYPE_DATETIME => 'timestamp',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'bytea',
Schema::TYPE_BOOLEAN => 'boolean',
Schema::TYPE_MONEY => 'numeric(19,4)',
);
}

View File

@@ -43,6 +43,7 @@ class Schema extends \yii\db\Schema
'circle' => self::TYPE_STRING,
'date' => self::TYPE_DATE,
'real' => self::TYPE_FLOAT,
'decimal' => self::TYPE_DECIMAL,
'double precision' => self::TYPE_DECIMAL,
'inet' => self::TYPE_STRING,
'smallint' => self::TYPE_SMALLINT,
@@ -55,7 +56,6 @@ class Schema extends \yii\db\Schema
'money' => self::TYPE_MONEY,
'name' => self::TYPE_STRING,
'numeric' => self::TYPE_STRING,
'numrange' => self::TYPE_DECIMAL,
'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
'path' => self::TYPE_STRING,
'point' => self::TYPE_STRING,
@@ -165,11 +165,11 @@ SQL;
$columns = explode(',', $constraint['columns']);
$fcolumns = explode(',', $constraint['foreign_columns']);
if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
$foreign_table = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
$foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
} else {
$foreign_table = $constraint['foreign_table_name'];
$foreignTable = $constraint['foreign_table_name'];
}
$citem = array($foreign_table);
$citem = array($foreignTable);
foreach ($columns as $idx => $column) {
$citem[] = array($fcolumns[$idx] => $column);
}
@@ -243,6 +243,9 @@ ORDER BY
SQL;
$columns = $this->db->createCommand($sql)->queryAll();
if (empty($columns)) {
return false;
}
foreach ($columns as $column) {
$column = $this->loadColumnSchema($column);
$table->columns[$column->name] = $column;
@@ -285,5 +288,4 @@ SQL;
$column->phpType = $this->getColumnPhpType($column);
return $column;
}
}

View File

@@ -179,4 +179,30 @@ class QueryBuilder extends \yii\db\QueryBuilder
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for adding a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint.
* @param string $table the table that the primary key constraint will be added to.
* @param string|array $columns comma separated string or array of columns that the primary key will consist of.
* @return string the SQL statement for adding a primary key constraint to an existing table.
* @throws NotSupportedException this is not supported by SQLite
*/
public function addPrimaryKey($name, $table, $columns)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for removing a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return string the SQL statement for removing a primary key constraint from an existing table.
* @throws NotSupportedException this is not supported by SQLite *
*/
public function dropPrimaryKey($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
}

View File

@@ -10,6 +10,7 @@
namespace yii\helpers\base;
use Yii;
use yii\helpers\StringHelper;
/**
* Filesystem helper
@@ -95,7 +96,7 @@ class FileHelper
}
}
return $checkExtension ? self::getMimeTypeByExtension($file) : null;
return $checkExtension ? static::getMimeTypeByExtension($file) : null;
}
/**
@@ -133,12 +134,21 @@ class FileHelper
*
* - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777.
* - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting.
* - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
* If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
* - filter: callback, a PHP callback that is called for each sub-directory or file.
* If the callback returns false, the the sub-directory or file will not be copied.
* The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be copied.
* - fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be copied.
* - only: array, list of patterns that the files or directories should match if they want to be copied.
* A path matches a pattern if it contains the pattern string at its end. For example,
* '/a/b' will match all files and directories ending with '/a/b'; and the '.svn' will match all files and
* directories whose name ends with '.svn'. Note, the '/' characters in a pattern matches both '/' and '\'.
* If a file/directory matches both a name in "only" and "except", it will NOT be copied.
* - except: array, list of patterns that the files or directories should NOT match if they want to be copied.
* For more details on how to specify the patterns, please refer to the "only" option.
* - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
* - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file to be copied from, while `$to` is the copy target.
* - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
* The signature of the callback is similar to that of `beforeCopy`.
* file copied from, while `$to` is the copy target.
*/
public static function copyDirectory($src, $dst, $options = array())
{
@@ -153,7 +163,7 @@ class FileHelper
}
$from = $src . DIRECTORY_SEPARATOR . $file;
$to = $dst . DIRECTORY_SEPARATOR . $file;
if (!isset($options['beforeCopy']) || call_user_func($options['beforeCopy'], $from, $to)) {
if (static::filterPath($from, $options)) {
if (is_file($from)) {
copy($from, $to);
if (isset($options['fileMode'])) {
@@ -169,4 +179,129 @@ class FileHelper
}
closedir($handle);
}
/**
* Removes a directory (and all its content) recursively.
* @param string $dir the directory to be deleted recursively.
*/
public static function removeDirectory($dir)
{
if (!is_dir($dir) || !($handle = opendir($dir))) {
return;
}
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_file($path)) {
unlink($path);
} else {
static::removeDirectory($path);
}
}
closedir($handle);
rmdir($dir);
}
/**
* Returns the files found under the specified directory and subdirectories.
* @param string $dir the directory under which the files will be looked for.
* @param array $options options for file searching. Valid options are:
*
* - filter: callback, a PHP callback that is called for each sub-directory or file.
* If the callback returns false, the the sub-directory or file will be excluded from the returning result.
* The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
* - fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be returned.
* - only: array, list of patterns that the files or directories should match if they want to be returned.
* A path matches a pattern if it contains the pattern string at its end. For example,
* '/a/b' will match all files and directories ending with '/a/b'; and the '.svn' will match all files and
* directories whose name ends with '.svn'. Note, the '/' characters in a pattern matches both '/' and '\'.
* If a file/directory matches both a name in "only" and "except", it will NOT be returned.
* - except: array, list of patterns that the files or directories should NOT match if they want to be returned.
* For more details on how to specify the patterns, please refer to the "only" option.
* - recursive: boolean, whether the files under the subdirectories should also be lookied for. Defaults to true.
* @return array files found under the directory. The file list is sorted.
*/
public static function findFiles($dir, $options = array())
{
$list = array();
$handle = opendir($dir);
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (static::filterPath($path, $options)) {
if (is_file($path)) {
$list[] = $path;
} elseif (!isset($options['recursive']) || $options['recursive']) {
$list = array_merge($list, static::findFiles($path, $options));
}
}
}
closedir($handle);
return $list;
}
/**
* Checks if the given file path satisfies the filtering options.
* @param string $path the path of the file or directory to be checked
* @param array $options the filtering options. See [[findFiles()]] for explanations of
* the supported options.
* @return boolean whether the file or directory satisfies the filtering options.
*/
public static function filterPath($path, $options)
{
if (isset($options['filter']) && !call_user_func($options['filter'], $path)) {
return false;
}
$path = str_replace('\\', '/', $path);
$n = StringHelper::strlen($path);
if (!empty($options['except'])) {
foreach ($options['except'] as $name) {
if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) {
return false;
}
}
}
if (!empty($options['only'])) {
foreach ($options['only'] as $name) {
if (StringHelper::substr($path, -StringHelper::strlen($name), $n) !== $name) {
return false;
}
}
}
if (!empty($options['fileTypes']) && is_file($path)) {
return in_array(pathinfo($path, PATHINFO_EXTENSION), $options['fileTypes']);
} else {
return true;
}
}
/**
* Makes directory.
*
* This method is similar to the PHP `mkdir()` function except that
* it uses `chmod()` to set the permission of the created directory
* in order to avoid the impact of the `umask` setting.
*
* @param string $path path to be created.
* @param integer $mode the permission to be set for created directory.
* @param boolean $recursive whether to create parent directories if they do not exist.
* @return boolean whether the directory is created successfully
*/
public static function mkdir($path, $mode = 0777, $recursive = true)
{
if (is_dir($path)) {
return true;
}
$parentDir = dirname($path);
if ($recursive && !is_dir($parentDir)) {
static::mkdir($parentDir, $mode, true);
}
$result = mkdir($path, $mode);
chmod($path, $mode);
return $result;
}
}

View File

@@ -43,8 +43,10 @@ class StringHelper
/**
* Returns the trailing name component of a path.
* This method does the same as the php function basename() except that it will
* This method does the same as the php function `basename()` except that it will
* always use \ and / as directory separators, independent of the operating system.
* This method was mainly created to work on php namespaces. When working with real
* file paths, php's `basename()` should work fine for you.
* Note: this method is not aware of the actual filesystem, or path components such as "..".
* @param string $path A path string.
* @param string $suffix If the name component ends in suffix this will also be cut off.

View File

@@ -14,7 +14,7 @@ $context = $this->context;
<meta charset="utf-8"/>
<title><?php
if ($exception instanceof \yii\base\HttpException) {
if ($exception instanceof \yii\web\HttpException) {
echo (int) $exception->statusCode . ' ' . $context->htmlEncode($exception->getName());
} elseif ($exception instanceof \yii\base\Exception) {
echo $context->htmlEncode($exception->getName() . ' ' . get_class($exception));
@@ -362,7 +362,7 @@ pre .diff .change{
<?php else: ?>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA6CAIAAACPssguAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjcwRURBOUQxQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjcwRURBOUQyQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzBFREE5Q0ZDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzBFREE5RDBDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4L6mYsAAAE9klEQVR42tRa227aTBDGiwkhJtD8EanSQ6RKqKpSBbWqql5U7QP0TfsSvWhvStMEO6cqDUpjRylSQ1QC+IQN/mdrhBIb413vgsheIDDr9XwzOzPfzFrwPC/Fe8Cauiybe3uersNPQZJylYr04oUgCNyfJXJfcTAYXH/5YoH0qjq66LbbTrtdfPcOITTXALDuf/40FSWlabeuq6rpeZkHD6Ryma8dOOvDdV19Zycg/XBoml6r9ft9vk9EnNV/dOT8+RM1oXdxof/4wdfrEF/1G7Va+uIiagL8BfaBafMIAHy38/17/+/fydNgAkyDyXMHwLEsS1EmqH9kBOvgACbPFwDQaPfr11S9TjT7+JijEfgAsFst8/CQYv7BQe/6el4AQGTsfPuGbqSt2CGcnrarVS4hlRUAxETz9+/eyQntjXAL3MgeUhG7+vXtbSr1Dx+sqoYss3sCYlS/cXLinJ8nu713emqoKqMRmAD4xAElBQA3GpA62DwBsagfiIN7ecnyeHZyIbKoP4o46KurY2+Rrq7CeQ08YenZs0wmM1MAI+KQDv3VKpd7r1+L2WwQsGH0q9VCyN3dZrOrKPdevUpGsxMCmEAcMqVSaWNjcXExcN00zRbQ7BAAWMSs1ZY2N7NLSzMCMJk4ZBFaXl4OAxBF0Yza68fH3Z2dzNu3Ceo1lMB3raurScQhgqjBDhGiibS1v98zzVlEIax+CJ0TM9fYqAIXB9HRBsiFXq0myGuIVv2xxGGQNCaCVcG2tCGVDkBi4jBU80ThYFlwLVojICr1kxAHFLHRsWoNgzu5oABASBw8247ygVScZJhcyDIVuUDk6tf39oiIg+exUAOwMNiZfAVSAI7jGPv7sSUvhwLl/Jyqc0EEwA+dsR2H+EFWy4OdwdqERiACQNhxGIaaaCcWyDY3Zni7u2BzPgDoOg64Yo92YvJ43e2CzUlCKmIlDsSZ2FcG4Qq4faQoJO0jRKJ+uszFq+tWr5PkNRRPHM7O2IWBpdxGYxrkAk2DODihbAqKdJpNkRIDJhdxniDGEgdavgpSWp8+XX7+HADg9vsJCnDcPnr+XHr0KKpeE6fScdA0l1P7AIwAu2BxfR3qIYotREEcpj8mkwsxkjjs7iYmDuRdCXJykSuXx3YuxEji0O2mE0lvlEpWpZK6vWsxv2s0BCjeE1kVk4ujo2KlEvYEkZE4jMlBCK1sbUFdf7NCx22YlRVHURKuCeSiVpM2N8NGEFmJwzgAIH2hUAhYAD6vGQ6J/bOpe2/eBDoXiJ04jNm1CAVsDT8Zj7ijyAViJQ6zHPV6Z3s7kNfQrcylqlCVpuZ42IeHgbMpdJM4QD2auFc+mxE+m0JUHQfSqHd2Fsg7mMxxWjxwNiX43yBzNT9+9GSZj6I2NsKZH5e5Y9+hSODQ79+vfvjgP0K8SRzSvCw9jgvxJBe/fpmaln/yBAe3WXYcOHYuRmdTCFctmsah4zBjhtdo+D08BGHVhs1zd9Q/MgKIDcJjCwzyefv+/dRdG86/zItzvri+7knS3ZLefPgQPX7sMxSUy+W8ly+NtbW7Ir2+tjaoVHL5fDqdFgFENpstlMtQ/bdlOc3pJZLpjX6x6G1t/ff0KegdH1v96yV7EEk7nU6r1bJtm+PrVPx9FyFQd7FYBMa+sLAwBODnMgirAAM+p/EqLDcuJAiwbaCsgU+fsf8vwADjle3ME1OGEwAAAABJRU5ErkJggg==" alt="Attention"/>
<h1><?php
if ($exception instanceof \yii\base\HttpException) {
if ($exception instanceof \yii\web\HttpException) {
echo '<span>' . $context->createHttpStatusLink($exception->statusCode, $context->htmlEncode($exception->getName())) . '</span>';
echo ' &ndash; ' . $context->addTypeLinks(get_class($exception));
} elseif ($exception instanceof \yii\base\Exception) {

View File

@@ -10,7 +10,7 @@ namespace yii\web;
use Yii;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\base\HttpException;
use yii\web\HttpException;
/**
* AccessControl provides simple access control based on a set of rules.

View File

@@ -8,7 +8,7 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\web\HttpException;
use yii\base\InvalidRouteException;
/**

View File

@@ -277,11 +277,8 @@ class CaptchaAction extends Action
imagecolordeallocate($image, $foreColor);
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Transfer-Encoding: binary');
header("Content-type: image/png");
$this->sendHttpHeaders();
imagepng($image);
imagedestroy($image);
}
@@ -319,12 +316,21 @@ class CaptchaAction extends Action
$x += (int)($fontMetrics['textWidth']) + $this->offset;
}
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Transfer-Encoding: binary');
header("Content-type: image/png");
$image->setImageFormat('png');
echo $image;
Yii::$app->getResponse()->content = (string)$image;
$this->sendHttpHeaders();
}
/**
* Sends the HTTP headers needed by image response.
*/
protected function sendHttpHeaders()
{
Yii::$app->getResponse()->getHeaders()
->set('Pragma', 'public')
->set('Expires', '0')
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->set('Content-Transfer-Encoding', 'binary')
->set('Content-type', 'image/png');
}
}

View File

@@ -8,7 +8,7 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\web\HttpException;
use yii\base\InlineAction;
/**

View File

@@ -45,7 +45,7 @@ class Cookie extends \yii\base\Object
* By setting this property to true, the cookie will not be accessible by scripting languages,
* such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
*/
public $httponly = false;
public $httpOnly = false;
/**
* Magic method to turn a cookie object into a string without having to explicitly access [[value]].

View File

@@ -9,7 +9,8 @@ namespace yii\web;
use Yii;
use ArrayIterator;
use yii\helpers\SecurityHelper;
use yii\base\InvalidCallException;
use yii\base\Object;
/**
* CookieCollection maintains the cookies available in the current request.
@@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable
class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{
/**
* @var boolean whether to enable cookie validation. By setting this property to true,
* if a cookie is tampered on the client side, it will be ignored when received on the server side.
* @var boolean whether this collection is read only.
*/
public $enableValidation = true;
/**
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
*/
public $validationKey;
public $readOnly = false;
/**
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
@@ -38,12 +34,14 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
/**
* Constructor.
* @param array $cookies the cookies that this collection initially contains. This should be
* an array of name-value pairs.s
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($config = array())
public function __construct($cookies = array(), $config = array())
{
$this->_cookies = $cookies;
parent::__construct($config);
$this->_cookies = $this->loadCookies();
}
/**
@@ -114,50 +112,53 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
* Adds a cookie to the collection.
* If there is already a cookie with the same name in the collection, it will be removed first.
* @param Cookie $cookie the cookie to be added
* @throws InvalidCallException if the cookie collection is read only
*/
public function add($cookie)
{
if (isset($this->_cookies[$cookie->name])) {
$c = $this->_cookies[$cookie->name];
setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly);
if ($this->readOnly) {
throw new InvalidCallException('The cookie collection is read only.');
}
$value = $cookie->value;
if ($this->enableValidation) {
if ($this->validationKey === null) {
$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
} else {
$key = $this->validationKey;
}
$value = SecurityHelper::hashData(serialize($value), $key);
}
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
$this->_cookies[$cookie->name] = $cookie;
}
/**
* Removes a cookie from the collection.
* Removes a cookie.
* If `$removeFromBrowser` is true, the cookie will be removed from the browser.
* In this case, a cookie with outdated expiry will be added to the collection.
* @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
* @param boolean $removeFromBrowser whether to remove the cookie from browser
* @throws InvalidCallException if the cookie collection is read only
*/
public function remove($cookie)
public function remove($cookie, $removeFromBrowser = true)
{
if (is_string($cookie) && isset($this->_cookies[$cookie])) {
$cookie = $this->_cookies[$cookie];
if ($this->readOnly) {
throw new InvalidCallException('The cookie collection is read only.');
}
if ($cookie instanceof Cookie) {
setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
$cookie->expire = 1;
$cookie->value = '';
} else {
$cookie = new Cookie(array(
'name' => $cookie,
'expire' => 1,
));
}
if ($removeFromBrowser) {
$this->_cookies[$cookie->name] = $cookie;
} else {
unset($this->_cookies[$cookie->name]);
}
}
/**
* Removes all cookies.
* @throws InvalidCallException if the cookie collection is read only
*/
public function removeAll()
{
foreach ($this->_cookies as $cookie) {
setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
if ($this->readOnly) {
throw new InvalidCallException('The cookie collection is read only.');
}
$this->_cookies = array();
}
@@ -222,36 +223,4 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
{
$this->remove($name);
}
/**
* Returns the current cookies in terms of [[Cookie]] objects.
* @return Cookie[] list of current cookies
*/
protected function loadCookies()
{
$cookies = array();
if ($this->enableValidation) {
if ($this->validationKey === null) {
$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
} else {
$key = $this->validationKey;
}
foreach ($_COOKIE as $name => $value) {
if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
$cookies[$name] = new Cookie(array(
'name' => $name,
'value' => @unserialize($value),
));
}
}
} else {
foreach ($_COOKIE as $name => $value) {
$cookies[$name] = new Cookie(array(
'name' => $name,
'value' => $value,
));
}
}
return $cookies;
}
}

View File

@@ -79,11 +79,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
* If there is already a header with the same name, it will be replaced.
* @param string $name the name of the header
* @param string $value the value of the header
* @return HeaderCollection the collection object itself
*/
public function set($name, $value)
public function set($name, $value = '')
{
$name = strtolower($name);
$this->_headers[$name] = (array)$value;
return $this;
}
/**
@@ -92,11 +94,29 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
* be appended to it instead of replacing it.
* @param string $name the name of the header
* @param string $value the value of the header
* @return HeaderCollection the collection object itself
*/
public function add($name, $value)
{
$name = strtolower($name);
$this->_headers[$name][] = $value;
return $this;
}
/**
* Adds a new header only if it does not exist yet.
* If there is already a header with the same name, the new one will be ignored.
* @param string $name the name of the header
* @param string $value the value of the header
* @return HeaderCollection the collection object itself
*/
public function addDefault($name, $value)
{
$name = strtolower($name);
if (empty($this->_headers[$name])) {
$this->_headers[$name][] = $value;
}
return $this;
}
/**

View File

@@ -50,7 +50,7 @@ class HttpCache extends ActionFilter
/**
* @var string HTTP cache control header. If null, the header will not be sent.
*/
public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
public $cacheControlHeader = 'max-age=3600, public';
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
@@ -60,7 +60,7 @@ class HttpCache extends ActionFilter
*/
public function beforeAction($action)
{
$verb = Yii::$app->request->getMethod();
$verb = Yii::$app->getRequest()->getMethod();
if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
return true;
}
@@ -75,17 +75,18 @@ class HttpCache extends ActionFilter
}
$this->sendCacheControlHeader();
$response = Yii::$app->getResponse();
if ($etag !== null) {
header("ETag: $etag");
$response->getHeaders()->set('Etag', $etag);
}
if ($this->validateCache($lastModified, $etag)) {
header('HTTP/1.1 304 Not Modified');
$response->setStatusCode(304);
return false;
}
if ($lastModified !== null) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
$response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
}
return true;
}
@@ -113,9 +114,10 @@ class HttpCache extends ActionFilter
protected function sendCacheControlHeader()
{
session_cache_limiter('public');
header('Pragma:', true);
$headers = Yii::$app->getResponse()->getHeaders();
$headers->set('Pragma');
if ($this->cacheControlHeader !== null) {
header($this->cacheControlHeader, true);
$headers->set('Cache-Control', $this->cacheControlHeader);
}
}

View File

@@ -5,8 +5,10 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
namespace yii\web;
use yii\base\UserException;
use yii\web\Response;
/**
* HttpException represents an exception caused by an improper request of the end-user.
@@ -43,8 +45,8 @@ class HttpException extends UserException
*/
public function getName()
{
if (isset(\yii\web\Response::$statusTexts[$this->statusCode])) {
return \yii\web\Response::$statusTexts[$this->statusCode];
if (isset(Response::$httpStatuses[$this->statusCode])) {
return Response::$httpStatuses[$this->statusCode];
} else {
return 'Error';
}

View File

@@ -8,8 +8,9 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\web\HttpException;
use yii\base\InvalidConfigException;
use yii\helpers\SecurityHelper;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
@@ -37,15 +38,11 @@ class Request extends \yii\base\Request
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
* @see Cookie
*/
public $csrfCookie = array('httponly' => true);
public $csrfCookie = array('httpOnly' => true);
/**
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
*/
public $enableCookieValidation = true;
/**
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
*/
public $cookieValidationKey;
/**
* @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
* request tunneled through POST. Default to '_method'.
@@ -717,14 +714,64 @@ class Request extends \yii\base\Request
public function getCookies()
{
if ($this->_cookies === null) {
$this->_cookies = new CookieCollection(array(
'enableValidation' => $this->enableCookieValidation,
'validationKey' => $this->cookieValidationKey,
$this->_cookies = new CookieCollection($this->loadCookies(), array(
'readOnly' => true,
));
}
return $this->_cookies;
}
/**
* Converts `$_COOKIE` into an array of [[Cookie]].
* @return array the cookies obtained from request
*/
protected function loadCookies()
{
$cookies = array();
if ($this->enableCookieValidation) {
$key = $this->getCookieValidationKey();
foreach ($_COOKIE as $name => $value) {
if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
$cookies[$name] = new Cookie(array(
'name' => $name,
'value' => @unserialize($value),
));
}
}
} else {
foreach ($_COOKIE as $name => $value) {
$cookies[$name] = new Cookie(array(
'name' => $name,
'value' => $value,
));
}
}
return $cookies;
}
private $_cookieValidationKey;
/**
* @return string the secret key used for cookie validation. If it was set previously,
* a random key will be generated and used.
*/
public function getCookieValidationKey()
{
if ($this->_cookieValidationKey === null) {
$this->_cookieValidationKey = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
}
return $this->_cookieValidationKey;
}
/**
* Sets the secret key used for cookie validation.
* @param string $value the secret key used for cookie validation.
*/
public function setCookieValidationKey($value)
{
$this->_cookieValidationKey = $value;
}
private $_csrfToken;
/**

View File

@@ -8,11 +8,12 @@
namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\web\HttpException;
use yii\base\InvalidParamException;
use yii\helpers\FileHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\helpers\SecurityHelper;
use yii\helpers\StringHelper;
/**
@@ -45,11 +46,10 @@ class Response extends \yii\base\Response
* @var string the version of the HTTP protocol to use
*/
public $version = '1.0';
/**
* @var array list of HTTP status codes and the corresponding texts
*/
public static $statusTexts = array(
public static $httpStatuses = array(
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
@@ -93,7 +93,7 @@ class Response extends \yii\base\Response
415 => 'Unsupported Media Type',
416 => 'Requested range unsatisfiable',
417 => 'Expectation failed',
418 => 'Im a teapot',
418 => 'I\'m a teapot',
422 => 'Unprocessable entity',
423 => 'Locked',
424 => 'Method failure',
@@ -117,7 +117,10 @@ class Response extends \yii\base\Response
511 => 'Network Authentication Required',
);
private $_statusCode = 200;
/**
* @var integer the HTTP status code to send with the response.
*/
private $_statusCode;
/**
* @var HeaderCollection
*/
@@ -131,18 +134,38 @@ class Response extends \yii\base\Response
}
}
public function begin()
{
parent::begin();
$this->beginBuffer();
}
public function end()
{
$this->content .= $this->endBuffer();
$this->send();
parent::end();
}
/**
* @return integer the HTTP status code to send with the response.
*/
public function getStatusCode()
{
return $this->_statusCode;
}
public function setStatusCode($value)
public function setStatusCode($value, $text = null)
{
$this->_statusCode = (int)$value;
if ($this->isInvalid()) {
if ($this->getIsInvalid()) {
throw new InvalidParamException("The HTTP status code is invalid: $value");
}
$this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : '';
if ($text === null) {
$this->statusText = isset(self::$httpStatuses[$this->_statusCode]) ? self::$httpStatuses[$this->_statusCode] : '';
} else {
$this->statusText = $text;
}
}
/**
@@ -160,15 +183,17 @@ class Response extends \yii\base\Response
public function renderJson($data)
{
$this->getHeaders()->set('content-type', 'application/json');
$this->getHeaders()->set('Content-Type', 'application/json');
$this->content = Json::encode($data);
$this->send();
}
public function renderJsonp($data, $callbackName)
{
$this->getHeaders()->set('content-type', 'text/javascript');
$this->getHeaders()->set('Content-Type', 'text/javascript');
$data = Json::encode($data);
$this->content = "$callbackName($data);";
$this->send();
}
/**
@@ -179,6 +204,25 @@ class Response extends \yii\base\Response
{
$this->sendHeaders();
$this->sendContent();
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} else {
for ($level = ob_get_level(); $level > 0; --$level) {
if (!@ob_end_flush()) {
ob_clean();
}
}
flush();
}
}
public function reset()
{
$this->_headers = null;
$this->_statusCode = null;
$this->statusText = null;
$this->content = null;
}
/**
@@ -186,13 +230,45 @@ class Response extends \yii\base\Response
*/
protected function sendHeaders()
{
header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}");
foreach ($this->_headers as $name => $values) {
foreach ($values as $value) {
header("$name: $value");
}
if (headers_sent()) {
return;
}
$this->_headers->removeAll();
$statusCode = $this->getStatusCode();
if ($statusCode !== null) {
header("HTTP/{$this->version} $statusCode {$this->statusText}");
}
if ($this->_headers) {
$headers = $this->getHeaders();
foreach ($headers as $name => $values) {
foreach ($values as $value) {
header("$name: $value", false);
}
}
$headers->removeAll();
}
$this->sendCookies();
}
/**
* Sends the cookies to the client.
*/
protected function sendCookies()
{
if ($this->_cookies === null) {
return;
}
$request = Yii::$app->getRequest();
if ($request->enableCookieValidation) {
$validationKey = $request->getCookieValidationKey();
}
foreach ($this->getCookies() as $cookie) {
$value = $cookie->value;
if ($cookie->expire != 1 && isset($validationKey)) {
$value = SecurityHelper::hashData(serialize($value), $validationKey);
}
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
}
$this->getCookies()->removeAll();
}
/**
@@ -205,89 +281,132 @@ class Response extends \yii\base\Response
}
/**
* Sends a file to user.
* @param string $fileName file name
* @param string $content content to be set.
* @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
* @param boolean $terminate whether to terminate the current application after calling this method
* @throws \yii\base\HttpException when range request is not satisfiable.
* Sends a file to the browser.
* @param string $filePath the path of the file to be sent.
* @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
* @param string $mimeType the MIME type of the content. If null, it will be guessed based on `$filePath`
*/
public function sendFile($fileName, $content, $mimeType = null, $terminate = true)
public function sendFile($filePath, $attachmentName = null, $mimeType = null)
{
if ($mimeType === null && (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null)) {
if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
$mimeType = 'application/octet-stream';
}
if ($attachmentName === null) {
$attachmentName = basename($filePath);
}
$handle = fopen($filePath, 'rb');
$this->sendStreamAsFile($handle, $attachmentName, $mimeType);
}
$fileSize = StringHelper::strlen($content);
$contentStart = 0;
$contentEnd = $fileSize - 1;
/**
* Sends the specified content as a file to the browser.
* @param string $content the content to be sent. The existing [[content]] will be discarded.
* @param string $attachmentName the file name shown to the user.
* @param string $mimeType the MIME type of the content.
*/
public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream')
{
$this->getHeaders()
->addDefault('Pragma', 'public')
->addDefault('Accept-Ranges', 'bytes')
->addDefault('Expires', '0')
->addDefault('Content-Type', $mimeType)
->addDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->addDefault('Content-Transfer-Encoding', 'binary')
->addDefault('Content-Length', StringHelper::strlen($content))
->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
// tell the client that we accept range requests
header('Accept-Ranges: bytes');
$this->content = $content;
$this->send();
}
if (isset($_SERVER['HTTP_RANGE'])) {
// client sent us a multibyte range, can not hold this one for now
if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
throw new HttpException(416, 'Requested Range Not Satisfiable');
}
/**
* Sends the specified stream as a file to the browser.
* @param resource $handle the handle of the stream to be sent.
* @param string $attachmentName the file name shown to the user.
* @param string $mimeType the MIME type of the stream content.
* @throws HttpException if the requested range cannot be satisfied.
*/
public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream')
{
$headers = $this->getHeaders();
fseek($handle, 0, SEEK_END);
$fileSize = ftell($handle);
$range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']);
// range requests starts from "-", so it means that data must be dumped the end point.
if ($range[0] === '-') {
$contentStart = $fileSize - substr($range, 1);
} else {
$range = explode('-', $range);
$contentStart = $range[0];
// check if the last-byte-pos presents in header
if ((isset($range[1]) && is_numeric($range[1]))) {
$contentEnd = $range[1];
}
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
$contentEnd = ($contentEnd > $fileSize) ? $fileSize - 1 : $contentEnd;
// Validate the requested range and return an error if it's not correct.
$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
if ($wrongContentStart) {
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
throw new HttpException(416, 'Requested Range Not Satisfiable');
}
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
} else {
header('HTTP/1.1 200 OK');
$range = $this->getHttpRange($fileSize);
if ($range === false) {
$headers->set('Content-Range', "bytes */$fileSize");
throw new HttpException(416, Yii::t('yii', 'Requested range not satisfiable'));
}
$length = $contentEnd - $contentStart + 1; // Calculate new content length
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: ' . $mimeType);
header('Content-Length: ' . $length);
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Transfer-Encoding: binary');
$content = StringHelper::substr($content, $contentStart, $length);
if ($terminate) {
// clean up the application first because the file downloading could take long time
// which may cause timeout of some resources (such as DB connection)
ob_start();
Yii::$app->end(0, false);
ob_end_clean();
echo $content;
exit(0);
list($begin, $end) = $range;
if ($begin !=0 || $end != $fileSize - 1) {
$this->setStatusCode(206);
$headers->set('Content-Range', "bytes $begin-$end/$fileSize");
} else {
echo $content;
$this->setStatusCode(200);
}
if (isset($options['mimeType'])) {
$headers->set('Content-Type', $options['mimeType']);
}
$length = $end - $begin + 1;
$headers->addDefault('Pragma', 'public')
->addDefault('Accept-Ranges', 'bytes')
->addDefault('Expires', '0')
->addDefault('Content-Type', $mimeType)
->addDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->addDefault('Content-Transfer-Encoding', 'binary')
->addDefault('Content-Length', $length)
->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
$this->send();
fseek($handle, $begin);
set_time_limit(0); // Reset time limit for big files
$chunkSize = 8 * 1024 * 1024; // 8MB per chunk
while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
if ($pos + $chunkSize > $end) {
$chunkSize = $end - $pos + 1;
}
echo fread($handle, $chunkSize);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}
fclose($handle);
}
/**
* Determines the HTTP range given in the request.
* @param integer $fileSize the size of the file that will be used to validate the requested HTTP range.
* @return array|boolean the range (begin, end), or false if the range request is invalid.
*/
protected function getHttpRange($fileSize)
{
if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
return array(0, $fileSize - 1);
}
if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
return false;
}
if ($matches[1] === '') {
$start = $fileSize - $matches[2];
$end = $fileSize - 1;
} elseif ($matches[2] !== '') {
$start = $matches[1];
$end = $matches[2];
if ($end >= $fileSize) {
$end = $fileSize - 1;
}
} else {
$start = $matches[1];
$end = $fileSize - 1;
}
if ($start < 0 || $start > $end) {
return false;
} else {
return array($start, $end);
}
}
@@ -305,86 +424,58 @@ class Response extends \yii\base\Response
* specified by that header using web server internals including all optimizations like caching-headers.
*
* As this header directive is non-standard different directives exists for different web servers applications:
* <ul>
* <li>Apache: {@link http://tn123.org/mod_xsendfile X-Sendfile}</li>
* <li>Lighttpd v1.4: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-LIGHTTPD-send-file}</li>
* <li>Lighttpd v1.5: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-Sendfile}</li>
* <li>Nginx: {@link http://wiki.nginx.org/XSendfile X-Accel-Redirect}</li>
* <li>Cherokee: {@link http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile X-Sendfile and X-Accel-Redirect}</li>
* </ul>
*
* - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile)
* - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
* - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
* - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
* - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
*
* So for this method to work the X-SENDFILE option/module should be enabled by the web server and
* a proper xHeader should be sent.
*
* <b>Note:</b>
* This option allows to download files that are not under web folders, and even files that are otherwise protected (deny from all) like .htaccess
* **Note**
*
* This option allows to download files that are not under web folders, and even files that are otherwise protected
* (deny from all) like `.htaccess`.
*
* <b>Side effects</b>:
* **Side effects**
*
* If this option is disabled by the web server, when this method is called a download configuration dialog
* will open but the downloaded file will have 0 bytes.
*
* <b>Known issues</b>:
* **Known issues**
*
* There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
* an error message like this: "Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.".
* You can work around this problem by removing the <code>Pragma</code>-header.
* an error message like this: "Internet Explorer was not able to open this Internet site. The requested site
* is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
*
* **Example**
*
* ~~~
* Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg');
* ~~~
*
* <b>Example</b>:
* <pre>
* <?php
* Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg', array(
* 'saveName' => 'image1.jpg',
* 'mimeType' => 'image/jpeg',
* 'terminate' => false,
* ));
* ?>
* </pre>
* @param string $filePath file name with full path
* @param array $options additional options:
* <ul>
* <li>saveName: file name shown to the user, if not set real file name will be used</li>
* <li>mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, if set to null no content-type header will be sent.</li>
* <li>xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"</li>
* <li>terminate: whether to terminate the current application after calling this method, defaults to true</li>
* <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true</li>
* <li>addHeaders: an array of additional http headers in header-value pairs</li>
* </ul>
* @todo
* @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
* @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
* @param string $xHeader the name of the x-sendfile header.
*/
public function xSendFile($filePath, $options = array())
public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile')
{
if (!isset($options['forceDownload']) || $options['forceDownload']) {
$disposition = 'attachment';
} else {
$disposition = 'inline';
if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
$mimeType = 'application/octet-stream';
}
if ($attachmentName === null) {
$attachmentName = basename($filePath);
}
if (!isset($options['saveName'])) {
$options['saveName'] = basename($filePath);
}
$this->getHeaders()
->addDefault($xHeader, $filePath)
->addDefault('Content-Type', $mimeType)
->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
if (!isset($options['mimeType'])) {
if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath)) === null) {
$options['mimeType'] = 'text/plain';
}
}
if (!isset($options['xHeader'])) {
$options['xHeader'] = 'X-Sendfile';
}
if ($options['mimeType'] !== null) {
header('Content-type: ' . $options['mimeType']);
}
header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
if (isset($options['addHeaders'])) {
foreach ($options['addHeaders'] as $header => $value) {
header($header . ': ' . $value);
}
}
header(trim($options['xHeader']) . ': ' . $filePath);
if (!isset($options['terminate']) || $options['terminate']) {
Yii::$app->end();
}
$this->send();
}
/**
@@ -422,7 +513,8 @@ class Response extends \yii\base\Response
if (Yii::$app->getRequest()->getIsAjax()) {
$statusCode = $this->ajaxRedirectCode;
}
header('Location: ' . $url, true, $statusCode);
$this->getHeaders()->set('Location', $url);
$this->setStatusCode($statusCode);
if ($terminate) {
Yii::$app->end();
}
@@ -441,6 +533,8 @@ class Response extends \yii\base\Response
$this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate);
}
private $_cookies;
/**
* Returns the cookie collection.
* Through the returned cookie collection, you add or remove cookies as follows,
@@ -462,13 +556,16 @@ class Response extends \yii\base\Response
*/
public function getCookies()
{
return Yii::$app->getRequest()->getCookies();
if ($this->_cookies === null) {
$this->_cookies = new CookieCollection;
}
return $this->_cookies;
}
/**
* @return boolean whether this response has a valid [[statusCode]].
*/
public function isInvalid()
public function getIsInvalid()
{
return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
}
@@ -476,15 +573,15 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response is informational
*/
public function isInformational()
public function getIsInformational()
{
return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
}
/**
* @return boolean whether this response is successfully
* @return boolean whether this response is successful
*/
public function isSuccessful()
public function getIsSuccessful()
{
return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
}
@@ -492,7 +589,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response is a redirection
*/
public function isRedirection()
public function getIsRedirection()
{
return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
}
@@ -500,7 +597,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response indicates a client error
*/
public function isClientError()
public function getIsClientError()
{
return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
}
@@ -508,7 +605,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response indicates a server error
*/
public function isServerError()
public function getIsServerError()
{
return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
}
@@ -516,7 +613,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response is OK
*/
public function isOk()
public function getIsOk()
{
return 200 === $this->getStatusCode();
}
@@ -524,7 +621,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response indicates the current request is forbidden
*/
public function isForbidden()
public function getIsForbidden()
{
return 403 === $this->getStatusCode();
}
@@ -532,7 +629,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response indicates the currently requested resource is not found
*/
public function isNotFound()
public function getIsNotFound()
{
return 404 === $this->getStatusCode();
}
@@ -540,7 +637,7 @@ class Response extends \yii\base\Response
/**
* @return boolean whether this response is empty
*/
public function isEmpty()
public function getIsEmpty()
{
return in_array($this->getStatusCode(), array(201, 204, 304));
}

View File

@@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @var array parameter-value pairs to override default session cookie parameters
*/
public $cookieParams = array(
'httponly' => true
'httpOnly' => true
);
/**
@@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function getCookieParams()
{
return session_get_cookie_params();
$params = session_get_cookie_params();
if (isset($params['httponly'])) {
$params['httpOnly'] = $params['httponly'];
unset($params['httponly']);
}
return $params;
}
/**
* Sets the session cookie parameters.
* The effect of this method only lasts for the duration of the script.
* Call this method before the session starts.
* @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly.
* @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
* @throws InvalidParamException if the parameters are incomplete.
* @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
*/
public function setCookieParams($value)
{
$data = session_get_cookie_params();
$data = $this->getCookieParams();
extract($data);
extract($value);
if (isset($lifetime, $path, $domain, $secure, $httponly)) {
session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
} else {
throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.');
throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.');
}
}

View File

@@ -9,7 +9,7 @@ namespace yii\web;
use Yii;
use yii\base\Component;
use yii\base\HttpException;
use yii\web\HttpException;
use yii\base\InvalidConfigException;
/**
@@ -56,7 +56,7 @@ class User extends Component
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
* @see Cookie
*/
public $identityCookie = array('name' => '_identity', 'httponly' => true);
public $identityCookie = array('name' => '_identity', 'httpOnly' => true);
/**
* @var integer the number of seconds in which the user will be logged out automatically if he
* remains inactive. If this property is not set, the user will be logged out after

View File

@@ -10,7 +10,7 @@ namespace yii\web;
use Yii;
use yii\base\ActionEvent;
use yii\base\Behavior;
use yii\base\HttpException;
use yii\web\HttpException;
/**
* VerbFilter is an action filter that filters by HTTP request methods.
@@ -70,7 +70,7 @@ class VerbFilter extends Behavior
/**
* @param ActionEvent $event
* @return boolean
* @throws \yii\base\HttpException when the request method is not allowed.
* @throws HttpException when the request method is not allowed.
*/
public function beforeAction($event)
{
@@ -81,7 +81,7 @@ class VerbFilter extends Behavior
if (!in_array($verb, $allowed)) {
$event->isValid = false;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
header('Allow: ' . implode(', ', $allowed));
Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
}
}