<commit_before><?php
namespace wcf\system\user\storage;
use wcf\system\cache\source\RedisCacheSource;
use wcf\system\cache\CacheHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\database\Redis;
use wcf\system\SingletonFactory;
use wcf\system\WCF;

/**
 * Handles the persistent user data storage.
 * 
 * @author	<NAME>
 * @copyright	2001-2016 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\System\Storage
 */
class UserStorageHandler extends SingletonFactory {
	/**
	 * data cache
	 * @var	mixed[][]
	 */
	protected $cache = [];
	
	/**
	 * list of outdated data records
	 * @var	mixed[][]
	 */
	protected $resetFields = [];
	
	/**
	 * list of updated or new data records
	 * @var	mixed[][]
	 */
	protected $updateFields = [];
	
	/**
	 * redis instance
	 * @var	Redis
	 */
	protected $redis;
	
	/**
	 * Checks whether Redis is available.
	 */
	protected function init() {
		$cacheSource = CacheHandler::getInstance()->getCacheSource();
		if ($cacheSource instanceof RedisCacheSource) {
			$this->redis = $cacheSource->getRedis();
		}
	}
	
	/**
	 * Loads storage for a given set of users.
	 * 
	 * @param	integer[]	$userIDs
	 */
	public function loadStorage(array $userIDs) {
		if ($this->redis) return;
		
		$tmp = [];
		foreach ($userIDs as $userID) {
			if (!isset($this->cache[$userID])) $tmp[] = $userID;
		}
		
		// ignore users whose storage data is already loaded
		if (empty($tmp)) return;
		
		$conditions = new PreparedStatementConditionBuilder();
		$conditions->add("userID IN (?)", [$tmp]);
		
		$sql = "SELECT	*
			FROM	wcf".WCF_N."_user_storage
			".$conditions;
		$statement = WCF::getDB()->prepareStatement($sql);
		$statement->execute($conditions->getParameters());
		while ($row = $statement->fetchArray()) {
			if (!isset($this->cache[$row['userID']])) {
				$this->cache[$row['userID']] = [];
			}
			
			$this->cache[$row['userID']][$row['field']] = $row['fieldValue'];
		}
	}
	
	/**
	 * Returns stored data for given users.
	 * 
	 * @param	integer[]	$userIDs
	 * @param	string		$field
	 * @return	mixed[]
	 */
	public function getStorage(array $userIDs, $field) {
		$data = [];
		
		if ($this->redis) {
			foreach ($userIDs as $userID) {
				$data[$userID] = $this->redis->hget($this->getRedisFieldName($field), $userID);
				if ($data[$userID] === false) $data[$userID] = null;
			}
			
			return $data;
		}
		
		foreach ($userIDs as $userID) {
			if (isset($this->cache[$userID][$field])) {
				$data[$userID] = $this->cache[$userID][$field];
			}
			else {
				$data[$userID] = null;
			}
		}
		
		return $data;
	}
	
	/**
	 * Returns the value of the given field for a certain user or null if no
	 * such value exists. If no userID is given, the id of the current user
	 * is used.
	 * 
	 * In contrast to getStorage(), this method calls loadStorage() if no stored
	 * data for the user has been loaded yet!
	 * 
	 * @param	string		$field
	 * @param	integer		$userID
	 * @return	mixed
	 */
	public function getField($field, $userID = null) {
		if ($userID === null) {
			$userID = WCF::getUser()->userID;
		}
		
		if (!$userID) {
			return null;
		}
		
		if ($this->redis) {
			$result = $this->redis->hget($this->getRedisFieldName($field), $userID);
			if ($result === false) return null;
			return $result;
		}
		
		// make sure stored data is loaded
		if (!isset($this->cache[$userID])) {
			$this->loadStorage([$userID]);
		}
		
		if (isset($this->cache[$userID][$field])) {
			return $this->cache[$userID][$field];
		}
		
		return null;
	}
	
	/**
	 * Inserts new data records into database.
	 * 
	 * @param	integer		$userID
	 * @param	string		$field
	 * @param	string		$fieldValue
	 */
	public function update($userID, $field, $fieldValue) {
		if ($this->redis) {
			$this->redis->hset($this->getRedisFieldName($field), $userID, $fieldValue);
			$this->redis->expire($this->getRedisFieldName($field), 86400);
			return;
		}
		
		$this->updateFields[$userID][$field] = $fieldValue;
		
		// update data cache for given user
		if (isset($this->cache[$userID])) {
			$this->cache[$userID][$field] = $fieldValue;
		}
	}
	
	/**
	 * Removes a data record from database.
	 * 
	 * @param	integer[]	$userIDs
	 * @param	string		$field
	 */
	public function reset(array $userIDs, $field) {
		if ($this->redis) {
			foreach ($userIDs as $userID) {
				$this->redis->hdel($this->getRedisFieldName($field), $userID);
			}
			return;
		}
		
		foreach ($userIDs as $userID) {
			$this->resetFields[$userID][] = $field;
			
			if (isset($this->cache[$userID][$field])) {
				unset($this->cache[$userID][$field]);
			}
		}
	}
	
	/**
	 * Removes a specific data record for all users.
	 * 
	 * @param	string		$field
	 */
	public function resetAll($field) {
		if ($this->redis) {
			$this->redis->del($this->getRedisFieldName($field));
			return;
		}
		
		$sql = "DELETE FROM	wcf".WCF_N."_user_storage<commit_msg>Fix redis method names in UserStorageHandler<commit_after><?php
namespace wcf\system\user\storage;
use wcf\system\cache\source\RedisCacheSource;
use wcf\system\cache\CacheHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\database\Redis;
use wcf\system\SingletonFactory;
use wcf\system\WCF;

/**
 * Handles the persistent user data storage.
 * 
 * @author	<NAME>
 * @copyright	2001-2016 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\System\Storage
 */
class UserStorageHandler extends SingletonFactory {
	/**
	 * data cache
	 * @var	mixed[][]
	 */
	protected $cache = [];
	
	/**
	 * list of outdated data records
	 * @var	mixed[][]
	 */
	protected $resetFields = [];
	
	/**
	 * list of updated or new data records
	 * @var	mixed[][]
	 */
	protected $updateFields = [];
	
	/**
	 * redis instance
	 * @var	Redis
	 */
	protected $redis;
	
	/**
	 * Checks whether Redis is available.
	 */
	protected function init() {
		$cacheSource = CacheHandler::getInstance()->getCacheSource();
		if ($cacheSource instanceof RedisCacheSource) {
			$this->redis = $cacheSource->getRedis();
		}
	}
	
	/**
	 * Loads storage for a given set of users.
	 * 
	 * @param	integer[]	$userIDs
	 */
	public function loadStorage(array $userIDs) {
		if ($this->redis) return;
		
		$tmp = [];
		foreach ($userIDs as $userID) {
			if (!isset($this->cache[$userID])) $tmp[] = $userID;
		}
		
		// ignore users whose storage data is already loaded
		if (empty($tmp)) return;
		
		$conditions = new PreparedStatementConditionBuilder();
		$conditions->add("userID IN (?)", [$tmp]);
		
		$sql = "SELECT	*
			FROM	wcf".WCF_N."_user_storage
			".$conditions;
		$statement = WCF::getDB()->prepareStatement($sql);
		$statement->execute($conditions->getParameters());
		while ($row = $statement->fetchArray()) {
			if (!isset($this->cache[$row['userID']])) {
				$this->cache[$row['userID']] = [];
			}
			
			$this->cache[$row['userID']][$row['field']] = $row['fieldValue'];
		}
	}
	
	/**
	 * Returns stored data for given users.
	 * 
	 * @param	integer[]	$userIDs
	 * @param	string		$field
	 * @return	mixed[]
	 */
	public function getStorage(array $userIDs, $field) {
		$data = [];
		
		if ($this->redis) {
			foreach ($userIDs as $userID) {
				$data[$userID] = $this->redis->hGet($this->getRedisFieldName($field), $userID);
				if ($data[$userID] === false) $data[$userID] = null;
			}
			
			return $data;
		}
		
		foreach ($userIDs as $userID) {
			if (isset($this->cache[$userID][$field])) {
				$data[$userID] = $this->cache[$userID][$field];
			}
			else {
				$data[$userID] = null;
			}
		}
		
		return $data;
	}
	
	/**
	 * Returns the value of the given field for a certain user or null if no
	 * such value exists. If no userID is given, the id of the current user
	 * is used.
	 * 
	 * In contrast to getStorage(), this method calls loadStorage() if no stored
	 * data for the user has been loaded yet!
	 * 
	 * @param	string		$field
	 * @param	integer		$userID
	 * @return	mixed
	 */
	public function getField($field, $userID = null) {
		if ($userID === null) {
			$userID = WCF::getUser()->userID;
		}
		
		if (!$userID) {
			return null;
		}
		
		if ($this->redis) {
			$result = $this->redis->hGet($this->getRedisFieldName($field), $userID);
			if ($result === false) return null;
			return $result;
		}
		
		// make sure stored data is loaded
		if (!isset($this->cache[$userID])) {
			$this->loadStorage([$userID]);
		}
		
		if (isset($this->cache[$userID][$field])) {
			return $this->cache[$userID][$field];
		}
		
		return null;
	}
	
	/**
	 * Inserts new data records into database.
	 * 
	 * @param	integer		$userID
	 * @param	string		$field
	 * @param	string		$fieldValue
	 */
	public function update($userID, $field, $fieldValue) {
		if ($this->redis) {
			$this->redis->hSet($this->getRedisFieldName($field), $userID, $fieldValue);
			$this->redis->expire($this->getRedisFieldName($field), 86400);
			return;
		}
		
		$this->updateFields[$userID][$field] = $fieldValue;
		
		// update data cache for given user
		if (isset($this->cache[$userID])) {
			$this->cache[$userID][$field] = $fieldValue;
		}
	}
	
	/**
	 * Removes a data record from database.
	 * 
	 * @param	integer[]	$userIDs
	 * @param	string		$field
	 */
	public function reset(array $userIDs, $field) {
		if ($this->redis) {
			foreach ($userIDs as $userID) {
				$this->redis->hDel($this->getRedisFieldName($field), $userID);
			}
			return;
		}
		
		foreach ($userIDs as $userID) {
			$this->resetFields[$userID][] = $field;
			
			if (isset($this->cache[$userID][$field])) {
				unset($this->cache[$userID][$field]);
			}
		}
	}
	
	/**
	 * Removes a specific data record for all users.
	 * 
	 * @param	string		$field
	 */
	public function resetAll($field) {
		if ($this->redis) {
			$this->redis->del($this->getRedisFieldName($field));
			return;
		}
		
		$sql = "DELETE FROM	wcf".WCF_N."_user_storage
{'fraction_non_alphanumeric': 0.17420199375692277, 'fraction_numerical': 0.0026180646460577986, 'mean_word_length': 3.3676341248900616, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '535893', 'n_tokens_mistral': 3923, 'n_tokens_neox': 3346, 'n_words': 790}