Fixes #4191: Fixed multiple PHP 7.2 issues, added a way to exclude classes from autoloader

This commit is contained in:
daniel1302
2018-01-30 21:08:29 +01:00
committed by Alexander Makarov
parent 1385c5ad39
commit 2ebe9b6513
6 changed files with 208 additions and 33 deletions

View File

@@ -9,6 +9,9 @@ Version 1.1.20 under development
- Chg #4160: Updated HTMLPurifier to version 4.9.3 (takobell)
- Bug #4162: Adjusted Zend Escaper to be compatible with PHP <5.3 and fixed usage of non-existing Exceptions (cebe)
- Bug #4168: Fixed `CDbHttpSession` PHP 7.1 compatibility (csears123)
- Enh #4191: Added option for filter classes loaded by YiiBase autoloader (daniel1302)
- Bug #4191: Fixed "Headers already sent." error in CHttpSession (daniel1302)
- Bug #4191: Fixed "CHtml::value() behaves differently for objects" for PHP 7.2 (daniel1302)
Version 1.1.19 June 8, 2017
---------------------------

View File

@@ -53,6 +53,13 @@ defined('YII_ZII_PATH') or define('YII_ZII_PATH',YII_PATH.DIRECTORY_SEPARATOR.'z
*/
class YiiBase
{
/**
* @var array filters for autoloading mechanism.
* It should be callable. For callable function autoloader pass className.
* If filter function returns true Yii autoloader will be skipped.
* @since 1.1.20
*/
public static $autoloaderFilters=array();
/**
* @var array class map used by the Yii autoloading mechanism.
* The array keys are the class names and the array values are the corresponding class file paths.
@@ -399,6 +406,30 @@ class YiiBase
*/
public static function autoload($className,$classMapOnly=false)
{
foreach (self::$autoloaderFilters as $filter)
{
if (is_array($filter)
&& isset($filter[0]) && isset($filter[1])
&& is_string($filter[0]) && is_string($filter[1])
&& true === call_user_func(array($filter[0], $filter[1]), $className)
)
{
return true;
}
elseif (is_string($filter)
&& true === call_user_func($filter, $className)
)
{
return true;
}
elseif (is_callable($filter)
&& true === $filter($className)
)
{
return true;
}
}
// use include so that the error PHP file may appear
if(isset(self::$classMap[$className]))
include(self::$classMap[$className]);

View File

@@ -76,6 +76,12 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
public $autoStart=true;
/**
* @var array Store frozen session data for ini_set in PHP7.2+
* @since 1.1.20
*/
protected static $frozenData = array();
/**
* Initializes the application component.
* This method is required by IApplicationComponent and is invoked by application.
@@ -270,18 +276,24 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
{
if($value==='none')
{
$this->freeze();
ini_set('session.use_cookies','0');
ini_set('session.use_only_cookies','0');
$this->unfreeze();
}
elseif($value==='allow')
{
$this->freeze();
ini_set('session.use_cookies','1');
ini_set('session.use_only_cookies','0');
$this->unfreeze();
}
elseif($value==='only')
{
$this->freeze();
ini_set('session.use_cookies','1');
ini_set('session.use_only_cookies','1');
$this->unfreeze();
}
else
throw new CException(Yii::t('yii','CHttpSession.cookieMode can only be "none", "allow" or "only".'));
@@ -303,9 +315,11 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
{
if($value>=0 && $value<=100)
{
$this->freeze();
// percent * 21474837 / 2147483647 ≈ percent * 0.01
ini_set('session.gc_probability',floor($value*21474836.47));
ini_set('session.gc_divisor',2147483647);
$this->unfreeze();
}
else
throw new CException(Yii::t('yii','CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.',
@@ -573,4 +587,53 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
{
unset($_SESSION[$offset]);
}
/**
* In PHP7.2 if session is started we cannot edit session ini settings.
* This function save session data to temporary variable and stop session.
*
* @see CHttpSession::unfreeze();
* @since 1.1.20
*/
protected function freeze()
{
if (version_compare(PHP_VERSION, '7.2.0', '<'))
{
return;
}
if ($this->getIsStarted())
{
self::$frozenData = $_SESSION;
$this->close();
}
else
{
self::$frozenData = null;
}
}
/**
* Start session and restore data from temporary variable
*
* @see CHttpSession::freeze();
* @since 1.1.20
*/
protected function unfreeze()
{
if (version_compare(PHP_VERSION, '7.2.0', '<'))
{
return;
}
if (self::$frozenData !== null)
{
@session_start();
$_SESSION = self::$frozenData;
}
self::$frozenData = array();
}
}

View File

@@ -996,13 +996,13 @@ class CHtml
* The 'empty' option can also be an array of value-label pairs.
* Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li>
* <li>options: array, specifies additional attributes for each OPTION tag.
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* <pre>
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* </pre>
* </li>
* </ul>
@@ -1064,13 +1064,13 @@ class CHtml
* The 'empty' option can also be an array of value-label pairs.
* Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li>
* <li>options: array, specifies additional attributes for each OPTION tag.
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* <pre>
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* </pre>
* </li>
* </ul>
@@ -1970,13 +1970,13 @@ EOD;
* The 'empty' option can also be an array of value-label pairs.
* Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li>
* <li>options: array, specifies additional attributes for each OPTION tag.
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* <pre>
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* </pre>
* </li>
* </ul>
@@ -2037,13 +2037,13 @@ EOD;
* The 'empty' option can also be an array of value-label pairs.
* Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li>
* <li>options: array, specifies additional attributes for each OPTION tag.
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* <pre>
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* </pre>
* </li>
* </ul>
@@ -2348,8 +2348,21 @@ EOD;
if(is_scalar($attribute) || $attribute===null)
foreach(explode('.',$attribute) as $name)
{
if(is_object($model) && isset($model->$name))
$model=$model->$name;
if(is_object($model))
{
if ((version_compare(PHP_VERSION, '7.2.0', '>=')
&& is_numeric($name))
|| !isset($model->$name)
)
{
return $defaultValue;
}
else
{
$model=$model->$name;
}
}
elseif(is_array($model) && isset($model[$name]))
$model=$model[$name];
else
@@ -2485,13 +2498,13 @@ EOD;
* The 'empty' option can also be an array of value-label pairs.
* Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li>
* <li>options: array, specifies additional attributes for each OPTION tag.
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* The array keys must be the option values, and the array values are the extra
* OPTION tag attributes in the name-value pairs. For example,
* <pre>
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* array(
* 'value1'=>array('disabled'=>true,'label'=>'value 1'),
* 'value2'=>array('label'=>'value 2'),
* );
* </pre>
* </li>
* <li>key: string, specifies the name of key attribute of the selection object(s).

62
tests/TestAutoloader.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
class TestAutoloader extends CTestCase
{
public function testAutoloaderForAnonymousCallableFilter()
{
Yii::$autoloaderFilters['smarty1'] = function($className)
{
if (strpos($className, 'Smarty') === 0) {
return true;
}
return false;
};
$this->assertTrue(Yii::autoload('SmartyFunctionMeta'));
$this->assertTrue(Yii::autoload('Smarty_Function_Meta'));
}
public function testAutoloaderForFunction()
{
function exampleFilter($className)
{
if (strpos($className, 'Zend') === 0) {
return true;
}
return false;
}
Yii::$autoloaderFilters['zend'] = 'exampleFilter';
$this->assertTrue(Yii::autoload('ZendFunctionMeta'));
$this->assertTrue(Yii::autoload('Zend_Function_Meta'));
}
public function testAutoloaderForStaticMethod()
{
eval('Class SmartyAutoloader {
public static function exampleFilter($className)
{
if (strpos($className, \'Smarty\') === 0) {
return true;
}
return false;
}
}');
Yii::$autoloaderFilters['smarty2'] = array('SmartyAutoloader', 'exampleFilter');
$this->assertTrue(Yii::autoload('SmartyFunctionMeta'));
$this->assertTrue(Yii::autoload('Smarty_Function_Meta'));
}
public function testAutoloaderWithoutFilter()
{
Yii::$enableIncludePath = false;
$this->assertFalse(Yii::autoload('SomeClass'));
$this->assertTrue(Yii::autoload('Yii'));
}
}

View File

@@ -14,6 +14,8 @@ class CHttpSessionTest extends CTestCase {
/**
* @covers CHttpSession::getGCProbability
* @covers CHttpSession::setGCProbability
*
* @runInSeparateProcess
*/
public function testSetGet() {
Yii::app()->setComponents(array('session' => array(
@@ -24,9 +26,10 @@ class CHttpSessionTest extends CTestCase {
'timeout' => 5,
)));
/** @var $sm CHttpSession */
$this->checkProb(1);
$this->checkProb(0);
$gcProb = 1.0;
while ($gcProb > 1 / 2147483647) {
$this->checkProb($gcProb);