Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1010 lines (894 sloc) 32.2 KB
<?php
/**
* Gdn_Model.
*
* @author Mark O'Sullivan <markm@vanillaforums.com>
* @copyright 2009-2019 Vanilla Forums Inc.
* @license GPL-2.0-only
* @package Core
* @since 2.0
*/
/**
* Model base class.
*
* This generic model can be instantiated (with the table name it is intended to
* represent) and used directly, or it can be extended and overridden for more
* complicated procedures related to different tables.
*/
class Gdn_Model extends Gdn_Pluggable {
/** @var Gdn_DataSet An object representation of the current working dataset. */
public $Data;
/** @var Gdn_Database Database object. */
public $Database;
/**
* @var string The name of the field that stores the insert date for a record. This
* field will be automatically filled by the model if it exists.
*/
public $DateInserted = 'DateInserted';
/**
* @var string The name of the field that stores the update date for a record. This
* field will be automatically filled by the model if it exists.
*/
public $DateUpdated = 'DateUpdated';
/**
* @var array The fields that should be filtered out via {@link Gdn_Model::filterForm()}.
*/
protected $filterFields;
/**
* @var string The name of the field that stores the id of the user that inserted it.
* This field will be automatically filled by the model if it exists and
* @@Session::UserID is a valid integer.
*/
public $InsertUserID = 'InsertUserID';
/**
* @var string The name of the table that this model is intended to represent. The
* default value assigned to $this->Name will be the name that the
* model was instantiated with (defined in $this->__construct()).
*/
public $Name;
/**
* @var stringThe name of the primary key field of this model. The default is 'id'. If
* $this->defineSchema() is called, this value will be automatically changed
* to any primary key discovered when examining the table schema.
*/
public $PrimaryKey = 'id';
/**
* @var Gdn_Schema An object that is used to store and examine database schema information
* related to this model. This object is defined and populated with $this->defineSchema().
*/
public $Schema;
/** @var Gdn_SQLDriver Contains the sql driver for the object. */
public $SQL;
/**
* @var string The name of the field that stores the id of the user that updated it.
* This field will be automatically filled by the model if it exists and @@Session::UserID is a valid integer.
*/
public $UpdateUserID = 'UpdateUserID';
/**
* @var Gdn_Validation An object that is used to manage and execute data integrity rules on this
* object. By default, this object only enforces maxlength, data types, and
* required fields (defined when $this->defineSchema() is called).
*/
public $Validation;
/**
* Class constructor. Defines the related database table name.
*
* @param string $name Optionally define the name of the table that this model represents.
* You can also explicitly set this value with $this->Name.
* @param Gdn_Validation $validation The validation dependency.
* If a validation object is not passed in the constructor then one will be created.
*/
public function __construct($name = '', Gdn_Validation $validation = null) {
if ($name == '') {
$name = get_class($this);
}
$this->Database = Gdn::database();
$this->SQL = $this->Database->sql();
if ($validation === null) {
$validation = Gdn::getContainer()->get(Gdn_Validation::class);
}
$this->Validation = $validation;
$this->Name = $name;
$this->PrimaryKey = $name.'ID';
$this->filterFields = [
'Attributes' => 0,
'DateInserted' => 0,
'InsertUserID' => 0,
'InsertIPAddress' => 0,
'CheckBoxes' => 0,
'DateUpdated' => 0,
'UpdateUserID' => 0,
'UpdateIPAddress' => 0,
'DeliveryMethod' => 0,
'DeliveryType' => 0,
'OK' => 0,
'TransientKey' => 0,
'hpt' => 0
];
parent::__construct();
}
/**
* Add one or more filter field names to the list of fields that will be removed during save.
*
* @param string|array $field Either a field name or an array of field names to filter.
* @return Gdn_Model Returns $this for chaining.
*/
public function addFilterField($field) {
if (is_array($field)) {
$this->filterFields = array_replace($this->filterFields, array_fill_keys($field, 0));
} else {
$this->filterFields[$field] = 0;
}
return $this;
}
/**
* A overridable function called before the various get queries.
*/
protected function _beforeGet() {
}
/**
* Get the validation object used to validate data upon saving.
*
* @return Gdn_Validation Returns the validation object.
*/
public function getValidation(): Gdn_Validation {
return $this->Validation;
}
/**
* Set the validation object used to validate data upon saving.
*
* @param Gdn_Validation $Validation The new validation object.
* @return $this
*/
public function setValidation(Gdn_Validation $Validation) {
$this->Validation = $Validation;
return $this;
}
/**
* Take all of the values that aren't in the schema and put them into the attributes column.
*
* @param array $data
* @param string $name
* @return array
*/
protected function collapseAttributes($data, $name = 'Attributes') {
$this->defineSchema();
$row = array_intersect_key($data, $this->Schema->fields());
$attributes = array_diff_key($data, $row);
touchValue($name, $row, []);
if (isset($row[$name]) && is_array($row[$name])) {
$row[$name] = array_merge($row[$name], $attributes);
} else {
$row[$name] = $attributes;
}
return $row;
}
/**
* Expand all of the values in the attributes column so they become part of the row.
*
* @param array $row
* @param string $name
* @return array
* @since 2.2
*/
protected function expandAttributes($row, $name = 'Attributes') {
if (isset($row[$name])) {
$attributes = $row[$name];
unset($row[$name]);
if (is_string($attributes)) {
$attributes = dbdecode($attributes);
}
if (is_array($attributes)) {
$row = array_merge($row, $attributes);
}
}
return $row;
}
/**
* Connects to the database and defines the schema associated with $this->Name.
*
* Also instantiates and automatically defines $this->Validation.
*
* @return Gdn_Schema Returns the schema for this model.
*/
public function defineSchema() {
if (!isset($this->Schema)) {
$this->Schema = new Gdn_Schema($this->Name, $this->Database);
$this->PrimaryKey = $this->Schema->primaryKey($this->Name, $this->Database);
if (is_array($this->PrimaryKey)) {
$this->PrimaryKey = $this->PrimaryKey[0];
}
$this->Validation->setSchema($this->Schema);
}
return $this->Schema;
}
/**
* Get all of the field names that will be filtered out during save.
*
* @return array Returns an array of field names.
*/
public function getFilterFields() {
return array_keys($this->filterFields);
}
/**
* Get the default page size limit.
*
* @return int
*/
public function getDefaultLimit() {
return 30;
}
/**
* Remove one or more fields from the filter field array.
*
* @param string|array $field One or more field names to remove.
* @return Gdn_Model Returns $this for chaining.
*/
public function removeFilterField($field) {
if (is_array($field)) {
$this->filterFields = array_diff_key($this->filterFields, array_fill_keys($field, 0));
} else {
unset($this->filterFields[$field]);
}
return $this;
}
/**
* Takes a set of form data ($Form->_PostValues), validates them, and
* inserts or updates them to the datatabase.
*
* @param array $formPostValues An associative array of $Field => $Value pairs that represent data posted
* from the form in the $_POST or $_GET collection.
* @param array $settings If a custom model needs special settings in order to perform a save, they
* would be passed in using this variable as an associative array.
* @return unknown
*/
public function save($formPostValues, $settings = false) {
// Define the primary key in this model's table.
$this->defineSchema();
// See if a primary key value was posted and decide how to save
$primaryKeyVal = val($this->PrimaryKey, $formPostValues, false);
$insert = $primaryKeyVal == false ? true : false;
if ($insert) {
$this->addInsertFields($formPostValues);
} else {
$this->addUpdateFields($formPostValues);
}
// Validate the form posted values
if ($this->validate($formPostValues, $insert) === true) {
$fields = $this->Validation->validationFields();
$fields = $this->coerceData($fields, false);
unset($fields[$this->PrimaryKey]); // Don't try to insert or update the primary key
if ($insert === false) {
$this->update($fields, [$this->PrimaryKey => $primaryKeyVal]);
} else {
$primaryKeyVal = $this->insert($fields);
}
} else {
$primaryKeyVal = false;
}
return $primaryKeyVal;
}
/**
* Update a row in the database.
*
* @since 2.1
* @param int $rowID
* @param array|string $property
* @param atom $value
*/
public function setField($rowID, $property, $value = false) {
if (!is_array($property)) {
$property = [$property => $value];
}
$this->defineSchema();
$set = array_intersect_key($property, $this->Schema->fields());
self::serializeRow($set);
$this->SQL->put($this->Name, $set, [$this->PrimaryKey => $rowID]);
}
/**
* Set the array of filter field names.
*
* @param array $fields An array of field names.
* @return Gdn_Model Returns $this for chaining.
*/
public function setFilterFields(array $fields) {
$this->filterFields = array_fill_keys($fields, 0);
return $this;
}
/**
* Serialize Attributes and Data columns in a row.
*
* @param array $row
* @since 2.1
*/
public static function serializeRow(&$row) {
foreach ($row as $name => &$value) {
if (is_array($value) && in_array($name, ['Attributes', 'Data'])) {
$value = empty($value) ? null : dbencode($value);
}
}
}
/**
* Strip database prefixes off a where clause.
*
* This method is mainly for backwards compatibility with model methods that demand a database prefix.
*
* @param array|false $where The where to strip.
* @return array Returns a where array without database prefixes.
*/
protected function stripWherePrefixes($where) {
if (empty($where)) {
return [];
}
$result = [];
foreach ((array)$where as $key => $value) {
$parts = explode('.', $key);
$key = $parts[count($parts) === 1 ? 0 : 1];
$result[$key] = $value;
}
return $result;
}
/**
* Split a where array into where values and options.
*
* Some model methods don't have an options parameter so their options are carried in the where clause.
* This method splits those options out
*
* @param array|false $where The where clause.
* @param array $options An array of option keys to default values.
* @return array Returns an array in the form `[$where, $options]`.
*/
protected function splitWhere($where, array $options) {
if (empty($where)) {
return [[], $options];
}
$result = [];
foreach ($where as $key => $value) {
if (array_key_exists($key, $options)) {
$options[$key] = $value;
} else {
$result[$key] = $value;
}
}
return [$result, $options];
}
/**
*
*
* @param array $fields
* @return bool
*/
public function insert($fields) {
$result = false;
$this->addInsertFields($fields);
if ($this->validate($fields, true)) {
// Strip out fields that aren't in the schema.
// This is done after validation to allow custom validations to work.
$schemaFields = $this->Schema->fields();
$fields = array_intersect_key($fields, $schemaFields);
// Quote all of the fields.
$quotedFields = [];
foreach ($fields as $name => $value) {
if (is_array($value) && in_array($name, ['Attributes', 'Data'])) {
$value = empty($value) ? null : dbencode($value);
}
$quotedFields[$name] = $value;
}
$result = $this->SQL->insert($this->Name, $quotedFields);
}
return $result;
}
/**
*
*
* @param array $fields
* @param array $where
* @param array $limit
* @return Gdn_Dataset
*/
public function update($fields, $where = false, $limit = false) {
$result = false;
// primary key (always included in $Where when updating) might be "required"
$allFields = $fields;
if (is_array($where)) {
$allFields = array_merge($fields, $where);
}
if ($this->validate($allFields)) {
$this->addUpdateFields($fields);
// Strip out fields that aren't in the schema.
// This is done after validation to allow custom validations to work.
$schemaFields = $this->Schema->fields();
$fields = array_intersect_key($fields, $schemaFields);
// Quote all of the fields.
$quotedFields = [];
foreach ($fields as $name => $value) {
if (is_array($value) && in_array($name, ['Attributes', 'Data'])) {
$value = empty($value) ? null : dbencode($value);
}
$quotedFields[$name] = $value;
}
$result = $this->SQL->put($this->Name, $quotedFields, $where, $limit);
}
return $result;
}
/**
* Delete records from a table.
*
* @param array|int $where The where clause to delete or an integer value.
* @param array|true $options An array of options to control the delete.
*
* - limit: A limit to the number of records to delete.
* - reset: Deprecated. Whether or not to reset this SQL statement after the delete. Defaults to false.
* @return Gdn_Dataset Returns the result of the delete.
*/
public function delete($where = [], $options = []) {
if (is_numeric($where)) {
deprecated('Gdn_Model->delete(int)', 'Gdn_Model->deleteID()');
$where = [$this->PrimaryKey => $where];
}
$resetData = false;
if ($options === true || val('reset', $options)) {
deprecated('Gdn_Model->delete() with reset true');
$resetData = true;
} elseif (is_numeric($options)) {
deprecated('The $limit parameter is deprecated in Gdn_Model->delete(). Use the limit option.');
$limit = $options;
} else {
$options += ['rest' => true, 'limit' => null];
$limit = $options['limit'];
}
if ($resetData) {
$result = $this->SQL->delete($this->Name, $where, $limit);
} else {
$result = $this->SQL->noReset()->delete($this->Name, $where, $limit);
}
return $result;
}
/**
* Delete a record by primary key.
*
* @param mixed $id The primary key value of the record to delete.
* @param array $options An array of options to affect the delete behaviour. Reserved for future use.
* @return bool Returns **true** if the delete was successful or **false** otherwise.
*/
public function deleteID($id, $options = []) {
$r = $this->delete(
[$this->PrimaryKey => $id]
);
return $r;
}
/**
* Filter out any potentially insecure fields before they go to the database.
*
* @param array $data The array of data to filter.
* @return array Returns a copy of {@link $data} with fields removed.
*/
public function filterForm($data) {
$data = array_diff_key($data, $this->filterFields);
return $data;
}
/**
* Returns an array with only those keys that are actually in the schema.
*
* @param array $data An array of key/value pairs.
* @return array The filtered array.
*/
public function filterSchema($data) {
$fields = $this->Schema->fields($this->Name);
$result = array_intersect_key($data, $fields);
return $result;
}
/**
*
*
* @param string $orderFields
* @param string $orderDirection
* @param int|bool $limit
* @param int|bool $Offset
* @return Gdn_Dataset
*/
public function get($orderFields = '', $orderDirection = 'asc', $limit = false, $pageNumber = false) {
$this->_beforeGet();
return $this->SQL->get($this->Name, $orderFields, $orderDirection, $limit, $pageNumber);
}
/**
* Returns a count of the # of records in the table.
*
* @param array $wheres
* @return Gdn_Dataset
*/
public function getCount($wheres = '') {
$this->_beforeGet();
$this->SQL
->select('*', 'count', 'Count')
->from($this->Name);
if (is_array($wheres)) {
$this->SQL->where($wheres);
}
$data = $this->SQL
->get()
->firstRow();
return $data === false ? 0 : $data->Count;
}
/**
* Get the data from the model based on its primary key.
*
* @param mixed $iD The value of the primary key in the database.
* @param string $datasetType The format of the result dataset.
* @param array $options options to pass to the database.
* @return array|object
*
* @since 2.3 Added the $options parameter.
*/
public function getID($iD, $datasetType = false, $options = []) {
$this->options($options);
$result = $this->getWhere([$this->PrimaryKey => $iD])->firstRow($datasetType);
$fields = ['Attributes', 'Data'];
foreach ($fields as $field) {
if (is_array($result)) {
if (isset($result[$field]) && is_string($result[$field])) {
$val = dbdecode($result[$field]);
if ($val) {
$result[$field] = $val;
} else {
$result[$field] = $val;
}
}
} elseif (is_object($result)) {
if (isset($result->$field) && is_string($result->$field)) {
$val = dbdecode($result->$field);
if ($val) {
$result->$field = $val;
} else {
$result->$field = null;
}
}
}
}
return $result;
}
/**
* Get a dataset for the model with a where filter.
*
* @param array|bool $where A filter suitable for passing to Gdn_SQLDriver::where().
* @param string $orderFields A comma delimited string to order the data.
* @param string $orderDirection One of **asc** or **desc**.
* @param int|false $limit The database limit.
* @param int|false $offset The database offset.
* @return Gdn_DataSet
*/
public function getWhere($where = false, $orderFields = '', $orderDirection = 'asc', $limit = false, $offset = false) {
$this->_beforeGet();
return $this->SQL->getWhere($this->Name, $where, $orderFields, $orderDirection, $limit, $offset);
}
/**
* Returns the $this->Validation->validationResults() array.
*
* @return array
*/
public function validationResults() {
return $this->Validation->results();
}
/**
* @param array $formPostValues
* @param bool $insert
* @return bool
*/
public function validate($formPostValues, $insert = false) {
$this->defineSchema();
return $this->Validation->validate($formPostValues, $insert);
}
/**
* Coerce the data in a given row to conform to the data types in this model's schema.
*
* This method doesn't do full data cleansing yet because that is too much of a change at this point. For this
* reason this method has been kept protected. The main purpose here is to clean the data enough to work better with
* MySQL's strict mode.
*
* @param array $row The row of data to coerce.
* @param bool $strip Whether or not to strip missing columns.
* @return array Returns a new data row with cleansed data.
*/
protected function coerceData($row, $strip = true) {
$columns = array_change_key_case($this->defineSchema()->fields());
$result = [];
foreach ($row as $key => $value) {
$name = strtolower($key);
if (isset($columns[$name])) {
$column = $columns[$name];
switch ($column->Type) {
case 'int':
case 'tinyint':
case 'smallint':
case 'bigint':
if ($value === '' || $value === null) {
$value = null;
} else {
$value = (int)$value;
}
break;
case 'enum':
$enums = array_change_key_case(array_combine($column->Enum, $column->Enum));
if (isset($enums[strtolower($value)])) {
$value = $enums[strtolower($value)];
} elseif (!$value) {
$value = null;
} else {
trigger_error("Enum value of '$value' not valid for {$column->Key}", E_USER_NOTICE);
$value = null;
}
break;
case 'float':
if ($value === '' || $value === null) {
$value = null;
} else {
$value = (float)$value;
}
break;
case 'double':
if ($value === '' || $value === null) {
$value = null;
} else {
$value = (double)$value;
}
break;
case 'datetime':
case 'timestamp':
$value = $value ?: null;
break;
case 'varchar':
case 'text':
case 'mediumtext':
case 'longtext':
// Should already have been validated.
break;
}
$result[$column->Name] = $value;
} elseif (!$strip) {
$result[$key] = $value;
}
}
return $result;
}
/**
* Adds $this->InsertUserID and $this->DateInserted fields to an associative
* array of fieldname/values if those fields exist on the table being
* inserted.
*
* @param array $fields The array of fields to add the values to.
*/
protected function addInsertFields(&$fields) {
$this->defineSchema();
if ($this->Schema->fieldExists($this->Name, $this->DateInserted)) {
if (!isset($fields[$this->DateInserted])) {
$fields[$this->DateInserted] = Gdn_Format::toDateTime();
}
}
$session = Gdn::session();
if ($session->UserID > 0 && $this->Schema->fieldExists($this->Name, $this->InsertUserID)) {
if (!isset($fields[$this->InsertUserID])) {
$fields[$this->InsertUserID] = $session->UserID;
}
}
if ($this->Schema->fieldExists($this->Name, 'InsertIPAddress') && !isset($fields['InsertIPAddress'])) {
$fields['InsertIPAddress'] = ipEncode(Gdn::request()->ipAddress());
}
}
/**
* Adds $this->UpdateUserID and $this->DateUpdated fields to an associative
* array of fieldname/values if those fields exist on the table being updated.
*
* @param array $fields The array of fields to add the values to.
*/
protected function addUpdateFields(&$fields) {
$this->defineSchema();
if ($this->Schema->fieldExists($this->Name, $this->DateUpdated)) {
if (!isset($fields[$this->DateUpdated])) {
$fields[$this->DateUpdated] = Gdn_Format::toDateTime();
}
}
$session = Gdn::session();
if ($session->UserID > 0 && $this->Schema->fieldExists($this->Name, $this->UpdateUserID)) {
if (!isset($fields[$this->UpdateUserID])) {
$fields[$this->UpdateUserID] = $session->UserID;
}
}
if ($this->Schema->fieldExists($this->Name, 'UpdateIPAddress') && !isset($fields['UpdateIPAddress'])) {
$fields['UpdateIPAddress'] = ipEncode(Gdn::request()->ipAddress());
}
}
/**
* Gets/sets an option on the object.
*
* @param string|array $key The key of the option.
* @param mixed $value The value of the option or not specified just to get the current value.
* @return mixed The value of the option or $this if $value is specified.
* @since 2.3
*/
public function options($key, $value = null) {
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->SQL->options($k, $v);
}
} else {
$this->SQL->options($key, $value);
}
return $this;
}
/**
*
*
* @param string $column
* @param int $rowID
* @param string $name
* @param string $value
* @return bool|Gdn_DataSet|object|string
* @throws Exception
*/
public function saveToSerializedColumn($column, $rowID, $name, $value = '') {
if (!isset($this->Schema)) {
$this->defineSchema();
}
// TODO: need to be sure that $this->PrimaryKey is only one primary key
$fieldName = $this->PrimaryKey;
// Load the existing values
$row = $this->SQL
->select($column)
->from($this->Name)
->where($fieldName, $rowID)
->get()
->firstRow();
if (!$row) {
throw new Exception(t('ErrorRecordNotFound'));
}
$values = dbdecode($row->$column);
if (is_string($values) && $values != '') {
throw new Exception(t('Serialized column failed to be unserialized.'));
}
if (!is_array($values)) {
$values = [];
}
if (!is_array($name)) {
// Assign the new value(s)
$name = [$name => $value];
}
$values = dbencode(array_merge($values, $name));
// Save the values back to the db
return $this->SQL
->from($this->Name)
->where($fieldName, $rowID)
->set($column, $values)
->put();
}
/**
*
*
* @param int $rowID
* @param string $property
* @param bool $forceValue
* @return bool|string
* @throws Exception
*/
public function setProperty($rowID, $property, $forceValue = false) {
if (!isset($this->Schema)) {
$this->defineSchema();
}
$primaryKey = $this->PrimaryKey;
if ($forceValue !== false) {
$value = $forceValue;
} else {
$row = $this->getID($rowID);
$value = ($row->$property == '1' ? '0' : '1');
}
$this->SQL
->update($this->Name)
->set($property, $value)
->where($primaryKey, $rowID)
->put();
return $value;
}
/**
* Get something from $record['Attributes'] by dot-formatted key.
*
* Pass record byref.
*
* @param array $record
* @param string $attribute
* @param mixed $default Optional.
* @return mixed
*/
public static function getRecordAttribute(&$record, $attribute, $default = null) {
$rV = "Attributes.{$attribute}";
return valr($rV, $record, $default);
}
/**
* Set something on $record['Attributes'] by dot-formatted key.
*
* Pass record byref.
*
* @param array $record
* @param string $attribute
* @param mixed $value
* @return mixed
*/
public static function setRecordAttribute(&$record, $attribute, $value) {
if (!array_key_exists('Attributes', $record)) {
$record['Attributes'] = [];
}
if (!is_array($record['Attributes'])) {
return null;
}
$work = &$record['Attributes'];
$parts = explode('.', $attribute);
while ($part = array_shift($parts)) {
$setValue = sizeof($parts) ? [] : $value;
$work[$part] = $setValue;
$work = &$work[$part];
}
return $value;
}
/**
* Checks whether the time frame for editing content has passed.
*
* @param object|array $data The content data to examine.
* @param int $timeLeft Sets the time left to edit or 0 if not applicable.
* @return bool Whether the time to edit the discussion has passed.
*/
public static function editContentTimeout($data, &$timeLeft = 0) {
// Determine if we still have time to edit.
$timeInserted = strtotime(val('DateInserted', $data));
$editContentTimeout = c('Garden.EditContentTimeout', -1);
$canEdit = false;
if ($editContentTimeout == -1) {
$canEdit = true;
} elseif ($timeInserted + $editContentTimeout > time()) {
$canEdit = true;
}
if ($canEdit && $editContentTimeout > 0) {
$timeLeft = $timeInserted + $editContentTimeout - time();
}
return $canEdit;
}
/**
* Locks a resource so that it can only be accessed one at a time.
*
* @param string $lockKey Cache key to be assigned.
* @param int $gracePeriod Period of time the key will stay valid.
* @return bool Whether a master key has been assigned.
*/
protected static function buildCacheLock(string $lockKey, int $gracePeriod = 60): bool {
// If caching isn't enabled bail out
$cacheEnabled = Gdn_Cache::activeEnabled();
if (!$cacheEnabled) {
return true;
}
/**
* Attempt to add lock using our process ID. A failure likely means the
* cache key already exists, which would mean the lock is already in place.
*/
$instanceKey = getmypid();
$added = Gdn::cache()->add($lockKey, $instanceKey, [
Gdn_Cache::FEATURE_EXPIRY => $gracePeriod
]);
if ($added) {
return true;
} else {
return ($instanceKey === Gdn::cache()->get($lockKey));
}
}
/**
* Releases a locked resource so that it can be used again.
*
* @param string $lockKey Cache key to be assigned.
* @return bool Whether a master key has been released.
*/
protected function releaseCacheLock(string $lockKey): bool {
// If caching isn't enabled bail out
$cacheEnabled = Gdn_Cache::activeEnabled();
if (!$cacheEnabled) {
return true;
}
$keyReleased = Gdn::cache()->remove($lockKey);
return $keyReleased;
}
}
You can’t perform that action at this time.