<?php
/**
* @file classes/plugins/PluginSettingsDAO.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PluginSettingsDAO
*
* @ingroup plugins
*
* @see Plugin
*
* @brief Operations for retrieving and modifying plugin settings.
*/
namespace PKP\plugins;
use Illuminate\Support\Facades\DB;
use PKP\cache\CacheManager;
use PKP\cache\GenericCache;
use PKP\xml\PKPXMLParser;
class PluginSettingsDAO extends \PKP\db\DAO
{
/**
* Get the cache for plugin settings.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
*
* @return GenericCache
*/
public function _getCache($contextId, $pluginName)
{
static $settingCache = [];
return $settingCache[(int) $contextId][$pluginName] ??= CacheManager::getManager()->getCache(
'pluginSettings-' . $contextId,
$pluginName,
[$this, '_cacheMiss']
);
}
/**
* Retrieve a plugin setting value.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
* @param string $name Setting name
*/
public function getSetting($contextId, $pluginName, $name)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
// Retrieve the setting.
$cache = $this->_getCache($contextId, $pluginName);
return $cache->get($name);
}
/**
* Does the plugin setting exist.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
* @param string $name Setting name
*
* @return bool
*/
public function settingExists($contextId, $pluginName, $name)
{
$pluginName = strtolower_codesafe($pluginName);
$result = $this->retrieve(
'SELECT COUNT(*) AS row_count FROM plugin_settings WHERE plugin_name = ? AND context_id = ? AND setting_name = ?',
[$pluginName, (int) $contextId, $name]
);
$row = $result->current();
return $row ? (bool) $row->row_count : false;
}
/**
* Callback for a cache miss.
*
* @param object $cache Cache object
* @param string $id Identifier to look up in cache
*/
public function _cacheMiss($cache, $id)
{
$contextParts = explode('-', $cache->getContext());
$contextId = array_pop($contextParts);
$settings = $this->getPluginSettings($contextId, $cache->getCacheId());
if (!isset($settings[$id])) {
// Make sure that even null values are cached
$cache->setCache($id, null);
return null;
}
return $settings[$id];
}
/**
* Retrieve and cache all settings for a plugin.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
*
* @return array
*/
public function getPluginSettings($contextId, $pluginName)
{
// Normalize plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$result = $this->retrieve(
'SELECT setting_name, setting_value, setting_type FROM plugin_settings WHERE plugin_name = ? AND context_id = ?',
[$pluginName, (int) $contextId]
);
$pluginSettings = [];
foreach ($result as $row) {
$pluginSettings[$row->setting_name] = $this->convertFromDB($row->setting_value, $row->setting_type);
}
$cache = $this->_getCache($contextId, $pluginName);
$cache->setEntireCache($pluginSettings);
return $pluginSettings;
}
/**
* Add/update a plugin setting.
*
* @param int $contextId Context ID
* @param string $pluginName Symbolic plugin name
* @param string $name Setting name
* @param mixed $value Setting value
* @param string $type data type of the setting. If omitted, type will be guessed
*/
public function updateSetting($contextId, $pluginName, $name, $value, $type = null)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->setCache($name, $value);
$value = $this->convertToDB($value, $type);
DB::table('plugin_settings')->updateOrInsert(
['context_id' => (int) $contextId, 'plugin_name' => $pluginName, 'setting_name' => $name],
['setting_value' => $value, 'setting_type' => $type]
);
}
/**
* Delete a plugin setting.
*
* @param int $contextId
* @param int $pluginName
* @param string $name
*/
public function deleteSetting($contextId, $pluginName, $name)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->setCache($name, null);
return $this->update(
'DELETE FROM plugin_settings WHERE plugin_name = ? AND setting_name = ? AND context_id = ?',
[$pluginName, $name, (int) $contextId]
);
}
/**
* Delete all settings for a plugin.
*
* @param int $contextId
* @param string $pluginName
*/
public function deleteSettingsByPlugin($contextId, $pluginName)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->flush();
return $this->update(
'DELETE FROM plugin_settings WHERE context_id = ? AND plugin_name = ?',
[(int) $contextId, $pluginName]
);
}
/**
* Delete all settings for a context.
*
* @param int $contextId
*/
public function deleteByContextId($contextId)
{
return $this->update(
'DELETE FROM plugin_settings WHERE context_id = ?',
[(int) $contextId]
);
}
/**
* Used internally by installSettings to perform variable and translation replacements.
*
* @param string $rawInput contains text including variable and/or translate replacements.
* @param array $paramArray contains variables for replacement
*
* @return string
*/
public function _performReplacement($rawInput, $paramArray = [])
{
$value = preg_replace_callback('{{translate key="([^"]+)"}}', fn ($matches) => __($matches[1]), (string) $rawInput);
foreach ($paramArray as $pKey => $pValue) {
$value = str_replace('{$' . $pKey . '}', $pValue, $value);
}
return $value;
}
/**
* Used internally by installSettings to recursively build nested arrays.
* Deals with translation and variable replacement calls.
*
* @param object $node XMLNode <array> tag
* @param array $paramArray Parameters to be replaced in key/value contents
*/
public function _buildObject($node, $paramArray = [])
{
$value = [];
foreach ($node->getChildren() as $element) {
$key = $element->getAttribute('key');
$childArray = $element->getChildByName('array');
if (isset($childArray)) {
$content = $this->_buildObject($childArray, $paramArray);
} else {
$content = $this->_performReplacement($element->getValue(), $paramArray);
}
if (!empty($key)) {
$key = $this->_performReplacement($key, $paramArray);
$value[$key] = $content;
} else {
$value[] = $content;
}
}
return $value;
}
/**
* Install plugin settings from an XML file.
*
* @param string $pluginName name of plugin for settings to apply to
* @param string $filename Name of XML file to parse and install
* @param array $paramArray Optional parameters for variable replacement in settings
*/
public function installSettings($contextId, $pluginName, $filename, $paramArray = [])
{
$xmlParser = new PKPXMLParser();
$tree = $xmlParser->parse($filename);
if (!$tree) {
return false;
}
// Check for existing settings and leave them if they are already in place.
$currentSettings = $this->getPluginSettings($contextId, $pluginName);
foreach ($tree->getChildren() as $setting) {
$nameNode = $setting->getChildByName('name');
$valueNode = $setting->getChildByName('value');
if (isset($nameNode) && isset($valueNode)) {
$type = $setting->getAttribute('type');
$name = $nameNode->getValue();
// If the setting already exists, respect it.
if (isset($currentSettings[$name])) {
continue;
}
if ($type == 'object') {
$arrayNode = $valueNode->getChildByName('array');
$value = $this->_buildObject($arrayNode, $paramArray);
} else {
$value = $this->_performReplacement($valueNode->getValue(), $paramArray);
}
// Replace translate calls with translated content
$this->updateSetting($contextId, $pluginName, $name, $value, $type);
}
}
}
}
/**
* Used internally by plugin setting installation code to perform translation
* function.
*
* @param array $matches
*
* @return string
*/
function _installer_plugin_regexp_callback($matches)
{
return __($matches[1]);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PluginSettingsDAO', '\PluginSettingsDAO');
}
|