table = CRM_Core_DAO_Cache::getTableName(); if (isset($config['group'])) { $this->group = $config['group']; } else { throw new RuntimeException("Cannot construct SqlGroup cache: missing group"); } if (isset($config['componentID'])) { $this->componentID = $config['componentID']; } else { $this->componentID = NULL; } $this->valueCache = []; if (CRM_Utils_Array::value('prefetch', $config, TRUE)) { $this->prefetch(); } } /** * @param string $key * @param mixed $value * @param null|int|\DateInterval $ttl * @return bool */ public function set($key, $value, $ttl = NULL) { CRM_Utils_Cache::assertValidKey($key); $lock = Civi::lockManager()->acquire("cache.{$this->group}_{$key}._null"); if (!$lock->isAcquired()) { throw new \CRM_Utils_Cache_CacheException("SqlGroup: Failed to acquire lock on cache key."); } if (is_int($ttl) && $ttl <= 0) { return $this->delete($key); } $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM {$this->table} WHERE {$this->where($key)}"); $expires = round(microtime(1)) + CRM_Utils_Date::convertCacheTtl($ttl, self::DEFAULT_TTL); $dataSerialized = CRM_Core_BAO_Cache::encode($value); // This table has a wonky index, so we cannot use REPLACE or // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE). if ($dataExists) { $sql = "UPDATE {$this->table} SET data = %1, created_date = FROM_UNIXTIME(%2), expired_date = FROM_UNIXTIME(%3) WHERE {$this->where($key)}"; $args = [ 1 => [$dataSerialized, 'String'], 2 => [time(), 'Positive'], 3 => [$expires, 'Positive'], ]; $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE); } else { $sql = "INSERT INTO {$this->table} (group_name,path,data,created_date,expired_date) VALUES (%1,%2,%3,FROM_UNIXTIME(%4),FROM_UNIXTIME(%5))"; $args = [ 1 => [$this->group, 'String'], 2 => [$key, 'String'], 3 => [$dataSerialized, 'String'], 4 => [time(), 'Positive'], 5 => [$expires, 'Positive'], ]; $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE); } $lock->release(); $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dataSerialized); $this->expiresCache[$key] = $expires; return TRUE; } /** * @param string $key * @param mixed $default * * @return mixed */ public function get($key, $default = NULL) { CRM_Utils_Cache::assertValidKey($key); if (!isset($this->expiresCache[$key]) || time() >= $this->expiresCache[$key]) { $sql = "SELECT path, data, UNIX_TIMESTAMP(expired_date) as expires FROM {$this->table} WHERE " . $this->where($key); $dao = CRM_Core_DAO::executeQuery($sql); while ($dao->fetch()) { $this->expiresCache[$key] = $dao->expires; $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dao->data); } } return (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key]) ? $this->reobjectify($this->valueCache[$key]) : $default; } private function reobjectify($value) { return is_object($value) ? unserialize(serialize($value)) : $value; } /** * @param $key * @param null $default * * @return mixed */ public function getFromFrontCache($key, $default = NULL) { if (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key] && $this->valueCache[$key]) { return $this->reobjectify($this->valueCache[$key]); } else { return $default; } } public function has($key) { $this->get($key); return isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key]; } /** * @param string $key * @return bool */ public function delete($key) { CRM_Utils_Cache::assertValidKey($key); // If we are triggering a deletion of a prevNextCache key in the civicrm_cache tabl // Alssure that the relevant prev_next_cache values are also removed. if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache')) { Civi::service('prevnext')->deleteItem(NULL, $key); } CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where($key)}"); unset($this->valueCache[$key]); unset($this->expiresCache[$key]); return TRUE; } public function flush() { if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache') && Civi::service('prevnext') instanceof CRM_Core_PrevNextCache_Sql) { // Use the standard PrevNextCache cleanup function here not just delete from civicrm_cache CRM_Core_BAO_PrevNextCache::cleanupCache(); } else { CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where()}"); } $this->valueCache = []; $this->expiresCache = []; return TRUE; } public function clear() { return $this->flush(); } public function prefetch() { $dao = CRM_Core_DAO::executeQuery("SELECT path, data, UNIX_TIMESTAMP(expired_date) AS expires FROM {$this->table} WHERE " . $this->where(NULL)); $this->valueCache = []; $this->expiresCache = []; while ($dao->fetch()) { $this->valueCache[$dao->path] = CRM_Core_BAO_Cache::decode($dao->data); $this->expiresCache[$dao->path] = $dao->expires; } } protected function where($path = NULL) { $clauses = []; $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($this->group) . '"'); if ($path) { $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"'); } return $clauses ? implode(' AND ', $clauses) : '(1)'; } }