aboutsummaryrefslogblamecommitdiffstats
path: root/Zotlabs/Lib/PConfig.php
blob: c08c11e758fc332481db201e9d7e5b8188e94de2 (plain) (tree)
1
2
3
4
     


                      













                                                                                          









                                                                                    
                                                                       
           
                                           
                                                   

                                     
                                               




                                                              





                                                                             












                                                                                        
                                                                                       


















                                                                                    

                                                          

                                                                   
                                                                           
 
                                                   
                                        

                                                           
                                         

                                                                                                                                  
                                        
 
                                                                                                                                                  


                                                                         















                                                                                        

                                            

                                               
                                                                                  
 
                                                                                 
                                                                                
                                                                 


                                                                                  
                                                     







                                                                                
                                          
                                 




                                                                                       

                 


                                                                        
                                                                             





                                                                                                                                              
 
                                                              




                                                                            
 
                                                                                                                          


                                               

                                                
                          
 







                                                                                                  

                                                                                

                      
                                                                                     

                                   





                                                                                                              
                                                                                                                                   
                                                        
                                                        

                                                       
                                                   
                                  


                                                                                        



                                                                                                                 

                 
 









                                                                                 




                                                                                             



















                                                                                   


                                            
           
                                                                             
 


                                                   


                                                                                              





                                                                                                                   

                             
                                                                
                                                                  




                                                                            
 




                                                                                          
 

                                                  




                                                                         



                            
<?php

namespace Zotlabs\Lib;

/**
 * @brief Class for handling channel specific configurations.
 *
 * <b>PConfig</b> is used for channel specific configurations and takes a
 * <i>channel_id</i> as identifier. It stores for example which features are
 * enabled per channel. The storage is of size MEDIUMTEXT.
 *
 * @code{.php}$var = Zotlabs\Lib\PConfig::Get('uid', 'category', 'key');
 * // with default value for non existent key
 * $var = Zotlabs\Lib\PConfig::Get('uid', 'category', 'unsetkey', 'defaultvalue');@endcode
 *
 * The old (deprecated?) way to access a PConfig value is:
 * @code{.php}$var = get_pconfig(local_channel(), 'category', 'key');@endcode
 */
class PConfig {

	/**
	 * @brief Loads all configuration values of a channel into a cached storage.
	 *
	 * All configuration values of the given channel are stored in global cache
	 * which is available under the global variable App::$config[$uid].
	 *
	 * @param string $uid
	 *  The channel_id
	 * @return void|false Nothing or false if $uid is null or false
	 */
	static public function Load($uid) {
		if(is_null($uid) || $uid === false)
			return false;

		if(! is_array(\App::$config)) {
			btlogger('App::$config not an array');
		}

		if(! array_key_exists($uid, \App::$config)) {
			\App::$config[$uid] = array();
		}

		if(! is_array(\App::$config[$uid])) {
			btlogger('App::$config[$uid] not an array: ' . $uid);
		}

		$r = q("SELECT * FROM pconfig WHERE uid = %d",
			intval($uid)
		);

		if($r) {
			foreach($r as $rr) {
				$k = $rr['k'];
				$c = $rr['cat'];
				if(! array_key_exists($c, \App::$config[$uid])) {
					\App::$config[$uid][$c] = array();
					\App::$config[$uid][$c]['config_loaded'] = true;
				}
				\App::$config[$uid][$c][$k] = $rr['v'];
				\App::$config[$uid][$c]['pcfgud:'.$k] = $rr['updated'];
			}
		}
	}

	/**
	 * @brief Get a particular channel's config variable given the category name
	 * ($family) and a key.
	 *
	 * Get a particular channel's config value from the given category ($family)
	 * and the $key from a cached storage in App::$config[$uid].
	 *
	 * Returns false if not set.
	 *
	 * @param string $uid
	 *  The channel_id
	 * @param string $family
	 *  The category of the configuration value
	 * @param string $key
	 *  The configuration key to query
	 * @param mixed $default (optional, default false)
	 *  Default value to return if key does not exist
	 * @return mixed Stored value or false if it does not exist
	 */
	static public function Get($uid, $family, $key, $default = false) {

		if(is_null($uid) || $uid === false)
			return $default;

		if(! array_key_exists($uid, \App::$config))
			self::Load($uid);

		if((! array_key_exists($family, \App::$config[$uid])) || (! array_key_exists($key, \App::$config[$uid][$family])))
			return $default;

		return ((! is_array(\App::$config[$uid][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', \App::$config[$uid][$family][$key]))
			? unserialize(\App::$config[$uid][$family][$key])
			: \App::$config[$uid][$family][$key]
		);
	}

	/**
	 * @brief Sets a configuration value for a channel.
	 *
	 * Stores a config value ($value) in the category ($family) under the key ($key)
	 * for the channel_id $uid.
	 *
	 * @param string $uid
	 *  The channel_id
	 * @param string $family
	 *  The category of the configuration value
	 * @param string $key
	 *  The configuration key to set
	 * @param string $value
	 *  The value to store
	 * @param string $updated (optional)
	 *  The datetime to store
	 * @return mixed Stored $value or false
	 */
	static public function Set($uid, $family, $key, $value, $updated = NULL) {

		// this catches subtle errors where this function has been called
		// with local_channel() when not logged in (which returns false)
		// and throws an error in array_key_exists below.
		// we provide a function backtrace in the logs so that we can find
		// and fix the calling function.

		if(is_null($uid) || $uid === false) {
			btlogger('UID is FALSE!', LOGGER_NORMAL, LOG_ERR);
			return;
		}

		// manage array value
		$dbvalue = ((is_array($value))  ? serialize($value) : $value);
		$dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue)  : $dbvalue);

		$now = datetime_convert();
		if (! $updated) {
			//Sometimes things happen fast... very fast.
			//To make sure legitimate updates aren't rejected
			//because not enough time has passed.  We say our updates
			//happened just a short time in the past rather than right now.
			$updated = datetime_convert('UTC','UTC','-2 seconds');
		}

		$hash = hash('sha256',$family.':'.$key);

		if (self::Get($uid, 'hz_delpconfig', $hash) !== false) {
			if (self::Get($uid, 'hz_delpconfig', $hash) > $now) {
				logger('Refusing to update pconfig with outdated info (Item deleted more recently).', LOGGER_NORMAL, LOG_ERR);
				return self::Get($uid,$family,$key);
			} else {
				self::Delete($uid,'hz_delpconfig',$hash);
			}
		}

		if(self::Get($uid, $family, $key) === false) {
			if(! array_key_exists($uid, \App::$config))
				\App::$config[$uid] = array();
			if(! array_key_exists($family, \App::$config[$uid]))
				\App::$config[$uid][$family] = array();


			$ret = q("INSERT INTO pconfig ( uid, cat, k, v, updated ) VALUES ( %d, '%s', '%s', '%s', '%s' ) ",
				intval($uid),
				dbesc($family),
				dbesc($key),
				dbesc($dbvalue),
				dbesc($updated)
			);

			// There is a possible race condition if another process happens
			// to insert something after this thread has Loaded and now.  We should
			// at least make a note of it if it happens.

			if (!$ret) {
				logger("Error: Insert to pconfig failed.",LOGGER_NORMAL, LOG_ERR);
			}

			\App::$config[$uid][$family]['pcfgud:'.$key] = $updated;

		}
		else {
			$new = (\App::$config[$uid][$family]['pcfgud:'.$key] < $now);

			if ($new) {

				// @NOTE There is still a possible race condition under limited circumstances
				// where a value will be updated by another thread with more current data than
				// we have.  At this point there is no easy way to test for it, so we update
				// and hope for the best.

				$ret = q("UPDATE pconfig SET v = '%s', updated = '%s' WHERE uid = %d and cat = '%s' AND k = '%s' ",
					dbesc($dbvalue),
					dbesc($updated),
					intval($uid),
					dbesc($family),
					dbesc($key)
				);

				\App::$config[$uid][$family]['pcfgud:'.$key] = $updated;

			} else {
				logger('Refusing to update pconfig with outdated info.', LOGGER_NORMAL, LOG_ERR);
				return self::Get($uid, $family, $key);
			}
		}


		// keep a separate copy for all variables which were
		// set in the life of this page. We need this to
		// synchronise channel clones.

		if(! array_key_exists('transient', \App::$config[$uid]))
			\App::$config[$uid]['transient'] = array();
		if(! array_key_exists($family, \App::$config[$uid]['transient']))
			\App::$config[$uid]['transient'][$family] = array();

		\App::$config[$uid][$family][$key] = $value;

		if ($new) {
			\App::$config[$uid]['transient'][$family][$key] = $value;
			\App::$config[$uid]['transient'][$family]['pcfgud:'.$key] = $updated;
		}

		if($ret)
			return $value;

		return $ret;
	}


	/**
	 * @brief Deletes the given key from the channel's configuration.
	 *
	 * Removes the configured value from the stored cache in App::$config[$uid]
	 * and removes it from the database.
	 *
	 * @param string $uid
	 *  The channel_id
	 * @param string $family
	 *  The category of the configuration value
	 * @param string $key
	 *  The configuration key to delete
	 * @param string $updated (optional)
	 *  The datetime to store
	 * @return boolean
	 */
	static public function Delete($uid, $family, $key, $updated = NULL) {

		if(is_null($uid) || $uid === false)
			return false;

		$updated = ($updated) ? $updated : datetime_convert('UTC','UTC','-2 seconds');
		$now = datetime_convert();
		$newer = (\App::$config[$uid][$family]['pcfgud:'.$key] < $now);

		if (! $newer) {
			logger('Refusing to delete pconfig with outdated delete request.', LOGGER_NORMAL, LOG_ERR);
			return false;
		}

		$ret = false;

		if (isset(\App::$config[$uid][$family][$key])) {
			unset(\App::$config[$uid][$family][$key]);
		}

		if (isset(\App::$config[$uid][$family]['pcfgud:'.$key])) {
			unset(\App::$config[$uid][$family]['pcfgud:'.$key]);
		}

		$ret = q("DELETE FROM pconfig WHERE uid = %d AND cat = '%s' AND k = '%s'",
			intval($uid),
			dbesc($family),
			dbesc($key)
		);

		// Synchronize delete with clones.

		if ($family != 'hz_delpconfig') {
			$hash = hash('sha256',$family.':'.$key);
			set_pconfig($uid,'hz_delpconfig',$hash,$updated);
		}

		return $ret;
	}

}