= 0) {
# Remove the version field. Leaving the rest of the plugin information. This allows for a config column to remain (future).
ps_query("UPDATE plugins SET inst_version = NULL WHERE name = ?", array("s", $name));
log_activity(null, LOG_CODE_DISABLED, '', 'plugins', 'inst_version', $name, 'name', $inst_version, null, true);
}
// Clear query cache
clear_query_cache("plugins");
}
/**
* Purge configuration of a plugin.
*
* Replaces config value in plugins table with NULL. Note, this function
* will operate on an activated plugin as well so its configuration can
* be 'defaulted' by the plugin's configuration page.
*
* @param string $name Name of plugin to purge configuration.
* @category PluginAuthors
*/
function purge_plugin_config($name)
{
ps_query("UPDATE plugins SET config = NULL, config_json = NULL where name = ?", array("s", $name));
// Clear query cache
clear_query_cache("plugins");
}
/**
* Load plugin .yaml file.
*
* Load a .yaml file for a plugin and return an array of its
* values.
*
* @param string $plugin Name of the plugin
* @param bool $validate Check that the .yaml file is complete. [optional, default=false]
* @param bool $translate Translate the contents to the user's selected language (default=true)
* @return array|bool Associative array of yaml values. If validate is false, this function will return an array of
* blank values if a yaml isn't available
*/
function get_plugin_yaml($plugin, $validate = true, $translate = true)
{
# We're not using a full YAML structure, so this parsing function will do
$plugin_yaml['name'] = $plugin;
$plugin_yaml['version'] = '0';
$plugin_yaml['author'] = '';
$plugin_yaml['info_url'] = '';
$plugin_yaml['update_url'] = '';
$plugin_yaml['config_url'] = '';
$plugin_yaml['desc'] = '';
$plugin_yaml['default_priority'] = '999';
$plugin_yaml['disable_group_select'] = '0';
$plugin_yaml['title'] = '';
$plugin_yaml['icon'] = '';
$plugin_yaml['icon-colour'] = '';
// Validate plugin name (prevent path traversal when getting .yaml)
if (!ctype_alnum(str_replace(["_","-"], "", $plugin))) {
return $validate ? false : $plugin_yaml;
}
// Find plugin YAML
$path = get_plugin_path($plugin) . "/" . $plugin . ".yaml";
if (!(file_exists($path) && is_readable($path))) {
return $validate ? false : $plugin_yaml;
}
$yaml_file_ptr = fopen($path, 'r');
if ($yaml_file_ptr != false) {
while (($line = fgets($yaml_file_ptr)) != '') {
if (
$line[0] != '#'
&& ($pos = strpos($line, ':')) != false
) {
# Exclude comments from parsing
$plugin_yaml[trim(substr($line, 0, $pos))] = trim(substr($line, $pos + 1));
}
}
if ($plugin_yaml['config_url'] != '' && $plugin_yaml['config_url'][0] == '/') {
# Strip leading spaces from the config url
$plugin_yaml['config_url'] = trim($plugin_yaml['config_url'], '/');
}
fclose($yaml_file_ptr);
if ($validate) {
if (isset($plugin_yaml['name']) && $plugin_yaml['name'] == basename($path, '.yaml') && isset($plugin_yaml['version'])) {
return $plugin_yaml;
} else {
return false;
}
}
} elseif ($validate) {
return false;
}
// Handle translations for base plugins
global $language,$languages,$lang;
if ($translate && $language != "en" && $language != "") {
// Include relevant language file and use the translations instead, if set.
if (!array_key_exists($language, $languages)) {
exit("Invalid language");
} // Prevent path traversal
$plugin_lang_file = __DIR__ . "/../plugins/" . $plugin_yaml["name"] . "/languages/" . $language . ".php";
if (file_exists($plugin_lang_file)) {
include_once $plugin_lang_file;
if (isset($lang["plugin-" . $plugin_yaml["name"] . "-title"])) {
// Append the translated title so the English title is still visible, this is so it's still possible to find the relevant plugin
// in the Knowledge Base (until we have Knowledge Base translations down the line)
$plugin_yaml["title"] .= " (" . ($lang["plugin-" . $plugin_yaml["name"] . "-title"] ?? $plugin_yaml["title"]) . ")";
}
$plugin_yaml["desc"] = $lang["plugin-" . $plugin_yaml["name"] . "-desc"] ?? $plugin_yaml["desc"];
}
$param = "plugin-category-" . strtolower(str_replace(" ", "-", $plugin_yaml["category"] ?? ""));
$plugin_yaml["category"] = $lang[$param] ?? $plugin_yaml["category"] ?? "";
}
return $plugin_yaml;
}
/**
* A subset json_encode function that only works on $config arrays but has none
* of the version-to-version variability and other "unusual" behavior of PHP's.
* implementation.
*
* @param $config mixed a configuration variables array. This *must* be an array
* whose elements are UTF-8 encoded strings, booleans, numbers or arrays
* of such elements and whose keys are either numbers or UTF-8 encoded
* strings.
* @return string encoded version of $config or null if $config is beyond our
* capabilities to encode
*/
function config_json_encode($config)
{
$i = 0;
$simple_keys = true;
foreach ($config as $name => $value) {
if (!is_numeric($name) || ($name != $i++)) {
$simple_keys = false;
break;
}
}
$output = $simple_keys ? '[' : '{';
foreach ($config as $name => $value) {
if (!$simple_keys) {
$output .= '"' . config_encode($name) . '":';
}
if (is_string($value)) {
$output .= '"' . config_encode($value) . '"';
} elseif (is_bool($value)) {
$output .= $value ? 'true' : 'false';
} elseif (is_numeric($value)) {
$output .= strval($value);
} elseif (is_array($value)) {
$output .= config_json_encode($value);
} else {
return null; // Give up; beyond our capabilities
}
$output .= ', ';
}
if (substr($output, -2) == ', ') {
$output = substr($output, 0, -2);
}
return $output . ($simple_keys ? ']' : '}');
}
/**
* Utility function to encode the passed string to something that conforms to
* the json spec for a string. Json doesn't allow strings with double-quotes,
* backslashes or control characters in them. For double-quote and backslash,
* the encoding is '\"' and '\\' respectively. The encoding for control
* characters is of the form '\uxxx' where "xxx" is the UTF-8 4-digit hex
* value of the encoded character.
*
* @param $input string the string that needs encoding
* @return an encoded version of $input
*/
function config_encode($input)
{
$output = '';
for ($i = 0; $i < strlen($input); $i++) {
$char = substr($input, $i, 1);
if (ord($char) < 32) {
$char = '\\u' . substr('0000' . dechex(ord($char)), -4);
} elseif ($char == '"') {
$char = '\\"';
} elseif ($char == '\\') {
$char = '\\\\';
}
$output .= $char;
}
return $output;
}
/**
* Return plugin config stored in plugins table for a given plugin name.
*
* Queries the plugins table for a stored config value and, if found,
* unserializes the data and returns the result. If config isn't found
* returns null.
*
* @param string $name Plugin name
* @return mixed|null Returns config data or null if no config.
* @see set_plugin_config
*/
function get_plugin_config($name)
{
global $mysql_charset;
# Need verbatim queries here
$configs = ps_query("SELECT config, config_json from plugins where name = ?", array("s", $name), 'plugins');
$configs = $configs[0] ?? [];
if (!array_key_exists('config', $configs) || is_null($configs['config_json'])) {
return null;
} elseif (array_key_exists('config_json', $configs) && function_exists('json_decode')) {
if (!isset($mysql_charset)) {
$configs['config_json'] = iconv('ISO-8859-1', 'UTF-8', $configs['config_json']);
}
return json_decode($configs['config_json'], true);
} else {
return unserialize(base64_decode($configs['config']));
}
}
/**
* Store a plugin's configuration in the database.
*
* Serializes the $config parameter and stores in the config
* and config_json columns of the plugins table.
*
*
*
*
* @param string $plugin_name Plugin name
* @param mixed $config Configuration variable to store.
* @see get_plugin_config
*/
function set_plugin_config($plugin_name, $config)
{
global $db, $mysql_charset;
$config = config_clean($config);
$config_ser_bin = base64_encode(serialize($config));
$config_ser_json = config_json_encode($config);
if (!isset($mysql_charset)) {
$config_ser_json = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $config_ser_json);
}
// We record the activity before running the query because log_activity() is trying to be clever and figure out the old value
// which will make the new value also show up (incorrectly) as the old value.
log_activity(null, LOG_CODE_EDITED, $config_ser_json, 'plugins', 'config_json', $plugin_name, 'name', null, null, true);
ps_query("UPDATE plugins SET config = ?, config_json = ? WHERE name = ?", array("s", $config_ser_bin, "s", $config_ser_json, "s", $plugin_name));
// Clear query cache
clear_query_cache("plugins");
return true;
}
/**
* Check if a plugin is activated.
*
* Returns true is a plugin is activated in the plugins database.
*
* @param $name Name of plugin to check
* @return bool Returns true is plugin is activated.
*/
function is_plugin_activated($name)
{
$activated = ps_query("SELECT name FROM plugins WHERE name = ? and inst_version IS NOT NULL", array("s", $name), "plugins");
return is_array($activated) && count($activated) > 0;
}
/**
* Get active plugins
*
* @return array
*/
function get_active_plugins()
{
return ps_query('SELECT name, enabled_groups, config, config_json FROM plugins WHERE inst_version >= 0 ORDER BY priority', array(), 'plugins');
}
/**
* Generate the first half of the "guts" of a plugin setup page from a page definition array. This
* function deals with processing the POST that comes (usually) as a result of clicking on the Save
* Configuration button.
*
* The page definition array is typically constructed by a series of calls to config_add_xxxx
* functions (see below). See the setup page for the sample plugin for information on how to use
* this and the associated functions.
*
* If wishing to store array of values in one config option, in your setup page have something like the
* following which adds a single definition for each key of your config option:
* foreach($usergroups as $k => $group)
* {
* global $usergroupemails;
* if(!isset($usergroupemails[$group["ref"]])){$usergroupemails[$group["ref"]]=array();} // Define any missing keys
* $page_def[] = config_add_text_list_input("usergroupemails[".$group["ref"]."]",$group["name"]); //need to pass a string that looks like: "$configoption["key"]"
* }
* The key can consist of numbers, letters or an underscore contained within "" or ''. If using numbers you don't need the quote marks
*
*
* @param $page_def mixed an array whose elements are generated by calls to config_add_xxxx functions
* each of which describes how one of the plugin's configuration variables.
* @param $plugin_name string the name of the plugin for which the function is being invoked.
* @return void|string Returns NULL
*/
function config_gen_setup_post($page_def, $plugin_name)
{
if ((getval('submit', '') != '' || getval('save', '') != '') && enforcePostRequest(false)) {
$config = array();
foreach ($page_def as $def) {
$array_offset = "";
if (preg_match("/\[[\"|']?\w+[\"|']?\]/", $def[1], $array_offset)) {
$array = preg_replace("/\[[\"|']?\w+[\"|']?\]/", "", $def[1]);
preg_match("/[\"|']?\w+[\"|']?/", $array_offset[0], $array_offset);
}
$omit = false;
if (!empty($array_offset)) {
$curr_post = getval($array, "");
if ($curr_post == "") {
continue;
} //Ignore if Array already handled or blank
foreach ($curr_post as $key => $val) {
$config[$array][$key] = explode(',', $val);
$GLOBALS[$array][$key] = explode(',', $val);
}
unset($_POST[$array]); //Unset once array has been handled to prevent duplicate changes
$omit = true;
} else {
$config_global = (isset($GLOBALS[$def[1]]) ? $GLOBALS[$def[1]] : false);
switch ($def[0]) {
case 'html':
case 'fixed_input':
$omit = true;
break;
case 'section_header':
$omit = true;
break;
case 'text_list':
$pval = getval($def[1], '');
$GLOBALS[$def[1]] = (trim($pval) != '') ? explode(',', $pval) : array();
break;
case 'hidden_param':
break;
default:
$def1_is_array = is_array($GLOBALS[$def[1]]);
$GLOBALS[$def[1]] = getval(
$def[1],
$def1_is_array ? [] : '',
false,
$def1_is_array ? 'is_array' : null
);
break;
}
hook('custom_config_post', '', array($def, $config, $omit, $config_global));
}
if (!$omit) {
$config[$def[1]] = $GLOBALS[$def[1]];
}
}
set_plugin_config($plugin_name, $config);
if (getval('submit', '') != '') {
redirect('pages/team/team_plugins.php');
}
}
}
/**
* Generate the second half of the "guts" of a plugin setup page from a page definition array. The
* page definition array is typically constructed by a series of calls to config_add_xxxx functions
* (see below). See the setup page for the sample plugin for information on how to use this and the
* associated functions.
*
* If wishing to ouput array of values for one config option, in your setup page have something like the
* following which adds a single definition for each key of your config option:
* foreach($usergroups as $k => $group)
* {
* global $usergroupemails;
* if(!isset($usergroupemails[$group["ref"]])){$usergroupemails[$group["ref"]]=array();} // Define any missing keys
* $page_def[] = config_add_text_list_input("usergroupemails[".$group["ref"]."]",$group["name"]); //need to pass a string that looks like: "$configoption["key"]"
* }
* The key can consist of numbers, letters or an underscore contained within "" or ''. If using numbers you don't need the quote marks
*
* @param $page_def mixed an array whose elements are generated by calls to config_add_xxxx functions
* each of which describes how one of the plugin's configuratoin variables.
* @param $plugin_name string the name of the plugin for which the function is being invoked.
* @param $upload_status string the status string returned by config_get_setup_post().
* @param $plugin_page_heading string the heading to be displayed for the setup page for this plugin,
* typically a $lang[] variable.
* @param $plugin_page_frontm string front matter for the setup page in html format. This material is
* placed after the page heading and before the form. Default: '' (i.e., no front matter).
*/
function config_gen_setup_html($page_def, $plugin_name, $upload_status, $plugin_page_heading, $plugin_page_frontm = '')
{
global $lang,$baseurl_short;
?>
' . escape($plugin['desc']) . '
'; echo ''; // Render activation/deactivation or disabled message if (!$is_legacy && (!in_array($plugin["name"], $disabled_plugins) || isset($plugin["inst_version"]))) { echo ''; echo ' ' . escape($label); echo ''; } elseif (in_array($plugin["name"], $disabled_plugins)) { $message = $disabled_plugins_message ?: $lang['plugins-disabled-plugin-message']; echo strip_tags_and_attributes(i18n_get_translated($message), ['a'], ['href', 'target']); } // Render "more info" link if available if (!empty($plugin['info_url'])) { echo ''; echo ' ' . escape($lang['plugins-moreinfo']); echo ' '; } // Render group access link for active plugins if ($active && empty($plugin['disable_group_select'])) { echo ' ' . escape($lang['groupaccess']); if (!empty(trim((string) $plugin['enabled_groups']))) { $s = explode(",", $plugin['enabled_groups']); echo ' ' . count($s) . ''; } echo ' '; } // Render configuration options link if available if (!empty($plugin['config_url']) && $active) { $plugin_config_url = (substr($plugin['config_url'], 0, 8) === "/plugins") ? str_replace("/plugins/" . $plugin['name'], get_plugin_path($plugin['name'], true), $plugin['config_url']) : $baseurl_short . $plugin['config_url']; echo ''; echo ' ' . escape($lang['options']); echo ' '; } // Render purge configuration link if applicable if (!empty($plugin['config'])) { echo ''; echo ' ' . escape($lang['purge']); echo ' '; } echo '