Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merge from 1.0

  • Loading branch information...
commit 260a80e4a94d16eede24dd797b7e854aa7991d54 1 parent 8104fe8
qiang.xue authored
Showing with 372 additions and 72 deletions.
  1. +14 −1 CHANGELOG
  2. +1 −1  demos/blog/protected/config/main.php
  3. +3 −3 demos/blog/protected/controllers/CommentController.php
  4. +2 −2 demos/blog/protected/controllers/PostController.php
  5. 0  demos/blog/protected/views/system/{error401.php → error403.php}
  6. +1 −1  docs/blog/comment.admin.txt
  7. +1 −1  docs/blog/final.error.txt
  8. +1 −1  docs/blog/post.admin.txt
  9. +1 −1  docs/blog/post.display.txt
  10. +5 −0 docs/guide/changes.txt
  11. +1 −1  docs/guide/topics.auth.txt
  12. +16 −0 docs/guide/topics.logging.txt
  13. +11 −0 docs/guide/topics.url.txt
  14. +1 −1  framework/YiiBase.php
  15. +1 −1  framework/base/CHttpException.php
  16. +2 −2 framework/cli/views/shell/crud/controller.php
  17. +24 −5 framework/db/CDbCommand.php
  18. +28 −0 framework/db/CDbConnection.php
  19. +3 −3 framework/db/ar/CActiveFinder.php
  20. +2 −6 framework/db/ar/CActiveRecord.php
  21. +19 −3 framework/db/schema/CDbCriteria.php
  22. +70 −0 framework/logging/CLogger.php
  23. +19 −1 framework/logging/CProfileLogRoute.php
  24. 0  framework/views/de/{error401.php → error403.php}
  25. 0  framework/views/{error401.php → error403.php}
  26. 0  framework/views/fr/{error401.php → error403.php}
  27. 0  framework/views/he/{error401.php → error403.php}
  28. 0  framework/views/id/{error401.php → error403.php}
  29. 0  framework/views/ja/{error401.php → error403.php}
  30. 0  framework/views/nl/{error401.php → error403.php}
  31. 0  framework/views/no/{error401.php → error403.php}
  32. 0  framework/views/pt/{error401.php → error403.php}
  33. 0  framework/views/ro/{error401.php → error403.php}
  34. 0  framework/views/sv/{error401.php → error403.php}
  35. 0  framework/views/zh_cn/{error401.php → error403.php}
  36. 0  framework/views/zh_tw/{error401.php → error403.php}
  37. +67 −34 framework/web/CUrlManager.php
  38. +1 −1  framework/web/auth/CAccessControlFilter.php
  39. +2 −2 framework/web/auth/CWebUser.php
  40. +1 −1  framework/web/widgets/pagers/CLinkPager.php
  41. +3 −0  framework/yiic.php
  42. +22 −0 tests/unit/framework/db/ar/CActiveRecordTest.php
  43. +50 −0 tests/unit/framework/db/data/models.php
View
15 CHANGELOG
@@ -8,12 +8,25 @@ Version 1.1a to be released
Version 1.0.6 to be released
----------------------------
+- Bug #308: typo in CLinkPager CSS class name (Qiang)
- Bug #310: Leading space in auto generated labels if they end with "ID" (Qiang)
+- Bug #312: defaultScope not honored when other sopes are applied (Qiang)
+- Bug #313: Dynamic parameter for lazy loading resets the parameters specified in default scope (Qiang)
+- Bug #321: CProfileLogRoute should be disabled for AJAX requests (Qiang)
+- Bug #331: HTTP 403 status code should be used to indicate auth failure (Qiang)
- Bug: Syntax errors in autoloaded classes are not reported (Qiang)
-- New #304: Adding flv mimeType to the mimeType array (Qiang)
+- New #304: Added flv mimeType to the mimeType array (Qiang)
+- New #315: Added CDbConnection.enableProfiling (Qiang)
+- New #320: Added support for customizing a single URL rule by setting its urlFormat and caseSensitive options (Qiang)
+- New #326: Yii::powered() will show Yii site in a new window (Qiang)
+- New #328: Make yiic to work with f-cgi (Qiang)
- New: Enhanced the 'with' option in relational rules so that it also applies in eager loading (Qiang)
- New: Added support to allow using named scopes with update and delete methods (Qiang)
- New: Refactored support for dynamic query options with relational AR (Qiang)
+- New: Added CDbCriteria::toArray() (Qiang)
+- New: Added support to allow merging CDbCriteria using 'OR' operator (Qiang)
+- New: Added CLogger::getStats() (Qiang)
+
Version 1.0.5 May 10, 2009
--------------------------
View
2  demos/blog/protected/config/main.php
@@ -37,7 +37,7 @@
'user'=>array(
// enable cookie-based authentication
'allowAutoLogin'=>true,
- // force 401 HTTP error if authentication needed
+ // force 403 HTTP error if authentication needed
'loginUrl'=>null,
),
'db'=>array(
View
6 demos/blog/protected/controllers/CommentController.php
@@ -82,7 +82,7 @@ public function actionDelete()
$this->redirect(array('post/show','id'=>$comment->postId));
}
else
- throw new CHttpException(500,'Invalid request. Please do not repeat this request again.');
+ throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
}
/**
@@ -98,7 +98,7 @@ public function actionApprove()
$this->redirect(array('post/show','id'=>$comment->postId,'#'=>'c'.$comment->id));
}
else
- throw new CHttpException(500,'Invalid request. Please do not repeat this request again.');
+ throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
}
/**
@@ -133,7 +133,7 @@ public function loadComment($id=null)
if($id!==null || isset($_GET['id']))
$this->_comment=Comment::model()->findbyPk($id!==null ? $id : $_GET['id']);
if($this->_comment===null)
- throw new CHttpException(500,'The requested comment does not exist.');
+ throw new CHttpException(404,'The requested comment does not exist.');
}
return $this->_comment;
}
View
4 demos/blog/protected/controllers/PostController.php
@@ -136,7 +136,7 @@ public function actionDelete()
$this->redirect(array('list'));
}
else
- throw new CHttpException(500,'Invalid request. Please do not repeat this request again.');
+ throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
}
/**
@@ -216,7 +216,7 @@ protected function loadPost($id=null)
if($id!==null || isset($_GET['id']))
$this->_post=Post::model()->findbyPk($id!==null ? $id : $_GET['id']);
if($this->_post===null || Yii::app()->user->isGuest && $this->_post->status!=Post::STATUS_PUBLISHED)
- throw new CHttpException(500,'The requested post does not exist.');
+ throw new CHttpException(404,'The requested post does not exist.');
}
return $this->_post;
}
View
0  demos/blog/protected/views/system/error401.php → demos/blog/protected/views/system/error403.php
File renamed without changes
View
2  docs/blog/comment.admin.txt
@@ -53,7 +53,7 @@ public function actionApprove()
'#'=>'c'.$comment->id));
}
else
- throw new CHttpException(500,'Invalid request...');
+ throw new CHttpException(400,'Invalid request...');
}
~~~
View
2  docs/blog/final.error.txt
@@ -5,7 +5,7 @@ Our blog application is using the templates provided by Yii to display various e
We first create a file named `error.php`. This is the default view that will be used to display all kinds of errors if a more specific error view file is not available. Because this view file is used when an error occurs, it should not contain very complex PHP logic that may cause further errors. Note also that error view files do not use layout. Therefore, each view file should have complete page display.
-We also create a file named `error401.php` to display 401 (unauthenticated) HTTP errors, and a file named `error404.php` to display 404 (page not found) HTTP errors.
+We also create a file named `error403.php` to display 403 (unauthenticated) HTTP errors, and a file named `error404.php` to display 404 (page not found) HTTP errors.
To learn more details about the naming of these error view files, please refer to [the Guide](http://www.yiiframework.com/doc/guide/topics.error#displaying-errors).
View
2  docs/blog/post.admin.txt
@@ -95,7 +95,7 @@ public function actionDelete()
$this->redirect(array('list'));
}
else
- throw new CHttpException(500,'Invalid request...');
+ throw new CHttpException(400,'Invalid request...');
}
~~~
View
2  docs/blog/post.display.txt
@@ -30,7 +30,7 @@ protected function loadPost($id=null)
$this->_post=Post::model()->findbyPk($id!==null ? $id : $_GET['id']);
if($this->_post===null || Yii::app()->user->isGuest &&
$this->_post->status!=Post::STATUS_PUBLISHED)
- throw new CHttpException(500,'The requested post does not exist.');
+ throw new CHttpException(404,'The requested post does not exist.');
}
return $this->_post;
}
View
5 docs/guide/changes.txt
@@ -12,6 +12,11 @@ Version 1.0.6
* Added support for using named scope in the `with` option of relational rules:
- [Relational Query with Named Scopes](/doc/guide/database.arr#relational-query-with-named-scopes)
+ * Added support for profiling SQL executions
+ - [Profiling SQL Executions](/doc/guide/topics.logging#profiling-sql-executions)
+
+ * Added support for customizing a single URL rule by setting its urlFormat and caseSensitive options:
+ - [User-friendly URLs](/doc/guide/topics.url#user-friendly-urls)
Version 1.0.5
-------------
View
2  docs/guide/topics.auth.txt
@@ -261,7 +261,7 @@ specified action, one of the following two scenarios may happen:
property of the user component is configured to be the URL of the login
page, the browser will be redirected to that page.
- - Otherwise an HTTP exception will be displayed with error code 401.
+ - Otherwise an HTTP exception will be displayed with error code 403.
When configuring the [loginUrl|CWebUser::loginUrl] property, one can
provide a relative or absolute URL. One can also provide an array which
View
16 docs/guide/topics.logging.txt
@@ -147,4 +147,20 @@ component with a [CProfileLogRoute] log route. This is the same as we do
with normal message routing. The [CProfileLogRoute] route will display the
performance results at the end of the current page.
+
+### Profiling SQL Executions
+
+Profiling is especially useful when working with database since SQL executions
+are often the main performance bottleneck of an application. While we can manually
+insert `beginProfile` and `endProfile` statements at appropriate places to measure
+the time spent in each SQL execution, starting from version 1.0.6, Yii provides
+a more systematic approach to solve this problem.
+
+By setting [CDbConnection::enableProfiling] to be true in the application configuration,
+every SQL statement being executed will be profiled. The results can be readily displayed
+using the aforementioned [CProfileLogRoute], which can show us how much time is spent
+in executing what SQL statement. We can also call [CDbConnection::getStats()] to retrieve
+the total number SQL statements executed and their total execution time.
+
+
<div class="revision">$Id$</div>
View
11 docs/guide/topics.url.txt
@@ -106,6 +106,17 @@ corresponding to a single rule. The pattern of a rule is a string
used to match the path info part of URLs. And the route of a rule
should refer to a valid controller [route](/doc/guide/basics.controller#route).
+> Info: Starting from version 1.0.6, a rule may be further customized
+> by setting its `urlSuffix` and `caseSensitive` options. To do so,
+> we should specify the route part of the rule as an array, like the following:
+>
+> ~~~
+> [php]
+> 'pattern1'=>array('route1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false)
+> ~~~
+>
+
+
### Using Named Parameters
A rule can be associated with a few GET parameters. These GET parameters
View
2  framework/YiiBase.php
@@ -396,7 +396,7 @@ public static function getLogger()
*/
public static function powered()
{
- return 'Powered by <a href="http://www.yiiframework.com/">Yii Framework</a>.';
+ return 'Powered by <a href="http://www.yiiframework.com/" target="_blank">Yii Framework</a>.';
}
/**
View
2  framework/base/CHttpException.php
@@ -22,7 +22,7 @@
class CHttpException extends CException
{
/**
- * @var integer HTTP status code, such as 401, 404, 500, etc.
+ * @var integer HTTP status code, such as 403, 404, 500, etc.
*/
public $statusCode;
View
4 framework/cli/views/shell/crud/controller.php
@@ -103,7 +103,7 @@ public function actionDelete()
$this->redirect(array('list'));
}
else
- throw new CHttpException(500,'Invalid request. Please do not repeat this request again.');
+ throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
}
/**
@@ -162,7 +162,7 @@ public function load{ModelClass}($id=null)
if($id!==null || isset($_GET['id']))
$this->_{ModelVar}={ModelClass}::model()->findbyPk($id!==null ? $id : $_GET['id']);
if($this->_{ModelVar}===null)
- throw new CHttpException(500,'The requested {ModelName} does not exist.');
+ throw new CHttpException(404,'The requested {ModelName} does not exist.');
}
return $this->_{ModelVar};
}
View
29 framework/db/CDbCommand.php
@@ -187,16 +187,26 @@ public function execute()
Yii::trace('Executing SQL: '.$this->getText().$params,'system.db.CDbCommand');
try
{
+ if($this->_connection->enableProfiling)
+ Yii::beginProfile('system.db.CDbCommand.execute('.$this->getText().')','system.db.CDbCommand.execute');
+
if($this->_statement instanceof PDOStatement)
{
$this->_statement->execute();
- return $this->_statement->rowCount();
+ $n=$this->_statement->rowCount();
}
else
- return $this->getConnection()->getPdoInstance()->exec($this->getText());
+ $n=$this->getConnection()->getPdoInstance()->exec($this->getText());
+
+ if($this->_connection->enableProfiling)
+ Yii::endProfile('system.db.CDbCommand.execute('.$this->getText().')','system.db.CDbCommand.execute');
+
+ return $n;
}
catch(Exception $e)
{
+ if($this->_connection->enableProfiling)
+ Yii::endProfile('system.db.CDbCommand.execute('.$this->getText().')','system.db.CDbCommand.execute');
Yii::log('Error in executing SQL: '.$this->getText().$params,CLogger::LEVEL_ERROR,'system.db.CDbCommand');
throw new CDbException(Yii::t('yii','CDbCommand failed to execute the SQL statement: {error}',
array('{error}'=>$e->getMessage())));
@@ -279,18 +289,27 @@ private function queryInternal($method,$mode)
Yii::trace('Querying SQL: '.$this->getText().$params,'system.db.CDbCommand');
try
{
+ if($this->_connection->enableProfiling)
+ Yii::beginProfile('system.db.CDbCommand.query('.$this->getText().')','system.db.CDbCommand.query');
+
if($this->_statement instanceof PDOStatement)
$this->_statement->execute();
else
$this->_statement=$this->getConnection()->getPdoInstance()->query($this->getText());
- if($method==='')
- return new CDbDataReader($this);
- $result=$this->_statement->{$method}($mode);
+
+ $result=$method==='' ? new CDbDataReader($this) : $this->_statement->{$method}($mode);
+
$this->_statement->closeCursor();
+
+ if($this->_connection->enableProfiling)
+ Yii::endProfile('system.db.CDbCommand.query('.$this->getText().')','system.db.CDbCommand.query');
+
return $result;
}
catch(Exception $e)
{
+ if($this->_connection->enableProfiling)
+ Yii::endProfile('system.db.CDbCommand.query('.$this->getText().')','system.db.CDbCommand.query');
Yii::log('Error in querying SQL: '.$this->getText().$params,CLogger::LEVEL_ERROR,'system.db.CDbCommand');
throw new CDbException(Yii::t('yii','CDbCommand failed to execute the SQL statement: {error}',
array('{error}'=>$e->getMessage())));
View
28 framework/db/CDbConnection.php
@@ -139,6 +139,13 @@ class CDbConnection extends CApplicationComponent
* @since 1.0.5
*/
public $enableParamLogging=false;
+ /**
+ * @var boolean whether to enable profiling the SQL statements being executed.
+ * Defaults to false. This should be mainly enabled and used during development
+ * to find out the bottleneck of SQL executions.
+ * @since 1.0.6
+ */
+ public $enableProfiling=false;
private $_attributes=array();
private $_active=false;
@@ -616,4 +623,25 @@ public function setAttribute($name,$value)
else
$this->_attributes[$name]=$value;
}
+
+ /**
+ * Returns the statistical results of SQL executions.
+ * The results returned include the number of SQL statements executed and
+ * the total time spent.
+ * In order to use this method, {@link enableProfiling} has to be set true.
+ * @return array the first element indicates the number of SQL statements executed,
+ * and the second element the total time spent in SQL execution.
+ * @since 1.0.6
+ */
+ public function getStats()
+ {
+ $logger=Yii::getLogger();
+ $timings=$logger->getProfilingResults(null,'system.db.CDbCommand.query');
+ $count=count($timings);
+ $time=array_sum($timings);
+ $timings=$logger->getProfilingResults(null,'system.db.CDbCommand.execute');
+ $count+=count($timings);
+ $time+=array_sum($timings);
+ return array($count,$time);
+ }
}
View
6 framework/db/ar/CActiveFinder.php
@@ -284,10 +284,10 @@ private function buildJoinTree($parent,$with,$options=null)
// $with is an array, keys are relation name, values are relation spec
foreach($with as $key=>$value)
{
- if(is_string($key) && is_array($value))
- $element=$this->buildJoinTree($parent,$key,$value);
- else if(is_string($value)) // the key is integer, so value is the relation name
+ if(is_string($value)) // the value is a relation name
$this->buildJoinTree($parent,$value);
+ else if(is_string($key) && is_array($value))
+ $element=$this->buildJoinTree($parent,$key,$value);
}
}
}
View
8 framework/db/ar/CActiveRecord.php
@@ -142,7 +142,7 @@
* Post::model()->updateByPk($postID,$attributes,$condition,$params);
*
* // update one or several counter columns
- * Post::model()->updateCounters($pk,$counters);
+ * Post::model()->updateCounters($counters,$condition,$params);
*
* // delete all records with the specified condition
* Post::model()->deleteAll($condition,$params);
@@ -491,11 +491,7 @@ public function __call($name,$parameters)
$scopes=$this->scopes();
if(isset($scopes[$name]))
{
- $scope=$scopes[$name];
- if($this->_c===null)
- $this->_c=new CDbCriteria($scope);
- else
- $this->_c->mergeWith($scope);
+ $this->getDbCriteria()->mergeWith($scopes[$name]);
return $this;
}
View
22 framework/db/schema/CDbCriteria.php
@@ -80,10 +80,14 @@ public function __construct($data=array())
* Also, the criteria passed as the parameter takes precedence in case
* two options cannot be merged (e.g. LIMIT, OFFSET).
* @param CDbCriteria the criteria to be merged with.
+ * @param boolean whether to use 'AND' to merge condition and having options.
+ * If false, 'OR' will be used instead. Defaults to 'AND'. This parameter has been
+ * available since version 1.0.6.
* @since 1.0.5
*/
- public function mergeWith($criteria)
+ public function mergeWith($criteria,$useAnd=true)
{
+ $and=$useAnd ? 'AND' : 'OR';
if(is_array($criteria))
$criteria=new self($criteria);
if($this->select!==$criteria->select)
@@ -103,7 +107,7 @@ public function mergeWith($criteria)
if($this->condition==='')
$this->condition=$criteria->condition;
else if($criteria->condition!=='')
- $this->condition="({$this->condition}) AND ({$criteria->condition})";
+ $this->condition="({$this->condition}) $and ({$criteria->condition})";
}
if($this->params!==$criteria->params)
@@ -144,7 +148,19 @@ public function mergeWith($criteria)
if($this->having==='')
$this->having=$criteria->having;
else if($criteria->having!=='')
- $this->having="({$this->having}) AND ({$criteria->having})";
+ $this->having="({$this->having}) $and ({$criteria->having})";
}
}
+
+ /**
+ * @return array the array representation of the criteria
+ * @since 1.0.6
+ */
+ public function toArray()
+ {
+ $result=array();
+ foreach(array('select', 'condition', 'params', 'limit', 'offset', 'order', 'group', 'join', 'having') as $name)
+ $result[$name]=$this->$name;
+ return $result;
+ }
}
View
70 framework/logging/CLogger.php
@@ -39,6 +39,11 @@ class CLogger extends CComponent
* @var array log categories for filtering (used when filtering)
*/
private $_categories;
+ /**
+ * @var array the profiling results (category, token => time in seconds)
+ * @since 1.0.6
+ */
+ private $_timings;
/**
* Logs a message.
@@ -164,4 +169,69 @@ public function getMemoryUsage()
}
}
}
+
+ /**
+ * Returns the profiling results.
+ * The results may be filtered by token and/or category.
+ * If no filter is specified, the returned results would be an array with each element
+ * being array($token,$category,$time).
+ * If a filter is specified, the results would be an array of timings.
+ * @param string token filter. Defaults to null, meaning not filtered by token.
+ * @param string category filter. Defaults to null, meaning not filtered by category.
+ * @param boolean whether to refresh the internal timing calculations. If false,
+ * only the first time calling this method will the timings be calculated internally.
+ * @return array the profiling results.
+ * @since 1.0.6
+ */
+ public function getProfilingResults($token=null,$category=null,$refresh=false)
+ {
+ if($this->_timings===null || $refresh)
+ $this->calculateTimings();
+ if($token===null && $category===null)
+ return $this->_timings;
+ $results=array();
+ foreach($this->_timings as $timing)
+ {
+ if(($category===null || $timing[1]===$category) && ($token===null || $timing[0]===$token))
+ $results[]=$timing[2];
+ }
+ return $results;
+ }
+
+ private function calculateTimings()
+ {
+ $this->_timings=array();
+
+ $stack=array();
+ foreach($this->_logs as $log)
+ {
+ if($log[1]!==CLogger::LEVEL_PROFILE)
+ continue;
+ list($message,$level,$category,$timestamp)=$log;
+ if(!strncasecmp($message,'begin:',6))
+ {
+ $log[0]=substr($message,6);
+ $stack[]=$log;
+ }
+ else if(!strncasecmp($message,'end:',4))
+ {
+ $token=substr($message,4);
+ if(($last=array_pop($stack))!==null && $last[0]===$token)
+ {
+ $delta=$log[3]-$last[3];
+ $this->_timings[]=array($message,$category,$delta);
+ }
+ else
+ throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
+ array('{token}'=>$token)));
+ }
+ }
+
+ $now=microtime(true);
+ while(($last=array_pop($stack))!==null)
+ {
+ $delta=$now-$last[3];
+ $this->_timings[]=array($last[0],$last[2],$delta);
+ }
+ }
}
View
20 framework/logging/CProfileLogRoute.php
@@ -28,6 +28,14 @@
class CProfileLogRoute extends CWebLogRoute
{
/**
+ * @var boolean whether to aggregate results according to profiling tokens.
+ * If false, the results will be aggregated by categories.
+ * Defaults to true. Note that this property only affects the summary report
+ * that is enabled when {@link report} is 'summary'.
+ * @since 1.0.6
+ */
+ public $groupByToken=true;
+ /**
* @var string type of profiling report to display
*/
private $_report='summary';
@@ -67,6 +75,10 @@ public function setReport($value)
*/
public function processLogs($logs)
{
+ $app=Yii::app();
+ if(!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest())
+ return;
+
if($this->getReport()==='summary')
$this->displaySummary($logs);
else
@@ -85,6 +97,8 @@ protected function displayCallstack($logs)
$n=0;
foreach($logs as $log)
{
+ if($log[1]!==CLogger::LEVEL_PROFILE)
+ continue;
$message=$log[0];
if(!strncasecmp($message,'begin:',6))
{
@@ -123,6 +137,8 @@ protected function displaySummary($logs)
$stack=array();
foreach($logs as $log)
{
+ if($log[1]!==CLogger::LEVEL_PROFILE)
+ continue;
$message=$log[0];
if(!strncasecmp($message,'begin:',6))
{
@@ -135,6 +151,8 @@ protected function displaySummary($logs)
if(($last=array_pop($stack))!==null && $last[0]===$token)
{
$delta=$log[3]-$last[3];
+ if(!$this->groupByToken)
+ $token=$log[2];
if(isset($results[$token]))
$results[$token]=$this->aggregateResult($results[$token],$delta);
else
@@ -150,7 +168,7 @@ protected function displaySummary($logs)
while(($last=array_pop($stack))!==null)
{
$delta=$now-$last[3];
- $token=$last[0];
+ $token=$this->groupByToken ? $last[0] : $last[2];
if(isset($results[$token]))
$results[$token]=$this->aggregateResult($results[$token],$delta);
else
View
0  framework/views/de/error401.php → framework/views/de/error403.php
File renamed without changes
View
0  framework/views/error401.php → framework/views/error403.php
File renamed without changes
View
0  framework/views/fr/error401.php → framework/views/fr/error403.php
File renamed without changes
View
0  framework/views/he/error401.php → framework/views/he/error403.php
File renamed without changes
View
0  framework/views/id/error401.php → framework/views/id/error403.php
File renamed without changes
View
0  framework/views/ja/error401.php → framework/views/ja/error403.php
File renamed without changes
View
0  framework/views/nl/error401.php → framework/views/nl/error403.php
File renamed without changes
View
0  framework/views/no/error401.php → framework/views/no/error403.php
File renamed without changes
View
0  framework/views/pt/error401.php → framework/views/pt/error403.php
File renamed without changes
View
0  framework/views/ro/error401.php → framework/views/ro/error403.php
File renamed without changes
View
0  framework/views/sv/error401.php → framework/views/sv/error403.php
File renamed without changes
View
0  framework/views/zh_cn/error401.php → framework/views/zh_cn/error403.php
File renamed without changes
View
0  framework/views/zh_tw/error401.php → framework/views/zh_tw/error403.php
File renamed without changes
View
101 framework/web/CUrlManager.php
@@ -189,7 +189,7 @@ public function createUrl($route,$params=array(),$ampersand='&')
$anchor='';
foreach($this->_rules as $rule)
{
- if(($url=$rule->createUrl($route,$params,$this->urlSuffix,$ampersand))!==false)
+ if(($url=$rule->createUrl($this,$route,$params,$ampersand))!==false)
return $this->getBaseUrl().'/'.$url.$anchor;
}
return $this->createUrlDefault($route,$params,$ampersand).$anchor;
@@ -209,12 +209,12 @@ protected function createUrlDefault($route,$params,$ampersand)
$url=rtrim($this->getBaseUrl().'/'.$route,'/');
if($this->appendParams)
{
- $url.='/'.self::createPathInfo($params,'/','/');
+ $url.='/'.$this->createPathInfo($params,'/','/');
return rtrim($url,'/').$this->urlSuffix;
}
else
{
- $query=self::createPathInfo($params,'=',$ampersand);
+ $query=$this->createPathInfo($params,'=',$ampersand);
return $query==='' ? $url : $url.'?'.$query;
}
}
@@ -226,10 +226,10 @@ protected function createUrlDefault($route,$params,$ampersand)
if($route!=='')
{
$url.='?'.$this->routeVar.'='.$route;
- if(($query=self::createPathInfo($params,'=',$ampersand))!=='')
+ if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
$url.=$ampersand.$query;
}
- else if(($query=self::createPathInfo($params,'=',$ampersand))!=='')
+ else if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
$url.='?'.$query;
return $url;
}
@@ -244,10 +244,11 @@ public function parseUrl($request)
{
if($this->getUrlFormat()===self::PATH_FORMAT)
{
- $pathInfo=$this->removeUrlSuffix($request->getPathInfo());
+ $rawPathInfo=urldecode($request->getPathInfo());
+ $pathInfo=$this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
foreach($this->_rules as $rule)
{
- if(($r=$rule->parseUrl($pathInfo))!==false)
+ if(($r=$rule->parseUrl($this,$pathInfo,$rawPathInfo))!==false)
return isset($_GET[$this->routeVar]) ? $_GET[$this->routeVar] : $r;
}
return $pathInfo;
@@ -273,9 +274,9 @@ public static function parsePathInfo($pathInfo)
$n=count($segs);
for($i=0;$i<$n-1;$i+=2)
{
- $key=urldecode($segs[$i]);
+ $key=$segs[$i];
if($key==='') continue;
- $value=urldecode($segs[$i+1]);
+ $value=$segs[$i+1];
if(($pos=strpos($key,'[]'))!==false)
$_GET[substr($key,0,$pos)][]=$value;
else
@@ -291,7 +292,7 @@ public static function parsePathInfo($pathInfo)
* @return string the created path info
* @since 1.0.3
*/
- public static function createPathInfo($params,$equal,$ampersand)
+ public function createPathInfo($params,$equal,$ampersand)
{
$pairs=array();
foreach($params as $key=>$value)
@@ -310,12 +311,13 @@ public static function createPathInfo($params,$equal,$ampersand)
/**
* Removes the URL suffix from path info.
* @param string path info part in the URL
+ * @param string the URL suffix to be removed
* @return string path info with URL suffix removed.
*/
- protected function removeUrlSuffix($pathInfo)
+ public function removeUrlSuffix($pathInfo,$urlSuffix)
{
- if(($ext=$this->urlSuffix)!=='' && substr($pathInfo,-strlen($ext))===$ext)
- return substr($pathInfo,0,-strlen($ext));
+ if($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
+ return substr($pathInfo,0,-strlen($urlSuffix));
else
return $pathInfo;
}
@@ -376,6 +378,19 @@ public function setUrlFormat($value)
class CUrlRule extends CComponent
{
/**
+ * @var string the URL suffix used for this rule.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+ * Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
+ * @since 1.0.6
+ */
+ public $urlSuffix;
+ /**
+ * @var boolean whether the rule is case sensitive. Defaults to null, meaning
+ * using the value of {@link CUrlManager::caseSensitive}.
+ * @since 1.0.1
+ */
+ public $caseSensitive;
+ /**
* @var string the controller/action pair
*/
public $route;
@@ -405,11 +420,6 @@ class CUrlRule extends CComponent
* @var boolean whether the URL allows additional parameters at the end of the path info.
*/
public $append;
- /**
- * @var boolean whether the rule is case sensitive. Defaults to true.
- * @since 1.0.1
- */
- public $caseSensitive=true;
/**
* Constructor.
@@ -418,7 +428,17 @@ class CUrlRule extends CComponent
*/
public function __construct($route,$pattern)
{
- $this->route=$route;
+ if(is_array($route))
+ {
+ $this->route=$route[0];
+ if(isset($route['urlSuffix']))
+ $this->urlSuffix=$route['urlSuffix'];
+ if(isset($route['caseSensitive']))
+ $this->caseSensitive=$route['caseSensitive'];
+ }
+ else
+ $this->route=$route;
+
$tr2['/']=$tr['/']='\\/';
if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
@@ -449,15 +469,9 @@ public function __construct($route,$pattern)
$this->pattern.='/u';
else
$this->pattern.='$/u';
- if(!$this->caseSensitive)
- $this->pattern.='i';
if($this->references!==array())
- {
$this->routePattern='/^'.strtr($this->route,$tr2).'$/u';
- if(!$this->caseSensitive)
- $this->routePattern.='i';
- }
if(YII_DEBUG && @preg_match($this->pattern,'test')===false)
throw new CException(Yii::t('yii','The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.',
@@ -465,18 +479,24 @@ public function __construct($route,$pattern)
}
/**
+ * Creates a URL based on this rule.
+ * @param CUrlManager the manager
* @param string the route
* @param array list of parameters
- * @param string URL suffix
* @param string the token separating name-value pairs in the URL.
* @return string the constructed URL
*/
- public function createUrl($route,$params,$suffix,$ampersand)
+ public function createUrl($manager,$route,$params,$ampersand)
{
+ if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
+ $case='';
+ else
+ $case='i';
+
$tr=array();
if($route!==$this->route)
{
- if($this->routePattern!==null && preg_match($this->routePattern,$route,$matches))
+ if($this->routePattern!==null && preg_match($this->routePattern.$case,$route,$matches))
{
foreach($this->references as $key=>$name)
$tr[$name]=$matches[$key];
@@ -496,37 +516,50 @@ public function createUrl($route,$params,$suffix,$ampersand)
return false;
}
+ $suffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
+
$url=strtr($this->template,$tr);
if(empty($params))
return $url!=='' ? $url.$suffix : $url;
if($this->append)
- $url.='/'.CUrlManager::createPathInfo($params,'/','/').$suffix;
+ $url.='/'.$manager->createPathInfo($params,'/','/').$suffix;
else
{
if($url!=='')
$url.=$suffix;
- $url.='?'.CUrlManager::createPathInfo($params,'=',$ampersand);
+ $url.='?'.$manager->createPathInfo($params,'=',$ampersand);
}
return $url;
}
/**
+ * Parases a URL based on this rule.
+ * @param CUrlManager the URL manager
* @param string path info part of the URL
+ * @param string path info that contains the potential URL suffix
* @return string the route that consists of the controller ID and action ID
*/
- public function parseUrl($pathInfo)
+ public function parseUrl($manager,$pathInfo,$rawPathInfo)
{
+ if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
+ $case='';
+ else
+ $case='i';
+
+ if($this->urlSuffix!==null)
+ $pathInfo=$manager->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
+
$pathInfo.='/';
- if(preg_match($this->pattern,$pathInfo,$matches))
+ if(preg_match($this->pattern.$case,$pathInfo,$matches))
{
$tr=array();
foreach($matches as $key=>$value)
{
if(isset($this->references[$key]))
- $tr[$this->references[$key]]=urldecode($value);
+ $tr[$this->references[$key]]=$value;
else if(isset($this->params[$key]))
- $_GET[$key]=urldecode($value);
+ $_GET[$key]=$value;
}
if($pathInfo!==$matches[0]) // there're additional GET params
CUrlManager::parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
View
2  framework/web/auth/CAccessControlFilter.php
@@ -120,7 +120,7 @@ protected function accessDenied($user)
if($user->getIsGuest())
$user->loginRequired();
else
- throw new CHttpException(401,Yii::t('yii','You are not authorized to perform this action.'));
+ throw new CHttpException(403,Yii::t('yii','You are not authorized to perform this action.'));
}
}
View
4 framework/web/auth/CWebUser.php
@@ -66,7 +66,7 @@ class CWebUser extends CApplicationComponent implements IWebUser
* @var string|array the URL for login. If using array, the first element should be
* the route to the login action, and the rest name-value pairs are GET parameters
* to construct the login URL (e.g. array('site/login')). If this property is null,
- * a 401 HTTP exception will be raised instead.
+ * a 403 HTTP exception will be raised instead.
* @see CController::createUrl
*/
public $loginUrl=array('site/login');
@@ -285,7 +285,7 @@ public function loginRequired()
$request->redirect($url);
}
else
- throw new CHttpException(401,Yii::t('yii','Login Required'));
+ throw new CHttpException(403,Yii::t('yii','Login Required'));
}
/**
View
2  framework/web/widgets/pagers/CLinkPager.php
@@ -21,7 +21,7 @@ class CLinkPager extends CBasePager
const CSS_FIRST_PAGE='first';
const CSS_LAST_PAGE='last';
const CSS_PREVIOUS_PAGE='previous';
- const CSS_NEXT_PAGE='previous';
+ const CSS_NEXT_PAGE='next';
const CSS_INTERNAL_PAGE='page';
const CSS_HIDDEN_PAGE='hidden';
const CSS_SELECTED_PAGE='selected';
View
3  framework/yiic.php
@@ -14,6 +14,9 @@
defined('YII_DEBUG') or define('YII_DEBUG',true);
+// fix for fcgi
+defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
+
// disable E_NOTICE so that "yiic shell" is more friendly
error_reporting(E_ALL ^ E_NOTICE);
View
22 tests/unit/framework/db/ar/CActiveRecordTest.php
@@ -722,6 +722,16 @@ public function testScopes()
$this->assertEquals(3,count($posts));
$this->assertEquals(5,$posts[0]->id);
$this->assertEquals(4,$posts[1]->id);
+
+ $posts=PostSpecial::model()->findAll();
+ $this->assertEquals(2,count($posts));
+ $this->assertEquals(2,$posts[0]->id);
+ $this->assertEquals(3,$posts[1]->id);
+
+ $posts=PostSpecial::model()->desc()->findAll();
+ $this->assertEquals(2,count($posts));
+ $this->assertEquals(3,$posts[0]->id);
+ $this->assertEquals(2,$posts[1]->id);
}
public function testLazyLoadingWithConditions()
@@ -739,6 +749,18 @@ public function testScopeWithRelations()
$this->assertEquals(2,count($user->posts));
$this->assertEquals(2,$user->posts[0]->id);
$this->assertEquals(3,$user->posts[1]->id);
+
+ $user=UserSpecial::model()->findByPk(2);
+ $posts=$user->posts;
+ $this->assertEquals(2,count($posts));
+ $this->assertEquals(2,$posts[0]->id);
+ $this->assertEquals(3,$posts[1]->id);
+
+ $user=UserSpecial::model()->findByPk(2);
+ $posts=$user->posts(array('params'=>array(':id1'=>4),'order'=>'posts.id DESC'));
+ $this->assertEquals(2,count($posts));
+ $this->assertEquals(4,$posts[0]->id);
+ $this->assertEquals(3,$posts[1]->id);
}
public function testDuplicateLazyLoadingBug()
View
50 tests/unit/framework/db/data/models.php
@@ -62,6 +62,7 @@ public function scopes()
return array(
'post23'=>array('condition'=>'posts.id=2 OR posts.id=3', 'alias'=>'posts', 'order'=>'posts.id'),
'post3'=>array('condition'=>'id=3'),
+ 'postX'=>array('condition'=>'id=:id1 OR id=:id2', 'params'=>array(':id1'=>2, ':id2'=>3)),
);
}
@@ -75,6 +76,55 @@ public function recent($limit=5)
}
}
+class PostSpecial extends CActiveRecord
+{
+ public static function model($class=__CLASS__)
+ {
+ return parent::model($class);
+ }
+
+ public function tableName()
+ {
+ return 'posts';
+ }
+
+ public function defaultScope()
+ {
+ return array(
+ 'condition'=>'posts.id=:id1 OR posts.id=:id2',
+ 'params'=>array(':id1'=>2, ':id2'=>3),
+ 'alias'=>'posts',
+ );
+ }
+
+ public function scopes()
+ {
+ return array(
+ 'desc'=>array('order'=>'id DESC'),
+ );
+ }
+}
+
+class UserSpecial extends CActiveRecord
+{
+ public static function model($class=__CLASS__)
+ {
+ return parent::model($class);
+ }
+
+ public function relations()
+ {
+ return array(
+ 'posts'=>array(self::HAS_MANY,'PostSpecial','author_id'),
+ );
+ }
+
+ public function tableName()
+ {
+ return 'users';
+ }
+}
+
class PostExt extends CActiveRecord
{
public $title='default title';
Please sign in to comment.
Something went wrong with that request. Please try again.