diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..fd5e1d7 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,1656 @@ + Yii Framework Change Log + ======================== + +Version 1.1.14 August 11, 2013 +------------------------------ +- Bug: There was unnecessary echo in CRUD views generated by Gii (samdark) +- Bug: CJavaScript::encode was formatting floats in a wrong way during encoding (samdark) +- Bug: Fixed minLength and maxLength range check in CCaptchaAction::generateVerifyCode so values are now always stay in bounds (samdark) +- Bug #101: CActiveFinder::buildJoinTree() no longer uses 'false' for 'select' value (klimov-paul) +- Bug #135: Fixed wrong CActiveRecord rows count with having (klimov-paul) +- Bug #139: Fixed Active Record lazy load through relation with condition (klimov-paul) +- Bug #150: Fixed CWidget was not switching between view paths when using themes (antoncpu) +- Bug #159: CUploadedFile::getInstancesByName() has been fixed allowing correct fetching files, which name is a part of other file name (klimov-paul) +- Bug #196: CActiveForm: models list whose errors should be displayed in error summary is now customizable when using AJAX validation (resurtm) +- Bug #662: Fixed incorrect Active Record lazy loading of relation through BELONGS_TO relation (klimov-paul) +- Bug #1464: Fixed transparent background for ImageMagick in CCaptchaAction (manuel-84, cebe) +- Bug #1669: CNumberValidator used to add wrong error messages in case non-numeric values being validated (resurtm) +- Bug #1692: CWebUser::renewCookie() and CWebUser::restoreFromCookie() now make use of the identityCookie options (f10i) +- Bug #1693: Fixed log file will rotate twice when high performance (monque) +- Bug #1724: Allow CClientScript registering scripts and script files with the HTML options (klimov-paul) +- Bug #1732: CWebLogRoute and CProfileLogRoute with enabled $showInFireBug: fixed bug related to JS `console` object in MSIE 8 and 9 (resurtm) +- Bug #1763: CSqlDataProvider was appending another ORDER BY string to an existing ORDER BY statement when using fieldname with dot (szako) +- Bug #1827: Gii wasn't properly handling table name with the schema part for PostgreSQL (resurtm) +- Bug #1895: Fixed erroneous language attributes in french views (located at `framework\views\fr`) (Ragazzo) +- Bug #1909: CGridView::$filterSelector now prevents default action after event has completed by returning false result in event handler function (matih) +- Bug #1911: MigrateCommand does not rely on cached value about migration table existance anymore as this info could be outdated in testing enviroment (cebe) +- Bug #1915: CDataProviderIterator: fixed init in case of disabled pagination (antoncpu) +- Bug #1916: CMssqlSchema::findColumns() issues an "invalid object name" error (resurtm) +- Bug #1924: CLogFilter::$dumper added: this property can be used to get around circular reference issue when using standard `var_export` dumper by changing it to `print_r` (resurtm) +- Bug #1933: fixed using "multiple" parameter with a value of false in CHtml::activeDropDownList, CHtml::ListBox and CHtml::DropDownList (adminnu) +- Bug #1941: yiiactiveform.js form reset now uses CHtml::errorCss instead of a hardcoded value (mdomba) +- Bug #1942: CActiveForm client/ajax validation will now remove error class from server side validation (mdomba) +- Bug #1945: Reference to undefined variable $column in CDbMigration::dropPrimaryKey (paystey) +- Bug #1955: Some validators used to cause warnings or errors in case non-scalar array typed values being checked (resurtm) +- Bug #1957: Add primary key support for MySQL schema (paystey) +- Bug #1984: CDbMigration: fix of undeclared variable usage in debug information in dropPrimaryKey (papulovskiy) +- Bug #1990: CDateFormatter::formatWeekInMonth(): incorrect result for a week which was last in a previous year and first in a next year simultaneously (resurtm) +- Bug #1996: Using yiic help for commands with parameters with array as default value resulted in PHP error with latest PHP versions (dInGd0nG, samdark) +- Bug #1997: Cache key in CGettextMessageSource::loadMessages wasn't specific enough (odevyatkov) +- Bug #2023: CHttpRequest::stripSlashes() now modifies array keys as well (etienneq) +- Bug #2030: Fixed problem with MySQL 4.x: Undefined Index: Comment in CMysqlSchema (cebe) +- Bug #2048: AR now uses alias from CActiveRecord::getTableAlias instead of always using default "t" (s-larionov) +- Bug #2049: CStatElement relation with join option throw exception when key-field present on joined table (Yiivgeny) +- Bug #2078: Fixed problem with "undefined" parameter in query string when using CListView or CGridView with enableHistory (Parpaing) +- Bug #2086: Fixed .hgignore rule for assets folder (GeXu3, Koduc) +- Bug #2087: CLocale: getLocaleDisplayName() was only returning the language display name, not the full locale display name (brandonkelly) +- Bug #2112: Fixed broken yiic shell CRUD command (mbischof) +- Bug #2121: CMssqlSchema::resetSequence() incorrectly resets sequence (resurtm, joewoodhouse) +- Bug #2122: CActiveRecord, lazy load: 'params' from relations used in 'through' option were not applied to the final SQL statement (resurtm) +- Bug #2123: Fixed error in plural rules handling if locale has no plural rules defined (cebe, stepanselyuk) +- Bug #2146: CEmailValidator fix when fsockopen() can output uncatched error 'Connection refused (61)' (armab) +- Bug #2159: Fixed SQL syntax for delete command with join in MySQL (serebrov) +- Bug #2184: CDbHttpSession now supports MS SQL Server BLOB data type (cheuschober, resurtm) +- Bug #2201: Cannot use "having" with bound params in CActiveRecord::count() (ivokund) +- Bug #2216: CDbCommandBuilder::createInCondition() has been updated, allowing to pass array of values with mixed keys for the single type column (klimov-paul) +- Bug #2223: CActiveForm::error does not respect CHtml::$errorMessageCss (ivokund) +- Bug #2239: Fixed CHtml::refresh() method to use proper syntax (mdomba) +- Bug #2241: COciSchema::resetSequence() now works the same way as the same methods for the other RDMBSes. PHPDocs of the CMssqlSchema::resetSequence(), CMysqlSchema::resetSequence(), CPgsqlSchema::resetSequence(), COciSchema::resetSequence() and CSqliteSchema::resetSequence() methods have been adjusted to fit their real functionality (resurtm) +- Bug #2244: MessageCommand has been updated, allowing to merge string with value '0' correctly (klimov-paul) +- Bug #2258: CJuiSliderInput didn't support string typed 'range' option (bookin) +- Bug #2283: Gii Model Generator's tooltips are not working and always invisible (resurtm) +- Bug #2289: CDbCacheDependency with reuseDependentData did not invalidate cache when getting cache across different requests (marcovtwout) +- Bug #2299: CMssqlSchema: findTableNames(), getTables() and getTableNames() methods used to prepend schema prefix to the table names twice (resurtm) +- Bug #2311: Fixed SQlite default value for timestamp CURRENT_TIMESTAMP (zeeke) +- Bug #2321: CGettextPoFile is now able to parse multiline msgid and msgstr declarations (resurtm) +- Bug #2325: Fixed UTF-8 troubles in CDateTimeParser (error in parsing chinese and thai dates) (s-larionov) +- Bug #2336: PostgreSQL: CDbCommandBuilder used `NULL` instead of `DEFAULT` as default value for the primary keys of serial type (resurtm) +- Bug #2368: Reset error CSS for ':input', which includes SELECT elements (blueyed) +- Bug #2398: Fixed 'Undefined variable: results' E_NOTICE at CProfileLogRoute (klimov-paul) +- Bug #2402: Fixed clientValidation incorrectly rendered as HTML attribute, when used in CActiveForm::error() (mdomba) +- Bug #2406: CUrlManager::$urlRuleClass now supports path alias value (as it was described in its PHPDoc before this fix) (resurtm) +- Bug #2423: Fixed CHtml::button() enforces "value" attribute for the image buttons (klimov-paul) +- Bug #2426: CDbCriteria::__wakeup() used to issue an error in case SQL containing fields were arrays and criteria parameters were specified (resurtm) +- Bug #2438: CViewAction now checks if requested view is a string to not fail when array was given (cebe) +- Bug #2449: CSqlDataProvider causes an error when CDbCommand with enabled PDO::FETCH_OBJ mode used as SQL source (resurtm) +- Bug #2454: Fix CMysqlColumnSchema::extractLimit for ENUM values containing comma (blueyed) +- Bug #2491: Prevent SQL exception being thrown when inserting a row into the session table whilst regenerating the session ID (mynameiszanders) +- Bug #2502: Fix match controller in access rule, match uniqueId instead id (slavcodev) +- Bug #2508: Fix CHtml::activeLabel() to resolve attribute input name for tabular input with custom 'for' (klimov-paul) +- Bug #2516: Fixed the bug that some $.fn.yiiGridView methods were not working always if a custom CGridView::template was used (buakos) +- Bug #2524: Fixed incorrect HTTPS detection (resurtm) +- Bug #2551: CWebUser::loginRequired() AJAX response now properly sends 403 (creocoder) +- Bug #2554: Fixed CRangeValidator when allowEmpty is false (samdark, creocoder) +- Bug #2565: CCaptchaAction in ImageMagick mode used to issue an exception in case $backColor or $foreColor have had leading zeros (resurtm) +- Bug #2581: Fixed the bug with empty ajaxVar in jquery.yiilistview.js and jquery.yiigridview.js (seregagl) +- Bug #2602: CUrlValidator and CEmailValidator now works correctly with display_errors = on and validateIDN = true (creocoder) +- Bug #2662: CLocale::getTerritory() used to return null value even for proper input values, bug fix #1622 made in 1.1.13 has been reverted (resurtm) +- Bug #2632: Fixed inability import non-build aliases by config on some case (Yiivgeny) +- Bug #2651: CHttpSession was using hardcoded GC probability/divisor values (marcovtwout, cebe, samdark) +- Enh: Better CFileLogRoute performance (Qiang, samdark) +- Enh: Refactored CHttpRequest::getDelete and CHttpRequest::getPut not to use _restParams directly (samdark) +- Enh #100: CLogFilter::$logVars can now be array of arrays intended for designating particular items of the $GLOBALS (resurtm, tomtomsen) +- Enh #129: Proper support of namespaced models in forms (LastDragon-ru, Ekstazi, pgaultier) +- Enh #169: Allow to set AJAX request type for CListView and CGridView (klimov-paul) +- Enh #289: Gii module could be submodule of an another module (resurtm) +- Enh #315: COciSchema::checkIntegrity() method added: allows to toggle integrity check (resurtm) +- Enh #755: Allow to get currently running command from CConsoleApplication (klimov-paul) +- Enh #1065: CJuiSliderInput now supports ranged slider when using it without model (resurtm) +- Enh #1142: CSecurityManager::computeHMAC() has been made public (resurtm) +- Enh #1353: Added onBeforeCount event to CActiveRecord (jakob-stoeck) +- Enh #1391: CDetailView: callables (including anonymous functions for PHP 5.3+) could be used as value generators of the attributes (resurtm) +- Enh #1447: CSqliteSchema: added enabling/disabling integrity check for sqlite (gleb-sternharz, resurtm) +- Enh #1589: Added HTTP range responses support to CHttpRequest::sendFile (Ragazzo, samdark) +- Enh #1604: Added method CDbCommandBuilder::createMultipleInsertCommand() to support multiple insertion (klimov-paul) +- Enh #1725: Added CFileHelper::removeDirectory() static method (resurtm) +- Enh #1743: Added CActiveForm::searchField() and CHtml::activeSearchField() to create HTML input field of type SEARCH (njasm) +- Enh #1794: Added ability to change widget ID via $htmlOptions['id'] array item in: CTabView, CBaseListView, CListView, CGridView, CDetailView, CMenu, and CPortlet (umrs) +- Enh #1796: Separate count criteria has been added to the CActiveDataProvider, it's useful for the counting queries simplification (resurtm) +- Enh #1818: Created a CLocalizedFormatter application component that allows formatting values according to current locale (cebe) +- Enh #1842: Added support for MySQL BIT(M) data type default values (migelsabre, cebe) +- Enh #1847: Added COutputCache::varyByLanguage to generate separate cache for different languages (Obramko) +- Enh #1863: Added CActiveFinder::getModel, added CActiveRecord::getActiveFinder, CExistValidator::getModel, CUniqueValidator::getModel, CActiveDataProvider::getModel, CSort::getModel (denisarius, samdark) +- Enh #1928: Gii is now able to use table columns' comments as the attribute labels of a new generated model (resurtm, tlikai) +- Enh #1948: Tidy up and improve html5 input support in CHtml and CActiveForm (phpnode) +- Enh #1977: CFormatter::normalizeDateValue() now is protected instead of private to enable child classes to override it (etienneq) +- Enh #2003: Gii now allows namespaced base classes to be defined in generators (etienneq) +- Enh #2038: CFormatter::formatNtext() method can replace newlines with `
` not just with `application.components.GoogleMap
: import the GoogleMap
class.application.components.*
: import the components
directory.application\components\GoogleMap
is similar to importing application.components.GoogleMap
.
+ * The difference is that the former class is using qualified name, while the latter unqualified.
+ *
+ * Note, importing a class in namespace format requires that the namespace corresponds to
+ * a valid path alias once backslash characters are replaced with dot characters.
+ * For example, the namespace application\components
must correspond to a valid
+ * path alias application.components
.
+ *
+ * @param string $alias path alias to be imported
+ * @param boolean $forceInclude whether to include the class file immediately. If false, the class file
+ * will be included only when the class is being used. This parameter is used only when
+ * the path alias refers to a class.
+ * @return string the class name or the directory that this alias refers to
+ * @throws CException if the alias is invalid
+ */
+ public static function import($alias,$forceInclude=false)
+ {
+ if(isset(self::$_imports[$alias])) // previously imported
+ return self::$_imports[$alias];
+
+ if(class_exists($alias,false) || interface_exists($alias,false))
+ return self::$_imports[$alias]=$alias;
+
+ if(($pos=strrpos($alias,'\\'))!==false) // a class name in PHP 5.3 namespace format
+ {
+ $namespace=str_replace('\\','.',ltrim(substr($alias,0,$pos),'\\'));
+ if(($path=self::getPathOfAlias($namespace))!==false)
+ {
+ $classFile=$path.DIRECTORY_SEPARATOR.substr($alias,$pos+1).'.php';
+ if($forceInclude)
+ {
+ if(is_file($classFile))
+ require($classFile);
+ else
+ throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.',array('{alias}'=>$alias)));
+ self::$_imports[$alias]=$alias;
+ }
+ else
+ self::$classMap[$alias]=$classFile;
+ return $alias;
+ }
+ else
+ {
+ // try to autoload the class with an autoloader
+ if (class_exists($alias,true))
+ return self::$_imports[$alias]=$alias;
+ else
+ throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',
+ array('{alias}'=>$namespace)));
+ }
+ }
+
+ if(($pos=strrpos($alias,'.'))===false) // a simple class name
+ {
+ if($forceInclude && self::autoload($alias))
+ self::$_imports[$alias]=$alias;
+ return $alias;
+ }
+
+ $className=(string)substr($alias,$pos+1);
+ $isClass=$className!=='*';
+
+ if($isClass && (class_exists($className,false) || interface_exists($className,false)))
+ return self::$_imports[$alias]=$className;
+
+ if(($path=self::getPathOfAlias($alias))!==false)
+ {
+ if($isClass)
+ {
+ if($forceInclude)
+ {
+ if(is_file($path.'.php'))
+ require($path.'.php');
+ else
+ throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing PHP file and the file is readable.',array('{alias}'=>$alias)));
+ self::$_imports[$alias]=$className;
+ }
+ else
+ self::$classMap[$className]=$path.'.php';
+ return $className;
+ }
+ else // a directory
+ {
+ if(self::$_includePaths===null)
+ {
+ self::$_includePaths=array_unique(explode(PATH_SEPARATOR,get_include_path()));
+ if(($pos=array_search('.',self::$_includePaths,true))!==false)
+ unset(self::$_includePaths[$pos]);
+ }
+
+ array_unshift(self::$_includePaths,$path);
+
+ if(self::$enableIncludePath && set_include_path('.'.PATH_SEPARATOR.implode(PATH_SEPARATOR,self::$_includePaths))===false)
+ self::$enableIncludePath=false;
+
+ return self::$_imports[$alias]=$path;
+ }
+ }
+ else
+ throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',
+ array('{alias}'=>$alias)));
+ }
+
+ /**
+ * Translates an alias into a file path.
+ * Note, this method does not ensure the existence of the resulting file path.
+ * It only checks if the root alias is valid or not.
+ * @param string $alias alias (e.g. system.web.CController)
+ * @return mixed file path corresponding to the alias, false if the alias is invalid.
+ */
+ public static function getPathOfAlias($alias)
+ {
+ if(isset(self::$_aliases[$alias]))
+ return self::$_aliases[$alias];
+ elseif(($pos=strpos($alias,'.'))!==false)
+ {
+ $rootAlias=substr($alias,0,$pos);
+ if(isset(self::$_aliases[$rootAlias]))
+ return self::$_aliases[$alias]=rtrim(self::$_aliases[$rootAlias].DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,substr($alias,$pos+1)),'*'.DIRECTORY_SEPARATOR);
+ elseif(self::$_app instanceof CWebApplication)
+ {
+ if(self::$_app->findModule($rootAlias)!==null)
+ return self::getPathOfAlias($alias);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create a path alias.
+ * Note, this method neither checks the existence of the path nor normalizes the path.
+ * @param string $alias alias to the path
+ * @param string $path the path corresponding to the alias. If this is null, the corresponding
+ * path alias will be removed.
+ */
+ public static function setPathOfAlias($alias,$path)
+ {
+ if(empty($path))
+ unset(self::$_aliases[$alias]);
+ else
+ self::$_aliases[$alias]=rtrim($path,'\\/');
+ }
+
+ /**
+ * Class autoload loader.
+ * This method is provided to be invoked within an __autoload() magic method.
+ * @param string $className class name
+ * @return boolean whether the class has been loaded successfully
+ */
+ public static function autoload($className)
+ {
+ // use include so that the error PHP file may appear
+ if(isset(self::$classMap[$className]))
+ include(self::$classMap[$className]);
+ elseif(isset(self::$_coreClasses[$className]))
+ include(YII_PATH.self::$_coreClasses[$className]);
+ else
+ {
+ // include class file relying on include_path
+ if(strpos($className,'\\')===false) // class without namespace
+ {
+ if(self::$enableIncludePath===false)
+ {
+ foreach(self::$_includePaths as $path)
+ {
+ $classFile=$path.DIRECTORY_SEPARATOR.$className.'.php';
+ if(is_file($classFile))
+ {
+ include($classFile);
+ if(YII_DEBUG && basename(realpath($classFile))!==$className.'.php')
+ throw new CException(Yii::t('yii','Class name "{class}" does not match class file "{file}".', array(
+ '{class}'=>$className,
+ '{file}'=>$classFile,
+ )));
+ break;
+ }
+ }
+ }
+ else
+ include($className.'.php');
+ }
+ else // class name with namespace in PHP 5.3
+ {
+ $namespace=str_replace('\\','.',ltrim($className,'\\'));
+ if(($path=self::getPathOfAlias($namespace))!==false)
+ include($path.'.php');
+ else
+ return false;
+ }
+ return class_exists($className,false) || interface_exists($className,false);
+ }
+ return true;
+ }
+
+ /**
+ * Writes a trace message.
+ * This method will only log a message when the application is in debug mode.
+ * @param string $msg message to be logged
+ * @param string $category category of the message
+ * @see log
+ */
+ public static function trace($msg,$category='application')
+ {
+ if(YII_DEBUG)
+ self::log($msg,CLogger::LEVEL_TRACE,$category);
+ }
+
+ /**
+ * Logs a message.
+ * Messages logged by this method may be retrieved via {@link CLogger::getLogs}
+ * and may be recorded in different media, such as file, email, database, using
+ * {@link CLogRouter}.
+ * @param string $msg message to be logged
+ * @param string $level level of the message (e.g. 'trace', 'warning', 'error'). It is case-insensitive.
+ * @param string $category category of the message (e.g. 'system.web'). It is case-insensitive.
+ */
+ public static function log($msg,$level=CLogger::LEVEL_INFO,$category='application')
+ {
+ if(self::$_logger===null)
+ self::$_logger=new CLogger;
+ if(YII_DEBUG && YII_TRACE_LEVEL>0 && $level!==CLogger::LEVEL_PROFILE)
+ {
+ $traces=debug_backtrace();
+ $count=0;
+ foreach($traces as $trace)
+ {
+ if(isset($trace['file'],$trace['line']) && strpos($trace['file'],YII_PATH)!==0)
+ {
+ $msg.="\nin ".$trace['file'].' ('.$trace['line'].')';
+ if(++$count>=YII_TRACE_LEVEL)
+ break;
+ }
+ }
+ }
+ self::$_logger->log($msg,$level,$category);
+ }
+
+ /**
+ * Marks the beginning of a code block for profiling.
+ * This has to be matched with a call to {@link endProfile()} with the same token.
+ * The begin- and end- calls must also be properly nested, e.g.,
+ * + * Yii::beginProfile('block1'); + * Yii::beginProfile('block2'); + * Yii::endProfile('block2'); + * Yii::endProfile('block1'); + *+ * The following sequence is not valid: + *
+ * Yii::beginProfile('block1'); + * Yii::beginProfile('block2'); + * Yii::endProfile('block1'); + * Yii::endProfile('block2'); + *+ * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile + */ + public static function beginProfile($token,$category='application') + { + self::log('begin:'.$token,CLogger::LEVEL_PROFILE,$category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to {@link beginProfile()} with the same token. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile + */ + public static function endProfile($token,$category='application') + { + self::log('end:'.$token,CLogger::LEVEL_PROFILE,$category); + } + + /** + * @return CLogger message logger + */ + public static function getLogger() + { + if(self::$_logger!==null) + return self::$_logger; + else + return self::$_logger=new CLogger; + } + + /** + * Sets the logger object. + * @param CLogger $logger the logger object. + * @since 1.1.8 + */ + public static function setLogger($logger) + { + self::$_logger=$logger; + } + + /** + * Returns a string that can be displayed on your Web page showing Powered-by-Yii information + * @return string a string that can be displayed on your Web page showing Powered-by-Yii information + */ + public static function powered() + { + return Yii::t('yii','Powered by {yii}.', array('{yii}'=>'Yii Framework')); + } + + /** + * Translates a message to the specified language. + * This method supports choice format (see {@link CChoiceFormat}), + * i.e., the message returned will be chosen from a few candidates according to the given + * number value. This feature is mainly used to solve plural format issue in case + * a message has different plural forms in some languages. + * @param string $category message category. Please use only word letters. Note, category 'yii' is + * reserved for Yii framework core code use. See {@link CPhpMessageSource} for + * more interpretation about message category. + * @param string $message the original message + * @param array $params parameters to be applied to the message using
strtr
.
+ * The first parameter can be a number without key.
+ * And in this case, the method will call {@link CChoiceFormat::format} to choose
+ * an appropriate message translation.
+ * Starting from version 1.1.6 you can pass parameter for {@link CChoiceFormat::format}
+ * or plural forms format without wrapping it with array.
+ * This parameter is then available as {n}
in the message translation string.
+ * @param string $source which message source application component to use.
+ * Defaults to null, meaning using 'coreMessages' for messages belonging to
+ * the 'yii' category and using 'messages' for the rest messages.
+ * @param string $language the target language. If null (default), the {@link CApplication::getLanguage application language} will be used.
+ * @return string the translated message
+ * @see CMessageSource
+ */
+ public static function t($category,$message,$params=array(),$source=null,$language=null)
+ {
+ if(self::$_app!==null)
+ {
+ if($source===null)
+ $source=($category==='yii'||$category==='zii')?'coreMessages':'messages';
+ if(($source=self::$_app->getComponent($source))!==null)
+ $message=$source->translate($category,$message,$language);
+ }
+ if($params===array())
+ return $message;
+ if(!is_array($params))
+ $params=array($params);
+ if(isset($params[0])) // number choice
+ {
+ if(strpos($message,'|')!==false)
+ {
+ if(strpos($message,'#')===false)
+ {
+ $chunks=explode('|',$message);
+ $expressions=self::$_app->getLocale($language)->getPluralRules();
+ if($n=min(count($chunks),count($expressions)))
+ {
+ for($i=0;$i<$n;$i++)
+ $chunks[$i]=$expressions[$i].'#'.$chunks[$i];
+
+ $message=implode('|',$chunks);
+ }
+ }
+ $message=CChoiceFormat::format($message,$params[0]);
+ }
+ if(!isset($params['{n}']))
+ $params['{n}']=$params[0];
+ unset($params[0]);
+ }
+ return $params!==array() ? strtr($message,$params) : $message;
+ }
+
+ /**
+ * Registers a new class autoloader.
+ * The new autoloader will be placed before {@link autoload} and after
+ * any other existing autoloaders.
+ * @param callback $callback a valid PHP callback (function name or array($className,$methodName)).
+ * @param boolean $append whether to append the new autoloader after the default Yii autoloader.
+ * Be careful using this option as it will disable {@link enableIncludePath autoloading via include path}
+ * when set to true. After this the Yii autoloader can not rely on loading classes via simple include anymore
+ * and you have to {@link import} all classes explicitly.
+ */
+ public static function registerAutoloader($callback, $append=false)
+ {
+ if($append)
+ {
+ self::$enableIncludePath=false;
+ spl_autoload_register($callback);
+ }
+ else
+ {
+ spl_autoload_unregister(array('YiiBase','autoload'));
+ spl_autoload_register($callback);
+ spl_autoload_register(array('YiiBase','autoload'));
+ }
+ }
+
+ /**
+ * @var array class map for core Yii classes.
+ * NOTE, DO NOT MODIFY THIS ARRAY MANUALLY. IF YOU CHANGE OR ADD SOME CORE CLASSES,
+ * PLEASE RUN 'build autoload' COMMAND TO UPDATE THIS ARRAY.
+ */
+ private static $_coreClasses=array(
+ 'CApplication' => '/base/CApplication.php',
+ 'CApplicationComponent' => '/base/CApplicationComponent.php',
+ 'CBehavior' => '/base/CBehavior.php',
+ 'CComponent' => '/base/CComponent.php',
+ 'CErrorEvent' => '/base/CErrorEvent.php',
+ 'CErrorHandler' => '/base/CErrorHandler.php',
+ 'CException' => '/base/CException.php',
+ 'CExceptionEvent' => '/base/CExceptionEvent.php',
+ 'CHttpException' => '/base/CHttpException.php',
+ 'CModel' => '/base/CModel.php',
+ 'CModelBehavior' => '/base/CModelBehavior.php',
+ 'CModelEvent' => '/base/CModelEvent.php',
+ 'CModule' => '/base/CModule.php',
+ 'CSecurityManager' => '/base/CSecurityManager.php',
+ 'CStatePersister' => '/base/CStatePersister.php',
+ 'CApcCache' => '/caching/CApcCache.php',
+ 'CCache' => '/caching/CCache.php',
+ 'CDbCache' => '/caching/CDbCache.php',
+ 'CDummyCache' => '/caching/CDummyCache.php',
+ 'CEAcceleratorCache' => '/caching/CEAcceleratorCache.php',
+ 'CFileCache' => '/caching/CFileCache.php',
+ 'CMemCache' => '/caching/CMemCache.php',
+ 'CRedisCache' => '/caching/CRedisCache.php',
+ 'CWinCache' => '/caching/CWinCache.php',
+ 'CXCache' => '/caching/CXCache.php',
+ 'CZendDataCache' => '/caching/CZendDataCache.php',
+ 'CCacheDependency' => '/caching/dependencies/CCacheDependency.php',
+ 'CChainedCacheDependency' => '/caching/dependencies/CChainedCacheDependency.php',
+ 'CDbCacheDependency' => '/caching/dependencies/CDbCacheDependency.php',
+ 'CDirectoryCacheDependency' => '/caching/dependencies/CDirectoryCacheDependency.php',
+ 'CExpressionDependency' => '/caching/dependencies/CExpressionDependency.php',
+ 'CFileCacheDependency' => '/caching/dependencies/CFileCacheDependency.php',
+ 'CGlobalStateCacheDependency' => '/caching/dependencies/CGlobalStateCacheDependency.php',
+ 'CAttributeCollection' => '/collections/CAttributeCollection.php',
+ 'CConfiguration' => '/collections/CConfiguration.php',
+ 'CList' => '/collections/CList.php',
+ 'CListIterator' => '/collections/CListIterator.php',
+ 'CMap' => '/collections/CMap.php',
+ 'CMapIterator' => '/collections/CMapIterator.php',
+ 'CQueue' => '/collections/CQueue.php',
+ 'CQueueIterator' => '/collections/CQueueIterator.php',
+ 'CStack' => '/collections/CStack.php',
+ 'CStackIterator' => '/collections/CStackIterator.php',
+ 'CTypedList' => '/collections/CTypedList.php',
+ 'CTypedMap' => '/collections/CTypedMap.php',
+ 'CConsoleApplication' => '/console/CConsoleApplication.php',
+ 'CConsoleCommand' => '/console/CConsoleCommand.php',
+ 'CConsoleCommandBehavior' => '/console/CConsoleCommandBehavior.php',
+ 'CConsoleCommandEvent' => '/console/CConsoleCommandEvent.php',
+ 'CConsoleCommandRunner' => '/console/CConsoleCommandRunner.php',
+ 'CHelpCommand' => '/console/CHelpCommand.php',
+ 'CDbCommand' => '/db/CDbCommand.php',
+ 'CDbConnection' => '/db/CDbConnection.php',
+ 'CDbDataReader' => '/db/CDbDataReader.php',
+ 'CDbException' => '/db/CDbException.php',
+ 'CDbMigration' => '/db/CDbMigration.php',
+ 'CDbTransaction' => '/db/CDbTransaction.php',
+ 'CActiveFinder' => '/db/ar/CActiveFinder.php',
+ 'CActiveRecord' => '/db/ar/CActiveRecord.php',
+ 'CActiveRecordBehavior' => '/db/ar/CActiveRecordBehavior.php',
+ 'CDbColumnSchema' => '/db/schema/CDbColumnSchema.php',
+ 'CDbCommandBuilder' => '/db/schema/CDbCommandBuilder.php',
+ 'CDbCriteria' => '/db/schema/CDbCriteria.php',
+ 'CDbExpression' => '/db/schema/CDbExpression.php',
+ 'CDbSchema' => '/db/schema/CDbSchema.php',
+ 'CDbTableSchema' => '/db/schema/CDbTableSchema.php',
+ 'CMssqlColumnSchema' => '/db/schema/mssql/CMssqlColumnSchema.php',
+ 'CMssqlCommandBuilder' => '/db/schema/mssql/CMssqlCommandBuilder.php',
+ 'CMssqlPdoAdapter' => '/db/schema/mssql/CMssqlPdoAdapter.php',
+ 'CMssqlSchema' => '/db/schema/mssql/CMssqlSchema.php',
+ 'CMssqlSqlsrvPdoAdapter' => '/db/schema/mssql/CMssqlSqlsrvPdoAdapter.php',
+ 'CMssqlTableSchema' => '/db/schema/mssql/CMssqlTableSchema.php',
+ 'CMysqlColumnSchema' => '/db/schema/mysql/CMysqlColumnSchema.php',
+ 'CMysqlCommandBuilder' => '/db/schema/mysql/CMysqlCommandBuilder.php',
+ 'CMysqlSchema' => '/db/schema/mysql/CMysqlSchema.php',
+ 'CMysqlTableSchema' => '/db/schema/mysql/CMysqlTableSchema.php',
+ 'COciColumnSchema' => '/db/schema/oci/COciColumnSchema.php',
+ 'COciCommandBuilder' => '/db/schema/oci/COciCommandBuilder.php',
+ 'COciSchema' => '/db/schema/oci/COciSchema.php',
+ 'COciTableSchema' => '/db/schema/oci/COciTableSchema.php',
+ 'CPgsqlColumnSchema' => '/db/schema/pgsql/CPgsqlColumnSchema.php',
+ 'CPgsqlCommandBuilder' => '/db/schema/pgsql/CPgsqlCommandBuilder.php',
+ 'CPgsqlSchema' => '/db/schema/pgsql/CPgsqlSchema.php',
+ 'CPgsqlTableSchema' => '/db/schema/pgsql/CPgsqlTableSchema.php',
+ 'CSqliteColumnSchema' => '/db/schema/sqlite/CSqliteColumnSchema.php',
+ 'CSqliteCommandBuilder' => '/db/schema/sqlite/CSqliteCommandBuilder.php',
+ 'CSqliteSchema' => '/db/schema/sqlite/CSqliteSchema.php',
+ 'CChoiceFormat' => '/i18n/CChoiceFormat.php',
+ 'CDateFormatter' => '/i18n/CDateFormatter.php',
+ 'CDbMessageSource' => '/i18n/CDbMessageSource.php',
+ 'CGettextMessageSource' => '/i18n/CGettextMessageSource.php',
+ 'CLocale' => '/i18n/CLocale.php',
+ 'CMessageSource' => '/i18n/CMessageSource.php',
+ 'CNumberFormatter' => '/i18n/CNumberFormatter.php',
+ 'CPhpMessageSource' => '/i18n/CPhpMessageSource.php',
+ 'CGettextFile' => '/i18n/gettext/CGettextFile.php',
+ 'CGettextMoFile' => '/i18n/gettext/CGettextMoFile.php',
+ 'CGettextPoFile' => '/i18n/gettext/CGettextPoFile.php',
+ 'CChainedLogFilter' => '/logging/CChainedLogFilter.php',
+ 'CDbLogRoute' => '/logging/CDbLogRoute.php',
+ 'CEmailLogRoute' => '/logging/CEmailLogRoute.php',
+ 'CFileLogRoute' => '/logging/CFileLogRoute.php',
+ 'CLogFilter' => '/logging/CLogFilter.php',
+ 'CLogRoute' => '/logging/CLogRoute.php',
+ 'CLogRouter' => '/logging/CLogRouter.php',
+ 'CLogger' => '/logging/CLogger.php',
+ 'CProfileLogRoute' => '/logging/CProfileLogRoute.php',
+ 'CWebLogRoute' => '/logging/CWebLogRoute.php',
+ 'CDateTimeParser' => '/utils/CDateTimeParser.php',
+ 'CFileHelper' => '/utils/CFileHelper.php',
+ 'CFormatter' => '/utils/CFormatter.php',
+ 'CLocalizedFormatter' => '/utils/CLocalizedFormatter.php',
+ 'CMarkdownParser' => '/utils/CMarkdownParser.php',
+ 'CPasswordHelper' => '/utils/CPasswordHelper.php',
+ 'CPropertyValue' => '/utils/CPropertyValue.php',
+ 'CTimestamp' => '/utils/CTimestamp.php',
+ 'CVarDumper' => '/utils/CVarDumper.php',
+ 'CBooleanValidator' => '/validators/CBooleanValidator.php',
+ 'CCaptchaValidator' => '/validators/CCaptchaValidator.php',
+ 'CCompareValidator' => '/validators/CCompareValidator.php',
+ 'CDateValidator' => '/validators/CDateValidator.php',
+ 'CDefaultValueValidator' => '/validators/CDefaultValueValidator.php',
+ 'CEmailValidator' => '/validators/CEmailValidator.php',
+ 'CExistValidator' => '/validators/CExistValidator.php',
+ 'CFileValidator' => '/validators/CFileValidator.php',
+ 'CFilterValidator' => '/validators/CFilterValidator.php',
+ 'CInlineValidator' => '/validators/CInlineValidator.php',
+ 'CNumberValidator' => '/validators/CNumberValidator.php',
+ 'CRangeValidator' => '/validators/CRangeValidator.php',
+ 'CRegularExpressionValidator' => '/validators/CRegularExpressionValidator.php',
+ 'CRequiredValidator' => '/validators/CRequiredValidator.php',
+ 'CSafeValidator' => '/validators/CSafeValidator.php',
+ 'CStringValidator' => '/validators/CStringValidator.php',
+ 'CTypeValidator' => '/validators/CTypeValidator.php',
+ 'CUniqueValidator' => '/validators/CUniqueValidator.php',
+ 'CUnsafeValidator' => '/validators/CUnsafeValidator.php',
+ 'CUrlValidator' => '/validators/CUrlValidator.php',
+ 'CValidator' => '/validators/CValidator.php',
+ 'CActiveDataProvider' => '/web/CActiveDataProvider.php',
+ 'CArrayDataProvider' => '/web/CArrayDataProvider.php',
+ 'CAssetManager' => '/web/CAssetManager.php',
+ 'CBaseController' => '/web/CBaseController.php',
+ 'CCacheHttpSession' => '/web/CCacheHttpSession.php',
+ 'CClientScript' => '/web/CClientScript.php',
+ 'CController' => '/web/CController.php',
+ 'CDataProvider' => '/web/CDataProvider.php',
+ 'CDataProviderIterator' => '/web/CDataProviderIterator.php',
+ 'CDbHttpSession' => '/web/CDbHttpSession.php',
+ 'CExtController' => '/web/CExtController.php',
+ 'CFormModel' => '/web/CFormModel.php',
+ 'CHttpCookie' => '/web/CHttpCookie.php',
+ 'CHttpRequest' => '/web/CHttpRequest.php',
+ 'CHttpSession' => '/web/CHttpSession.php',
+ 'CHttpSessionIterator' => '/web/CHttpSessionIterator.php',
+ 'COutputEvent' => '/web/COutputEvent.php',
+ 'CPagination' => '/web/CPagination.php',
+ 'CSort' => '/web/CSort.php',
+ 'CSqlDataProvider' => '/web/CSqlDataProvider.php',
+ 'CTheme' => '/web/CTheme.php',
+ 'CThemeManager' => '/web/CThemeManager.php',
+ 'CUploadedFile' => '/web/CUploadedFile.php',
+ 'CUrlManager' => '/web/CUrlManager.php',
+ 'CWebApplication' => '/web/CWebApplication.php',
+ 'CWebModule' => '/web/CWebModule.php',
+ 'CWidgetFactory' => '/web/CWidgetFactory.php',
+ 'CAction' => '/web/actions/CAction.php',
+ 'CInlineAction' => '/web/actions/CInlineAction.php',
+ 'CViewAction' => '/web/actions/CViewAction.php',
+ 'CAccessControlFilter' => '/web/auth/CAccessControlFilter.php',
+ 'CAuthAssignment' => '/web/auth/CAuthAssignment.php',
+ 'CAuthItem' => '/web/auth/CAuthItem.php',
+ 'CAuthManager' => '/web/auth/CAuthManager.php',
+ 'CBaseUserIdentity' => '/web/auth/CBaseUserIdentity.php',
+ 'CDbAuthManager' => '/web/auth/CDbAuthManager.php',
+ 'CPhpAuthManager' => '/web/auth/CPhpAuthManager.php',
+ 'CUserIdentity' => '/web/auth/CUserIdentity.php',
+ 'CWebUser' => '/web/auth/CWebUser.php',
+ 'CFilter' => '/web/filters/CFilter.php',
+ 'CFilterChain' => '/web/filters/CFilterChain.php',
+ 'CHttpCacheFilter' => '/web/filters/CHttpCacheFilter.php',
+ 'CInlineFilter' => '/web/filters/CInlineFilter.php',
+ 'CForm' => '/web/form/CForm.php',
+ 'CFormButtonElement' => '/web/form/CFormButtonElement.php',
+ 'CFormElement' => '/web/form/CFormElement.php',
+ 'CFormElementCollection' => '/web/form/CFormElementCollection.php',
+ 'CFormInputElement' => '/web/form/CFormInputElement.php',
+ 'CFormStringElement' => '/web/form/CFormStringElement.php',
+ 'CGoogleApi' => '/web/helpers/CGoogleApi.php',
+ 'CHtml' => '/web/helpers/CHtml.php',
+ 'CJSON' => '/web/helpers/CJSON.php',
+ 'CJavaScript' => '/web/helpers/CJavaScript.php',
+ 'CJavaScriptExpression' => '/web/helpers/CJavaScriptExpression.php',
+ 'CPradoViewRenderer' => '/web/renderers/CPradoViewRenderer.php',
+ 'CViewRenderer' => '/web/renderers/CViewRenderer.php',
+ 'CWebService' => '/web/services/CWebService.php',
+ 'CWebServiceAction' => '/web/services/CWebServiceAction.php',
+ 'CWsdlGenerator' => '/web/services/CWsdlGenerator.php',
+ 'CActiveForm' => '/web/widgets/CActiveForm.php',
+ 'CAutoComplete' => '/web/widgets/CAutoComplete.php',
+ 'CClipWidget' => '/web/widgets/CClipWidget.php',
+ 'CContentDecorator' => '/web/widgets/CContentDecorator.php',
+ 'CFilterWidget' => '/web/widgets/CFilterWidget.php',
+ 'CFlexWidget' => '/web/widgets/CFlexWidget.php',
+ 'CHtmlPurifier' => '/web/widgets/CHtmlPurifier.php',
+ 'CInputWidget' => '/web/widgets/CInputWidget.php',
+ 'CMarkdown' => '/web/widgets/CMarkdown.php',
+ 'CMaskedTextField' => '/web/widgets/CMaskedTextField.php',
+ 'CMultiFileUpload' => '/web/widgets/CMultiFileUpload.php',
+ 'COutputCache' => '/web/widgets/COutputCache.php',
+ 'COutputProcessor' => '/web/widgets/COutputProcessor.php',
+ 'CStarRating' => '/web/widgets/CStarRating.php',
+ 'CTabView' => '/web/widgets/CTabView.php',
+ 'CTextHighlighter' => '/web/widgets/CTextHighlighter.php',
+ 'CTreeView' => '/web/widgets/CTreeView.php',
+ 'CWidget' => '/web/widgets/CWidget.php',
+ 'CCaptcha' => '/web/widgets/captcha/CCaptcha.php',
+ 'CCaptchaAction' => '/web/widgets/captcha/CCaptchaAction.php',
+ 'CBasePager' => '/web/widgets/pagers/CBasePager.php',
+ 'CLinkPager' => '/web/widgets/pagers/CLinkPager.php',
+ 'CListPager' => '/web/widgets/pagers/CListPager.php',
+ );
+}
+
+spl_autoload_register(array('YiiBase','autoload'));
+require(YII_PATH.'/base/interfaces.php');
diff --git a/framework/base/CApplication.php b/framework/base/CApplication.php
new file mode 100644
index 0000000..0219478
--- /dev/null
+++ b/framework/base/CApplication.php
@@ -0,0 +1,990 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CApplication is the base class for all application classes.
+ *
+ * An application serves as the global context that the user request
+ * is being processed. It manages a set of application components that
+ * provide specific functionalities to the whole application.
+ *
+ * The core application components provided by CApplication are the following:
+ * $message ($file:$line)
\n"; + echo ''; + + $trace=debug_backtrace(); + // skip the first 3 stacks as they do not tell the error position + if(count($trace)>3) + $trace=array_slice($trace,3); + foreach($trace as $i=>$t) + { + if(!isset($t['file'])) + $t['file']='unknown'; + if(!isset($t['line'])) + $t['line']=0; + if(!isset($t['function'])) + $t['function']='unknown'; + echo "#$i {$t['file']}({$t['line']}): "; + if(isset($t['object']) && is_object($t['object'])) + echo get_class($t['object']).'->'; + echo "{$t['function']}()\n"; + } + + echo ''; + } + else + { + echo "
$message
\n"; + } + } + + /** + * Displays the uncaught PHP exception. + * This method displays the exception in HTML when there is + * no active error handler. + * @param Exception $exception the uncaught exception + */ + public function displayException($exception) + { + if(YII_DEBUG) + { + echo ''.$exception->getMessage().' ('.$exception->getFile().':'.$exception->getLine().')
'; + echo ''.$exception->getTraceAsString().''; + } + else + { + echo '
'.$exception->getMessage().'
'; + } + } + + /** + * Initializes the class autoloader and error handlers. + */ + protected function initSystemHandlers() + { + if(YII_ENABLE_EXCEPTION_HANDLER) + set_exception_handler(array($this,'handleException')); + if(YII_ENABLE_ERROR_HANDLER) + set_error_handler(array($this,'handleError'),error_reporting()); + } + + /** + * Registers the core application components. + * @see setComponents + */ + protected function registerCoreComponents() + { + $components=array( + 'coreMessages'=>array( + 'class'=>'CPhpMessageSource', + 'language'=>'en_us', + 'basePath'=>YII_PATH.DIRECTORY_SEPARATOR.'messages', + ), + 'db'=>array( + 'class'=>'CDbConnection', + ), + 'messages'=>array( + 'class'=>'CPhpMessageSource', + ), + 'errorHandler'=>array( + 'class'=>'CErrorHandler', + ), + 'securityManager'=>array( + 'class'=>'CSecurityManager', + ), + 'statePersister'=>array( + 'class'=>'CStatePersister', + ), + 'urlManager'=>array( + 'class'=>'CUrlManager', + ), + 'request'=>array( + 'class'=>'CHttpRequest', + ), + 'format'=>array( + 'class'=>'CFormatter', + ), + ); + + $this->setComponents($components); + } +} diff --git a/framework/base/CApplicationComponent.php b/framework/base/CApplicationComponent.php new file mode 100644 index 0000000..48a42ec --- /dev/null +++ b/framework/base/CApplicationComponent.php @@ -0,0 +1,57 @@ + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CApplicationComponent is the base class for application component classes. + * + * CApplicationComponent implements the basic methods required by {@link IApplicationComponent}. + * + * When developing an application component, try to put application component initialization code in + * the {@link init()} method instead of the constructor. This has the advantage that + * the application component can be customized through application configuration. + * + * @property boolean $isInitialized Whether this application component has been initialized (ie, {@link init()} is invoked). + * + * @author Qiang Xue+ * $a=$component->text; // equivalent to $a=$component->getText(); + * $component->text='abc'; // equivalent to $component->setText('abc'); + *+ * The signatures of getter and setter methods are as follows, + *
+ * // getter, defines a readable property 'text' + * public function getText() { ... } + * // setter, defines a writable property 'text' with $value to be set to the property + * public function setText($value) { ... } + *+ * + * An event is defined by the presence of a method whose name starts with 'on'. + * The event name is the method name. When an event is raised, functions + * (called event handlers) attached to the event will be invoked automatically. + * + * An event can be raised by calling {@link raiseEvent} method, upon which + * the attached event handlers will be invoked automatically in the order they + * are attached to the event. Event handlers must have the following signature, + *
+ * function eventHandler($event) { ... } + *+ * where $event includes parameters associated with the event. + * + * To attach an event handler to an event, see {@link attachEventHandler}. + * You can also use the following syntax: + *
+ * $component->onClick=$callback; // or $component->onClick->add($callback); + *+ * where $callback refers to a valid PHP callback. Below we show some callback examples: + *
+ * 'handleOnClick' // handleOnClick() is a global function + * array($object,'handleOnClick') // using $object->handleOnClick() + * array('Page','handleOnClick') // using Page::handleOnClick() + *+ * + * To raise an event, use {@link raiseEvent}. The on-method defining an event is + * commonly written like the following: + *
+ * public function onClick($event) + * { + * $this->raiseEvent('onClick',$event); + * } + *+ * where
$event
is an instance of {@link CEvent} or its child class.
+ * One can then raise the event by calling the on-method instead of {@link raiseEvent} directly.
+ *
+ * Both property names and event names are case-insensitive.
+ *
+ * CComponent supports behaviors. A behavior is an
+ * instance of {@link IBehavior} which is attached to a component. The methods of
+ * the behavior can be invoked as if they belong to the component. Multiple behaviors
+ * can be attached to the same component.
+ *
+ * To attach a behavior to a component, call {@link attachBehavior}; and to detach the behavior
+ * from the component, call {@link detachBehavior}.
+ *
+ * A behavior can be temporarily enabled or disabled by calling {@link enableBehavior}
+ * or {@link disableBehavior}, respectively. When disabled, the behavior methods cannot
+ * be invoked via the component.
+ *
+ * Starting from version 1.1.0, a behavior's properties (either its public member variables or
+ * its properties defined via getters and/or setters) can be accessed through the component it
+ * is attached to.
+ *
+ * @author Qiang Xue + * $value=$component->propertyName; + * $handlers=$component->eventName; + *+ * @param string $name the property name or event name + * @return mixed the property value, event handlers attached to the event, or the named behavior + * @throws CException if the property or event is not defined + * @see __set + */ + public function __get($name) + { + $getter='get'.$name; + if(method_exists($this,$getter)) + return $this->$getter(); + elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) + { + // duplicating getEventHandlers() here for performance + $name=strtolower($name); + if(!isset($this->_e[$name])) + $this->_e[$name]=new CList; + return $this->_e[$name]; + } + elseif(isset($this->_m[$name])) + return $this->_m[$name]; + elseif(is_array($this->_m)) + { + foreach($this->_m as $object) + { + if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name))) + return $object->$name; + } + } + throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.', + array('{class}'=>get_class($this), '{property}'=>$name))); + } + + /** + * Sets value of a component property. + * Do not call this method. This is a PHP magic method that we override + * to allow using the following syntax to set a property or attach an event handler + *
+ * $this->propertyName=$value; + * $this->eventName=$callback; + *+ * @param string $name the property name or the event name + * @param mixed $value the property value or callback + * @return mixed + * @throws CException if the property/event is not defined or the property is read only. + * @see __get + */ + public function __set($name,$value) + { + $setter='set'.$name; + if(method_exists($this,$setter)) + return $this->$setter($value); + elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) + { + // duplicating getEventHandlers() here for performance + $name=strtolower($name); + if(!isset($this->_e[$name])) + $this->_e[$name]=new CList; + return $this->_e[$name]->add($value); + } + elseif(is_array($this->_m)) + { + foreach($this->_m as $object) + { + if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name))) + return $object->$name=$value; + } + } + if(method_exists($this,'get'.$name)) + throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.', + array('{class}'=>get_class($this), '{property}'=>$name))); + else + throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.', + array('{class}'=>get_class($this), '{property}'=>$name))); + } + + /** + * Checks if a property value is null. + * Do not call this method. This is a PHP magic method that we override + * to allow using isset() to detect if a component property is set or not. + * @param string $name the property name or the event name + * @return boolean + */ + public function __isset($name) + { + $getter='get'.$name; + if(method_exists($this,$getter)) + return $this->$getter()!==null; + elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) + { + $name=strtolower($name); + return isset($this->_e[$name]) && $this->_e[$name]->getCount(); + } + elseif(is_array($this->_m)) + { + if(isset($this->_m[$name])) + return true; + foreach($this->_m as $object) + { + if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name))) + return $object->$name!==null; + } + } + return false; + } + + /** + * Sets a component property to be null. + * Do not call this method. This is a PHP magic method that we override + * to allow using unset() to set a component property to be null. + * @param string $name the property name or the event name + * @throws CException if the property is read only. + * @return mixed + */ + public function __unset($name) + { + $setter='set'.$name; + if(method_exists($this,$setter)) + $this->$setter(null); + elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) + unset($this->_e[strtolower($name)]); + elseif(is_array($this->_m)) + { + if(isset($this->_m[$name])) + $this->detachBehavior($name); + else + { + foreach($this->_m as $object) + { + if($object->getEnabled()) + { + if(property_exists($object,$name)) + return $object->$name=null; + elseif($object->canSetProperty($name)) + return $object->$setter(null); + } + } + } + } + elseif(method_exists($this,'get'.$name)) + throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.', + array('{class}'=>get_class($this), '{property}'=>$name))); + } + + /** + * Calls the named method which is not a class method. + * Do not call this method. This is a PHP magic method that we override + * to implement the behavior feature. + * @param string $name the method name + * @param array $parameters method parameters + * @throws CException if current class and its behaviors do not have a method or closure with the given name + * @return mixed the method return value + */ + public function __call($name,$parameters) + { + if($this->_m!==null) + { + foreach($this->_m as $object) + { + if($object->getEnabled() && method_exists($object,$name)) + return call_user_func_array(array($object,$name),$parameters); + } + } + if(class_exists('Closure', false) && $this->canGetProperty($name) && $this->$name instanceof Closure) + return call_user_func_array($this->$name, $parameters); + throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".', + array('{class}'=>get_class($this), '{name}'=>$name))); + } + + /** + * Returns the named behavior object. + * The name 'asa' stands for 'as a'. + * @param string $behavior the behavior name + * @return IBehavior the behavior object, or null if the behavior does not exist + */ + public function asa($behavior) + { + return isset($this->_m[$behavior]) ? $this->_m[$behavior] : null; + } + + /** + * Attaches a list of behaviors to the component. + * Each behavior is indexed by its name and should be an instance of + * {@link IBehavior}, a string specifying the behavior class, or an + * array of the following structure: + *
+ * array( + * 'class'=>'path.to.BehaviorClass', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ) + *+ * @param array $behaviors list of behaviors to be attached to the component + */ + public function attachBehaviors($behaviors) + { + foreach($behaviors as $name=>$behavior) + $this->attachBehavior($name,$behavior); + } + + /** + * Detaches all behaviors from the component. + */ + public function detachBehaviors() + { + if($this->_m!==null) + { + foreach($this->_m as $name=>$behavior) + $this->detachBehavior($name); + $this->_m=null; + } + } + + /** + * Attaches a behavior to this component. + * This method will create the behavior object based on the given + * configuration. After that, the behavior object will be initialized + * by calling its {@link IBehavior::attach} method. + * @param string $name the behavior's name. It should uniquely identify this behavior. + * @param mixed $behavior the behavior configuration. This is passed as the first + * parameter to {@link YiiBase::createComponent} to create the behavior object. + * You can also pass an already created behavior instance (the new behavior will replace an already created + * behavior with the same name, if it exists). + * @return IBehavior the behavior object + */ + public function attachBehavior($name,$behavior) + { + if(!($behavior instanceof IBehavior)) + $behavior=Yii::createComponent($behavior); + $behavior->setEnabled(true); + $behavior->attach($this); + return $this->_m[$name]=$behavior; + } + + /** + * Detaches a behavior from the component. + * The behavior's {@link IBehavior::detach} method will be invoked. + * @param string $name the behavior's name. It uniquely identifies the behavior. + * @return IBehavior the detached behavior. Null if the behavior does not exist. + */ + public function detachBehavior($name) + { + if(isset($this->_m[$name])) + { + $this->_m[$name]->detach($this); + $behavior=$this->_m[$name]; + unset($this->_m[$name]); + return $behavior; + } + } + + /** + * Enables all behaviors attached to this component. + */ + public function enableBehaviors() + { + if($this->_m!==null) + { + foreach($this->_m as $behavior) + $behavior->setEnabled(true); + } + } + + /** + * Disables all behaviors attached to this component. + */ + public function disableBehaviors() + { + if($this->_m!==null) + { + foreach($this->_m as $behavior) + $behavior->setEnabled(false); + } + } + + /** + * Enables an attached behavior. + * A behavior is only effective when it is enabled. + * A behavior is enabled when first attached. + * @param string $name the behavior's name. It uniquely identifies the behavior. + */ + public function enableBehavior($name) + { + if(isset($this->_m[$name])) + $this->_m[$name]->setEnabled(true); + } + + /** + * Disables an attached behavior. + * A behavior is only effective when it is enabled. + * @param string $name the behavior's name. It uniquely identifies the behavior. + */ + public function disableBehavior($name) + { + if(isset($this->_m[$name])) + $this->_m[$name]->setEnabled(false); + } + + /** + * Determines whether a property is defined. + * A property is defined if there is a getter or setter method + * defined in the class. Note, property names are case-insensitive. + * @param string $name the property name + * @return boolean whether the property is defined + * @see canGetProperty + * @see canSetProperty + */ + public function hasProperty($name) + { + return method_exists($this,'get'.$name) || method_exists($this,'set'.$name); + } + + /** + * Determines whether a property can be read. + * A property can be read if the class has a getter method + * for the property name. Note, property name is case-insensitive. + * @param string $name the property name + * @return boolean whether the property can be read + * @see canSetProperty + */ + public function canGetProperty($name) + { + return method_exists($this,'get'.$name); + } + + /** + * Determines whether a property can be set. + * A property can be written if the class has a setter method + * for the property name. Note, property name is case-insensitive. + * @param string $name the property name + * @return boolean whether the property can be written + * @see canGetProperty + */ + public function canSetProperty($name) + { + return method_exists($this,'set'.$name); + } + + /** + * Determines whether an event is defined. + * An event is defined if the class has a method named like 'onXXX'. + * Note, event name is case-insensitive. + * @param string $name the event name + * @return boolean whether an event is defined + */ + public function hasEvent($name) + { + return !strncasecmp($name,'on',2) && method_exists($this,$name); + } + + /** + * Checks whether the named event has attached handlers. + * @param string $name the event name + * @return boolean whether an event has been attached one or several handlers + */ + public function hasEventHandler($name) + { + $name=strtolower($name); + return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0; + } + + /** + * Returns the list of attached event handlers for an event. + * @param string $name the event name + * @return CList list of attached event handlers for the event + * @throws CException if the event is not defined + */ + public function getEventHandlers($name) + { + if($this->hasEvent($name)) + { + $name=strtolower($name); + if(!isset($this->_e[$name])) + $this->_e[$name]=new CList; + return $this->_e[$name]; + } + else + throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', + array('{class}'=>get_class($this), '{event}'=>$name))); + } + + /** + * Attaches an event handler to an event. + * + * An event handler must be a valid PHP callback, i.e., a string referring to + * a global function name, or an array containing two elements with + * the first element being an object and the second element a method name + * of the object. + * + * An event handler must be defined with the following signature, + *
+ * function handlerName($event) {} + *+ * where $event includes parameters associated with the event. + * + * This is a convenient method of attaching a handler to an event. + * It is equivalent to the following code: + *
+ * $component->getEventHandlers($eventName)->add($eventHandler); + *+ * + * Using {@link getEventHandlers}, one can also specify the excution order + * of multiple handlers attaching to the same event. For example: + *
+ * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler); + *+ * makes the handler to be invoked first. + * + * @param string $name the event name + * @param callback $handler the event handler + * @throws CException if the event is not defined + * @see detachEventHandler + */ + public function attachEventHandler($name,$handler) + { + $this->getEventHandlers($name)->add($handler); + } + + /** + * Detaches an existing event handler. + * This method is the opposite of {@link attachEventHandler}. + * @param string $name event name + * @param callback $handler the event handler to be removed + * @return boolean if the detachment process is successful + * @see attachEventHandler + */ + public function detachEventHandler($name,$handler) + { + if($this->hasEventHandler($name)) + return $this->getEventHandlers($name)->remove($handler)!==false; + else + return false; + } + + /** + * Raises an event. + * This method represents the happening of an event. It invokes + * all attached handlers for the event. + * @param string $name the event name + * @param CEvent $event the event parameter + * @throws CException if the event is undefined or an event handler is invalid. + */ + public function raiseEvent($name,$event) + { + $name=strtolower($name); + if(isset($this->_e[$name])) + { + foreach($this->_e[$name] as $handler) + { + if(is_string($handler)) + call_user_func($handler,$event); + elseif(is_callable($handler,true)) + { + if(is_array($handler)) + { + // an array: 0 - object, 1 - method name + list($object,$method)=$handler; + if(is_string($object)) // static method call + call_user_func($handler,$event); + elseif(method_exists($object,$method)) + $object->$method($event); + else + throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', + array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1]))); + } + else // PHP 5.3: anonymous function + call_user_func($handler,$event); + } + else + throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', + array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler)))); + // stop further handling if param.handled is set true + if(($event instanceof CEvent) && $event->handled) + return; + } + } + elseif(YII_DEBUG && !$this->hasEvent($name)) + throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', + array('{class}'=>get_class($this), '{event}'=>$name))); + } + + /** + * Evaluates a PHP expression or callback under the context of this component. + * + * Valid PHP callback can be class method name in the form of + * array(ClassName/Object, MethodName), or anonymous function (only available in PHP 5.3.0 or above). + * + * If a PHP callback is used, the corresponding function/method signature should be + *
+ * function foo($param1, $param2, ..., $component) { ... } + *+ * where the array elements in the second parameter to this method will be passed + * to the callback as $param1, $param2, ...; and the last parameter will be the component itself. + * + * If a PHP expression is used, the second parameter will be "extracted" into PHP variables + * that can be directly accessed in the expression. See {@link http://us.php.net/manual/en/function.extract.php PHP extract} + * for more details. In the expression, the component object can be accessed using $this. + * + * A PHP expression can be any PHP code that has a value. To learn more about what an expression is, + * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}. + * + * @param mixed $_expression_ a PHP expression or PHP callback to be evaluated. + * @param array $_data_ additional parameters to be passed to the above expression/callback. + * @return mixed the expression result + * @since 1.1.0 + */ + public function evaluateExpression($_expression_,$_data_=array()) + { + if(is_string($_expression_)) + { + extract($_data_); + return eval('return '.$_expression_.';'); + } + else + { + $_data_[]=$this; + return call_user_func_array($_expression_, $_data_); + } + } +} + + +/** + * CEvent is the base class for all event classes. + * + * It encapsulates the parameters associated with an event. + * The {@link sender} property describes who raises the event. + * And the {@link handled} property indicates if the event is handled. + * If an event handler sets {@link handled} to true, those handlers + * that are not invoked yet will not be invoked anymore. + * + * @author Qiang Xue
+ * class TextAlign extends CEnumerable + * { + * const Left='Left'; + * const Right='Right'; + * } + *+ * Then, one can use the enumerable values such as TextAlign::Left and + * TextAlign::Right. + * + * @author Qiang Xue
exception.php
;
+ * error<StatusCode>.php
;
+ * themes/ThemeName/views/system
: when a theme is active.protected/views/system
framework/views
'.$output.'
+ * array('attribute list', 'validator name', 'on'=>'scenario name', ...validation parameters...) + *+ * where + *
+ * // $params refers to validation parameters given in the rule + * function validatorName($attribute,$params) + *+ * A built-in validator refers to one of the validators declared in {@link CValidator::builtInValidators}. + * And a validator class is a class extending {@link CValidator}.
+ * array( + * array('username', 'required'), + * array('username', 'length', 'min'=>3, 'max'=>12), + * array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), + * array('password', 'authenticate', 'on'=>'login'), + * ); + *+ * + * Note, in order to inherit rules defined in the parent class, a child class needs to + * merge the parent rules with child rules using functions like array_merge(). + * + * @return array validation rules to be applied when {@link validate()} is called. + * @see scenario + */ + public function rules() + { + return array(); + } + + /** + * Returns a list of behaviors that this model should behave as. + * The return value should be an array of behavior configurations indexed by + * behavior names. Each behavior configuration can be either a string specifying + * the behavior class or an array of the following structure: + *
+ * 'behaviorName'=>array( + * 'class'=>'path.to.BehaviorClass', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ) + *+ * + * Note, the behavior classes must implement {@link IBehavior} or extend from + * {@link CBehavior}. Behaviors declared in this method will be attached + * to the model when it is instantiated. + * + * For more details about behaviors, see {@link CComponent}. + * @return array the behavior configurations (behavior name=>behavior configuration) + */ + public function behaviors() + { + return array(); + } + + /** + * Returns the attribute labels. + * Attribute labels are mainly used in error messages of validation. + * By default an attribute label is generated using {@link generateAttributeLabel}. + * This method allows you to explicitly specify attribute labels. + * + * Note, in order to inherit labels defined in the parent class, a child class needs to + * merge the parent labels with child labels using functions like array_merge(). + * + * @return array attribute labels (name=>label) + * @see generateAttributeLabel + */ + public function attributeLabels() + { + return array(); + } + + /** + * Performs the validation. + * + * This method executes the validation rules as declared in {@link rules}. + * Only the rules applicable to the current {@link scenario} will be executed. + * A rule is considered applicable to a scenario if its 'on' option is not set + * or contains the scenario. + * + * Errors found during the validation can be retrieved via {@link getErrors}. + * + * @param array $attributes list of attributes that should be validated. Defaults to null, + * meaning any attribute listed in the applicable validation rules should be + * validated. If this parameter is given as a list of attributes, only + * the listed attributes will be validated. + * @param boolean $clearErrors whether to call {@link clearErrors} before performing validation + * @return boolean whether the validation is successful without any error. + * @see beforeValidate + * @see afterValidate + */ + public function validate($attributes=null, $clearErrors=true) + { + if($clearErrors) + $this->clearErrors(); + if($this->beforeValidate()) + { + foreach($this->getValidators() as $validator) + $validator->validate($this,$attributes); + $this->afterValidate(); + return !$this->hasErrors(); + } + else + return false; + } + + /** + * This method is invoked after a model instance is created by new operator. + * The default implementation raises the {@link onAfterConstruct} event. + * You may override this method to do postprocessing after model creation. + * Make sure you call the parent implementation so that the event is raised properly. + */ + protected function afterConstruct() + { + if($this->hasEventHandler('onAfterConstruct')) + $this->onAfterConstruct(new CEvent($this)); + } + + /** + * This method is invoked before validation starts. + * The default implementation calls {@link onBeforeValidate} to raise an event. + * You may override this method to do preliminary checks before validation. + * Make sure the parent implementation is invoked so that the event can be raised. + * @return boolean whether validation should be executed. Defaults to true. + * If false is returned, the validation will stop and the model is considered invalid. + */ + protected function beforeValidate() + { + $event=new CModelEvent($this); + $this->onBeforeValidate($event); + return $event->isValid; + } + + /** + * This method is invoked after validation ends. + * The default implementation calls {@link onAfterValidate} to raise an event. + * You may override this method to do postprocessing after validation. + * Make sure the parent implementation is invoked so that the event can be raised. + */ + protected function afterValidate() + { + $this->onAfterValidate(new CEvent($this)); + } + + /** + * This event is raised after the model instance is created by new operator. + * @param CEvent $event the event parameter + */ + public function onAfterConstruct($event) + { + $this->raiseEvent('onAfterConstruct',$event); + } + + /** + * This event is raised before the validation is performed. + * @param CModelEvent $event the event parameter + */ + public function onBeforeValidate($event) + { + $this->raiseEvent('onBeforeValidate',$event); + } + + /** + * This event is raised after the validation is performed. + * @param CEvent $event the event parameter + */ + public function onAfterValidate($event) + { + $this->raiseEvent('onAfterValidate',$event); + } + + /** + * Returns all the validators declared in the model. + * This method differs from {@link getValidators} in that the latter + * would only return the validators applicable to the current {@link scenario}. + * Also, since this method return a {@link CList} object, you may + * manipulate it by inserting or removing validators (useful in behaviors). + * For example,
$model->validatorList->add($newValidator)
.
+ * The change made to the {@link CList} object will persist and reflect
+ * in the result of the next call of {@link getValidators}.
+ * @return CList all the validators declared in the model.
+ * @since 1.1.2
+ */
+ public function getValidatorList()
+ {
+ if($this->_validators===null)
+ $this->_validators=$this->createValidators();
+ return $this->_validators;
+ }
+
+ /**
+ * Returns the validators applicable to the current {@link scenario}.
+ * @param string $attribute the name of the attribute whose validators should be returned.
+ * If this is null, the validators for ALL attributes in the model will be returned.
+ * @return array the validators applicable to the current {@link scenario}.
+ */
+ public function getValidators($attribute=null)
+ {
+ if($this->_validators===null)
+ $this->_validators=$this->createValidators();
+
+ $validators=array();
+ $scenario=$this->getScenario();
+ foreach($this->_validators as $validator)
+ {
+ if($validator->applyTo($scenario))
+ {
+ if($attribute===null || in_array($attribute,$validator->attributes,true))
+ $validators[]=$validator;
+ }
+ }
+ return $validators;
+ }
+
+ /**
+ * Creates validator objects based on the specification in {@link rules}.
+ * This method is mainly used internally.
+ * @throws CException if current class has an invalid validation rule
+ * @return CList validators built based on {@link rules()}.
+ */
+ public function createValidators()
+ {
+ $validators=new CList;
+ foreach($this->rules() as $rule)
+ {
+ if(isset($rule[0],$rule[1])) // attributes, validator name
+ $validators->add(CValidator::createValidator($rule[1],$this,$rule[0],array_slice($rule,2)));
+ else
+ throw new CException(Yii::t('yii','{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.',
+ array('{class}'=>get_class($this))));
+ }
+ return $validators;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is required.
+ * This is determined by checking if the attribute is associated with a
+ * {@link CRequiredValidator} validation rule in the current {@link scenario}.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is required
+ */
+ public function isAttributeRequired($attribute)
+ {
+ foreach($this->getValidators($attribute) as $validator)
+ {
+ if($validator instanceof CRequiredValidator)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is safe for massive assignments.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is safe for massive assignments
+ * @since 1.1
+ */
+ public function isAttributeSafe($attribute)
+ {
+ $attributes=$this->getSafeAttributeNames();
+ return in_array($attribute,$attributes);
+ }
+
+ /**
+ * Returns the text label for the specified attribute.
+ * @param string $attribute the attribute name
+ * @return string the attribute label
+ * @see generateAttributeLabel
+ * @see attributeLabels
+ */
+ public function getAttributeLabel($attribute)
+ {
+ $labels=$this->attributeLabels();
+ if(isset($labels[$attribute]))
+ return $labels[$attribute];
+ else
+ return $this->generateAttributeLabel($attribute);
+ }
+
+ /**
+ * Returns a value indicating whether there is any validation error.
+ * @param string $attribute attribute name. Use null to check all attributes.
+ * @return boolean whether there is any error.
+ */
+ public function hasErrors($attribute=null)
+ {
+ if($attribute===null)
+ return $this->_errors!==array();
+ else
+ return isset($this->_errors[$attribute]);
+ }
+
+ /**
+ * Returns the errors for all attribute or a single attribute.
+ * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
+ * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
+ */
+ public function getErrors($attribute=null)
+ {
+ if($attribute===null)
+ return $this->_errors;
+ else
+ return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();
+ }
+
+ /**
+ * Returns the first error of the specified attribute.
+ * @param string $attribute attribute name.
+ * @return string the error message. Null is returned if no error.
+ */
+ public function getError($attribute)
+ {
+ return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
+ }
+
+ /**
+ * Adds a new error to the specified attribute.
+ * @param string $attribute attribute name
+ * @param string $error new error message
+ */
+ public function addError($attribute,$error)
+ {
+ $this->_errors[$attribute][]=$error;
+ }
+
+ /**
+ * Adds a list of errors.
+ * @param array $errors a list of errors. The array keys must be attribute names.
+ * The array values should be error messages. If an attribute has multiple errors,
+ * these errors must be given in terms of an array.
+ * You may use the result of {@link getErrors} as the value for this parameter.
+ */
+ public function addErrors($errors)
+ {
+ foreach($errors as $attribute=>$error)
+ {
+ if(is_array($error))
+ {
+ foreach($error as $e)
+ $this->addError($attribute, $e);
+ }
+ else
+ $this->addError($attribute, $error);
+ }
+ }
+
+ /**
+ * Removes errors for all attributes or a single attribute.
+ * @param string $attribute attribute name. Use null to remove errors for all attribute.
+ */
+ public function clearErrors($attribute=null)
+ {
+ if($attribute===null)
+ $this->_errors=array();
+ else
+ unset($this->_errors[$attribute]);
+ }
+
+ /**
+ * Generates a user friendly attribute label.
+ * This is done by replacing underscores or dashes with blanks and
+ * changing the first letter of each word to upper case.
+ * For example, 'department_name' or 'DepartmentName' becomes 'Department Name'.
+ * @param string $name the column name
+ * @return string the attribute label
+ */
+ public function generateAttributeLabel($name)
+ {
+ return ucwords(trim(strtolower(str_replace(array('-','_','.'),' ',preg_replace('/(?value).
+ */
+ public function getAttributes($names=null)
+ {
+ $values=array();
+ foreach($this->attributeNames() as $name)
+ $values[$name]=$this->$name;
+
+ if(is_array($names))
+ {
+ $values2=array();
+ foreach($names as $name)
+ $values2[$name]=isset($values[$name]) ? $values[$name] : null;
+ return $values2;
+ }
+ else
+ return $values;
+ }
+
+ /**
+ * Sets the attribute values in a massive way.
+ * @param array $values attribute values (name=>value) to be set.
+ * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
+ * A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
+ * @see getSafeAttributeNames
+ * @see attributeNames
+ */
+ public function setAttributes($values,$safeOnly=true)
+ {
+ if(!is_array($values))
+ return;
+ $attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
+ foreach($values as $name=>$value)
+ {
+ if(isset($attributes[$name]))
+ $this->$name=$value;
+ elseif($safeOnly)
+ $this->onUnsafeAttribute($name,$value);
+ }
+ }
+
+ /**
+ * Sets the attributes to be null.
+ * @param array $names list of attributes to be set null. If this parameter is not given,
+ * all attributes as specified by {@link attributeNames} will have their values unset.
+ * @since 1.1.3
+ */
+ public function unsetAttributes($names=null)
+ {
+ if($names===null)
+ $names=$this->attributeNames();
+ foreach($names as $name)
+ $this->$name=null;
+ }
+
+ /**
+ * This method is invoked when an unsafe attribute is being massively assigned.
+ * The default implementation will log a warning message if YII_DEBUG is on.
+ * It does nothing otherwise.
+ * @param string $name the unsafe attribute name
+ * @param mixed $value the attribute value
+ * @since 1.1.1
+ */
+ public function onUnsafeAttribute($name,$value)
+ {
+ if(YII_DEBUG)
+ Yii::log(Yii::t('yii','Failed to set unsafe attribute "{attribute}" of "{class}".',array('{attribute}'=>$name, '{class}'=>get_class($this))),CLogger::LEVEL_WARNING);
+ }
+
+ /**
+ * Returns the scenario that this model is used in.
+ *
+ * Scenario affects how validation is performed and which attributes can
+ * be massively assigned.
+ *
+ * A validation rule will be performed when calling {@link validate()}
+ * if its 'except' value does not contain current scenario value while
+ * 'on' option is not set or contains the current scenario value.
+ *
+ * And an attribute can be massively assigned if it is associated with
+ * a validation rule for the current scenario. Note that an exception is
+ * the {@link CUnsafeValidator unsafe} validator which marks the associated
+ * attributes as unsafe and not allowed to be massively assigned.
+ *
+ * @return string the scenario that this model is in.
+ */
+ public function getScenario()
+ {
+ return $this->_scenario;
+ }
+
+ /**
+ * Sets the scenario for the model.
+ * @param string $value the scenario that this model is in.
+ * @see getScenario
+ */
+ public function setScenario($value)
+ {
+ $this->_scenario=$value;
+ }
+
+ /**
+ * Returns the attribute names that are safe to be massively assigned.
+ * A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
+ * @return array safe attribute names
+ */
+ public function getSafeAttributeNames()
+ {
+ $attributes=array();
+ $unsafe=array();
+ foreach($this->getValidators() as $validator)
+ {
+ if(!$validator->safe)
+ {
+ foreach($validator->attributes as $name)
+ $unsafe[]=$name;
+ }
+ else
+ {
+ foreach($validator->attributes as $name)
+ $attributes[$name]=true;
+ }
+ }
+
+ foreach($unsafe as $name)
+ unset($attributes[$name]);
+ return array_keys($attributes);
+ }
+
+ /**
+ * Returns an iterator for traversing the attributes in the model.
+ * This method is required by the interface IteratorAggregate.
+ * @return CMapIterator an iterator for traversing the items in the list.
+ */
+ public function getIterator()
+ {
+ $attributes=$this->getAttributes();
+ return new CMapIterator($attributes);
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return property_exists($this,$offset);
+ }
+
+ /**
+ * Returns the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ return $this->$offset;
+ }
+
+ /**
+ * Sets the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer $offset the offset to set element
+ * @param mixed $item the element value
+ */
+ public function offsetSet($offset,$item)
+ {
+ $this->$offset=$item;
+ }
+
+ /**
+ * Unsets the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->$offset);
+ }
+}
diff --git a/framework/base/CModelBehavior.php b/framework/base/CModelBehavior.php
new file mode 100644
index 0000000..c4a55fe
--- /dev/null
+++ b/framework/base/CModelBehavior.php
@@ -0,0 +1,68 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CModelBehavior is a base class for behaviors that are attached to a model component.
+ * The model should extend from {@link CModel} or its child classes.
+ *
+ * @property CModel $owner The owner model that this behavior is attached to.
+ *
+ * @author Qiang Xue + * array( + * 'models'=>'application.models', // an existing alias + * 'extensions'=>'application.extensions', // an existing alias + * 'backend'=>dirname(__FILE__).'/../backend', // a directory + * ) + *. + * + * @author Qiang Xue
+ * array( + * 'models'=>'application.models', // an existing alias + * 'extensions'=>'application.extensions', // an existing alias + * 'backend'=>dirname(__FILE__).'/../backend', // a directory + * ) + *+ */ + public function setAliases($mappings) + { + foreach($mappings as $name=>$alias) + { + if(($path=Yii::getPathOfAlias($alias))!==false) + Yii::setPathOfAlias($name,$path); + else + Yii::setPathOfAlias($name,$alias); + } + } + + /** + * Returns the parent module. + * @return CModule the parent module. Null if this module does not have a parent. + */ + public function getParentModule() + { + return $this->_parentModule; + } + + /** + * Retrieves the named application module. + * The module has to be declared in {@link modules}. A new instance will be created + * when calling this method with the given ID for the first time. + * @param string $id application module ID (case-sensitive) + * @return CModule the module instance, null if the module is disabled or does not exist. + */ + public function getModule($id) + { + if(isset($this->_modules[$id]) || array_key_exists($id,$this->_modules)) + return $this->_modules[$id]; + elseif(isset($this->_moduleConfig[$id])) + { + $config=$this->_moduleConfig[$id]; + if(!isset($config['enabled']) || $config['enabled']) + { + Yii::trace("Loading \"$id\" module",'system.base.CModule'); + $class=$config['class']; + unset($config['class'], $config['enabled']); + if($this===Yii::app()) + $module=Yii::createComponent($class,$id,null,$config); + else + $module=Yii::createComponent($class,$this->getId().'/'.$id,$this,$config); + return $this->_modules[$id]=$module; + } + } + } + + /** + * Returns a value indicating whether the specified module is installed. + * @param string $id the module ID + * @return boolean whether the specified module is installed. + * @since 1.1.2 + */ + public function hasModule($id) + { + return isset($this->_moduleConfig[$id]) || isset($this->_modules[$id]); + } + + /** + * Returns the configuration of the currently installed modules. + * @return array the configuration of the currently installed modules (module ID => configuration) + */ + public function getModules() + { + return $this->_moduleConfig; + } + + /** + * Configures the sub-modules of this module. + * + * Call this method to declare sub-modules and configure them with their initial property values. + * The parameter should be an array of module configurations. Each array element represents a single module, + * which can be either a string representing the module ID or an ID-configuration pair representing + * a module with the specified ID and the initial property values. + * + * For example, the following array declares two modules: + *
+ * array( + * 'admin', // a single module ID + * 'payment'=>array( // ID-configuration pair + * 'server'=>'paymentserver.com', + * ), + * ) + *+ * + * By default, the module class is determined using the expression
ucfirst($moduleID).'Module'
.
+ * And the class file is located under modules/$moduleID
.
+ * You may override this default by explicitly specifying the 'class' option in the configuration.
+ *
+ * You may also enable or disable a module by specifying the 'enabled' option in the configuration.
+ *
+ * @param array $modules module configurations.
+ */
+ public function setModules($modules)
+ {
+ foreach($modules as $id=>$module)
+ {
+ if(is_int($id))
+ {
+ $id=$module;
+ $module=array();
+ }
+ if(!isset($module['class']))
+ {
+ Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id);
+ $module['class']=$id.'.'.ucfirst($id).'Module';
+ }
+
+ if(isset($this->_moduleConfig[$id]))
+ $this->_moduleConfig[$id]=CMap::mergeArray($this->_moduleConfig[$id],$module);
+ else
+ $this->_moduleConfig[$id]=$module;
+ }
+ }
+
+ /**
+ * Checks whether the named component exists.
+ * @param string $id application component ID
+ * @return boolean whether the named application component exists (including both loaded and disabled.)
+ */
+ public function hasComponent($id)
+ {
+ return isset($this->_components[$id]) || isset($this->_componentConfig[$id]);
+ }
+
+ /**
+ * Retrieves the named application component.
+ * @param string $id application component ID (case-sensitive)
+ * @param boolean $createIfNull whether to create the component if it doesn't exist yet.
+ * @return IApplicationComponent the application component instance, null if the application component is disabled or does not exist.
+ * @see hasComponent
+ */
+ public function getComponent($id,$createIfNull=true)
+ {
+ if(isset($this->_components[$id]))
+ return $this->_components[$id];
+ elseif(isset($this->_componentConfig[$id]) && $createIfNull)
+ {
+ $config=$this->_componentConfig[$id];
+ if(!isset($config['enabled']) || $config['enabled'])
+ {
+ Yii::trace("Loading \"$id\" application component",'system.CModule');
+ unset($config['enabled']);
+ $component=Yii::createComponent($config);
+ $component->init();
+ return $this->_components[$id]=$component;
+ }
+ }
+ }
+
+ /**
+ * Puts a component under the management of the module.
+ * The component will be initialized by calling its {@link CApplicationComponent::init() init()}
+ * method if it has not done so.
+ * @param string $id component ID
+ * @param array|IApplicationComponent $component application component
+ * (either configuration array or instance). If this parameter is null,
+ * component will be unloaded from the module.
+ * @param boolean $merge whether to merge the new component configuration
+ * with the existing one. Defaults to true, meaning the previously registered
+ * component configuration with the same ID will be merged with the new configuration.
+ * If set to false, the existing configuration will be replaced completely.
+ * This parameter is available since 1.1.13.
+ */
+ public function setComponent($id,$component,$merge=true)
+ {
+ if($component===null)
+ {
+ unset($this->_components[$id]);
+ return;
+ }
+ elseif($component instanceof IApplicationComponent)
+ {
+ $this->_components[$id]=$component;
+
+ if(!$component->getIsInitialized())
+ $component->init();
+
+ return;
+ }
+ elseif(isset($this->_components[$id]))
+ {
+ if(isset($component['class']) && get_class($this->_components[$id])!==$component['class'])
+ {
+ unset($this->_components[$id]);
+ $this->_componentConfig[$id]=$component; //we should ignore merge here
+ return;
+ }
+
+ foreach($component as $key=>$value)
+ {
+ if($key!=='class')
+ $this->_components[$id]->$key=$value;
+ }
+ }
+ elseif(isset($this->_componentConfig[$id]['class'],$component['class'])
+ && $this->_componentConfig[$id]['class']!==$component['class'])
+ {
+ $this->_componentConfig[$id]=$component; //we should ignore merge here
+ return;
+ }
+
+ if(isset($this->_componentConfig[$id]) && $merge)
+ $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component);
+ else
+ $this->_componentConfig[$id]=$component;
+ }
+
+ /**
+ * Returns the application components.
+ * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
+ * then all components specified in the configuration will be returned, whether they are loaded or not.
+ * Loaded components will be returned as objects, while unloaded components as configuration arrays.
+ * This parameter has been available since version 1.1.3.
+ * @return array the application components (indexed by their IDs)
+ */
+ public function getComponents($loadedOnly=true)
+ {
+ if($loadedOnly)
+ return $this->_components;
+ else
+ return array_merge($this->_componentConfig, $this->_components);
+ }
+
+ /**
+ * Sets the application components.
+ *
+ * When a configuration is used to specify a component, it should consist of
+ * the component's initial property values (name-value pairs). Additionally,
+ * a component can be enabled (default) or disabled by specifying the 'enabled' value
+ * in the configuration.
+ *
+ * If a configuration is specified with an ID that is the same as an existing
+ * component or configuration, the existing one will be replaced silently.
+ *
+ * The following is the configuration for two components:
+ * + * array( + * 'db'=>array( + * 'class'=>'CDbConnection', + * 'connectionString'=>'sqlite:path/to/file.db', + * ), + * 'cache'=>array( + * 'class'=>'CDbCache', + * 'connectionID'=>'db', + * 'enabled'=>!YII_DEBUG, // enable caching in non-debug mode + * ), + * ) + *+ * + * @param array $components application components(id=>component configuration or instances) + * @param boolean $merge whether to merge the new component configuration with the existing one. + * Defaults to true, meaning the previously registered component configuration of the same ID + * will be merged with the new configuration. If false, the existing configuration will be replaced completely. + */ + public function setComponents($components,$merge=true) + { + foreach($components as $id=>$component) + $this->setComponent($id,$component,$merge); + } + + /** + * Configures the module with the specified configuration. + * @param array $config the configuration array + */ + public function configure($config) + { + if(is_array($config)) + { + foreach($config as $key=>$value) + $this->$key=$value; + } + } + + /** + * Loads static application components. + */ + protected function preloadComponents() + { + foreach($this->preload as $id) + $this->getComponent($id); + } + + /** + * Preinitializes the module. + * This method is called at the beginning of the module constructor. + * You may override this method to do some customized preinitialization work. + * Note that at this moment, the module is not configured yet. + * @see init + */ + protected function preinit() + { + } + + /** + * Initializes the module. + * This method is called at the end of the module constructor. + * Note that at this moment, the module has been configured, the behaviors + * have been attached and the application components have been registered. + * @see preinit + */ + protected function init() + { + } +} diff --git a/framework/base/CSecurityManager.php b/framework/base/CSecurityManager.php new file mode 100644 index 0000000..e4c4640 --- /dev/null +++ b/framework/base/CSecurityManager.php @@ -0,0 +1,492 @@ + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CSecurityManager provides private keys, hashing and encryption functions. + * + * CSecurityManager is used by Yii components and applications for security-related purpose. + * For example, it is used in cookie validation feature to prevent cookie data + * from being tampered. + * + * CSecurityManager is mainly used to protect data from being tampered and viewed. + * It can generate HMAC and encrypt the data. The private key used to generate HMAC + * is set by {@link setValidationKey ValidationKey}. The key used to encrypt data is + * specified by {@link setEncryptionKey EncryptionKey}. If the above keys are not + * explicitly set, random keys will be generated and used. + * + * To protected data with HMAC, call {@link hashData()}; and to check if the data + * is tampered, call {@link validateData()}, which will return the real data if + * it is not tampered. The algorithm used to generated HMAC is specified by + * {@link validation}. + * + * To encrypt and decrypt data, call {@link encrypt()} and {@link decrypt()} + * respectively, which uses 3DES encryption algorithm. Note, the PHP Mcrypt + * extension must be installed and loaded. + * + * CSecurityManager is a core application component that can be accessed via + * {@link CApplication::getSecurityManager()}. + * + * @property string $validationKey The private key used to generate HMAC. + * If the key is not explicitly set, a random one is generated and returned. + * @property string $encryptionKey The private key used to encrypt/decrypt data. + * If the key is not explicitly set, a random one is generated and returned. + * @property string $validation + * + * @author Qiang Xue
array('rijndael-256', '', 'ofb', '')
.
+ *
+ * Defaults to 'des', meaning using DES crypt algorithm.
+ * @since 1.1.3
+ */
+ public $cryptAlgorithm='des';
+
+ private $_validationKey;
+ private $_encryptionKey;
+ private $_mbstring;
+
+ public function init()
+ {
+ parent::init();
+ $this->_mbstring=extension_loaded('mbstring');
+ }
+
+ /**
+ * @return string a randomly generated private key.
+ * @deprecated in favor of {@link generateRandomString()} since 1.1.14. Never use this method.
+ */
+ protected function generateRandomKey()
+ {
+ return $this->generateRandomString(32);
+ }
+
+ /**
+ * @return string the private key used to generate HMAC.
+ * If the key is not explicitly set, a random one is generated and returned.
+ * @throws CException in case random string cannot be generated.
+ */
+ public function getValidationKey()
+ {
+ if($this->_validationKey!==null)
+ return $this->_validationKey;
+ else
+ {
+ if(($key=Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY))!==null)
+ $this->setValidationKey($key);
+ else
+ {
+ if(($key=$this->generateRandomString(32,true))===false)
+ if(($key=$this->generateRandomString(32,false))===false)
+ throw new CException(Yii::t('yii',
+ 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.'));
+ $this->setValidationKey($key);
+ Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY,$key);
+ }
+ return $this->_validationKey;
+ }
+ }
+
+ /**
+ * @param string $value the key used to generate HMAC
+ * @throws CException if the key is empty
+ */
+ public function setValidationKey($value)
+ {
+ if(!empty($value))
+ $this->_validationKey=$value;
+ else
+ throw new CException(Yii::t('yii','CSecurityManager.validationKey cannot be empty.'));
+ }
+
+ /**
+ * @return string the private key used to encrypt/decrypt data.
+ * If the key is not explicitly set, a random one is generated and returned.
+ * @throws CException in case random string cannot be generated.
+ */
+ public function getEncryptionKey()
+ {
+ if($this->_encryptionKey!==null)
+ return $this->_encryptionKey;
+ else
+ {
+ if(($key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY))!==null)
+ $this->setEncryptionKey($key);
+ else
+ {
+ if(($key=$this->generateRandomString(32,true))===false)
+ if(($key=$this->generateRandomString(32,false))===false)
+ throw new CException(Yii::t('yii',
+ 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.'));
+ $this->setEncryptionKey($key);
+ Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY,$key);
+ }
+ return $this->_encryptionKey;
+ }
+ }
+
+ /**
+ * @param string $value the key used to encrypt/decrypt data.
+ * @throws CException if the key is empty
+ */
+ public function setEncryptionKey($value)
+ {
+ if(!empty($value))
+ $this->_encryptionKey=$value;
+ else
+ throw new CException(Yii::t('yii','CSecurityManager.encryptionKey cannot be empty.'));
+ }
+
+ /**
+ * This method has been deprecated since version 1.1.3.
+ * Please use {@link hashAlgorithm} instead.
+ * @return string -
+ * @deprecated
+ */
+ public function getValidation()
+ {
+ return $this->hashAlgorithm;
+ }
+
+ /**
+ * This method has been deprecated since version 1.1.3.
+ * Please use {@link hashAlgorithm} instead.
+ * @param string $value -
+ * @deprecated
+ */
+ public function setValidation($value)
+ {
+ $this->hashAlgorithm=$value;
+ }
+
+ /**
+ * Encrypts data.
+ * @param string $data data to be encrypted.
+ * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
+ * @return string the encrypted data
+ * @throws CException if PHP Mcrypt extension is not loaded
+ */
+ public function encrypt($data,$key=null)
+ {
+ $module=$this->openCryptModule();
+ $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module));
+ srand();
+ $iv=mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
+ mcrypt_generic_init($module,$key,$iv);
+ $encrypted=$iv.mcrypt_generic($module,$data);
+ mcrypt_generic_deinit($module);
+ mcrypt_module_close($module);
+ return $encrypted;
+ }
+
+ /**
+ * Decrypts data
+ * @param string $data data to be decrypted.
+ * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
+ * @return string the decrypted data
+ * @throws CException if PHP Mcrypt extension is not loaded
+ */
+ public function decrypt($data,$key=null)
+ {
+ $module=$this->openCryptModule();
+ $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module));
+ $ivSize=mcrypt_enc_get_iv_size($module);
+ $iv=$this->substr($data,0,$ivSize);
+ mcrypt_generic_init($module,$key,$iv);
+ $decrypted=mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data)));
+ mcrypt_generic_deinit($module);
+ mcrypt_module_close($module);
+ return rtrim($decrypted,"\0");
+ }
+
+ /**
+ * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
+ * @throws CException if failed to initialize the mcrypt module or PHP mcrypt extension
+ * @return resource the mycrypt module handle.
+ * @since 1.1.3
+ */
+ protected function openCryptModule()
+ {
+ if(extension_loaded('mcrypt'))
+ {
+ if(is_array($this->cryptAlgorithm))
+ $module=@call_user_func_array('mcrypt_module_open',$this->cryptAlgorithm);
+ else
+ $module=@mcrypt_module_open($this->cryptAlgorithm,'', MCRYPT_MODE_CBC,'');
+
+ if($module===false)
+ throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.'));
+
+ return $module;
+ }
+ else
+ throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
+ }
+
+ /**
+ * Prefixes data with an HMAC.
+ * @param string $data data to be hashed.
+ * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
+ * @return string data prefixed with HMAC
+ */
+ public function hashData($data,$key=null)
+ {
+ return $this->computeHMAC($data,$key).$data;
+ }
+
+ /**
+ * Validates if data is tampered.
+ * @param string $data data to be validated. The data must be previously
+ * generated using {@link hashData()}.
+ * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
+ * @return string the real data with HMAC stripped off. False if the data
+ * is tampered.
+ */
+ public function validateData($data,$key=null)
+ {
+ $len=$this->strlen($this->computeHMAC('test'));
+ if($this->strlen($data)>=$len)
+ {
+ $hmac=$this->substr($data,0,$len);
+ $data2=$this->substr($data,$len,$this->strlen($data));
+ return $hmac===$this->computeHMAC($data2,$key)?$data2:false;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Computes the HMAC for the data with {@link getValidationKey validationKey}. This method has been made public
+ * since 1.1.14.
+ * @param string $data data to be generated HMAC.
+ * @param string|null $key the private key to be used for generating HMAC. Defaults to null, meaning using
+ * {@link validationKey} value.
+ * @param string|null $hashAlgorithm the name of the hashing algorithm to be used.
+ * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
+ * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
+ * Defaults to null, meaning using {@link hashAlgorithm} value.
+ * @return string the HMAC for the data.
+ * @throws CException on unsupported hash algorithm given.
+ */
+ public function computeHMAC($data,$key=null,$hashAlgorithm=null)
+ {
+ if($key===null)
+ $key=$this->getValidationKey();
+ if($hashAlgorithm===null)
+ $hashAlgorithm=$this->hashAlgorithm;
+
+ if(function_exists('hash_hmac'))
+ return hash_hmac($hashAlgorithm,$data,$key);
+
+ if(0===strcasecmp($hashAlgorithm,'sha1'))
+ {
+ $pack='H40';
+ $func='sha1';
+ }
+ elseif(0===strcasecmp($hashAlgorithm,'md5'))
+ {
+ $pack='H32';
+ $func='md5';
+ }
+ else
+ {
+ throw new CException(Yii::t('yii','Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.'));
+ }
+ if($this->strlen($key)>64)
+ $key=pack($pack,$func($key));
+ if($this->strlen($key)<64)
+ $key=str_pad($key,64,chr(0));
+ $key=$this->substr($key,0,64);
+ return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data)));
+ }
+
+ /**
+ * Generate a random ASCII string. Generates only [0-9a-zA-z_~] characters which are all
+ * transparent in raw URL encoding.
+ * @param integer $length length of the generated string in characters.
+ * @param boolean $cryptographicallyStrong set this to require cryptographically strong randomness.
+ * @return string|boolean random string or false in case it cannot be generated.
+ * @since 1.1.14
+ */
+ public function generateRandomString($length,$cryptographicallyStrong=true)
+ {
+ if(($randomBytes=$this->generateRandomBytes($length+2,$cryptographicallyStrong))!==false)
+ return strtr($this->substr(base64_encode($randomBytes),0,$length),array('+'=>'_','/'=>'~'));
+ return false;
+ }
+
+ /**
+ * Generates a string of random bytes.
+ * @param integer $length number of random bytes to be generated.
+ * @param boolean $cryptographicallyStrong whether to fail if a cryptographically strong
+ * result cannot be generated. The method attempts to read from a cryptographically strong
+ * pseudorandom number generator (CS-PRNG), see
+ * {@link https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Requirements Wikipedia}.
+ * However, in some runtime environments, PHP has no access to a CS-PRNG, in which case
+ * the method returns false if $cryptographicallyStrong is true. When $cryptographicallyStrong is false,
+ * the method always returns a pseudorandom result but may fall back to using {@link generatePseudoRandomBlock}.
+ * This method does not guarantee that entropy, from sources external to the CS-PRNG, was mixed into
+ * the CS-PRNG state between each successive call. The caller can therefore expect non-blocking
+ * behavior, unlike, for example, reading from /dev/random on Linux, see
+ * {@link http://eprint.iacr.org/2006/086.pdf Gutterman et al 2006}.
+ * @return boolean|string generated random binary string or false on failure.
+ * @since 1.1.14
+ */
+ public function generateRandomBytes($length,$cryptographicallyStrong=true)
+ {
+ $bytes='';
+ if(function_exists('openssl_random_pseudo_bytes'))
+ {
+ $bytes=openssl_random_pseudo_bytes($length,$strong);
+ if($this->strlen($bytes)>=$length && ($strong || !$cryptographicallyStrong))
+ return $this->substr($bytes,0,$length);
+ }
+
+ if(function_exists('mcrypt_create_iv') &&
+ ($bytes=mcrypt_create_iv($length, MCRYPT_DEV_URANDOM))!==false &&
+ $this->strlen($bytes)>=$length)
+ {
+ return $this->substr($bytes,0,$length);
+ }
+
+ if(($file=@fopen('/dev/urandom','rb'))!==false &&
+ ($bytes=@fread($file,$length))!==false &&
+ (fclose($file) || true) &&
+ $this->strlen($bytes)>=$length)
+ {
+ return $this->substr($bytes,0,$length);
+ }
+
+ $i=0;
+ while($this->strlen($bytes)<$length &&
+ ($byte=$this->generateSessionRandomBlock())!==false &&
+ ++$i<3)
+ {
+ $bytes.=$byte;
+ }
+ if($this->strlen($bytes)>=$length)
+ return $this->substr($bytes,0,$length);
+
+ if ($cryptographicallyStrong)
+ return false;
+
+ while($this->strlen($bytes)<$length)
+ $bytes.=$this->generatePseudoRandomBlock();
+ return $this->substr($bytes,0,$length);
+ }
+
+ /**
+ * Generate a pseudo random block of data using several sources. On some systems this may be a bit
+ * better than PHP's {@link mt_rand} built-in function, which is not really random.
+ * @return string of 64 pseudo random bytes.
+ * @since 1.1.14
+ */
+ public function generatePseudoRandomBlock()
+ {
+ $bytes='';
+
+ if (function_exists('openssl_random_pseudo_bytes')
+ && ($bytes=openssl_random_pseudo_bytes(512))!==false
+ && $this->strlen($bytes)>=512)
+ {
+ return $this->substr($bytes,0,512);
+ }
+
+ for($i=0;$i<32;++$i)
+ $bytes.=pack('S',mt_rand(0,0xffff));
+
+ // On UNIX and UNIX-like operating systems the numerical values in `ps`, `uptime` and `iostat`
+ // ought to be fairly unpredictable. Gather the non-zero digits from those.
+ foreach(array('ps','uptime','iostat') as $command) {
+ @exec($command,$commandResult,$retVal);
+ if(is_array($commandResult) && !empty($commandResult) && $retVal==0)
+ $bytes.=preg_replace('/[^1-9]/','',implode('',$commandResult));
+ }
+
+ // Gather the current time's microsecond part. Note: this is only a source of entropy on
+ // the first call! If multiple calls are made, the entropy is only as much as the
+ // randomness in the time between calls.
+ $bytes.=$this->substr(microtime(),2,6);
+
+ // Concatenate everything gathered, mix it with sha512. hash() is part of PHP core and
+ // enabled by default but it can be disabled at compile time but we ignore that possibility here.
+ return hash('sha512',$bytes,true);
+ }
+
+ /**
+ * Get random bytes from the system entropy source via PHP session manager.
+ * @return boolean|string 20-byte random binary string or false on error.
+ * @since 1.1.14
+ */
+ public function generateSessionRandomBlock()
+ {
+ ini_set('session.entropy_length',20);
+ if(ini_get('session.entropy_length')!=20)
+ return false;
+
+ // These calls are (supposed to be, according to PHP manual) safe even if
+ // there is already an active session for the calling script.
+ @session_start();
+ @session_regenerate_id();
+
+ $bytes=session_id();
+ if(!$bytes)
+ return false;
+
+ // $bytes has 20 bytes of entropy but the session manager converts the binary
+ // random bytes into something readable. We have to convert that back.
+ // SHA-1 should do it without losing entropy.
+ return sha1($bytes,true);
+ }
+
+ /**
+ * Returns the length of the given string.
+ * If available uses the multibyte string function mb_strlen.
+ * @param string $string the string being measured for length
+ * @return integer the length of the string
+ */
+ private function strlen($string)
+ {
+ return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string);
+ }
+
+ /**
+ * Returns the portion of string specified by the start and length parameters.
+ * If available uses the multibyte string function mb_substr
+ * @param string $string the input string. Must be one character or longer.
+ * @param integer $start the starting position
+ * @param integer $length the desired portion length
+ * @return string the extracted part of string, or FALSE on failure or an empty string.
+ */
+ private function substr($string,$start,$length)
+ {
+ return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length);
+ }
+}
diff --git a/framework/base/CStatePersister.php b/framework/base/CStatePersister.php
new file mode 100644
index 0000000..b298722
--- /dev/null
+++ b/framework/base/CStatePersister.php
@@ -0,0 +1,107 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CStatePersister implements a file-based persistent data storage.
+ *
+ * It can be used to keep data available through multiple requests and sessions.
+ *
+ * By default, CStatePersister stores data in a file named 'state.bin' that is located
+ * under the application {@link CApplication::getRuntimePath runtime path}.
+ * You may change the location by setting the {@link stateFile} property.
+ *
+ * To retrieve the data from CStatePersister, call {@link load()}. To save the data,
+ * call {@link save()}.
+ *
+ * Comparison among state persister, session and cache is as follows:
+ * $filterChain->run()
.
+ * @param CFilterChain $filterChain the filter chain that the filter is on.
+ */
+ public function filter($filterChain);
+}
+
+
+/**
+ * IAction is the interface that must be implemented by controller actions.
+ *
+ * @package system.base
+ * @since 1.0
+ */
+interface IAction
+{
+ /**
+ * @return string id of the action
+ */
+ public function getId();
+ /**
+ * @return CController the controller instance
+ */
+ public function getController();
+}
+
+
+/**
+ * IWebServiceProvider interface may be implemented by Web service provider classes.
+ *
+ * If this interface is implemented, the provider instance will be able
+ * to intercept the remote method invocation (e.g. for logging or authentication purpose).
+ * @author Qiang Xue count($provider->getData())
.
+ * When {@link pagination} is set false, this returns the same value as {@link totalItemCount}.
+ * @param boolean $refresh whether the number of data items should be re-calculated.
+ * @return integer the number of data items in the current page.
+ */
+ public function getItemCount($refresh=false);
+ /**
+ * Returns the total number of data items.
+ * When {@link pagination} is set false, this returns the same value as {@link itemCount}.
+ * @param boolean $refresh whether the total number of data items should be re-calculated.
+ * @return integer total number of possible data items.
+ */
+ public function getTotalItemCount($refresh=false);
+ /**
+ * Returns the data items currently available.
+ * @param boolean $refresh whether the data should be re-fetched from persistent storage.
+ * @return array the list of data items currently available in this data provider.
+ */
+ public function getData($refresh=false);
+ /**
+ * Returns the key values associated with the data items.
+ * @param boolean $refresh whether the keys should be re-calculated.
+ * @return array the list of key values corresponding to {@link data}. Each data item in {@link data}
+ * is uniquely identified by the corresponding key value in this array.
+ */
+ public function getKeys($refresh=false);
+ /**
+ * @return CSort the sorting object. If this is false, it means the sorting is disabled.
+ */
+ public function getSort();
+ /**
+ * @return CPagination the pagination object. If this is false, it means the pagination is disabled.
+ */
+ public function getPagination();
+}
+
+
+/**
+ * ILogFilter is the interface that must be implemented by log filters.
+ *
+ * A log filter preprocesses the logged messages before they are handled by a log route.
+ * You can attach classes that implement ILogFilter to {@link CLogRoute::$filter}.
+ *
+ * @package system.logging
+ * @since 1.1.11
+ */
+interface ILogFilter
+{
+ /**
+ * This method should be implemented to perform actual filtering of log messages
+ * by working on the array given as the first parameter.
+ * Implementation might reformat, remove or add information to logged messages.
+ * @param array $logs list of messages. Each array element represents one message
+ * with the following structure:
+ * array(
+ * [0] => message (string)
+ * [1] => level (string)
+ * [2] => category (string)
+ * [3] => timestamp (float, obtained by microtime(true));
+ */
+ public function filter(&$logs);
+}
+
diff --git a/framework/caching/CApcCache.php b/framework/caching/CApcCache.php
new file mode 100644
index 0000000..ffc3235
--- /dev/null
+++ b/framework/caching/CApcCache.php
@@ -0,0 +1,108 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CApcCache provides APC caching in terms of an application component.
+ *
+ * The caching is based on {@link http://www.php.net/apc APC}.
+ * To use this application component, the APC PHP extension must be loaded.
+ *
+ * See {@link CCache} manual for common cache operations that are supported by CApcCache.
+ *
+ * @author Qiang Xue protected/runtime/cache-YiiVersion.db
.
+ */
+ public $connectionID;
+ /**
+ * @var string name of the DB table to store cache content. Defaults to 'YiiCache'.
+ * Note, if {@link autoCreateCacheTable} is false and you want to create the DB table
+ * manually by yourself, you need to make sure the DB table is of the following structure:
+ * + * (id CHAR(128) PRIMARY KEY, expire INTEGER, value BLOB) + *+ * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable + * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.) + * @see autoCreateCacheTable + */ + public $cacheTableName='YiiCache'; + /** + * @var boolean whether the cache DB table should be created automatically if it does not exist. Defaults to true. + * If you already have the table created, it is recommended you set this property to be false to improve performance. + * @see cacheTableName + */ + public $autoCreateCacheTable=true; + /** + * @var CDbConnection the DB connection instance + */ + private $_db; + private $_gcProbability=100; + private $_gced=false; + + /** + * Initializes this application component. + * + * This method is required by the {@link IApplicationComponent} interface. + * It ensures the existence of the cache DB table. + * It also removes expired data items from the cache. + */ + public function init() + { + parent::init(); + + $db=$this->getDbConnection(); + $db->setActive(true); + if($this->autoCreateCacheTable) + { + $sql="DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<".time(); + try + { + $db->createCommand($sql)->execute(); + } + catch(Exception $e) + { + $this->createCacheTable($db,$this->cacheTableName); + } + } + } + + /** + * @return integer the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + */ + public function getGCProbability() + { + return $this->_gcProbability; + } + + /** + * @param integer $value the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. + */ + public function setGCProbability($value) + { + $value=(int)$value; + if($value<0) + $value=0; + if($value>1000000) + $value=1000000; + $this->_gcProbability=$value; + } + + /** + * Creates the cache DB table. + * @param CDbConnection $db the database connection + * @param string $tableName the name of the table to be created + */ + protected function createCacheTable($db,$tableName) + { + $driver=$db->getDriverName(); + if($driver==='mysql') + $blob='LONGBLOB'; + elseif($driver==='pgsql') + $blob='BYTEA'; + else + $blob='BLOB'; + $sql=<<