eZ Publish  [trunk]
ezini.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZINI class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*!
00012   \class eZINI ezini.php
00013   \ingroup eZUtils
00014   \brief Reads and writes .ini style configuration files
00015 
00016   The most common way of using it is.
00017   \code
00018 
00019   $ini = eZINI::instance( "site.ini" );
00020 
00021   // get a variable from the file.
00022   $iniVar = $ini->variable( "BlockName", "Variable" );
00023 
00024   \endcode
00025 
00026   The default ini file is site.ini but others can be passed to the instance() function
00027   among with some others. It will create one unique instance for each ini file and rootdir,
00028   this means that the next time instance() is used with the same parameters the same
00029   object will be returned and no new parsing is required.
00030 
00031   The class will by default try to create a cache file in var/cache/ini, however to change
00032   this behaviour the static setIsCacheEnabled() function can be used, or use the $useCache
00033   parameter in instance() for setting this for one object only.
00034 
00035   The class will also handle charset conversion using eZTextCodec, to turn this behaviour
00036   off use the static setIsTextCodecEnabled() function or set the $useTextCodec parameter
00037   in instance() for a per object basis setting.
00038 
00039   Normally the eZINI class will not give out much information about what it's doing,
00040   it's only when errors occur that you'll see this. To enable internal debugging use
00041   the static setIsDebugEnabled() function. The class will then give information about
00042   which files are load, if cache files are used and when cache files are written.
00043 */
00044 class eZINI
00045 {
00046     /**
00047      * Constant path to directory for configuration cache
00048      *
00049      * @var string
00050      */
00051     const CONFIG_CACHE_DIR = 'var/cache/ini/';
00052 
00053     /**
00054      * Constant integer to check against configuration cache format revision
00055      *
00056      * @var int
00057      */
00058     const CONFIG_CACHE_REV = 2;
00059 
00060     /**
00061      * Set EZP_INI_FILEMTIME_CHECK constant to false to improve performance by
00062      * not checking modified time on ini files. You can also set it to a string, the name
00063      * of a ini file you still want to check modified time on, best example would be to
00064      * set it to 'site.ini' to make the system still check that but not the rest.
00065      *
00066      * @var null|bool
00067      */
00068     static protected $checkFileMtime = null;
00069 
00070     /**
00071      * set EZP_INI_FILE_PERMISSION constant to the permissions you want saved
00072      * ini and cache files to have.
00073      *
00074      * @var null|int
00075      */
00076     static protected $filePermission = null;
00077 
00078     /**
00079      * Array of eZINI instances
00080      *
00081      * @var array(eZINI)
00082      */
00083     static protected $instances = array();
00084 
00085     /**
00086      * Contains whether INI cache is globally enabled.
00087      *
00088      * @var bool
00089      */
00090     static protected $cacheEnabled = true;
00091 
00092     /**
00093      * Contains whether internals debugging is enabled.
00094      *
00095      * @var bool
00096      */
00097     static protected $debugEnabled = false;
00098 
00099     /**
00100      * Contains whether textcodec conversion  is enabled.
00101      *
00102      * @var bool
00103      */
00104     static protected $textCodecEnabled = true;
00105 
00106     /**
00107      * Initialization of eZINI object
00108      *
00109      * Enter description here ...
00110      * @param string $fileName
00111      * @param string $rootDir
00112      * @param null|bool $useTextCodec
00113      * @param null|bool $useCache
00114      * @param null|bool $useLocalOverrides
00115      * @param bool $directAccess
00116      * @param bool $addArrayDefinition
00117      * @param bool $load @since 4.5 Lets you disable automatic loading of ini values in
00118      *                   cases where changes on instance will be done first.
00119      */
00120     function eZINI( $fileName = 'site.ini', $rootDir = '', $useTextCodec = null, $useCache = null, $useLocalOverrides = null, $directAccess = false, $addArrayDefinition = false, $load = true )
00121     {
00122         $this->Charset = 'utf8';
00123         if ( $fileName == '' )
00124             $fileName = 'site.ini';
00125         if ( $rootDir !== false && $rootDir == '' )
00126             $rootDir = 'settings';
00127         if ( $useCache === null )
00128             $useCache = self::isCacheEnabled();
00129         if ( eZINI::isNoCacheAdviced() )
00130         {
00131             $useCache = false;
00132         }
00133         if ( $useTextCodec === null )
00134             $useTextCodec = self::isTextCodecEnabled();
00135 
00136         $this->UseTextCodec = $useTextCodec;
00137         $this->Codec = null;
00138         $this->FileName = $fileName;
00139         $this->RootDir = $rootDir;
00140         $this->UseCache = $useCache;
00141         $this->DirectAccess = $directAccess;
00142         $this->UseLocalOverrides = $useLocalOverrides;
00143         $this->AddArrayDefinition = $addArrayDefinition;
00144 
00145         if ( self::$checkFileMtime === null )
00146         {
00147             if ( defined('EZP_INI_FILEMTIME_CHECK') )
00148                 self::$checkFileMtime = EZP_INI_FILEMTIME_CHECK;
00149             else
00150                 self::$checkFileMtime = true;
00151         }
00152 
00153         if ( self::$GlobalOverrideDirArray === null )
00154         {
00155             self::$GlobalOverrideDirArray = self::defaultOverrideDirs();
00156         }
00157 
00158         if ( $this->UseLocalOverrides == true )
00159         {
00160             $this->LocalOverrideDirArray = self::$GlobalOverrideDirArray;
00161         }
00162 
00163         if ( self::$filePermission === null )
00164         {
00165             if ( defined( 'EZP_INI_FILE_PERMISSION' ) )
00166                 self::$filePermission = EZP_INI_FILE_PERMISSION;
00167             else
00168                 self::$filePermission = 0666;
00169         }
00170 
00171         if ( $load )
00172             $this->load();
00173     }
00174 
00175     /*!
00176      \return the filename.
00177     */
00178     function filename()
00179     {
00180         return $this->FileName;
00181     }
00182 
00183     /**
00184      * Returns whether INI cache is enabled globally, by default it is true.
00185      *
00186      * @see setIsCacheEnabled().
00187      *
00188      * @return bool
00189      */
00190     static function isCacheEnabled()
00191     {
00192          return self::$cacheEnabled;
00193     }
00194 
00195     /*!
00196      \return true if cache is not adviced to be used.
00197      \note The no-cache-adviced flag might not be modified in time for site.ini and some other important files to be affected.
00198     */
00199     static function isNoCacheAdviced()
00200     {
00201         if ( !isset( $GLOBALS['eZSiteBasics'] ) )
00202             return false;
00203         $siteBasics = $GLOBALS['eZSiteBasics'];
00204         if ( !isset( $siteBasics['no-cache-adviced'] ) )
00205             return false;
00206         return $siteBasics['no-cache-adviced'];
00207     }
00208 
00209     /**
00210      * Sets whether caching is enabled for INI files or not. This setting is global
00211      * and can be overriden in the instance() function.
00212      *
00213      * @see isCacheEnabled().
00214      *
00215      * @param bool $enabled
00216      */
00217     static function setIsCacheEnabled( $enabled )
00218     {
00219         self::$cacheEnabled = (bool)$enabled;
00220     }
00221 
00222     /**
00223      * Returns whether debugging of internals is enabled.
00224      *
00225      * This will display which files are loaded an when cache files are created.
00226      *
00227      * @see setIsDebugEnabled()
00228      *
00229      * @return bool
00230      */
00231     static function isDebugEnabled()
00232     {
00233         return self::$debugEnabled;
00234     }
00235 
00236     /**
00237      * Sets whether internal debugging is enabled or not. This setting is global
00238      * and can be overriden in the instance() function.
00239      *
00240      * @see isDebugEnabled().
00241      *
00242      * @param bool $enabled
00243      */
00244     static function setIsDebugEnabled( $enabled )
00245     {
00246         self::$debugEnabled = (bool)$enabled;
00247     }
00248 
00249     /**
00250      * Returns whether textcodecs is to be used, this will use the eZTextCodec
00251      * class in the eZI18N library for text conversion.
00252      *
00253      * @see setIsTextCodecEnabled()
00254      *
00255      * @return bool
00256      */
00257     static function isTextCodecEnabled()
00258     {
00259         return self::$textCodecEnabled;
00260     }
00261 
00262     /**
00263      * Sets whether textcodec conversion is enabled or not.
00264      *
00265      * @see isTextCodecEnabled().
00266      *
00267      * @param bool $enabled
00268      */
00269     static function setIsTextCodecEnabled( $enabled )
00270     {
00271         self::$textCodecEnabled = (bool)$enabled;
00272     }
00273 
00274     /**
00275      * Check whether a specified parameter in a specified section is set in a specified file
00276      *
00277      * @deprecated Since 4.4
00278      * @param string fileName file name (optional)
00279      * @param string rootDir directory (optional)
00280      * @param string section section name
00281      * @param string parameter parameter name
00282      * @return bool True if the the parameter is set.
00283      */
00284     static function parameterSet( $fileName = 'site.ini', $rootDir = 'settings', &$section, &$parameter )
00285     {
00286         if ( !eZINI::exists( $fileName, $rootDir ) )
00287             return false;
00288 
00289         $iniInstance = eZINI::instance( $fileName, $rootDir, null, null, null, true );
00290         return $iniInstance->hasVariable( $section, $parameter );
00291     }
00292 
00293     /*!
00294      \static
00295      \return true if the INI file \a $fileName exists in the root dir \a $rootDir.
00296      $fileName defaults to site.ini and rootDir to settings.
00297     */
00298     static function exists( $fileName = "site.ini", $rootDir = "settings" )
00299     {
00300         if ( $fileName == "" )
00301             $fileName = "site.ini";
00302         if ( $rootDir == "" )
00303             $rootDir = "settings";
00304         if ( file_exists( $rootDir . '/' . $fileName ) )
00305             return true;
00306         else if ( file_exists( $rootDir . '/' . $fileName . '.append.php' ) )
00307             return true;
00308         else if ( file_exists( $rootDir . '/' . $fileName . '.append' ) )
00309             return true;
00310         return false;
00311     }
00312 
00313     /**
00314      * Tries to load the ini file specified in the constructor or instance() function.
00315      * If cache files should be used and a cache file is found it loads that instead.
00316      *
00317      * @param bool $reset Reset ini values on instance
00318      */
00319     public function load( $reset = true )
00320     {
00321         if ( $this->UseCache )
00322         {
00323             $this->loadCache( $reset );
00324         }
00325         else
00326         {
00327             $this->parse( false, false, $reset );
00328         }
00329     }
00330 
00331     /**
00332      * Tries to load the ini file placement specified in the constructor or instance() function.
00333      * If cache files should be used and a cache file is found it loads that instead.
00334      *
00335      * @param bool $reset Reset ini values on instance
00336      */
00337     public function loadPlacement( $reset = true )
00338     {
00339         if ( $this->UseCache )
00340         {
00341             $this->loadCache( $reset, true );
00342         }
00343         else
00344         {
00345             $this->parse( false, false, $reset, true );
00346         }
00347     }
00348 
00349     /*!
00350      \private
00351      Looks trough all known settings and override folders to find relevant INI files.
00352      The result is a list with expanded paths to the files.
00353      \return the expanded file list.
00354     */
00355     function findInputFiles( &$inputFiles, &$iniFile )
00356     {
00357         if ( $this->RootDir !== false )
00358             $iniFile = eZDir::path( array( $this->RootDir, $this->FileName ) );
00359         else
00360             $iniFile = eZDir::path( array( $this->FileName ) );
00361 
00362         $inputFiles = array();
00363 
00364         if ( $this->FileName === 'override.ini' )
00365         {
00366             eZExtension::prependExtensionSiteAccesses( false, $this, true, false, false );
00367         }
00368 
00369         if ( file_exists( $iniFile ) )
00370             $inputFiles[] = $iniFile;
00371 
00372         // try the same file name with '.append.php' replace with '.append'
00373         if ( strpos($iniFile, '.append.php') !== false && preg_match('#^(.+.append).php$#i', $iniFile, $matches ) && file_exists( $matches[1] ) )
00374             $inputFiles[] = $matches[1];
00375 
00376         if ( strpos($iniFile, '.php') === false && file_exists ( $iniFile . '.php' ) )
00377             $inputFiles[] = $iniFile . '.php';
00378 
00379         if ( $this->DirectAccess )
00380         {
00381             if ( file_exists ( $iniFile . '.append' ) )
00382             {
00383                 // recursion eZDebug::writeStrict( "INI files with *.ini.append suffix is DEPRECATED, use *.ini or *.ini.append.php instead: $iniFile.append", __METHOD__ );
00384                 $inputFiles[] = $iniFile . '.append';
00385             }
00386 
00387             if ( file_exists ( $iniFile . '.append.php' ) )
00388                 $inputFiles[] = $iniFile . '.append.php';
00389         }
00390         else
00391         {
00392             $overrideDirs = $this->overrideDirs();
00393             $fileName = $this->FileName;
00394             $rootDir = $this->RootDir;
00395             foreach ( $overrideDirs as $overrideDirItem )
00396             {
00397                 $overrideDir = $overrideDirItem[0];
00398                 $isGlobal = $overrideDirItem[1];
00399                 if ( $isGlobal )
00400                     $overrideFile = eZDir::path( array( $overrideDir, $fileName ) );
00401                  else
00402                     $overrideFile = eZDir::path( array( $rootDir, $overrideDir, $fileName ) );
00403 
00404                 if ( file_exists( $overrideFile . '.php' ) )
00405                 {
00406                     // recursion eZDebug::writeStrict( "INI files with *.ini.php suffix is DEPRECATED, use *.ini or *.ini.append.php instead: $overrideFile.php", __METHOD__ );
00407                     $inputFiles[] = $overrideFile . '.php';
00408                 }
00409 
00410                 if ( file_exists( $overrideFile ) )
00411                 {
00412                     $inputFiles[] = $overrideFile;
00413                 }
00414 
00415                 if ( file_exists( $overrideFile . '.append.php' ) )
00416                 {
00417                     $inputFiles[] = $overrideFile . '.append.php';
00418                 }
00419 
00420                 if ( file_exists( $overrideFile . '.append' ) )
00421                 {
00422                     // recursion eZDebug::writeStrict( "INI files with *.ini.append suffix is DEPRECATED, use *.ini or *.ini.append.php instead: $overrideFile.append", __METHOD__ );
00423                     $inputFiles[] = $overrideFile . '.append';
00424                 }
00425             }
00426         }
00427     }
00428 
00429     /*!
00430       \protected
00431       Generates cache name for loadCache
00432     */
00433     protected function cacheFileName( $placement = false )
00434     {
00435         $cacheFileName = $this->FileName . '-' . $this->RootDir . '-' . $this->DirectAccess;
00436 
00437         if ( !$this->DirectAccess )
00438         {
00439             $cacheFileName .= '-' . serialize( $this->overrideDirs() );
00440         }
00441         if ( $this->UseTextCodec )
00442         {
00443             $cacheFileName .= '-' . eZTextCodec::internalCharset();
00444         }
00445         if ( $placement )
00446         {
00447             $cacheFileName .= '-placement:' . $placement;
00448         }
00449         $filePreFix = explode( '.', $this->FileName);
00450         return $filePreFix[0] . '-' . md5( $cacheFileName ) . '.php';
00451     }
00452 
00453     /**
00454      * Will load a cached version of the ini file if it exists,
00455      * if not it will parse the original file and create the cache file.
00456      *
00457      * @access protected
00458      * @internal Please use {@link eZINI::load()} or {@link eZINI::loadPlacement()}
00459      * @param bool $reset Reset ini values on instance
00460      * @param bool $placement Load cache for placment info, not the ini values themself.
00461      */
00462     function loadCache( $reset = true, $placement = false )
00463     {
00464         eZDebug::accumulatorStart( 'ini', 'Ini load', 'Load cache' );
00465         if ( $reset )
00466             $this->reset();
00467         $cachedDir = self::CONFIG_CACHE_DIR;
00468 
00469         $fileName = $this->cacheFileName( $placement );
00470         $cachedFile = $cachedDir . $fileName;
00471         if ( $placement )
00472         {
00473             $this->PlacementCacheFile = $cachedFile;
00474         }
00475         else
00476         {
00477             $this->CacheFile = $cachedFile;
00478         }
00479 
00480         $data = false;// this will contain cache data if cache data is valid
00481         if ( file_exists( $cachedFile ) )
00482         {
00483             if ( self::isDebugEnabled() )
00484                 eZDebug::writeNotice( "Loading cache '$cachedFile' for file '" . $this->FileName . "'", __METHOD__ );
00485 
00486             include( $cachedFile );
00487 
00488             if ( !isset( $data['rev'] ) || $data['rev'] != eZINI::CONFIG_CACHE_REV )
00489             {
00490                 if ( self::isDebugEnabled() )
00491                     eZDebug::writeNotice( "Old structure in cache file used, recreating '$cachedFile' to new structure", __METHOD__ );
00492                 $data = false;
00493                 $this->reset();
00494             }
00495             else if ( self::$checkFileMtime === true || self::$checkFileMtime === $this->FileName )
00496             {
00497                 eZDebug::accumulatorStart( 'ini_check_mtime', 'Ini load', 'Check MTime' );
00498                 $currentTime = time();
00499                 $cacheCreatedTime = strtotime( $data['created'] );
00500                 $iniFile = $data['file'];// used by findInputFiles further down
00501                 $inputFiles = $data['files'];
00502                 foreach ( $inputFiles as $inputFile )
00503                 {
00504                     $fileTime = file_exists( $inputFile ) ? filemtime( $inputFile ) : false;
00505                     if ( $fileTime === false )// Refresh cache & input files if file is gone
00506                     {
00507                         unset( $inputFiles );
00508                         $data = false;
00509                         $this->reset();
00510                         break;
00511                     }
00512                     else if ( $fileTime > $currentTime )
00513                     {
00514                         eZDebug::writeError( 'Input file "' . $inputFile . '" has a timestamp higher then current time, ignoring to avoid infinite recursion!', __METHOD__ );
00515                     }
00516                     else if ( $fileTime > $cacheCreatedTime )// Refresh cache if file has been changed
00517                     {
00518                         $data = false;
00519                         $this->reset();
00520                         break;
00521                     }
00522                 }
00523                 eZDebug::accumulatorStop( 'ini_check_mtime' );
00524             }
00525         }
00526 
00527         if ( $data )// if we have cache data on this point, use it
00528         {
00529             $this->Charset = $data['charset'];
00530             $this->ModifiedBlockValues = array();
00531             if ( $placement )
00532                 $this->BlockValuesPlacement = $data['val'];
00533             else
00534                 $this->BlockValues = $data['val'];
00535             unset( $data );
00536         }
00537         else
00538         {
00539             if ( !isset( $inputFiles ) )// use $inputFiles from cache if defined
00540             {
00541                 eZDebug::accumulatorStart( 'ini_find_files', 'Ini load', 'Find INI Files' );
00542                 $this->findInputFiles( $inputFiles, $iniFile );
00543                 eZDebug::accumulatorStop( 'ini_find_files' );
00544                 if ( count( $inputFiles ) === 0 )
00545                 {
00546                     eZDebug::accumulatorStop( 'ini' );
00547                     return false;
00548                 }
00549             }
00550 
00551             eZDebug::accumulatorStart( 'ini_files_parse', 'Ini load', 'Parse' );
00552             $this->parse( $inputFiles, $iniFile, false, $placement );
00553             eZDebug::accumulatorStop( 'ini_files_parse' );
00554             eZDebug::accumulatorStart( 'ini_files_save', 'Ini load', 'Save Cache' );
00555             $cacheSaved = $this->saveCache( $cachedDir, $cachedFile, $placement ? $this->BlockValuesPlacement : $this->BlockValues, $inputFiles, $iniFile );
00556             eZDebug::accumulatorStop( 'ini_files_save' );
00557 
00558             if ( $cacheSaved )
00559             {
00560                 // Write log message to storage.log
00561                 eZLog::writeStorageLog( $fileName, $cachedDir );
00562             }
00563         }
00564 
00565         eZDebug::accumulatorStop( 'ini' );
00566     }
00567 
00568     /**
00569      * Stores the content of the INI object to the cache file \a $cachedFile.
00570      *
00571      * @param string $cachedDir Cache dir, usually "var/cache/ini/"
00572      * @param string $cachedFile Name of cache file as returned by cacheFileName()
00573      * @param array $data Configuration data as an associative array structure
00574      * @param array $inputFiles List of input files used as basis for cache (for use in load cache to check mtime)
00575      * @param string $iniFile Ini file path string returned by findInputFiles() for main ini file
00576      * @return bool
00577      */
00578     protected function saveCache( $cachedDir, $cachedFile, array $data, array $inputFiles, $iniFile )
00579     {
00580         if ( !file_exists( $cachedDir ) )
00581         {
00582             if ( !eZDir::mkdir( $cachedDir, 0777, true ) )
00583             {
00584                 eZDebug::writeError( "Couldn't create cache directory $cachedDir, perhaps wrong permissions", __METHOD__ );
00585                 return false;
00586             }
00587         }
00588 
00589         // Save the data to a temp cached file
00590         $tmpCacheFile = $cachedFile . '_' . substr( md5( mt_rand() ), 0, 8 );
00591         $fp = @fopen( $tmpCacheFile, "w" );
00592         if ( $fp === false )
00593         {
00594             eZDebug::writeError( "Couldn't create cache file '$cachedFile', perhaps wrong permissions?", __METHOD__ );
00595             return false;
00596         }
00597 
00598         // Write cache data as a php structure with some meta information for use while reading cache
00599         fwrite( $fp, "<?php\n// This is a auto generated ini cache file, time created:" . date( DATE_RFC822 ) . "\n" );
00600 
00601         fwrite( $fp, "\$data = array(\n" );
00602         fwrite( $fp, "'rev' => " . eZINI::CONFIG_CACHE_REV . ",\n" );
00603         fwrite( $fp, "'created' => '" . date('c') . "',\n" );
00604 
00605         if ( $this->Codec )
00606             fwrite( $fp, "'charset' => \"".$this->Codec->RequestedOutputCharsetCode."\",\n" );
00607         else
00608             fwrite( $fp, "'charset' => \"$this->Charset\",\n" );
00609 
00610         fwrite( $fp, "'files' => " . preg_replace( "@\n[\s]+@", '', var_export( $inputFiles, true ) ) . ",\n" );
00611         fwrite( $fp, "'file' => '$iniFile',\n" );
00612 
00613         fwrite( $fp, "'val' => " . preg_replace( "@\n[\s]+@", '', var_export( $data, true ) ) . ");" );
00614         fwrite( $fp, "\n?>" );
00615         fclose( $fp );
00616 
00617         // Rename cache temp file to final desitination and set permissions
00618         if( eZFile::rename( $tmpCacheFile, $cachedFile ) )
00619         {
00620             chmod( $cachedFile, self::$filePermission );
00621         }
00622 
00623 
00624         if ( self::isDebugEnabled() )
00625             eZDebug::writeNotice( "Wrote cache file '$cachedFile'", __METHOD__ );
00626 
00627         return true;
00628     }
00629 
00630     /*!
00631       \private
00632       Parses either the override ini file or the standard file and then the append
00633       override file if it exists.
00634      */
00635     function parse( $inputFiles = false, $iniFile = false, $reset = true, $placement = false )
00636     {
00637         if ( $inputFiles === false or
00638              $iniFile === false )
00639         {
00640             eZDebug::accumulatorStart( 'ini_parse_find_files', 'Ini load', 'Find INI Files2' );
00641             $this->findInputFiles( $inputFiles, $iniFile );
00642             eZDebug::accumulatorStop( 'ini_parse_find_files' );
00643         }
00644 
00645         if ( $reset )
00646             $this->reset();
00647 
00648         foreach ( $inputFiles as $inputFile )
00649         {
00650             if ( file_exists( $inputFile ) )
00651             {
00652                 $this->parseFile( $inputFile, $placement );
00653             }
00654         }
00655     }
00656 
00657     /*!
00658       \private
00659       Will parse the INI file and store the variables in the variable $this->BlockValues
00660      */
00661     function parseFile( $file, $placement = false )
00662     {
00663         if ( self::isDebugEnabled() )
00664             eZDebug::writeNotice( "Parsing file '$file'", __METHOD__ );
00665 
00666         $contents = file_get_contents( $file );
00667         if ( $contents === false )
00668         {
00669             eZDebug::writeError( "Failed opening file '$file' for reading", __METHOD__ );
00670             return false;
00671         }
00672 
00673         $contents = str_replace( "\r", '', $contents );
00674         $endOfLine = strpos( $contents, "\n" );
00675         $line = substr( $contents, 0, $endOfLine );
00676 
00677         $currentBlock = "";
00678         if ( $line )
00679         {
00680             // check for charset
00681             if ( preg_match( "/#\?ini(.+)\?/", $line, $ini_arr ) )
00682             {
00683                 $args = explode( " ", trim( $ini_arr[1] ) );
00684                 foreach ( $args as $arg )
00685                 {
00686                     $vars = explode( '=', trim( $arg ) );
00687                     if ( $vars[0] == "charset" )
00688                     {
00689                         $val = $vars[1];
00690                         if ( $val[0] == '"' and
00691                              strlen( $val ) > 0 and
00692                              $val[strlen($val)-1] == '"' )
00693                             $val = substr( $val, 1, strlen($val) - 2 );
00694                         $this->Charset = $val;
00695                     }
00696                 }
00697             }
00698         }
00699 
00700         unset( $this->Codec );
00701         if ( $this->UseTextCodec )
00702         {
00703             $this->Codec = eZTextCodec::instance( $this->Charset, false, false );
00704 
00705             if ( $this->Codec )
00706             {
00707                 eZDebug::accumulatorStart( 'ini_conversion', false, 'INI string conversion' );
00708                 $contents = $this->Codec->convertString( $contents );
00709                 eZDebug::accumulatorStop( 'ini_conversion' );
00710             }
00711         }
00712         else
00713             $this->Codec = null;
00714 
00715         foreach ( explode( "\n", $contents ) as $line )
00716         {
00717             if ( $line == '' or $line[0] == '#' )
00718                 continue;
00719             if ( preg_match( "/^(.+)##.*/", $line, $regs ) )
00720                 $line = $regs[1];
00721             if ( trim( $line ) == '' )
00722                 continue;
00723             // check for new block
00724             if ( preg_match("#^\[(.+)\]\s*$#", $line, $newBlockNameArray ) )
00725             {
00726                 $newBlockName = trim( $newBlockNameArray[1] );
00727                 $currentBlock = $newBlockName;
00728                 continue;
00729             }
00730 
00731             // check for variable
00732             if ( preg_match("#^([\w_*@-]+)\\[\\]$#", $line, $valueArray ) )
00733             {
00734                 $varName = trim( $valueArray[1] );
00735 
00736                 if ( $placement )
00737                 {
00738                     if ( isset( $this->BlockValuesPlacement[$currentBlock][$varName] ) &&
00739                          !is_array( $this->BlockValuesPlacement[$currentBlock][$varName] ) )
00740                     {
00741                         eZDebug::writeError( "Wrong operation on the ini setting array '$varName'", __METHOD__ );
00742                         continue;
00743                     }
00744 
00745                     $this->BlockValuesPlacement[$currentBlock][$varName][] = $file;
00746                 }
00747                 else
00748                 {
00749                     $this->BlockValues[$currentBlock][$varName] = array();
00750 
00751                     // In direct access mode we create empty elements at the beginning of an array
00752                     // in case it is redefined in this ini file. So when we will save it, definition
00753                     // will be created as well.
00754                     if ( $this->AddArrayDefinition )
00755                     {
00756                         $this->BlockValues[$currentBlock][$varName][] = "";
00757                     }
00758                 }
00759             }
00760             else if ( preg_match("#^([\w_*@-]+)(\\[([^\\]]*)\\])?=(.*)$#", $line, $valueArray ) )
00761             {
00762                 $varName = trim( $valueArray[1] );
00763                 $varValue = $valueArray[4];
00764 
00765                 if ( $valueArray[2] )
00766                 {
00767                     if ( $valueArray[3] )
00768                     {
00769                         $keyName = $valueArray[3];
00770                         if ( $placement )
00771                         {
00772                             $this->BlockValuesPlacement[$currentBlock][$varName][$keyName] = $file;
00773                         }
00774                         else
00775                         {
00776                             $this->BlockValues[$currentBlock][$varName][$keyName] = $varValue;
00777                         }
00778                     }
00779                     else
00780                     {
00781                         if ( $placement )
00782                         {
00783                             $this->BlockValuesPlacement[$currentBlock][$varName][] = $file;
00784                         }
00785                         else
00786                         {
00787                             $this->BlockValues[$currentBlock][$varName][] = $varValue;
00788                         }
00789                     }
00790                 }
00791                 else
00792                 {
00793                     if ( $placement )
00794                     {
00795                         $this->BlockValuesPlacement[$currentBlock][$varName] = $file;
00796                     }
00797                     else
00798                     {
00799                         $this->BlockValues[$currentBlock][$varName] = $varValue;
00800                     }
00801                 }
00802             }
00803         }
00804     }
00805 
00806     /*!
00807      \removes the cache file if it exists.
00808     */
00809     function resetCache()
00810     {
00811         if ( $this->CacheFile && file_exists( $this->CacheFile ) )
00812             unlink( $this->CacheFile );
00813         if ( $this->PlacementCacheFile && file_exists( $this->PlacementCacheFile ) )
00814             unlink( $this->PlacementCacheFile );
00815     }
00816 
00817 
00818     /*!
00819       Saves the file to disk.
00820       If filename is given the file is saved with that name if not the current name is used.
00821       If \a $useOverride is true then the file will be placed in the override directory,
00822       if \a $useOverride is "append" it will append ".append" to the filename.
00823     */
00824     function save( $fileName = false, $suffix = false, $useOverride = false,
00825                    $onlyModified = false, $useRootDir = true, $resetArrays = false,
00826                    $encapsulateInPHP = true )
00827     {
00828         $lineSeparator = eZSys::lineSeparator();
00829         $pathArray = array();
00830         $dirArray = array();
00831         if ( $fileName === false )
00832             $fileName = $this->FileName;
00833         if ( $useRootDir === true )
00834         {
00835             $pathArray[] = $this->RootDir;
00836             $dirArray[] = $this->RootDir;
00837         }
00838         else if ( is_string( $useRootDir ) )
00839         {
00840             $pathArray[] = $useRootDir;
00841             $dirArray[] = $useRootDir;
00842         }
00843         if ( $useOverride )
00844         {
00845             $pathArray[] = 'override';
00846             $dirArray[] = 'override';
00847         }
00848         if ( $useOverride === 'append' )
00849             $fileName .= '.append';
00850         if ( $suffix !== false )
00851             $fileName .= $suffix;
00852 
00853         /* Try to guess which filename would fit better: 'xxx.apend' or 'xxx.append.php'.
00854          * We choose 'xxx.append.php' in all cases except when
00855          * 'xxx.append' exists already and 'xxx.append.php' does not exist.
00856          */
00857         if( strstr( $fileName, '.append' ) )
00858         {
00859             $fnAppend    = preg_replace( '#\.php$#', '', $fileName );
00860             $fnAppendPhp = $fnAppend.'.php';
00861             $fpAppend    = eZDir::path( array_merge( $pathArray, array( $fnAppend ) ) );
00862             $fpAppendPhp = eZDir::path( array_merge( $pathArray, array( $fnAppendPhp ) ) );
00863             $fileName = ( file_exists( $fpAppend ) && !file_exists( $fpAppendPhp ) )
00864                        ? $fnAppend : $fnAppendPhp;
00865         }
00866 
00867         $originalFileName = $fileName;
00868         $backupFileName = $originalFileName . eZSys::backupFilename();
00869         $fileName .= '.tmp';
00870 
00871         $dirPath = eZDir::path( $dirArray );
00872         if ( !file_exists( $dirPath ) )
00873             eZDir::mkdir( $dirPath, octdec( '777' ), true );
00874 
00875         $filePath = eZDir::path( array_merge( $pathArray, array( $fileName ) ) );
00876         $originalFilePath = eZDir::path( array_merge( $pathArray, array( $originalFileName ) ) );
00877         $backupFilePath = eZDir::path( array_merge( $pathArray, array( $backupFileName ) ) );
00878 
00879         $fp = @fopen( $filePath, "w+");
00880         if ( !$fp )
00881         {
00882             eZDebug::writeError( "Failed opening file '$filePath' for writing", __METHOD__ );
00883             return false;
00884         }
00885         $writeOK = true;
00886         $written = 0;
00887 
00888         $charset = $this->Codec ? $this->Codec->RequestedOutputCharsetCode : $this->Charset;
00889         if ( $encapsulateInPHP )
00890         {
00891             $written = fwrite( $fp, "<?php /* #?ini charset=\"$charset\"?$lineSeparator$lineSeparator" );
00892         }
00893         else
00894         {
00895             $written = fwrite( $fp, "#?ini charset=\"$charset\"?$lineSeparator$lineSeparator" );
00896         }
00897 
00898         if ( $written === false )
00899             $writeOK = false;
00900         $i = 0;
00901         if ( $writeOK )
00902         {
00903             foreach( array_keys( $this->BlockValues ) as $blockName )
00904             {
00905                 if ( $onlyModified )
00906                 {
00907                     $groupHasModified = false;
00908                     if ( isset( $this->ModifiedBlockValues[$blockName] ) )
00909                     {
00910                         foreach ( $this->ModifiedBlockValues[$blockName] as $modifiedValue )
00911                         {
00912                             if ( $modifiedValue )
00913                                 $groupHasModified = true;
00914                         }
00915                     }
00916                     if ( !$groupHasModified )
00917                         continue;
00918                 }
00919                 $written = 0;
00920                 if ( $i > 0 )
00921                     $written = fwrite( $fp, "$lineSeparator" );
00922                 if ( $written === false )
00923                 {
00924                     $writeOK = false;
00925                     break;
00926                 }
00927                 $written = fwrite( $fp, "[$blockName]$lineSeparator" );
00928                 if ( $written === false )
00929                 {
00930                     $writeOK = false;
00931                     break;
00932                 }
00933                 foreach( array_keys( $this->BlockValues[$blockName] ) as $blockVariable )
00934                 {
00935                     if ( $onlyModified )
00936                     {
00937                         if ( !isset( $this->ModifiedBlockValues[$blockName][$blockVariable] ) or
00938                              !$this->ModifiedBlockValues[$blockName][$blockVariable] )
00939                             continue;
00940                     }
00941                     $varKey = $blockVariable;
00942                     $varValue = $this->BlockValues[$blockName][$blockVariable];
00943                     if ( is_array( $varValue ) )
00944                     {
00945                         if ( count( $varValue ) > 0 )
00946                         {
00947                             $customResetArray = ( isset( $this->BlockValues[$blockName]['ResetArrays'] ) and
00948                                                   $this->BlockValues[$blockName]['ResetArrays'] == 'false' )
00949                                                 ? true
00950                                                 : false;
00951                             if ( $resetArrays and !$customResetArray )
00952                                 $written = fwrite( $fp, "$varKey" . "[]$lineSeparator" );
00953                             foreach ( $varValue as $varArrayKey => $varArrayValue )
00954                             {
00955                                 if ( is_string( $varArrayKey ) )
00956                                     $written = fwrite( $fp, "$varKey" . "[$varArrayKey]=$varArrayValue$lineSeparator" );
00957                                 else
00958                                 {
00959                                     if ( $varArrayValue == NULL )
00960                                         $written = fwrite( $fp, "$varKey" . "[]$lineSeparator" );
00961                                     else
00962                                         $written = fwrite( $fp, "$varKey" . "[]=$varArrayValue$lineSeparator" );
00963                                 }
00964                                 if ( $written === false )
00965                                     break;
00966                             }
00967                         }
00968                         else
00969                             $written = fwrite( $fp, "$varKey" . "[]$lineSeparator" );
00970                     }
00971                     else
00972                     {
00973                         $written = fwrite( $fp, "$varKey=$varValue$lineSeparator" );
00974                     }
00975                     if ( $written === false )
00976                     {
00977                         $writeOK = false;
00978                         break;
00979                     }
00980                 }
00981                 if ( !$writeOK )
00982                     break;
00983                 ++$i;
00984             }
00985         }
00986         if ( $writeOK )
00987         {
00988             if ( $encapsulateInPHP )
00989             {
00990                 $written = fwrite( $fp, "*/ ?>" );
00991 
00992                 if ( $written === false )
00993                     $writeOK = false;
00994             }
00995         }
00996         @fclose( $fp );
00997         if ( !$writeOK )
00998         {
00999             unlink( $filePath );
01000             return false;
01001         }
01002 
01003         chmod( $filePath, self::$filePermission );
01004 
01005         if ( file_exists( $backupFilePath ) )
01006             unlink( $backupFilePath );
01007         if ( file_exists( $originalFilePath ) )
01008         {
01009             if ( !rename( $originalFilePath, $backupFilePath ) )
01010                 return false;
01011         }
01012         if ( !rename( $filePath, $originalFilePath ) )
01013         {
01014             rename( $backupFilePath, $originalFilePath );
01015             return false;
01016         }
01017 
01018         return true;
01019     }
01020 
01021     /*!
01022      Removes all read data from .ini files.
01023     */
01024     function reset()
01025     {
01026         $this->BlockValues = array();
01027         $this->ModifiedBlockValues = array();
01028     }
01029 
01030     /*!
01031      \return the root directory from where all .ini and override files are read.
01032 
01033      This is set by the instance() or eZINI() functions.
01034     */
01035     function rootDir()
01036     {
01037         return $this->RootDir;
01038     }
01039 
01040     /**
01041      * Return the override directories witch raw override dir data, or within a scope if $scope is set,
01042      * see {@link eZINI::defaultOverrideDirs()} for how the raw data looks like.
01043      *
01044      * @param string|null|false $scope See {@link eZINI::defaultOverrideDirs()} for possible scope values
01045      *              If false then you'll get raw override dir structure, null (default) is a simplified
01046      *              variant withouth scopes that is easy to iterate over.
01047      * @return array
01048      */
01049     function overrideDirs( $scope = null )
01050     {
01051         if ( $this->UseLocalOverrides == true )
01052             $dirs =& $this->LocalOverrideDirArray;
01053         else
01054             $dirs =& self::$GlobalOverrideDirArray;
01055 
01056         return self::overrideDirsByScope( $dirs, $scope );
01057     }
01058 
01059     /**
01060      * Return the global override directories witch raw override dir data, or within a scope if $scope is set,
01061      * see {@link eZINI::defaultOverrideDirs()} for how the raw data looks like.
01062      *
01063      * @param string|false|null $scope See {@link eZINI::defaultOverrideDirs()} for possible scope values
01064      *              If false then you'll get raw override dir structure, null (default) is a simplified
01065      *              variant withouth scopes that is easy to iterate over.
01066      * @return array
01067      */
01068     public static function globalOverrideDirs( $scope = null )
01069     {
01070         return self::overrideDirsByScope( self::$GlobalOverrideDirArray, $scope );
01071     }
01072 
01073     /**
01074      * Return the override directories witch raw override dir data, or within a scope if $scope is set,
01075      * see {@link eZINI::defaultOverrideDirs()} for how the raw data looks like.
01076      *
01077      * @param array $dirs Directories directly from internal raw structure (see above).
01078      * @param string|null|false $scope See {@link eZINI::defaultOverrideDirs()} for possible scope values
01079      *              If false then you'll get raw override dir structure, null (default) is a simplified
01080      *              variant withouth scopes that is easy to iterate over.
01081      * @return array
01082      */
01083     protected static function overrideDirsByScope( array $dirs, $scope = null )
01084     {
01085         if ( $scope !== null )
01086         {
01087             if ( $scope === false )
01088                 return $dirs;
01089             if ( isset( $dirs[$scope] ) )
01090                 return $dirs[$scope];
01091             eZDebug::writeWarning( "Undefined override dir scope: '$scope'", __METHOD__ );
01092         }
01093 
01094         return array_merge( $dirs['sa-extension'], $dirs['siteaccess'], $dirs['extension'], $dirs['override'] );
01095     }
01096 
01097     /**
01098      * Default override directories as raw array data
01099      *
01100      * @return array An associated array of associated arrays of arrays..
01101      *               First level keys are the scope and values are arrays
01102      *               Second level keys are identifier (numberic if not defined by caller) and value is arrays
01103      *               Third level contains (string) override dir, (bool) global flag if false then
01104      *               relative to {@link $RootDir} and (string|false) optional identifier as used by
01105      *               {@link eZINI::prependOverrideDir()} to match and replace values on.
01106      */
01107     static public function defaultOverrideDirs()
01108     {
01109         static $def =  array(
01110                 'sa-extension' => array(),
01111                 'siteaccess' => array(),
01112                 'extension' => array(),
01113                 'override' => array( array( 'override', false ) )
01114         );
01115         return $def;
01116     }
01117 
01118     /**
01119      * Set the override directories witch raw override dir data, or within a scope if $scope is set,
01120      * see {@link eZINI::defaultOverrideDirs()} for how the raw data looks like.
01121      *
01122      * @param array $newDirs
01123      * @param string|false $scope See {@link eZINI::defaultOverrideDirs()} for possible scope values
01124      */
01125     function setOverrideDirs( array $newDirs, $scope = false )
01126     {
01127         if ( $this->UseLocalOverrides == true )
01128             $dirs =& $this->LocalOverrideDirArray;
01129         else
01130             $dirs =& self::$GlobalOverrideDirArray;
01131 
01132         if ( $scope === false )
01133             $dirs = $newDirs;
01134         else if ( isset( $dirs[$scope] ) )
01135             $dirs[$scope] = $newDirs;
01136         else
01137             eZDebug::writeWarning( "Undefined override dir scope: '$scope'", __METHOD__ );
01138 
01139         $this->CacheFile = false;
01140     }
01141 
01142     /**
01143      * Reset the global override directories with data from {@link eZINI::defaultOverrideDirs()}
01144      */
01145     static public function resetGlobalOverrideDirs()
01146     {
01147         self::$GlobalOverrideDirArray = self::defaultOverrideDirs();
01148     }
01149 
01150     /**
01151      * Reset the override directories with data from {@link eZINI::defaultOverrideDirs()}
01152      */
01153     public function resetOverrideDirs()
01154     {
01155         $this->setOverrideDirs( self::defaultOverrideDirs() );
01156     }
01157 
01158     /**
01159      * Removes an override dir by identifier
01160      * See {@link eZINI::defaultOverrideDirs()} for how these parameters are used.
01161      *
01162      * @param string $identifier Will remove existing directory with identifier it it exists
01163      * @param string $scope By default 'extension'
01164      * @return bool True if new dir was appended, false if there was a $identifier match and a overwrite
01165      */
01166     function removeOverrideDir( $identifier, $scope = 'extension' )
01167     {
01168         if ( $this->UseLocalOverrides == true )
01169             $dirs =& $this->LocalOverrideDirArray;
01170         else
01171             $dirs =& self::$GlobalOverrideDirArray;
01172 
01173         if ( !$identifier || !is_string( $identifier ) )
01174         {
01175             eZDebug::writeError( "\$identifier must be a string", __METHOD__ );
01176             return false;
01177         }
01178 
01179         if ( !isset( $dirs[$scope] ) )
01180         {
01181             eZDebug::writeWarning( "Undefined override dir scope: '$scope'", __METHOD__ );
01182             $scope = 'extension';
01183         }
01184 
01185         $overrideRemoved = false;
01186         if ( isset( $dirs[$scope][$identifier] ) )
01187         {
01188             unset( $dirs[$scope][$identifier] );
01189             $overrideRemoved = true;
01190             $this->CacheFile = false;
01191         }
01192 
01193         return $overrideRemoved;
01194     }
01195 
01196     /**
01197      * Prepends the override directory $dir to the override directory list.
01198      * Prepends override dir to 'extension' scope by default, bellow siteaccess and override settings.
01199      * See {@link eZINI::defaultOverrideDirs()} for how these parameters are used.
01200      *
01201      * @param string $dir
01202      * @param bool $globalDir
01203      * @param string|false $identifier Will overwrite existing directory with same identifier if set
01204      * @param string|null $scope
01205      * @return bool True if new dir was prepended, false if there was a $identifier match and a overwrite
01206      */
01207     function prependOverrideDir( $dir, $globalDir = false, $identifier = false, $scope = null )
01208     {
01209         if ( self::isDebugEnabled() )
01210             eZDebug::writeNotice( "Prepending override dir '$dir'", "eZINI" );
01211 
01212         if ( $this->UseLocalOverrides == true )
01213             $dirs =& $this->LocalOverrideDirArray;
01214         else
01215             $dirs =& self::$GlobalOverrideDirArray;
01216 
01217         $scope = self::selectOverrideScope( $scope, $identifier, $dir, 'extension' );
01218 
01219         // Check if the override with the current identifier already exists
01220         $overrideOverwritten = false;
01221         if ( $identifier && isset( $dirs[$scope][$identifier] ) )
01222         {
01223             $dirs[$scope][$identifier] = array( $dir, $globalDir );
01224             $overrideOverwritten = true;
01225         }
01226         else
01227         {
01228             // array_merge() is to be used below instead of the "+" operator because the
01229             // position of elements in this mixed hash/list is important!
01230             if ( $identifier )
01231                 $dirs[$scope] = array_merge( array( $identifier => array( $dir, $globalDir ) ), $dirs[$scope] );
01232             else
01233                 $dirs[$scope] = array_merge( array( array( $dir, $globalDir ) ), $dirs[$scope] );
01234         }
01235 
01236         $this->CacheFile = false;
01237         return $overrideOverwritten === false;
01238      }
01239 
01240     /**
01241      * Appends the override directory $dir to the override directory list.
01242      * Appends override dir to 'override' scope if scope is not defined, meaning above anything else.
01243      * See {@link eZINI::defaultOverrideDirs()} for how these parameters are used.
01244      *
01245      * @param string $dir
01246      * @param bool $globalDir
01247      * @param string|false $identifier Will overwrite existing directory with same identifier if set
01248      * @param string|null $scope
01249      * @return bool True if new dir was appended, false if there was a $identifier match and a overwrite
01250      */
01251     function appendOverrideDir( $dir, $globalDir = false, $identifier = false, $scope = null )
01252     {
01253         if ( self::isDebugEnabled() )
01254             eZDebug::writeNotice( "Appending override dir '$dir'", __METHOD__ );
01255 
01256         if ( $this->UseLocalOverrides == true )
01257             $dirs =& $this->LocalOverrideDirArray;
01258         else
01259             $dirs =& self::$GlobalOverrideDirArray;
01260 
01261         $scope = self::selectOverrideScope( $scope, $identifier, $dir, 'override' );
01262 
01263         // Check if the override with the current identifier already exists
01264         $overrideOverwritten = false;
01265         if ( $identifier && isset( $dirs[$scope][$identifier] ) )
01266         {
01267             $dirs[$scope][$identifier] = array( $dir, $globalDir );
01268             $overrideOverwritten = true;
01269         }
01270         else
01271         {
01272             if ( $identifier )
01273                 $dirs[$scope][$identifier] = array( $dir, $globalDir );
01274             else
01275                 $dirs[$scope][] = array( $dir, $globalDir );
01276         }
01277 
01278         $this->CacheFile = false;
01279         return $overrideOverwritten === false;
01280     }
01281 
01282     /**
01283      * Function to handle bc with code from pre 4.4 that does not know about scopes
01284      *
01285      * @since 4.4
01286      * @param string|null $scope
01287      * @param string $identifier
01288      * @param string $dir
01289      * @param string $default
01290      * @return string
01291      */
01292     protected static function selectOverrideScope( $scope, $identifier, $dir, $default )
01293     {
01294         if ( $scope !== null )
01295         {
01296             $def = self::defaultOverrideDirs();
01297             if ( isset( $def[$scope] ) )
01298                 return $scope;
01299             eZDebug::writeWarning( "Undefined override dir scope: '$scope' with dir: '$dir'", __METHOD__ );
01300         }
01301         if ( $identifier === 'siteaccess' )
01302             return 'siteaccess';
01303         else if ( $identifier && strpos($identifier, 'extension:') === 0 )
01304             return 'extension';
01305         else if ( strpos($dir, 'siteaccess') !== false )
01306             return 'siteaccess';
01307         else if ( strpos($dir, 'extension') !== false )
01308             return 'extension';
01309 
01310         eZDebug::writeStrict( "Could not figgure out INI scope for \$identifier: '$identifier' with \$dir: '$dir', falling back to '$default'", __METHOD__ );
01311         return $default;
01312     }
01313 
01314     /*!
01315       Reads a variable from the ini file and puts it in the parameter \a $variable.
01316       \note \a $variable is not modified if the variable does not exist
01317     */
01318     function assign( $blockName, $varName, &$variable )
01319     {
01320         if ( isset( $this->BlockValues[$blockName][$varName] ) )
01321             $variable = $this->BlockValues[$blockName][$varName];
01322         else
01323             return false;
01324         return true;
01325     }
01326 
01327     /*!
01328       Reads a variable from the ini file.
01329       false is returned if the variable was not found.
01330     */
01331     function variable( $blockName, $varName )
01332     {
01333         if ( isset( $this->BlockValues[$blockName][$varName] ) )
01334             return $this->BlockValues[$blockName][$varName];
01335         else if ( !isset( $this->BlockValues[$blockName] ) )
01336             eZDebug::writeError( "Undefined group: '$blockName' in " . $this->FileName, __METHOD__ );
01337         else
01338             eZDebug::writeError( "Undefined variable: '$varName' in group '$blockName' in " . $this->FileName, __METHOD__ );
01339         return false;
01340     }
01341 
01342     /*!
01343       Reads multiple variables from the ini file.
01344       false is returned if the variable was not found.
01345     */
01346     function variableMulti( $blockName, $varNames, $signatures = array() )
01347     {
01348         $ret = array();
01349 
01350         if ( !isset( $this->BlockValues[$blockName] ) )
01351         {
01352             eZDebug::writeError( "Undefined group: '$blockName' in " . $this->FileName, "eZINI" );
01353             return false;
01354         }
01355         foreach ( $varNames as $key => $varName )
01356         {
01357             if ( isset( $this->BlockValues[$blockName][$varName] ) )
01358             {
01359                 $ret[$key] = $this->BlockValues[$blockName][$varName];
01360 
01361                 if ( isset( $signatures[$key] ) )
01362                 {
01363                     switch ( $signatures[$key] )
01364                     {
01365                         case 'enabled':
01366                             $ret[$key] = $this->BlockValues[$blockName][$varName] == 'enabled';
01367                             break;
01368                     }
01369                 }
01370             }
01371             else
01372             {
01373                 $ret[] = null;
01374             }
01375         }
01376 
01377         return $ret;
01378     }
01379 
01380     /*!
01381       Checks if a variable is set. Returns true if the variable exists, false if not.
01382     */
01383     function hasVariable( $blockName, $varName )
01384     {
01385         return isset( $this->BlockValues[$blockName][$varName] );
01386     }
01387 
01388     /*!
01389       Check if a block/section is set. Returns true if the section/block is set, false if not
01390     */
01391     function hasSection( $sectionName )
01392     {
01393         return isset( $this->BlockValues[$sectionName] );
01394     }
01395 
01396     /*!
01397       \return true if the variable \a $varName in group \a $blockName has been modified.
01398     */
01399     function isVariableModified( $blockName, $varName )
01400     {
01401         return ( isset( $this->ModifiedBlockValues[$blockName][$varName] ) and
01402                  $this->ModifiedBlockValues[$blockName][$varName] );
01403     }
01404 
01405     /*!
01406       Reads a variable from the ini file. The variable
01407       will be returned as an array. ; is used as delimiter.
01408      */
01409     function variableArray( $blockName, $varName )
01410     {
01411         if ( isset( $this->BlockValues[$blockName][$varName] ) )
01412             $ret = $this->BlockValues[$blockName][$varName];
01413         else
01414             return false;
01415 
01416         if ( is_array( $ret ) )
01417         {
01418             $arr = array();
01419             foreach ( $ret as $key => $retItem )
01420             {
01421                 $arr[$key] = explode( ';', $retItem );
01422             }
01423             $ret = $arr;
01424         }
01425         else if ( $ret !== false )
01426         {
01427             $ret = trim( $ret ) === '' ? array() : explode( ';', $ret );
01428         }
01429 
01430         return $ret;
01431     }
01432 
01433     /*!
01434       Checks if group $blockName is set. Returns true if the group exists, false if not.
01435     */
01436     function hasGroup( $blockName )
01437     {
01438         return isSet( $this->BlockValues[$blockName] );
01439     }
01440 
01441     /*!
01442       Fetches a variable group and returns it as an associative array.
01443      */
01444     function &group( $blockName )
01445     {
01446         if ( !isset( $this->BlockValues[$blockName] ) )
01447         {
01448             eZDebug::writeError( "Unknown group: '$blockName'", __METHOD__ );
01449             $ret = null;
01450             return $ret;
01451         }
01452         $ret = $this->BlockValues[$blockName];
01453 
01454         return $ret;
01455     }
01456 
01457     function isSettingReadOnly( $fileName = false, $blockName = false, $settingName = false )
01458     {
01459         if ( !$this->readOnlySettingsCheck() )
01460             return true;
01461 
01462         $ini = eZINI::instance();
01463         if ( !$ini->hasVariable( 'eZINISettings', 'ReadonlySettingList' ) )
01464             return true;
01465 
01466         $fileName = $fileName === false ? $ini->FileName : $fileName;
01467         $fileNameExploded = explode( '.', $fileName );
01468         $realFileName = $fileNameExploded[0] . '.' . $fileNameExploded[1];
01469         $blockName = $blockName === false ? '*' : $blockName;
01470         $settingName = $settingName === false ? '*' : $settingName;
01471         $currentSetting = $realFileName . '/' . $blockName . '/' . $settingName;
01472 
01473         $settingList = $ini->variable( 'eZINISettings', 'ReadonlySettingList' );
01474         $settingList[] = 'site.ini/eZINISettings/*';
01475 
01476         $result = !( in_array( $realFileName . '/*' , $settingList ) or
01477                      in_array( $realFileName . '/' . $blockName . '/*'  , $settingList ) or
01478                      in_array( $realFileName . '/' . $blockName . '/' . $settingName  , $settingList ) );
01479 
01480         return $result;
01481     }
01482     /*!
01483       Removes the group and all it's settings from the .ini file
01484     */
01485     function removeGroup( $blockName )
01486     {
01487         unset( $this->BlockValues[$blockName] );
01488         unset( $this->BlockValuesPlacement[$blockName] );
01489     }
01490 
01491     function removeSetting( $blockName, $settingName )
01492     {
01493         unset( $this->BlockValues[$blockName][$settingName] );
01494         unset( $this->BlockValuesPlacement[$blockName][$settingName] );
01495         if ( $this->BlockValues[$blockName] == null )
01496             $this->removeGroup( $blockName );
01497     }
01498 
01499     /*!
01500      Fetches all defined groups and returns them as an associative array
01501     */
01502     function &groups()
01503     {
01504         return $this->BlockValues;
01505     }
01506 
01507     /*!
01508      Fetches all defined placements for every setting and returns them as an associative array
01509     */
01510     function &groupPlacements()
01511     {
01512         if ( !$this->BlockValuesPlacement )
01513         {
01514             $this->loadPlacement();
01515         }
01516         return $this->BlockValuesPlacement;
01517     }
01518 
01519     /**
01520      * Gives you the location of a ini file based on it's path, format is same as used internally
01521      * for $identifer for override dirs-
01522      * Eg: default / ext-siteaccess:<ext> / siteaccess / extension:<ext> / override
01523      *
01524      * @param string $path
01525      * @return string
01526      */
01527     function findSettingPlacement( $path )
01528     {
01529         if ( is_array( $path ) && isset( $path[0] ) )
01530             $path = $path[0];
01531         $exploded = explode( '/', $path );
01532         $directoryCount = count( $exploded );
01533         switch ( $directoryCount )
01534         {
01535             case 2:
01536                 $placement = 'default';
01537             break;
01538             case 3:
01539                 $placement = 'override';
01540             break;
01541             case 4:
01542             {
01543                 if ( $exploded[0] === 'extension' )
01544                     $placement = 'extension:' . $exploded[1];
01545                 else
01546                     $placement = 'siteaccess';
01547             }
01548             break;
01549             case 6:
01550             {
01551                 $placement = 'ext-siteaccess:' . $exploded[1];
01552             }
01553             break;
01554             default:
01555                 $placement = 'undefined';
01556             break;
01557         }
01558         return $placement;
01559     }
01560 
01561     function settingType( $settingValue )
01562     {
01563         if ( is_array( $settingValue ) )
01564             return 'array';
01565 
01566         if ( is_numeric( $settingValue ) )
01567             return 'numeric';
01568 
01569         if ( $settingValue == 'true' or $settingValue == 'false' )
01570         {
01571             return 'true/false';
01572         }
01573         if ( $settingValue == 'enabled' or $settingValue == 'disabled' )
01574         {
01575             return 'enable/disable';
01576         }
01577 
01578         return 'string';
01579     }
01580 
01581     /*!
01582      Sets all groups overwriting the current values
01583     */
01584     function setGroups( $groupArray )
01585     {
01586         $resultArray = array();
01587         // Check for readOnly
01588         foreach ( $groupArray as $blockName => $blockVariables )
01589         {
01590             foreach ( $blockVariables as $variableName => $variableValue )
01591             {
01592                 if ( !$this->isSettingReadOnly( $this->FileName, $blockName, $variableName ) )
01593                     continue;
01594                 $resultArray[$blockName][$variableName] = $variableValue;
01595             }
01596         }
01597         $this->BlockValues = $resultArray;
01598     }
01599 
01600     /*!
01601      Sets multiple variables from the array \a $variables.
01602      \param $variables Contains an associative array with groups as first key,
01603                        variable names as second key and variable values as values.
01604      \code
01605      $ini->setVariables( array( 'SiteSettings' => array( 'SiteName' => 'mysite',
01606                                                          'SiteURL'  => 'mysite.com' ) ) );
01607      \endcode
01608      \sa setVariable
01609     */
01610     function setVariables( $variables )
01611     {
01612         foreach ( $variables as $blockName => $blockVariables )
01613         {
01614             foreach ( $blockVariables as $variableName => $variableValue )
01615             {
01616                 $this->setVariable( $blockName, $variableName, $variableValue );
01617             }
01618         }
01619     }
01620 
01621     /*!
01622      Sets an INI file variable.
01623      \code
01624      $ini->setVariable( 'SiteSettings', 'SiteName', 'mysite' );
01625      \endcode
01626      \sa setVariables
01627     */
01628     function setVariable( $blockName, $variableName, $variableValue )
01629     {
01630         if ( !$this->isSettingReadOnly( $this->filename(), $blockName, $variableName ) )
01631             return false;
01632 
01633         $this->BlockValues[$blockName][$variableName] = $variableValue;
01634         $this->ModifiedBlockValues[$blockName][$variableName] = true;
01635     }
01636 
01637     /*!
01638       Returns BlockValues, which is a nicely named Array
01639     */
01640     function getNamedArray()
01641     {
01642         return $this->BlockValues;
01643     }
01644 
01645     /**
01646      * Returns whether the mentioned ini file has been loaded.
01647      *
01648      * @param string $fileName
01649      * @param string $rootDir
01650      * @param null|bool $useLocalOverrides default system setting if null
01651      *
01652      * @return bool
01653      */
01654     static function isLoaded( $fileName = 'site.ini', $rootDir = 'settings', $useLocalOverrides = null )
01655     {
01656         return isset( self::$instances["$rootDir-$fileName-$useLocalOverrides"] );
01657     }
01658 
01659     /**
01660      * Returns a shared instance of the eZINI class pr $fileName, $rootDir and $useLocalOverrides
01661      * param combinations.
01662      * If $useLocalOverrides is set to true you will get a copy of the current overrides,
01663      * but changes to the override settings will not be global.
01664      * Direct access is for accessing the filename directly in the specified path. .append and .append.php is automaticly added to filename
01665      * note: Use create() if you need to get a unique copy which you can alter.
01666      *
01667      * @param string $fileName
01668      * @param string $rootDir
01669      * @param null|bool $useTextCodec Default system setting if null (instance not used if not null!)
01670      * @param null|bool $useCache Default system setting if null (instance not used if not null!)
01671      * @param null|bool $useLocalOverrides Default system setting if null
01672      * @param bool $directAccess Direct access to specific file instead of values from several (instance not used if true!)
01673      * @param bool $addArrayDefinition @deprecated since version 4.5, use "new eZINI()" (instance not used if true!)
01674      * @return eZINI
01675      */
01676     static function instance( $fileName = 'site.ini', $rootDir = 'settings', $useTextCodec = null, $useCache = null, $useLocalOverrides = null, $directAccess = false, $addArrayDefinition = false )
01677     {
01678         if ( $addArrayDefinition !== false  || $directAccess !== false || $useTextCodec !== null || $useCache !== null )
01679         {
01680             // Could have trown strict error here but will cause issues if ini system has not been setup yet..
01681             return new eZINI( $fileName, $rootDir, $useTextCodec, $useCache, $useLocalOverrides, $directAccess, $addArrayDefinition );
01682         }
01683 
01684         $key = "$rootDir-$fileName-$useLocalOverrides";
01685         if ( !isset( self::$instances[$key] ) )
01686         {
01687             self::$instances[$key] = new eZINI( $fileName, $rootDir, $useTextCodec, $useCache, $useLocalOverrides, $directAccess, $addArrayDefinition );
01688         }
01689         return self::$instances[$key];
01690     }
01691 
01692     /*!
01693      Fetches the ini file \a $fileName and returns the INI object for it.
01694      \note This will not use the override system or read cache files, this is a direct fetch from one file.
01695     */
01696     static function fetchFromFile( $fileName, $useTextCodec = null )
01697     {
01698         return new eZINI( $fileName, false, $useTextCodec, false, false, true );
01699     }
01700 
01701     /**
01702      * Get ini file for a specific siteaccess (not incl extesnions or overrides)
01703      * use {@link eZSiteAccess::getIni()} instead if you want to have full ini env.
01704      *
01705      * @param string $siteAccess
01706      * @param string $iniFile
01707      * @return eZINI
01708      */
01709     static function getSiteAccessIni( $siteAccess, $iniFile )
01710     {
01711         $saPath = eZSiteAccess::findPathToSiteAccess( $siteAccess );
01712         return self::fetchFromFile( "$saPath/$iniFile" );
01713     }
01714 
01715     /*!
01716       \static
01717       Similar to instance() but will always create a new copy.
01718     */
01719     static function create( $fileName = 'site.ini', $rootDir = 'settings', $useTextCodec = null, $useCache = null, $useLocalOverrides = null )
01720     {
01721         $impl = new eZINI( $fileName, $rootDir, $useTextCodec, $useCache, $useLocalOverrides );
01722         return $impl;
01723     }
01724 
01725     /*!
01726        Sets ReadonlySettingsCheck variable.
01727     */
01728     function setReadOnlySettingsCheck( $readOnly = true )
01729     {
01730         $this->ReadOnlySettingsCheck = $readOnly;
01731     }
01732 
01733     /*!
01734        \return ReadonlySettingsCheck variable.
01735     */
01736     function readOnlySettingsCheck()
01737     {
01738         return $this->ReadOnlySettingsCheck;
01739     }
01740 
01741     /**
01742      * Resets a specific instance of eZINI.
01743      *
01744      * @deprecated since 4.5, use resetInstance() instead
01745      *
01746      * @param string $fileName
01747      * @param string $rootDir
01748      * @param null|bool $useLocalOverrides default system setting if null
01749      *
01750      * @see resetInstance()
01751      */
01752     static function resetGlobals( $fileName = 'site.ini', $rootDir = 'settings', $useLocalOverrides = null )
01753     {
01754         self::resetInstance( $fileName, $rootDir, $useLocalOverrides );
01755     }
01756 
01757     /**
01758      * Resets a specific instance of eZINI.
01759      *
01760      * @since 4.5
01761      *
01762      * @param string $fileName
01763      * @param string $rootDir
01764      * @param null|bool $useLocalOverrides default system setting if null
01765      */
01766     static function resetInstance( $fileName = 'site.ini', $rootDir = 'settings', $useLocalOverrides = null )
01767     {
01768         unset( self::$instances["$rootDir-$fileName-$useLocalOverrides"] );
01769     }
01770 
01771     /**
01772      * Reset all eZINI instances as well override dirs ( optional )
01773      *
01774      * @deprecated since 4.5, use resetAllInstances() instead
01775      *
01776      * @param bool $resetOverrideDirs Specify if you don't want to clear override dirs
01777      *
01778      * @see resetAllInstances()
01779      */
01780     static function resetAllGlobals( $resetGlobalOverrideDirs = true )
01781     {
01782         self::resetAllInstances( $resetGlobalOverrideDirs );
01783     }
01784 
01785     /**
01786      * Reset all eZINI instances as well override dirs ( optional )
01787      *
01788      * @since 4.5
01789      *
01790      * @param bool $resetOverrideDirs Specify if you don't want to clear override dirs
01791      */
01792     static function resetAllInstances( $resetGlobalOverrideDirs = true )
01793     {
01794         self::$instances = array();
01795 
01796         if ( $resetGlobalOverrideDirs )
01797             self::resetGlobalOverrideDirs();
01798     }
01799 
01800     /// \privatesection
01801     /// The charset of the ini file
01802     public $Charset;
01803 
01804     /// Variable to store the textcodec.
01805     public $Codec;
01806 
01807     /// Variable to store the ini file values.
01808     public $BlockValues;
01809 
01810     /// Variable to store the setting placement (which file is the setting in).
01811     public $BlockValuesPlacement;
01812 
01813     /// Variable to store whether variables are modified or not
01814     public $ModifiedBlockValues;
01815 
01816     /// Stores the filename
01817     public $FileName;
01818 
01819     /// The root of all ini files
01820     public $RootDir;
01821 
01822     /// Whether to use the text codec when reading the ini file or not
01823     public $UseTextCodec;
01824 
01825     /// Stores the path and filename of the value cache file
01826     public $CacheFile;
01827 
01828     /// Stores the path and filename of the placement cache file
01829     public $PlacementCacheFile;
01830 
01831     /// true if cache should be used
01832     public $UseCache;
01833 
01834     /// true if the overrides should only be changed locally
01835     public $UseLocalOverrides;
01836 
01837     /// Contains the override dirs, if in local mode
01838     public $LocalOverrideDirArray;
01839 
01840     /// Contains global override dirs
01841     static protected $GlobalOverrideDirArray = null;
01842 
01843     /// If \c true then all file loads are done directly on the filename.
01844     public $DirectAccess;
01845 
01846     /// If \c true empty element will be created in the beginning of array if it is defined in this ini file.
01847     public $AddArrayDefinition;
01848 
01849     /// If \c true eZINI will check each setting (before saving) for correspondence of settings in site.ini[eZINISetting].ReadonlySettingList
01850     public $ReadOnlySettingsCheck = true;
01851 
01852 }
01853 
01854 ?>