first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
/**
* A helper class to escape JSON data
*/
class Escape
{
/**
* Escapes a string returned from Text::_() for use with Javascript
*
* @param $string string The string to escape
* @param $extras string The characters to escape
*
* @return string The escaped string
*/
static function escapeJS($string, $extras = '')
{
// Make sure we escape single quotes, slashes and brackets
if (empty($extras))
{
$extras = "'\\[]\"";
}
return addcslashes($string, $extras);
}
}

View File

@@ -0,0 +1,754 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
use Awf\Html\Html;
use Awf\Text\Text;
use Awf\Utils\ArrayHelper;
use RuntimeException;
use stdClass;
abstract class FEFSelect
{
/**
* Default values for options. Organized by option group.
*
* @var array
*/
protected static $optionDefaults = array(
'option' => array(
'option.attr' => null,
'option.disable' => 'disable',
'option.id' => null,
'option.key' => 'value',
'option.key.toHtml' => true,
'option.label' => null,
'option.label.toHtml' => true,
'option.text' => 'text',
'option.text.toHtml' => true,
'option.class' => 'class',
'option.onclick' => 'onclick',
),
);
/**
* Generates a yes/no radio list.
*
* @param string $name The value of the HTML name attribute
* @param array $attribs Additional HTML attributes for the `<select>` tag
* @param string $selected The key that is selected
* @param string $yes Language key for Yes
* @param string $no Language key for no
* @param mixed $id The id for the field or false for no id
*
* @return string HTML for the radio list
*
* @since 1.5
* @see JFormFieldRadio
*/
public static function booleanlist($name, $attribs = array(), $selected = null, $yes = 'AWF_YES', $no = 'AWF_NO', $id = false)
{
$arr = [
static::option('0', Text::_($no)),
static::option('1', Text::_($yes))
];
return static::radiolist($arr, $name, $attribs, 'value', 'text', (int) $selected, $id);
}
/**
* Generates an HTML selection list.
*
* @param array $data An array of objects, arrays, or scalars.
* @param string $name The value of the HTML name attribute.
* @param mixed $attribs Additional HTML attributes for the `<select>` tag. This
* can be an array of attributes, or an array of options. Treated as options
* if it is the last argument passed. Valid options are:
* Format options, see {@see JHtml::$formatOptions}.
* Selection options, see {@see JHtmlSelect::options()}.
* list.attr, string|array: Additional attributes for the select
* element.
* id, string: Value to use as the select element id attribute.
* Defaults to the same as the name.
* list.select, string|array: Identifies one or more option elements
* to be selected, based on the option key values.
* @param string $optKey The name of the object variable for the option value. If
* set to null, the index of the value array is used.
* @param string $optText The name of the object variable for the option text.
* @param mixed $selected The key that is selected (accepts an array or a string).
* @param mixed $idtag Value of the field id or null by default
* @param boolean $translate True to translate
*
* @return string HTML for the select list.
*
* @since 1.5
*/
public static function genericlist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false,
$translate = false)
{
// Set default options
$options = array_merge(Html::$formatOptions, array('format.depth' => 0, 'id' => false));
if (is_array($attribs) && func_num_args() === 3)
{
// Assume we have an options array
$options = array_merge($options, $attribs);
}
else
{
// Get options from the parameters
$options['id'] = $idtag;
$options['list.attr'] = $attribs;
$options['list.translate'] = $translate;
$options['option.key'] = $optKey;
$options['option.text'] = $optText;
$options['list.select'] = $selected;
}
$attribs = '';
if (isset($options['list.attr']))
{
if (is_array($options['list.attr']))
{
$attribs = ArrayHelper::toString($options['list.attr']);
}
else
{
$attribs = $options['list.attr'];
}
if ($attribs !== '')
{
$attribs = ' ' . $attribs;
}
}
$id = $options['id'] !== false ? $options['id'] : $name;
$id = str_replace(array('[', ']', ' '), '', $id);
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '" ' . $attribs . '>' . $options['format.eol']
. static::options($data, $options) . $baseIndent . '</select>' . $options['format.eol'];
return $html;
}
/**
* Generates a grouped HTML selection list from nested arrays.
*
* @param array $data An array of groups, each of which is an array of options.
* @param string $name The value of the HTML name attribute
* @param array $options Options, an array of key/value pairs. Valid options are:
* Format options, {@see JHtml::$formatOptions}.
* Selection options. See {@see JHtmlSelect::options()}.
* group.id: The property in each group to use as the group id
* attribute. Defaults to none.
* group.label: The property in each group to use as the group
* label. Defaults to "text". If set to null, the data array index key is
* used.
* group.items: The property in each group to use as the array of
* items in the group. Defaults to "items". If set to null, group.id and
* group. label are forced to null and the data element is assumed to be a
* list of selections.
* id: Value to use as the select element id attribute. Defaults to
* the same as the name.
* list.attr: Attributes for the select element. Can be a string or
* an array of key/value pairs. Defaults to none.
* list.select: either the value of one selected option or an array
* of selected options. Default: none.
* list.translate: Boolean. If set, text and labels are translated via
* JText::_().
*
* @return string HTML for the select list
*
* @throws RuntimeException If a group has contents that cannot be processed.
*/
public static function groupedlist($data, $name, $options = array())
{
// Set default options and overwrite with anything passed in
$options = array_merge(
Html::$formatOptions,
array('format.depth' => 0, 'group.items' => 'items', 'group.label' => 'text', 'group.label.toHtml' => true, 'id' => false),
$options
);
// Apply option rules
if ($options['group.items'] === null)
{
$options['group.label'] = null;
}
$attribs = '';
if (isset($options['list.attr']))
{
if (is_array($options['list.attr']))
{
$attribs = ArrayHelper::toString($options['list.attr']);
}
else
{
$attribs = $options['list.attr'];
}
if ($attribs !== '')
{
$attribs = ' ' . $attribs;
}
}
$id = $options['id'] !== false ? $options['id'] : $name;
$id = str_replace(array('[', ']', ' '), '', $id);
// Disable groups in the options.
$options['groups'] = false;
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '"' . $attribs . '>' . $options['format.eol'];
$groupIndent = str_repeat($options['format.indent'], $options['format.depth']++);
foreach ($data as $dataKey => $group)
{
$label = $dataKey;
$id = '';
$noGroup = is_int($dataKey);
if ($options['group.items'] == null)
{
// Sub-list is an associative array
$subList = $group;
}
elseif (is_array($group))
{
// Sub-list is in an element of an array.
$subList = $group[$options['group.items']];
if (isset($group[$options['group.label']]))
{
$label = $group[$options['group.label']];
$noGroup = false;
}
if (isset($options['group.id']) && isset($group[$options['group.id']]))
{
$id = $group[$options['group.id']];
$noGroup = false;
}
}
elseif (is_object($group))
{
// Sub-list is in a property of an object
$subList = $group->{$options['group.items']};
if (isset($group->{$options['group.label']}))
{
$label = $group->{$options['group.label']};
$noGroup = false;
}
if (isset($options['group.id']) && isset($group->{$options['group.id']}))
{
$id = $group->{$options['group.id']};
$noGroup = false;
}
}
else
{
throw new RuntimeException('Invalid group contents.', 1);
}
if ($noGroup)
{
$html .= static::options($subList, $options);
}
else
{
$html .= $groupIndent . '<optgroup' . (empty($id) ? '' : ' id="' . $id . '"') . ' label="'
. ($options['group.label.toHtml'] ? htmlspecialchars($label, ENT_COMPAT, 'UTF-8') : $label) . '">' . $options['format.eol']
. static::options($subList, $options) . $groupIndent . '</optgroup>' . $options['format.eol'];
}
}
$html .= $baseIndent . '</select>' . $options['format.eol'];
return $html;
}
/**
* Generates a selection list of integers.
*
* @param integer $start The start integer
* @param integer $end The end integer
* @param integer $inc The increment
* @param string $name The value of the HTML name attribute
* @param mixed $attribs Additional HTML attributes for the `<select>` tag, an array of
* attributes, or an array of options. Treated as options if it is the last
* argument passed.
* @param mixed $selected The key that is selected
* @param string $format The printf format to be applied to the number
*
* @return string HTML for the select list
*/
public static function integerlist($start, $end, $inc, $name, $attribs = null, $selected = null, $format = '')
{
// Set default options
$options = array_merge(Html::$formatOptions, array('format.depth' => 0, 'option.format' => '', 'id' => null));
if (is_array($attribs) && func_num_args() === 5)
{
// Assume we have an options array
$options = array_merge($options, $attribs);
// Extract the format and remove it from downstream options
$format = $options['option.format'];
unset($options['option.format']);
}
else
{
// Get options from the parameters
$options['list.attr'] = $attribs;
$options['list.select'] = $selected;
}
$start = (int) $start;
$end = (int) $end;
$inc = (int) $inc;
$data = array();
for ($i = $start; $i <= $end; $i += $inc)
{
$data[$i] = $format ? sprintf($format, $i) : $i;
}
// Tell genericlist() to use array keys
$options['option.key'] = null;
return static::genericlist($data, $name, $options);
}
/**
* Create an object that represents an option in an option list.
*
* @param string $value The value of the option
* @param string $text The text for the option
* @param mixed $optKey If a string, the returned object property name for
* the value. If an array, options. Valid options are:
* attr: String|array. Additional attributes for this option.
* Defaults to none.
* disable: Boolean. If set, this option is disabled.
* label: String. The value for the option label.
* option.attr: The property in each option array to use for
* additional selection attributes. Defaults to none.
* option.disable: The property that will hold the disabled state.
* Defaults to "disable".
* option.key: The property that will hold the selection value.
* Defaults to "value".
* option.label: The property in each option array to use as the
* selection label attribute. If a "label" option is provided, defaults to
* "label", if no label is given, defaults to null (none).
* option.text: The property that will hold the the displayed text.
* Defaults to "text". If set to null, the option array is assumed to be a
* list of displayable scalars.
* @param string $optText The property that will hold the the displayed text. This
* parameter is ignored if an options array is passed.
* @param boolean $disable Not used.
*
* @return stdClass
*/
public static function option($value, $text = '', $optKey = 'value', $optText = 'text', $disable = false)
{
$options = array(
'attr' => null,
'disable' => false,
'option.attr' => null,
'option.disable' => 'disable',
'option.key' => 'value',
'option.label' => null,
'option.text' => 'text',
);
if (is_array($optKey))
{
// Merge in caller's options
$options = array_merge($options, $optKey);
}
else
{
// Get options from the parameters
$options['option.key'] = $optKey;
$options['option.text'] = $optText;
$options['disable'] = $disable;
}
$obj = new stdClass;
$obj->{$options['option.key']} = $value;
$obj->{$options['option.text']} = trim($text) ? $text : $value;
/*
* If a label is provided, save it. If no label is provided and there is
* a label name, initialise to an empty string.
*/
$hasProperty = $options['option.label'] !== null;
if (isset($options['label']))
{
$labelProperty = $hasProperty ? $options['option.label'] : 'label';
$obj->$labelProperty = $options['label'];
}
elseif ($hasProperty)
{
$obj->{$options['option.label']} = '';
}
// Set attributes only if there is a property and a value
if ($options['attr'] !== null)
{
$attrKey = !empty($options['option.attr']) ? $options['option.attr'] : 'attr';
$obj->{$attrKey} = $options['attr'];
}
// Set disable only if it has a property and a value
if ($options['disable'] !== null)
{
$obj->{$options['option.disable']} = $options['disable'];
}
return $obj;
}
/**
* Generates the option tags for an HTML select list (with no select tag
* surrounding the options).
*
* @param array $arr An array of objects, arrays, or values.
* @param mixed $optKey If a string, this is the name of the object variable for
* the option value. If null, the index of the array of objects is used. If
* an array, this is a set of options, as key/value pairs. Valid options are:
* -Format options, {@see JHtml::$formatOptions}.
* -groups: Boolean. If set, looks for keys with the value
* "&lt;optgroup>" and synthesizes groups from them. Deprecated. Defaults
* true for backwards compatibility.
* -list.select: either the value of one selected option or an array
* of selected options. Default: none.
* -list.translate: Boolean. If set, text and labels are translated via
* JText::_(). Default is false.
* -option.id: The property in each option array to use as the
* selection id attribute. Defaults to none.
* -option.key: The property in each option array to use as the
* selection value. Defaults to "value". If set to null, the index of the
* option array is used.
* -option.label: The property in each option array to use as the
* selection label attribute. Defaults to null (none).
* -option.text: The property in each option array to use as the
* displayed text. Defaults to "text". If set to null, the option array is
* assumed to be a list of displayable scalars.
* -option.attr: The property in each option array to use for
* additional selection attributes. Defaults to none.
* -option.disable: The property that will hold the disabled state.
* Defaults to "disable".
* -option.key: The property that will hold the selection value.
* Defaults to "value".
* -option.text: The property that will hold the the displayed text.
* Defaults to "text". If set to null, the option array is assumed to be a
* list of displayable scalars.
* @param string $optText The name of the object variable for the option text.
* @param mixed $selected The key that is selected (accepts an array or a string)
* @param boolean $translate Translate the option values.
*
* @return string HTML for the select list
*/
public static function options($arr, $optKey = 'value', $optText = 'text', $selected = null, $translate = false)
{
$options = array_merge(
Html::$formatOptions,
static::$optionDefaults['option'],
array('format.depth' => 0, 'groups' => true, 'list.select' => null, 'list.translate' => false)
);
if (is_array($optKey))
{
// Set default options and overwrite with anything passed in
$options = array_merge($options, $optKey);
}
else
{
// Get options from the parameters
$options['option.key'] = $optKey;
$options['option.text'] = $optText;
$options['list.select'] = $selected;
$options['list.translate'] = $translate;
}
$html = '';
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']);
foreach ($arr as $elementKey => &$element)
{
$attr = '';
$extra = '';
$label = '';
$id = '';
if (is_array($element))
{
$key = $options['option.key'] === null ? $elementKey : $element[$options['option.key']];
$text = $element[$options['option.text']];
if (isset($element[$options['option.attr']]))
{
$attr = $element[$options['option.attr']];
}
if (isset($element[$options['option.id']]))
{
$id = $element[$options['option.id']];
}
if (isset($element[$options['option.label']]))
{
$label = $element[$options['option.label']];
}
if (isset($element[$options['option.disable']]) && $element[$options['option.disable']])
{
$extra .= ' disabled="disabled"';
}
}
elseif (is_object($element))
{
$key = $options['option.key'] === null ? $elementKey : $element->{$options['option.key']};
$text = $element->{$options['option.text']};
if (isset($element->{$options['option.attr']}))
{
$attr = $element->{$options['option.attr']};
}
if (isset($element->{$options['option.id']}))
{
$id = $element->{$options['option.id']};
}
if (isset($element->{$options['option.label']}))
{
$label = $element->{$options['option.label']};
}
if (isset($element->{$options['option.disable']}) && $element->{$options['option.disable']})
{
$extra .= ' disabled="disabled"';
}
if (isset($element->{$options['option.class']}) && $element->{$options['option.class']})
{
$extra .= ' class="' . $element->{$options['option.class']} . '"';
}
if (isset($element->{$options['option.onclick']}) && $element->{$options['option.onclick']})
{
$extra .= ' onclick="' . $element->{$options['option.onclick']} . '"';
}
}
else
{
// This is a simple associative array
$key = $elementKey;
$text = $element;
}
/*
* The use of options that contain optgroup HTML elements was
* somewhat hacked for J1.5. J1.6 introduces the grouplist() method
* to handle this better. The old solution is retained through the
* "groups" option, which defaults true in J1.6, but should be
* deprecated at some point in the future.
*/
$key = (string) $key;
if ($key === '<OPTGROUP>' && $options['groups'])
{
$html .= $baseIndent . '<optgroup label="' . ($options['list.translate'] ? Text::_($text) : $text) . '">' . $options['format.eol'];
$baseIndent = str_repeat($options['format.indent'], ++$options['format.depth']);
}
elseif ($key === '</OPTGROUP>' && $options['groups'])
{
$baseIndent = str_repeat($options['format.indent'], --$options['format.depth']);
$html .= $baseIndent . '</optgroup>' . $options['format.eol'];
}
else
{
// If no string after hyphen - take hyphen out
$splitText = explode(' - ', $text, 2);
$text = $splitText[0];
if (isset($splitText[1]) && $splitText[1] !== '' && !preg_match('/^[\s]+$/', $splitText[1]))
{
$text .= ' - ' . $splitText[1];
}
if (!empty($label) && $options['list.translate'])
{
$label = Text::_($label);
}
if ($options['option.label.toHtml'])
{
$label = htmlentities($label);
}
if (is_array($attr))
{
$attr = ArrayHelper::toString($attr);
}
else
{
$attr = trim($attr);
}
$extra = ($id ? ' id="' . $id . '"' : '') . ($label ? ' label="' . $label . '"' : '') . ($attr ? ' ' . $attr : '') . $extra;
if (is_array($options['list.select']))
{
foreach ($options['list.select'] as $val)
{
$key2 = is_object($val) ? $val->{$options['option.key']} : $val;
if ($key == $key2)
{
$extra .= ' selected="selected"';
break;
}
}
}
elseif ((string) $key === (string) $options['list.select'])
{
$extra .= ' selected="selected"';
}
if ($options['list.translate'])
{
$text = Text::_($text);
}
// Generate the option, encoding as required
$html .= $baseIndent . '<option value="' . ($options['option.key.toHtml'] ? htmlspecialchars($key, ENT_COMPAT, 'UTF-8') : $key) . '"'
. $extra . '>';
$html .= $options['option.text.toHtml'] ? htmlentities(html_entity_decode($text, ENT_COMPAT, 'UTF-8'), ENT_COMPAT, 'UTF-8') : $text;
$html .= '</option>' . $options['format.eol'];
}
}
return $html;
}
/**
* Generates an HTML radio list.
*
* @param array $data An array of objects
* @param string $name The value of the HTML name attribute
* @param string $attribs Additional HTML attributes for the `<select>` tag
* @param mixed $optKey The key that is selected
* @param string $optText The name of the object variable for the option value
* @param string $selected The name of the object variable for the option text
* @param boolean $idtag Value of the field id or null by default
* @param boolean $translate True if options will be translated
*
* @return string HTML for the select list
*/
public static function radiolist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false,
$translate = false)
{
$forToggle = false;
$colorBoolean = false;
if (isset($attribs['forToggle']))
{
$forToggle = (bool) $attribs['forToggle'];
unset($attribs['forToggle']);
}
if (isset($attribs['colorBoolean']))
{
$colorBoolean = (bool) $attribs['colorBoolean'];
unset($attribs['colorBoolean']);
}
if (is_array($attribs))
{
$attribs = ArrayHelper::toString($attribs);
}
$id_text = empty($idtag) ? $name : $idtag;
$html = '';
foreach ($data as $optionObject)
{
$optionValue = $optionObject->$optKey;
$labelText = $translate ? Text::_($optionObject->$optText) : $optionObject->$optText;
$id = (isset($optionObject->id) ? $optionObject->id : '');
$extra = '';
$id = ($id ? $id : $id_text) . '_' . $optionValue;
if (is_array($selected))
{
foreach ($selected as $val)
{
$k2 = is_object($val) ? $val->$optKey : $val;
if ($optionValue == $k2)
{
$extra .= ' selected="selected" ';
break;
}
}
}
else
{
$extra .= ((string) $optionValue === (string) $selected ? ' checked="checked" ' : '');
}
$labelAttributes = (isset($optionObject->attr) && is_array($optionObject->attr)) ? $optionObject->attr : [];
if ($forToggle && $colorBoolean)
{
$labelAttributes['class'] = isset($labelAttributes['class']) ? $labelAttributes['class'] : '';
$labelAttributes['class'] .= ' ' . ($optionValue ? 'green' : 'red');
}
$labelAttributes = ArrayHelper::toString($labelAttributes);
if ($forToggle)
{
$html .= "\n\t" . '<input type="radio" name="' . $name . '" id="' . $id . '" value="' . $optionValue . '" ' . $extra
. $attribs . ' />';
$html .= "\n\t" . '<label for="' . $id . '" id="' . $id . '-lbl" ' . $labelAttributes . '>' . $labelText . '</label>';
}
else
{
$html .= "\n\t" . '<label for="' . $id . '" id="' . $id . '-lbl" ' . $labelAttributes . '>';
$html .= "\n\t" . '<input type="radio" name="' . $name . '" id="' . $id . '" value="' . $optionValue . '" ' . $extra
. $attribs . ' />' . $labelText;
$html .= "\n\t" . '</label>';
}
}
$html .= "\n";
return $html;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
abstract class Format
{
/**
* Format a size in bytes in a human readable format (e.g. 1.2Gb)
*
* @param integer $sizeInBytes The size to convert, in bytes
* @param integer $decimals Accuracy, in decimal points (default: 2)
* @param boolean|string $force_unit Force a particular unit? Choose one of b, Kb, Mb, Gb, Tb or false for
* automatic determination of the best unit.
* @param string $dec_char Decimal separator character, default dot
* @param string $thousands_char Thousands separator character, default none
*
* @return string The formatted number
*/
public static function fileSize($sizeInBytes, $decimals = 2, $force_unit = false, $dec_char = '.', $thousands_char = '')
{
if ($sizeInBytes <= 0)
{
return '-';
}
$units = array('b', 'KB', 'MB', 'GB', 'TB', 'PB');
if ($force_unit === false)
{
$unit = floor(log($sizeInBytes, 2) / 10);
}
else
{
$unit = $force_unit;
}
if ($unit == 0)
{
$decimals = 0;
}
return number_format($sizeInBytes / (1024 ** $unit), $decimals, $dec_char, $thousands_char) . ' ' . $units[$unit];
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
use Akeeba\Engine\Factory;
use Awf\Application\Application;
abstract class SecretWord
{
/**
* Enforce (reversible) encryption for the component setting $settingsKey
*
* @param string $settingsKey The key for the setting containing the secret word
*
* @return void
*
* @throws \Awf\Exception\App
*
* @since 5.5.2
*/
public static function enforceEncryption($settingsKey)
{
$params = Application::getInstance()->getContainer()->appConfig;
// If encryption is not enabled in the Engine we can't encrypt the Secret Word
if ($params->get('useencryption', -1) == 0)
{
return;
}
// If encryption is not supported on this server we can't encrypt the Secret Word
if (!Factory::getSecureSettings()->supportsEncryption())
{
return;
}
// Get the raw version of frontend_secret_word and check if it has a valid encryption signature
$raw = $params->get('options.' . $settingsKey, '');
$signature = substr($raw, 0, 12);
$validSignatures = array('###AES128###', '###CTR128###');
// If the setting is already encrypted I have nothing to do here
if (in_array($signature, $validSignatures))
{
return;
}
// The setting was NOT encrypted. I need to encrypt it.
$secureSettings = Factory::getSecureSettings();
$encrypted = $secureSettings->encryptSettings($raw);
// Finally, I need to save it back to the database
$params->set('options.' . $settingsKey, $encrypted);
$params->saveConfiguration();
}
/**
* Forcibly store the Secret Word settings $settingsKey unencrypted in the database. This is meant to be called when
* the user disables settings encryption. Since the encryption key will be deleted we need to decrypt the Secret
* Word at the same time as the Engine settings. Otherwise we will never be able to access it again.
*
* @param string $settingsKey The key of the Secret Word parameter
* @param string|null $encryptionKey (Optional) The AES key with which to decrypt the parameter
*
* @return void
*
* @throws \Awf\Exception\App
*
* @since 5.5.2
*/
public static function enforceDecrypted($settingsKey, $encryptionKey = null)
{
// Get the raw version of frontend_secret_word and check if it has a valid encryption signature
$params = Application::getInstance()->getContainer()->appConfig;
$raw = $params->get('options.' . $settingsKey, '');
$signature = substr($raw, 0, 12);
$validSignatures = array('###AES128###', '###CTR128###');
// If the setting is not already encrypted I have nothing to decrypt
if (!in_array($signature, $validSignatures))
{
return;
}
// The setting was encrypted. I need to decrypt it.
$secureSettings = Factory::getSecureSettings();
$decrypted = $secureSettings->decryptSettings($raw, $encryptionKey);
// Finally, I need to save it back to the database
$params->set('options.' . $settingsKey, $decrypted);
$params->saveConfiguration();
}
}

View File

@@ -0,0 +1,349 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
use Akeeba\Engine\Factory;
use Awf\Database\Driver;
use Awf\Html\Select;
use Awf\Text\Text;
/**
* Setup helper class
*/
abstract class Setup
{
/**
* Get a dropdown list for database drivers
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: driver
*
* @return string HTML
*/
public static function databaseTypesSelect($selected = '', $name = 'driver')
{
$connectors = Driver::getConnectors();
$html = '<select name="' . $name . '" id="' . $name . '">' . "\n";
foreach ($connectors as $connector)
{
// Unsupported driver types
if (in_array(strtoupper($connector), ['PDO', 'NONE', 'SQLITE', 'PGSQL']))
{
continue;
}
$checked = (strtoupper($selected) == strtoupper($connector)) ? 'selected="selected"' : '';
$html .= "\t<option value=\"$connector\" $checked>" . Text::_('SOLO_SETUP_LBL_DATABASE_DRIVER_' . $connector) . "</option>\n";
}
$html .= "</select>";
return $html;
}
/**
* Get a dropdown list for script types
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: installer
*
* @return string HTML
*/
public static function restorationScriptSelect($selected = '', $name = 'installer')
{
$installers = Factory::getEngineParamsProvider()->getInstallerList(true);
$options = array();
foreach ($installers as $key => $installer)
{
$options[] = Select::option($key, $installer['name']);
}
return Select::genericList($options, $name, array(), 'value', 'text', $selected, $name, false);
}
/**
* Get a dropdown list for restoration scripts
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: scripttype
*
* @return string HTML
*/
public static function scriptTypesSelect($selected = '', $name = 'scripttype')
{
$scriptTypes = array(
'generic',
'joomla',
'wordpress',
);
$options = array();
foreach ($scriptTypes as $scriptType)
{
$options[] = Select::option($scriptType, Text::_('SOLO_CONFIG_PLATFORM_SCRIPTTYPE_' . $scriptType));
}
return Select::genericList($options, $name, array(), 'value', 'text', $selected, $name, false);
}
/**
* Get a dropdown list for mailer engines
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: mailer
*
* @return string HTML
*/
public static function mailerSelect($selected = '', $name = 'mailer')
{
$scriptTypes = array('mail', 'smtp', 'sendmail');
$options = array();
foreach ($scriptTypes as $scriptType)
{
$options[] = Select::option($scriptType, Text::_('SOLO_SYSCONFIG_EMAIL_MAILER_' . $scriptType));
}
return Select::genericList($options, $name, array(), 'value', 'text', $selected, $name, false);
}
/**
* Get a dropdown list for SMTP security settings
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: smtpsecure
*
* @return string HTML
*/
public static function smtpSecureSelect($selected = '', $name = 'smtpsecure')
{
$options = array();
$options[] = Select::option(0, Text::_('SOLO_SYSCONFIG_EMAIL_SMTPSECURE_NONE'));
$options[] = Select::option(1, Text::_('SOLO_SYSCONFIG_EMAIL_SMTPSECURE_SSL'));
$options[] = Select::option(2, Text::_('SOLO_SYSCONFIG_EMAIL_SMTPSECURE_TLS'));
return Select::genericList($options, $name, array(), 'value', 'text', $selected, $name, false);
}
/**
* Get a dropdown of available timezones
*
* @param string $selected Pre-selected value
* @param string $name The name and id of the input element
*
* @return string HTML
*/
public static function timezoneSelect($selected = '', $name = 'timezone', $includeDefaults = false, $disabled = false)
{
$groups = array();
$zoneHeaders = array(
'Africa',
'America',
'Antarctica',
'Arctic',
'Asia',
'Atlantic',
'Australia',
'Europe',
'Indian',
'Pacific',
);
$zones = \DateTimeZone::listIdentifiers();
// Build the group lists.
foreach ($zones as $zone)
{
// Time zones not in a group we will ignore.
if (strpos($zone, '/') === false)
{
continue;
}
// Get the group/locale from the timezone.
list ($group, $locale) = explode('/', $zone, 2);
// Only use known groups.
if (true || in_array($group, $zoneHeaders))
{
// Initialize the group if necessary.
if (!isset($groups[$group]))
{
$groups[$group] = array();
}
// Only add options where a locale exists.
if (!empty($locale))
{
$groups[$group][$zone] = Select::option($zone, str_replace('_', ' ', $locale));
}
}
}
// Sort the group lists.
ksort($groups);
foreach ($groups as &$location)
{
sort($location);
}
if ($includeDefaults)
{
$defaultGroup = array(
Select::option('GMT', 'GMT'),
Select::option('AKEEBA/DEFAULT', Text::_('COM_AKEEBA_CONFIG_FORCEDBACKUPTZ_DEFAULT')),
);
$groups[Text::_('COM_AKEEBA_CONFIG_FORCEDBACKUPTZ_DEFAULTGROUP')] = $defaultGroup;
ksort($groups);
}
$options = array(
'id' => $name,
'list.select' => $selected,
'group.items' => null,
);
if ($disabled)
{
$options['list.attr'] = ['disabled' => 'disabled'];
}
return Select::groupedList($groups, $name, $options);
}
/**
* Get a dropdown of available timezone formats
*
* @param string $selected Pre-selected value
*
* @return string HTML
*/
public static function timezoneFormatSelect($selected = '')
{
$rawOptions = array(
'COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_NONE' => '',
'COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_ABBREVIATION' => 'T',
'COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_GMTOFFSET' => '\\G\\M\\TP',
);
$html = '<select name="timezonetext" id="timezonetext">' . "\n";
foreach ($rawOptions as $label => $value)
{
$checked = (strtoupper($selected) == strtoupper($value)) ? 'selected="selected"' : '';
$label = Text::_($label);
$html .= "\t<option value=\"$value\" $checked>$label</option>\n";
}
$html .= "</select>";
return $html;
}
/**
* Get a dropdown for the filesystem driver selection
*
* @param string $selected The pre-selected value
*
* @return string HTML
*/
public static function fsDriverSelect($selected = '', $showDirect = true)
{
$drivers = array();
if ($showDirect)
{
$drivers[] = 'file';
}
if (function_exists('ftp_connect'))
{
$drivers[] = 'ftp';
}
if (extension_loaded('ssh2'))
{
$drivers[] = 'sftp';
}
$html = '<select name="fs_driver" id="fs_driver">' . "\n";
foreach ($drivers as $driver)
{
$checked = (strtoupper($selected) == strtoupper($driver)) ? 'selected="selected"' : '';
$html .= "\t<option value=\"$driver\" $checked>" . Text::_('SOLO_SETUP_LBL_FS_DRIVER_' . $driver) . "</option>\n";
}
$html .= "</select>";
return $html;
}
/**
* Get a dropdown for the minimum update stability
*
* @param string $selected The pre-selected value
*
* @return string HTML
*/
public static function minstabilitySelect($selected = '')
{
$levels = array('alpha', 'beta', 'rc', 'stable');
$html = '<select name="options[minstability]" id="minstability">' . "\n";
foreach ($levels as $level)
{
$checked = (strtoupper($selected) == strtoupper($level)) ? 'selected="selected"' : '';
$html .= "\t<option value=\"$level\" $checked>" . Text::_('SOLO_CONFIG_MINSTABILITY_' . $level) . "</option>\n";
}
$html .= "</select>";
return $html;
}
/**
* Get a dropdown for the two factor authentication methods
*
* @param string $name The name of the field
* @param string $selected The pre-selected value
*
* @return string HTML
*/
public static function tfaMethods($name = 'tfamethod', $selected = 'none')
{
$methods = array('none', 'yubikey', 'google');
$html = '<select name="' . $name . '" id="' . $name . '">' . "\n";
foreach ($methods as $method)
{
$checked = (strtoupper($selected) == strtoupper($method)) ? 'selected="selected"' : '';
$html .= "\t<option value=\"$method\" $checked>" . Text::_('SOLO_USERS_TFA_' . $method) . "</option>\n";
}
$html .= "</select>";
return $html;
}
}

View File

@@ -0,0 +1,248 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Awf\Application\Application;
use Awf\Date\Date;
use Awf\Text\Text;
/**
* Status helper. Used by the Control Panel and the backup page to report detected warnings which may impact your backup
* experience.
*/
class Status
{
/**
* Are we ready to take a new backup?
*
* @var bool
*/
public $status = false;
/**
* Is the output directory writable?
*
* @var bool
*/
public $outputWritable = false;
/**
* The detected warnings
*
* @var array
*/
protected $warnings = array();
/**
* Get a Singleton instance
*
* @return self
*/
public static function &getInstance()
{
static $instance = null;
if (empty($instance))
{
$instance = new self();
}
return $instance;
}
/**
* Public constructor. Automatically initializes the object with the status and warnings.
*
* @return self
*/
public function __construct()
{
$this->status = Factory::getConfigurationChecks()->getShortStatus();
$this->warnings = Factory::getConfigurationChecks()->getDetailedStatus();
$status = Factory::getConfigurationChecks()->getFolderStatus();
$this->outputWritable = $status['output'];
}
/**
* Returns the HTML for the backup status cell
*
* @return string HTML
*/
public function getStatusCell()
{
$status = Factory::getConfigurationChecks()->getShortStatus();
$quirks = Factory::getConfigurationChecks()->getDetailedStatus();
if ($status && empty($quirks))
{
$html = '<div class="akeeba-block--success"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_OK') . '</p></div>';
}
elseif ($status && !empty($quirks))
{
$html = '<div class="akeeba-block--warning"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_WARNING') . '</p></div>';
}
else
{
$html = '<div class="akeeba-block--failure"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_ERROR') . '</p></div>';
}
return $html;
}
/**
* Returns HTML for the warnings (status details)
*
* @param bool $onlyErrors Should I only return errors? If false (default) errors AND warnings are returned.
*
* @return string HTML
*/
public function getQuirksCell($onlyErrors = false)
{
$html = '<p>' . Text::_('COM_AKEEBA_CPANEL_WARNING_QNONE') . '</p>';
$quirks = Factory::getConfigurationChecks()->getDetailedStatus();
if (!empty($quirks))
{
$html = "<ul>\n";
foreach ($quirks as $quirk)
{
$html .= $this->renderWarnings($quirk, $onlyErrors);
}
$html .= "</ul>\n";
}
return $html;
}
/**
* Returns a boolean value, indicating if warnings have been detected.
*
* @return bool True if there is at least one detected warnings
*/
public function hasQuirks()
{
$quirks = Factory::getConfigurationChecks()->getDetailedStatus();
return !empty($quirks);
}
/**
* Gets the HTML for a single line of the warnings area.
*
* @param array $quirk A quirk definition array
* @param bool $onlyErrors Should I only return errors? If false (default) errors AND warnings are returned.
*
* @return string HTML
*/
private function renderWarnings($quirk, $onlyErrors = false)
{
if ($onlyErrors && ($quirk['severity'] != 'critical'))
{
return '';
}
$quirk['severity'] = $quirk['severity'] == 'critical' ? 'high' : $quirk['severity'];
return '<li><a class="severity-' . $quirk['severity'] .
'" href="' . $quirk['help_url'] . '" target="_blank">' . $quirk['description'] . '</a>' . "</li>\n";
}
/**
* Returns the details of the latest backup as HTML
*
* @return string HTML
* @throws \Awf\Exception\App
*/
public function getLatestBackupDetails()
{
$db = Application::getInstance()->getContainer()->db;
$query = $db->getQuery(true)
->select('MAX(' . $db->qn('id') . ')')
->from($db->qn('#__ak_stats'));
$db->setQuery($query);
$id = $db->loadResult();
$backup_types = Factory::getEngineParamsProvider()->loadScripting();
if (empty($id))
{
return '<p class="label">' . Text::_('COM_AKEEBA_BACKUP_STATUS_NONE') . '</p>';
}
$record = Platform::getInstance()->get_statistics($id);
switch ($record['status'])
{
case 'run':
$status = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_PENDING');
$statusClass = "akeeba-label--warning";
break;
case 'fail':
$status = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_FAIL');
$statusClass = "akeeba-label--failure";
break;
case 'complete':
$status = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_OK');
$statusClass = "akeeba-label--success";
break;
default:
$status = '';
$statusClass = '';
}
switch ($record['origin'])
{
case 'frontend':
$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_FRONTEND');
break;
case 'backend':
$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_BACKEND');
break;
case 'cli':
$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_CLI');
break;
default:
$origin = '&ndash;';
break;
}
$type = '';
if (array_key_exists($record['type'], $backup_types['scripts']))
{
$type = Platform::getInstance()->translate($backup_types['scripts'][$record['type']]['text']);
}
$startTime = new Date($record['backupstart'], 'UTC');
$tz = new \DateTimeZone(Application::getInstance()->getContainer()->appConfig->get('timezone', 'UTC'));
$startTime->setTimezone($tz);
$html = '<table class="akeeba-table--striped">';
$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_START') . '</td><td>' . $startTime->format(Text::_('DATE_FORMAT_LC2'), true) . '</td></tr>';
$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION') . '</td><td>' . $record['description'] . '</td></tr>';
$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS') . '</td><td><span class="label ' . $statusClass . '">' . $status . '</span></td></tr>';
$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN') . '</td><td>' . $origin . '</td></tr>';
$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_TYPE') . '</td><td>' . $type . '</td></tr>';
$html .= '</table>';
return $html;
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Helper;
use Awf\Input\Filter;
use Awf\Text\Text;
use Awf\Uri\Uri;
/**
* Various utility methods
*/
class Utils
{
/**
* Get the relative path of a directory ($to) against a base directory ($from). Both directories are given as
* absolute paths.
*
* @param string $from The base directory
* @param string $to The directory to convert to a relative path
*
* @return string The path of $to relative to $from
*/
public static function getRelativePath($from, $to)
{
// Some compatibility fixes for Windows paths
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);
$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;
foreach ($from as $depth => $dir)
{
// find first non-matching dir
if ($dir === $to[ $depth ])
{
// ignore this directory
array_shift($relPath);
}
else
{
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if ($remaining > 1)
{
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * - 1;
$relPath = array_pad($relPath, $padLength, '..');
break;
}
else
{
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}
/**
* Get a dropdown list for database drivers
*
* @param string $selected Selected value
* @param string $name The name (also used for id) of the field, default: driver
*
* @return string HTML
*/
public static function engineDatabaseTypesSelect($selected = '', $name = 'driver')
{
$connectors = array('mysql', 'mysqli', 'none', 'pdomysql', 'sqlite');
$html = '<select class="form-control" name="' . $name . '" id="' . $name . '">' . "\n";
foreach($connectors as $connector)
{
$checked = (strtoupper($selected) == strtoupper($connector)) ? 'selected="selected"' : '';
$html .= "\t<option value=\"$connector\" $checked>" . Text::_('SOLO_SETUP_LBL_DATABASE_DRIVER_' . $connector) . "</option>\n";
}
$html .= "</select>";
return $html;
}
/**
* Safely decode a return URL, used in the Backup view.
*
* Return URLs can have two sources:
* - The Backup on Update plugin. In this case the URL is base sixty four encoded and we need to decode it first.
* - A custom backend menu item. In this case the URL is a simple string which does not need decoding.
*
* Further to that, we have to make a few security checks:
* - The URL must be internal, i.e. starts with our site's base URL or index.php (this check is executed by Joomla)
* - It must not contain single quotes, double quotes, lower than or greater than signs (could be used to execute
* arbitrary JavaScript).
*
* If any of these violations is detected we return an empty string.
*
* @param ?string $returnUrl
*
* @return string
*/
static function safeDecodeReturnUrl($returnUrl)
{
// Nulls and non-strings are not allowed
if (is_null($returnUrl) || !is_string($returnUrl))
{
return '';
}
// Make sure it's not an empty string
$returnUrl = trim($returnUrl);
if (empty($returnUrl))
{
return '';
}
// Decode a base sixty four encoded string.
$filter = new Filter();
$encoded = $filter->clean($returnUrl, 'base64');
if (($returnUrl == $encoded) && (strpos($returnUrl, 'index.php') === false) && (strpos($returnUrl, 'akeebabackupwp') === false))
{
$possibleReturnUrl = base64_decode($returnUrl);
if ($possibleReturnUrl !== false)
{
$returnUrl = $possibleReturnUrl;
}
}
// Check if it's an internal URL
if (!Uri::isInternal($returnUrl))
{
return '';
}
$disallowedCharacters = ['"' ,"'", '>', '<'];
foreach ($disallowedCharacters as $check)
{
if (strpos($returnUrl, $check) !== false)
{
return '';
}
}
return $returnUrl;
}
}