primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?php
// Only used for dealing with old Joomla 3 extensions (not reinstalled after upgrade to Joomla 3)

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Object\CMSObject as JCMSObject;
/**
* @var JCMSObject $displayData
*/
$button = $displayData;
if ( ! $button->get('name'))
{
return;
}
$is_modal = $button->get('modal');
$class = 'btn';
$class .= $button->get('class') ? ' ' . $button->get('class') : ' btn-secondary';
$class .= $is_modal ? ' modal-button' : null;
$onclick = $button->get('onclick') ? ' onclick="' . str_replace('"', '&quot;', $button->get('onclick')) . '"' : '';
$title = $button->get('title') ? $button->get('title') : $button->get('text');
$icon = $button->get('icon') ? $button->get('icon') : $button->get('name');
$href = $is_modal
? 'data-bs-target="#' . strtolower($button->get('name')) . '_modal"'
: 'href="' . $button->get('link', '#') . '"';
?>
<button type="button" <?php echo $href; ?> class="<?php echo $class; ?>" <?php echo $button->get('modal') ? 'data-bs-toggle="modal"' : '' ?> title="<?php echo $title; ?>" <?php echo $onclick; ?>>
<span class="icon-<?php echo $icon; ?>" aria-hidden="true"></span>
<?php echo $button->get('text'); ?>
</button>

View File

@ -0,0 +1,85 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Object\CMSObject as JCMSObject;
use Joomla\CMS\Uri\Uri as JUri;
/**
* @var JCMSObject $displayData
*/
if ( ! function_exists('RegularLabsModalRenderButton'))
{
function RegularLabsModalRenderButton($options, $prefix = 'close', $default_text = 'JLIB_HTML_BEHAVIOR_CLOSE')
{
$class = $options[$prefix . 'Class'] ?? ($prefix == 'close' ? 'btn btn-secondary' : 'btn btn-success');
$text = $options[$prefix . 'Text'] ?? JText::_($default_text);
$onclick = $options[$prefix . 'Callback'] ?? '';
$dismiss = $prefix == 'close' || ! empty($options[$prefix . 'Close']) ? 'data-bs-dismiss="modal"' : '';
$icon = ! empty($options[$prefix . 'Icon']) ? '<span class="icon-' . $options[$prefix . 'Icon'] . '" aria-hidden="true"></span>' : '';
return '<button type="button" class="' . $class . '" ' . $dismiss . ' onclick="' . $onclick . '">'
. $icon . $text . ' </button>';
}
}
$button = $displayData;
if ( ! $button->get('modal'))
{
return;
}
if ( ! $button->get('name'))
{
return;
}
$link = ($button->get('link')) ? JUri::base() . $button->get('link') : null;
$title = ($button->get('title')) ? $button->get('title') : $button->get('text');
$options = is_array($button->get('options')) ? $button->get('options') : [];
$buttons = [];
if (isset($options['confirmCallback'])
)
{
$buttons[] = RegularLabsModalRenderButton($options, 'confirm', 'JSAVE');
}
if (isset($options['confirm2Callback'])
)
{
$buttons[] = RegularLabsModalRenderButton($options, 'confirm2', 'JSAVE');
}
$buttons[] = RegularLabsModalRenderButton($options, 'close', 'JLIB_HTML_BEHAVIOR_CLOSE');
$id = str_replace(' ', '', $button->get('id', strtolower($button->get('name')) . '_modal'));
echo JHtml::_(
'bootstrap.renderModal',
$id,
[
'url' => $link,
'title' => $title,
'height' => array_key_exists('height', $options) ? $options['height'] : '400px',
'width' => array_key_exists('width', $options) ? $options['width'] : '800px',
'bodyHeight' => array_key_exists('bodyHeight', $options) ? $options['bodyHeight'] : '70',
'modalWidth' => array_key_exists('modalWidth', $options) ? $options['modalWidth'] : '80',
'keyboard' => array_key_exists('keyboard', $options) ? $options['keyboard'] : true,
'backdrop' => array_key_exists('backdrop', $options) ? $options['backdrop'] : true,
'footer' => implode('', $buttons),
]
);

View File

@ -0,0 +1,98 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var string $autocomplete Autocomplete attribute for the field.
* @var boolean $autofocus Is autofocus enabled?
* @var string $class Classes for the input.
* @var string $description Description of the field.
* @var boolean $disabled Is this field disabled?
* @var string $group Group the field belongs to. <fields> section in form XML.
* @var boolean $hidden Is this field hidden in the form?
* @var string $hint Placeholder for the field.
* @var string $id DOM id of the field.
* @var string $label Label of the field.
* @var string $labelclass Classes to apply to the label.
* @var boolean $multiple Does this field support multiple values?
* @var string $name Name of the input field.
* @var string $onchange Onchange attribute for the field.
* @var string $onclick Onclick attribute for the field.
* @var string $pattern Pattern (Reg Ex) of value of the form field.
* @var boolean $readonly Is this field read only?
* @var boolean $repeat Allows extensions to duplicate elements.
* @var boolean $required Is this field required?
* @var integer $size Size attribute of the input.
* @var boolean $spellcheck Spellcheck state for the form field.
* @var string $validate Validation rules to apply.
* @var string $value Value attribute of the field.
* @var array $checkedOptions Options that will be set as checked.
* @var boolean $hasValue Has this field a value assigned?
* @var array $options Options available for this field.
* @var string $dataAttribute Miscellaneous data attributes preprocessed for HTML output
* @var array $dataAttributes Miscellaneous data attributes for eg, data-*.
*/
/**
* The format of the input tag to be filled in using sprintf.
* %1 - id
* %2 - name
* %3 - value
* %4 = any other attributes
*/
$format = '<input type="checkbox" id="%1$s" name="%2$s" value="%3$s" %4$s>';
// The alt option for Text::alt
$alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $name);
?>
<fieldset id="<?php echo $id; ?>" class="<?php echo trim($class . ' checkboxes'); ?>"
<?php echo $required ? 'required' : ''; ?>
<?php echo $autofocus ? 'autofocus' : ''; ?>
<?php echo $dataAttribute; ?>>
<legend class="visually-hidden"><?php echo $label; ?></legend>
<?php foreach ($options as $i => $option) : ?>
<?php if (is_string($option)) : ?>
<h4 class="mt-2 mb-0"><?php echo $option; ?></h4>
<?php else: ?>
<?php
// Initialize some option attributes.
$checked = in_array((string) $option->value, $checkedOptions, true) ? 'checked' : '';
// In case there is no stored value, use the option's default state.
$checked = ( ! $hasValue && $option->checked) ? 'checked' : $checked;
$optionClass = ! empty($option->class) ? 'class="form-check-input ' . $option->class . '"' : ' class="form-check-input"';
$optionDisabled = ! empty($option->disable) || $disabled ? 'disabled' : '';
// Initialize some JavaScript option attributes.
$onclick = ! empty($option->onclick) ? 'onclick="' . $option->onclick . '"' : '';
$onchange = ! empty($option->onchange) ? 'onchange="' . $option->onchange . '"' : '';
$oid = $id . $i;
$value = htmlspecialchars($option->value, ENT_COMPAT, 'UTF-8');
$attributes = array_filter([$checked, $optionClass, $optionDisabled, $onchange, $onclick]);
?>
<div class="form-check form-check-inline">
<?php echo sprintf($format, $oid, $name, $value, implode(' ', $attributes)); ?>
<label for="<?php echo $oid; ?>" class="form-check-label">
<?php echo $option->text; ?>
</label>
</div>
<?php endif; ?>
<?php endforeach; ?>
</fieldset>

View File

@ -0,0 +1,88 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var string $autocomplete Autocomplete attribute for the field.
* @var boolean $autofocus Is autofocus enabled?
* @var string $class Classes for the input.
* @var string $description Description of the field.
* @var boolean $disabled Is this field disabled?
* @var string $group Group the field belongs to. <fields> section in form XML.
* @var boolean $hidden Is this field hidden in the form?
* @var string $hint Placeholder for the field.
* @var string $id DOM id of the field.
* @var string $label Label of the field.
* @var string $labelclass Classes to apply to the label.
* @var boolean $multiple Does this field support multiple values?
* @var string $name Name of the input field.
* @var string $onchange Onchange attribute for the field.
* @var string $onclick Onclick attribute for the field.
* @var string $pattern Pattern (Reg Ex) of value of the form field.
* @var boolean $readonly Is this field read only?
* @var boolean $repeat Allows extensions to duplicate elements.
* @var boolean $required Is this field required?
* @var integer $size Size attribute of the input.
* @var boolean $spellcheck Spellcheck state for the form field.
* @var string $validate Validation rules to apply.
* @var string $value Value attribute of the field.
* @var array $checkedOptions Options that will be set as checked.
* @var boolean $hasValue Has this field a value assigned?
* @var array $options Options available for this field.
* @var array $inputType Options available for this field.
* @var string $accept File types that are accepted.
*/
$attributes = [
'class="' . $class . '"',
'allow-custom',
'search-placeholder="' . $this->escape(Text::_('RL_ENTER_NEW_VALUES')) . '" ',
];
$selectAttr = [
$disabled ? 'disabled' : '',
$readonly ? 'readonly' : '',
strlen($hint) ? 'placeholder="' . $this->escape(Text::_('RL_ENTER_NEW_VALUES')) . '"' : '',
$onchange ? 'onchange="' . $onchange . '"' : '',
$autofocus ? 'autofocus' : '',
'multiple',
];
if ($required)
{
$selectAttr[] = 'required class="required"';
$attributes[] = 'required';
}
Text::script('JGLOBAL_SELECT_NO_RESULTS_MATCH');
Text::script('JGLOBAL_SELECT_PRESS_TO_SELECT');
Factory::getDocument()->getWebAssetManager()
->usePreset('choicesjs')
->useScript('webcomponent.field-fancy-select');
?>
<joomla-field-fancy-select <?php echo implode(' ', $attributes); ?>><?php
echo HTMLHelper::_('select.genericlist', $options, $name,
implode(' ', $selectAttr),
'value', 'text', $value, $id
);
?></joomla-field-fancy-select>

View File

@ -0,0 +1,89 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
defined('_JEXEC') or die;
/**
* @var array $displayData
* @var int $id
* @var string $extension
* @var int $cloak_length
* @var bool $use_modal
* @var bool $show_label
* @var bool $hidden
* @var string $callback
*/
extract($displayData);
$extension ??= 'all';
$cloak_length ??= 4;
$use_modal ??= false;
$hidden ??= false;
$show_label ??= false;
$callback = htmlspecialchars($callback ?? '', ENT_QUOTES, 'UTF-8');
?>
<div id="downloadKeyWrapper_<?php echo $id; ?>" class="rl-download-key" data-callback="<?php echo $callback; ?>">
<div class="rl-download-key-wrapper mb-4<?php echo $hidden ? ' hidden' : ''; ?>">
<div class="<?php echo ! $show_label ? ' hidden' : ''; ?>">
<label for="<?php echo $id; ?>">
<span class="initialism text-muted">
<?php echo JText::_('RL_DOWNLOAD_KEY'); ?>
</span>
<span class="rl-popover rl-popover-full">
<small class="form-text">
<?php echo JText::_('RL_DOWNLOAD_KEY_DESC'); ?>
</small>
</span>
</label>
</div>
<span class="rl-spinner"></span>
<div class="input-group">
<input type="text" id="<?php echo $id; ?>" data-key-extension="<?php echo $extension; ?>" data-key-cloak-length="<?php echo $cloak_length; ?>"
class="form-control rl-download-key-field form-control inactive rl-code-field hidden">
<button type="button" class="btn btn-primary button-edit hidden">
<span class="icon-edit" aria-hidden="true"></span><span class="visually-hidden"><?php echo JText::_('JEDIT'); ?></span>
</button>
<button type="button" class="btn btn-success button-apply hidden">
<span class="icon-checkmark" aria-hidden="true"></span><span class="visually-hidden"><?php echo JText::_('JAPPLY'); ?></span>
</button>
<button type="button" class="btn btn-danger button-cancel hidden">
<span class="icon-times" aria-hidden="true"></span><span class="visually-hidden"><?php echo JText::_('JCANCEL'); ?></span>
</button>
</div>
<?php
echo (new JFileLayout(
'regularlabs.form.field.downloadkey_errors',
JPATH_SITE . '/libraries/regularlabs/layouts'
))->render([
'id' => $id,
'extension' => $extension,
]);
?>
</div>
<?php
if ($use_modal)
{
echo (new JFileLayout(
'regularlabs.form.field.downloadkey_modal',
JPATH_SITE . '/libraries/regularlabs/layouts'
))->render([
'id' => $id,
'extension' => $extension,
]);
}
?>
</div>

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\Language\Text as JText;
defined('_JEXEC') or die;
/**
* @var array $displayData
* @var int $id
* @var string $extension
*/
extract($displayData);
$extension = $extension ?: 'all';
$extension_name = $extension == 'all'
? JText::_('RL_REGULAR_LABS_EXTENSIONS')
: JText::_(strtoupper($extension));
?>
<div class="key-errors">
<div class="key-error-empty alert alert-danger hidden">
<?php echo JText::sprintf('RL_DOWNLOAD_KEY_ERROR_EMPTY', $extension_name, '<a href="https://regularlabs.com/download-keys" target="_blank">', '</a>'); ?>
</div>
<div class="key-error-invalid alert alert-danger hidden">
<?php echo JText::sprintf('RL_DOWNLOAD_KEY_ERROR_INVALID', '<a href="https://regularlabs.com/download-keys" target="_blank">', '</a>'); ?>
</div>
<div class="key-error-expired alert alert-danger hidden">
<?php echo JText::sprintf('RL_DOWNLOAD_KEY_ERROR_EXPIRED', '<a href="https://regularlabs.com/purchase" target="_blank">', '</a>'); ?>
</div>
<div class="key-error-local alert alert-danger hidden">
<?php echo JText::_('RL_DOWNLOAD_KEY_ERROR_LOCAL'); ?>
</div>
<div class="key-error-external alert alert-danger hidden">
<?php echo JText::sprintf('RL_DOWNLOAD_KEY_ERROR_EXTERNAL', '<a href="https://regularlabs.com/forum" target="_blank">', '</a>'); ?>
</div>
</div>

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
defined('_JEXEC') or die;
/**
* @var array $displayData
* @var int $id
* @var string $extension
*/
extract($displayData);
$body = (new JFileLayout(
'regularlabs.form.field.downloadkey_modal_body',
JPATH_SITE . '/libraries/regularlabs/layouts'
))->render([
'id' => $id,
'extension' => $extension,
]);
$onclick = 'RegularLabs.DownloadKey.save(\'' . $extension . '\', document.querySelector(\'#' . $id . '_modal\').value, document.querySelector(\'#downloadKeyModal_' . $id . '\'));';
echo JHtml::_(
'bootstrap.renderModal',
'downloadKeyModal_' . $id,
[
'title' => JText::_('RL_REGULAR_LABS_DOWNLOAD_KEY'),
'modalWidth' => '60',
'footer' => '<button type="button" class="btn btn-success" onclick="' . $onclick . '">'
. JText::_('JAPPLY')
. '</button>'
. '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-hidden="true">'
. JText::_('JLIB_HTML_BEHAVIOR_CLOSE')
. '</button>',
],
$body
);

View File

@ -0,0 +1,45 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
defined('_JEXEC') or die;
/**
* @var array $displayData
* @var int $id
* @var string $extension
*/
extract($displayData);
$extension = $extension ?: 'all';
?>
<div class="content p-3">
<?php
echo (new JFileLayout(
'regularlabs.form.field.downloadkey_errors',
JPATH_SITE . '/libraries/regularlabs/layouts'
))->render([
'id' => $id,
'extension' => $extension,
]);
?>
<p>
<?php echo html_entity_decode(JText::_('RL_DOWNLOAD_KEY_ENTER')); ?>:
</p>
<div class="input-group">
<input type="text" id="<?php echo $id; ?>_modal" placeholder="ABC123..." class="rl-download-key-field form-control rl-code-field">
</div>
</div>

View File

@ -0,0 +1,54 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var string $id DOM id of the field.
* @var string $icon1 Icon to show when the field is off.
* @var string $icon2 Icon to show when the field is on.
* @var string $text1 Text to show when the field is off.
* @var string $text2 Text to show when the field is on.
* @var string $class1 Class to add to the field when it is off.
* @var string $class2 Class to add to the field when it is on.
* @var string $name Name of the input field.
*/
$text1 = JText::_($text1);
$text2 = JText::_($text2);
RL_Document::useScript('showon');
?>
<fieldset>
<input type="hidden" name="<?php echo $name; ?>" id="<?php echo $id; ?>" value="0">
<div data-showon='[{"field":"<?php echo $name; ?>","values":["0"],"sign":"=","op":""}]' class="hidden">
<span class="<?php echo $class1; ?>" role="button" onclick="el=document.getElementById('<?php echo $id; ?>');el.value = 1;el.dispatchEvent(new Event('change'));">
<span class="icon-<?php echo $icon1; ?>" aria-label="<?php echo $text1 ?: JText::_('JSHOW'); ?>"></span>
<?php echo $text1; ?>
</span>
</div>
<div data-showon='[{"field":"<?php echo $name; ?>","values":["1"],"sign":"=","op":""}]' class="hidden">
<span class="<?php echo $class2; ?>" role="button" onclick="el=document.getElementById('<?php echo $id; ?>');el.value = 0;el.dispatchEvent(new Event('change'));">
<span class="icon-<?php echo $icon2; ?>" aria-label="<?php echo $text2 ?: JText::_('JHIDE'); ?>"></span>
<?php echo $text2; ?>
</span>
</div>
</fieldset>

View File

@ -0,0 +1,146 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
/**
* @var array $displayData
* @var string $autocomplete Autocomplete attribute for the field.
* @var boolean $autofocus Is autofocus enabled?
* @var string $class Classes for the input.
* @var string $description Description of the field.
* @var boolean $disabled Is this field disabled?
* @var string $group Group the field belongs to. <fields> section in form XML.
* @var boolean $hidden Is this field hidden in the form?
* @var string $hint Placeholder for the field.
* @var string $id DOM id of the field.
* @var string $label Label of the field.
* @var string $labelclass Classes to apply to the label.
* @var boolean $multiple Does this field support multiple values?
* @var string $name Name of the input field.
* @var string $onchange Onchange attribute for the field.
* @var string $onclick Onclick attribute for the field.
* @var string $pattern Pattern (Reg Ex) of value of the form field.
* @var boolean $readonly Is this field read only?
* @var boolean $repeat Allows extensions to duplicate elements.
* @var boolean $required Is this field required?
* @var integer $size Size attribute of the input.
* @var boolean $spellcheck Spellcheck state for the form field.
* @var string $validate Validation rules to apply.
* @var string $value Value attribute of the field.
* @var array $checkedOptions Options that will be set as checked.
* @var boolean $hasValue Has this field a value assigned?
* @var array $options Options available for this field.
* @var array $inputType Options available for this field.
* @var string $accept File types that are accepted.
* @var string $dataAttribute Miscellaneous data attributes preprocessed for HTML output
* @var array $dataAttributes Miscellaneous data attribute for eg, data-*.
* @var int $min
* @var int $max
* @var string $prepend
* @var string $append
* @var string $class_range
*/
extract($displayData);
$value = is_numeric($value) ? (float) $value : $min;
$class = $class ?: 'rl-w-6em font-monospace text-right';
$class_range = $class_range ?: 'rl-w-16em';
// Initialize some field attributes.
$attributes_range = [
$class ? 'class="' . $class_range . '"' : '',
$disabled ? 'disabled' : '',
$readonly ? 'readonly' : '',
! empty($onchange) ? 'onchange="' . $onchange . '"' : '',
! empty($max) ? 'max="' . $max . '"' : '',
! empty($step) ? 'step="' . $step . '"' : '',
! empty($min) ? 'min="' . $min . '"' : '',
$autofocus ? 'autofocus' : '',
'oninput="document.querySelector(\'input[name=\\\'' . $name . '\\\']\').value=this.value;document.querySelector(\'input[name=\\\'' . $name . '\\\']\').dispatchEvent(new Event(\'change\'));"',
];
$attributes_number = [
$class ? 'class="form-control ' . $class . '"' : 'class="form-control"',
! empty($description) ? 'aria-describedby="' . $name . '-desc"' : '',
$disabled ? 'disabled' : '',
$readonly ? 'readonly' : '',
strlen($hint) ? 'placeholder="' . htmlspecialchars($hint, ENT_COMPAT, 'UTF-8') . '"' : '',
! empty($onchange) ? 'onchange="' . $onchange . '"' : '',
isset($max) ? 'max="' . $max . '"' : '',
! empty($step) ? 'step="' . $step . '"' : '',
isset($min) ? 'min="' . $min . '"' : '',
$required ? 'required aria-required="true"' : '',
$autocomplete,
$autofocus ? 'autofocus' : '',
$dataAttribute,
'oninput="document.querySelector(\'input[data-for=\\\'' . $name . '\\\']\').value=this.value;"',
];
$chars = (strlen($max) ?: $size) ?: 4;
$width = $chars * 8;
$classes = [];
if ($prepend)
{
$classes[] = 'input-prepend';
}
if ($append)
{
$classes[] = 'input-append';
}
if (str_starts_with($prepend, 'icon-'))
{
$prepend = '<span class="' . $prepend . '"></span>';
}
if (str_starts_with($append, 'icon-'))
{
$append = '<span class="' . $append . '"></span>';
}
if ($prepend && preg_match('#^[A-Z][A-Z0-9_]+$#', $prepend))
{
$prepend = Text::_($prepend);
}
if ($append && preg_match('#^[A-Z][A-Z0-9_]+$#', $append))
{
$append = Text::_($append);
}
?>
<div class="rl-flex">
<span class="rl-mr-1 <?php echo implode(' ', $classes); ?>">
<?php if ($prepend): ?>
<span class="add-on"><?php echo $prepend; ?></span>
<?php endif; ?>
<input type="number" inputmode="numeric" name="<?php echo $name; ?>" id="<?php echo $id; ?>"
value="<?php echo htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); ?>"
<?php echo implode(' ', $attributes_number); ?>>
<?php if ($append): ?>
<span class="add-on"><?php echo $append; ?></span>
<?php endif; ?>
</span>
<input type="range" data-for="<?php echo $name; ?>" value="<?php echo $value; ?>"
<?php echo implode(' ', $attributes_range); ?> />
</div>

View File

@ -0,0 +1,98 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use RegularLabs\Library\Document as RL_Document;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var string $autocomplete Autocomplete attribute for the field.
* @var boolean $autofocus Is autofocus enabled?
* @var string $class Classes for the input.
* @var string $description Description of the field.
* @var boolean $disabled Is this field disabled?
* @var string $group Group the field belongs to. <fields> section in form XML.
* @var boolean $hidden Is this field hidden in the form?
* @var string $hint Placeholder for the field.
* @var string $id DOM id of the field.
* @var string $label Label of the field.
* @var string $labelclass Classes to apply to the label.
* @var boolean $multiple Does this field support multiple values?
* @var string $name Name of the input field.
* @var string $onchange Onchange attribute for the field.
* @var string $onclick Onclick attribute for the field.
* @var string $pattern Pattern (Reg Ex) of value of the form field.
* @var boolean $readonly Is this field read only?
* @var boolean $repeat Allows extensions to duplicate elements.
* @var boolean $required Is this field required?
* @var integer $size Size attribute of the input.
* @var boolean $spellcheck Spellcheck state for the form field.
* @var string $validate Validation rules to apply.
* @var string $value Value attribute of the field.
* @var array $checkedOptions Options that will be set as checked.
* @var boolean $hasValue Has this field a value assigned?
* @var boolean $allowCustom Allow custom values.
* @var array $options Options available for this field.
* @var array $inputType Options available for this field.
* @var string $accept File types that are accepted.
*/
$attributes = [
'class="' . $class . '"',
'search-placeholder="' . $this->escape($hint) . '" ',
];
if ($allowCustom)
{
$attributes[] = 'allow-custom';
}
$selectAttr = [
$disabled ? 'disabled' : '',
$readonly ? 'readonly' : '',
strlen($hint) ? 'placeholder="' . $this->escape($hint) . '"' : '',
$onchange ? 'onchange="' . $onchange . '"' : '',
$autofocus ? 'autofocus' : '',
];
if ($required)
{
$selectAttr[] = 'required class="required"';
$attributes[] = 'required';
}
Text::script('JGLOBAL_SELECT_NO_RESULTS_MATCH');
Text::script('JGLOBAL_SELECT_PRESS_TO_SELECT');
Factory::getDocument()->getWebAssetManager()
->usePreset('choicesjs')
->useScript('webcomponent.field-fancy-select');
RL_Document::script('regularlabs.simplecategory');
?>
<rl-field-simple-category>
<joomla-field-fancy-select <?php echo implode(' ', $attributes); ?>><?php
echo HTMLHelper::_('select.genericlist', $options, $name, [
'id' => $id,
'list.select' => $value,
'list.attr' => implode(' ', $selectAttr),
]
);
?></joomla-field-fancy-select>
</rl-field-simple-category>

View File

@ -0,0 +1,82 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var JForm $tmpl The Empty form for template
* @var array $forms Array of JForm instances for render the rows
* @var bool $multiple The multiple state for the form field
* @var int $min Count of minimum repeating in multiple mode
* @var int $max Count of maximum repeating in multiple mode
* @var string $name Name of the input field.
* @var string $field The field
* @var string $fieldname The field name
* @var string $fieldId The field ID
* @var string $control The forms control
* @var string $label The field label
* @var string $description The field description
* @var string $class Classes for the container
* @var array $buttons Array of the buttons that will be rendered
* @var bool $groupByFieldset Whether group the subform fields by it`s fieldset
*/
// Add script
if ($multiple)
{
Factory::getDocument()->getWebAssetManager()
->useScript('webcomponent.field-subform');
}
$class = $class ? ' ' . $class : '';
$sublayout = (empty($groupByFieldset) ? 'section' : 'section-byfieldsets');
$add_button_text = $field->getAttribute('add_button_text');
?>
<div class="subform-repeatable-wrapper subform-layout">
<joomla-field-subform class="subform-repeatable<?php echo $class; ?>" name="<?php echo $name; ?>"
button-add=".group-add" button-remove=".group-remove" button-move="<?php echo empty($buttons['move']) ? '' : '.group-move' ?>"
repeatable-element=".subform-repeatable-group" minimum="<?php echo $min; ?>" maximum="<?php echo $max; ?>">
<?php if ( ! empty($buttons['add'])) : ?>
<div class="btn-toolbar">
<div class="btn-group">
<button type="button" class="group-add btn btn-sm button btn-success" aria-label="<?php echo Text::_($add_button_text ?: 'JGLOBAL_FIELD_ADD'); ?>">
<span class="icon-plus icon-white" aria-hidden="true"></span>
<?php echo Text::_($add_button_text ?: ''); ?>
</button>
</div>
</div>
<?php endif; ?>
<?php
foreach ($forms as $k => $form) :
echo $this->sublayout($sublayout, [
'form' => $form, 'basegroup' => $fieldname, 'group' => $fieldname . $k, 'buttons' => $buttons, 'field' => $field,
]);
endforeach;
?>
<?php if ($multiple) : ?>
<template class="subform-repeatable-template-section hidden"><?php
echo trim($this->sublayout($sublayout, [
'form' => $tmpl, 'basegroup' => $fieldname, 'group' => $fieldname . 'X', 'buttons' => $buttons,
'field' => $field,
]));
?></template>
<?php endif; ?>
</joomla-field-subform>
</div>

View File

@ -0,0 +1,70 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var JForm $form The form instance for render the section
* @var JForm $field The field
* @var string $basegroup The base group name
* @var string $group Current group name
* @var array $buttons Array of the buttons that will be rendered
*/
$add_button_text = $field->getAttribute('add_button_text');
?>
<div class="subform-repeatable-group" data-base-name="<?php echo $basegroup; ?>" data-group="<?php echo $group; ?>">
<?php if ( ! empty($buttons)) : ?>
<div class="btn-toolbar text-end">
<div class="btn-group">
<?php if ( ! empty($buttons['add'])): ?>
<button type="button" class="group-add btn btn-sm btn-success" aria-label="<?php echo Text::_($add_button_text ?: 'JGLOBAL_FIELD_ADD'); ?>">
<span class="icon-plus icon-white" aria-hidden="true"></span>
<?php echo Text::_($add_button_text ?: ''); ?>
</button>
<?php endif; ?>
<?php if ( ! empty($buttons['remove'])): ?>
<button type="button" class="group-remove btn btn-sm btn-danger" aria-label="<?php echo Text::_('JGLOBAL_FIELD_REMOVE'); ?>">
<span class="icon-minus icon-white" aria-hidden="true"></span>
</button>
<?php endif; ?>
<?php if ( ! empty($buttons['move'])): ?>
<button type="button" class="group-move btn btn-sm btn-primary" aria-label="<?php echo Text::_('JGLOBAL_FIELD_MOVE'); ?>">
<span class="icon-arrows-alt icon-white" aria-hidden="true"></span>
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<div class="row">
<?php foreach ($form->getFieldsets() as $fieldset) : ?>
<fieldset class="<?php if ( ! empty($fieldset->class))
{
echo $fieldset->class;
} ?>">
<?php if ( ! empty($fieldset->label)) : ?>
<legend><?php echo Text::_($fieldset->label); ?></legend>
<?php endif; ?>
<?php foreach ($form->getFieldset($fieldset->name) as $field) : ?>
<?php echo $field->renderField(); ?>
<?php endforeach; ?>
</fieldset>
<?php endforeach; ?>
</div>
</div>

View File

@ -0,0 +1,59 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var JForm $form The form instance for render the section
* @var JForm $field The field
* @var string $basegroup The base group name
* @var string $group Current group name
* @var array $buttons Array of the buttons that will be rendered
*/
$add_button_text = $field->getAttribute('add_button_text');
?>
<div class="subform-repeatable-group" data-base-name="<?php echo $basegroup; ?>" data-group="<?php echo $group; ?>">
<?php if ( ! empty($buttons)) : ?>
<div class="btn-toolbar text-end">
<div class="btn-group">
<?php if ( ! empty($buttons['add'])): ?>
<button type="button" class="group-add btn btn-sm btn-success" aria-label="<?php echo Text::_($add_button_text ?: 'JGLOBAL_FIELD_ADD'); ?>">
<span class="icon-plus icon-white" aria-hidden="true"></span>
<?php echo Text::_($add_button_text ?: ''); ?>
</button>
<?php endif; ?>
<?php if ( ! empty($buttons['remove'])): ?>
<button type="button" class="group-remove btn btn-sm btn-danger" aria-label="<?php echo Text::_('JGLOBAL_FIELD_REMOVE'); ?>">
<span class="icon-minus icon-white" aria-hidden="true"></span>
</button>
<?php endif; ?>
<?php if ( ! empty($buttons['move'])): ?>
<button type="button" class="group-move btn btn-sm btn-primary" aria-label="<?php echo Text::_('JGLOBAL_FIELD_MOVE'); ?>">
<span class="icon-arrows-alt icon-white" aria-hidden="true"></span>
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php foreach ($form->getGroup('') as $field) : ?>
<?php echo $field->renderField(); ?>
<?php endforeach; ?>
</div>

View File

@ -0,0 +1,117 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Date\Date as JDate;
use Joomla\CMS\Factory;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Language\Text as JText;
extract($displayData);
/**
* Layout variables
* -----------------
*
* @var object $field
* @var string $autocomplete Autocomplete attribute for the field.
* @var boolean $autofocus Is autofocus enabled?
* @var string $class Classes for the input.
* @var string $description Description of the field.
* @var boolean $disabled Is this field disabled?
* @var string $group Group the field belongs to. <fields> section in form XML.
* @var boolean $hidden Is this field hidden in the form?
* @var string $hint Placeholder for the field.
* @var string $id DOM id of the field.
* @var string $label Label of the field.
* @var string $labelclass Classes to apply to the label.
* @var boolean $multiple Does this field support multiple values?
* @var string $name Name of the input field.
* @var string $onchange Onchange attribute for the field.
* @var string $onclick Onclick attribute for the field.
* @var string $pattern Pattern (Reg Ex) of value of the form field.
* @var boolean $readonly Is this field read only?
* @var boolean $repeat Allows extensions to duplicate elements.
* @var boolean $required Is this field required?
* @var integer $size Size attribute of the input.
* @var boolean $spellcheck Spellcheck state for the form field.
* @var string $validate Validation rules to apply.
* @var string $value Value attribute of the field.
* @var array $checkedOptions Options that will be set as checked.
* @var boolean $hasValue Has this field a value assigned?
* @var array $options Options available for this field.
* @var array $inputType Options available for this field.
* @var string $accept File types that are accepted.
* @var boolean $charcounter Does this field support a character counter?
* @var string $dataAttribute Miscellaneous data attributes preprocessed for HTML output
* @var array $dataAttributes Miscellaneous data attribute for eg, data-*.
* @var string $columns
* @var string $rows
* @var string $maxlength
* @var boolean $show_insert_date_name
* @var boolean $add_separator
*/
// Initialize some field attributes.
if ($charcounter)
{
// Load the js file
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('short-and-sweet');
// Set the css class to be used as the trigger
$charcounter = ' charcount';
// Set the text
$counterlabel = 'data-counter-label="' . $this->escape(Text::_('JFIELD_META_DESCRIPTION_COUNTER')) . '"';
}
$attributes = [
$columns ?: '',
$rows ?: '',
! empty($class) ? 'class="form-control ' . $class . $charcounter . '"' : 'class="form-control' . $charcounter . '"',
! empty($description) ? 'aria-describedby="' . $name . '-desc"' : '',
strlen($hint) ? 'placeholder="' . htmlspecialchars($hint, ENT_COMPAT, 'UTF-8') . '"' : '',
$disabled ? 'disabled' : '',
$readonly ? 'readonly' : '',
$onchange ? 'onchange="' . $onchange . '"' : '',
$onclick ? 'onclick="' . $onclick . '"' : '',
$required ? 'required' : '',
! empty($autocomplete) ? 'autocomplete="' . $autocomplete . '"' : '',
$autofocus ? 'autofocus' : '',
$spellcheck ? '' : 'spellcheck="false"',
$maxlength ?: '',
! empty($counterlabel) ? $counterlabel : '',
$dataAttribute,
];
if ($show_insert_date_name)
{
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$date_name = JDate::getInstance()->format('[Y-m-d]') . ' ' . $user->name . ' : ';
$separator = $add_separator ? '---' : 'none';
$onclick = "RegularLabs.TextArea.prependTextarea('" . $id . "', '" . addslashes($date_name) . "', '" . $separator . "');";
}
?>
<?php if ($show_insert_date_name) : ?>
<span role="button" class="btn btn-sm btn-primary text-bg-primary" onclick="<?php echo $onclick; ?>">
<span class="icon-pencil" aria-hidden="true"></span>
<?php echo JText::_('RL_INSERT_DATE_NAME'); ?>
</span>
<?php endif; ?>
<textarea name="<?php
echo $name; ?>" id="<?php
echo $id; ?>" <?php
echo implode(' ', $attributes); ?> ><?php echo htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); ?></textarea>

View File

@ -0,0 +1,168 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\Language\Text;
use RegularLabs\Library\Document as RL_Document;
defined('_JEXEC') or die;
/**
* @var array $displayData
* @var int $id
* @var string $name
* @var array $value
* @var array $options
* @var bool $collapse_children
*/
extract($displayData);
if (empty($options))
{
return;
}
RL_Document::script('regularlabs.treeselect');
RL_Document::scriptDeclaration("
document.addEventListener('DOMContentLoaded', () => {RegularLabs.TreeSelect.init('" . $id . "')});
");
if ( ! is_array($value))
{
$value = [$value];
}
?>
<div class="card rl-treeselect" id="rl-treeselect-<?php echo $id; ?>">
<div class="card-header">
<section class="d-flex align-items-center flex-wrap w-100">
<div class="d-flex align-items-center flex-fill mb-1" role="group"
aria-label="<?php echo Text::_('JSELECT'); ?>">
<?php echo Text::_('JSELECT'); ?>
<button data-action="checkAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('JALL'); ?>
</button>
<button data-action="uncheckAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('JNONE'); ?>
</button>
<button data-action="toggleAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('RL_TOGGLE'); ?>
</button>
</div>
<div class="d-flex align-items-center mb-1 flex-fill" role="group"
aria-label="<?php echo Text::_('RL_EXPAND'); ?>">
<?php echo Text::_('RL_EXPAND'); ?>
<button data-action="expandAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('JALL'); ?>
</button>
<button data-action="collapseAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('JNONE'); ?>
</button>
</div>
<div class="d-flex align-items-center mb-1 flex-fill" role="group"
aria-label="<?php echo Text::_('JSHOW'); ?>">
<?php echo Text::_('JSHOW'); ?>
<button data-action="showAll" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('JALL'); ?>
</button>
<button data-action="showSelected" class="btn btn-secondary btn-sm mx-1" type="button">
<?php echo Text::_('RL_SELECTED'); ?>
</button>
</div>
<div role="search" class="flex-grow-1">
<label for="treeselectfilter" class="visually-hidden">
<?php echo Text::_('JSEARCH_FILTER'); ?>
</label>
<input type="text" name="treeselectfilter" class="form-control search-query" autocomplete="off"
placeholder="<?php echo Text::_('JSEARCH_FILTER'); ?>">
</div>
</section>
</div>
<div class="card-body">
<ul class="treeselect">
<?php $previous_level = 0; ?>
<?php foreach ($options
as $i => $option) : ?>
<?php
$option->level ??= 0;
if ($previous_level < $option->level)
{
echo '<ul class="treeselect-sub">';
}
if ($previous_level > $option->level)
{
for ($i = 0; $i < $previous_level - $option->level; $i++)
{
echo '</ul></li>';
}
}
$selected = in_array($option->value, $value);
?>
<li>
<div class="treeselect-item">
<?php if (empty($option->no_checkbox)) : ?>
<input type="checkbox" class="novalidate"
name="<?php echo $name; ?>" id="<?php echo $id . $option->value; ?>" value="<?php echo $option->value; ?>"
<?php echo $selected ? ' checked="checked"' : ''; ?>
<?php echo ! empty($option->disable) ? ' disabled="disabled"' : ''; ?>
<?php echo $collapse_children && ! $option->level ? ' data-rl-treeselect-collapse-children="true"' : ''; ?>>
<?php endif; ?>
<label for="<?php echo $id . $option->value; ?>" class="">
<span class="<?php echo ! empty($option->disable) ? ' disabled' : ''; ?><?php echo ! empty($option->heading) ? ' text-uppercase fw-bold' : ''; ?>">
<?php echo $option->text; ?>
</span>
<?php if (isset($option->published) && $option->published == 0) : ?>
<?php echo ' <span class="badge bg-secondary">' . Text::_('JUNPUBLISHED') . '</span>'; ?>
<?php endif; ?>
</label>
</div>
<?php
$previous_level = $option->level;
?>
<?php endforeach; ?>
<?php
for ($i = 0; $i < $previous_level; $i++)
{
echo '</ul></li>';
}
?>
</ul>
<joomla-alert type="warning" style="display:none"><?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?></joomla-alert>
<div class="sub-tree-select hidden">
<div class="nav-hover treeselect-menu">
<div class="dropdown">
<button type="button" data-bs-toggle="dropdown" class="dropdown-toggle btn btn-sm btn-light">
<span class="visually-hidden"><?php echo Text::sprintf('JGLOBAL_TOGGLE_DROPDOWN'); ?></span>
</button>
<div class="dropdown-menu">
<h1 class="dropdown-header"><?php echo Text::_('RL_SUBITEMS'); ?></h1>
<button type="button" data-action="checkNested" class="dropdown-item">
<span class="icon-check-square" aria-hidden="true"></span>
<?php echo Text::_('RL_SELECT_ALL'); ?>
</button>
<button type="button" data-action="uncheckNested" class="dropdown-item">
<span class="icon-square" aria-hidden="true"></span>
<?php echo Text::_('RL_UNSELECT_ALL'); ?>
</button>
<button type="button" data-action="toggleNested" class="dropdown-item">
<span class="icon-question-circle" aria-hidden="true"></span>
<?php echo Text::_('RL_TOGGLE_SELECTION'); ?>
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<extension version="4.0" type="library" method="upgrade">
<name>Regular Labs Library</name>
<description>Regular Labs Library - used by Regular Labs extensions</description>
<version>24.11.1459</version>
<creationDate>November 2024</creationDate>
<author>Regular Labs (Peter van Westen)</author>
<authorEmail>info@regularlabs.com</authorEmail>
<authorUrl>https://regularlabs.com</authorUrl>
<copyright>Copyright © 2024 Regular Labs - All Rights Reserved</copyright>
<license>GNU General Public License version 2 or later</license>
<libraryname>regularlabs</libraryname>
<namespace path="src">RegularLabs\Library</namespace>
<scriptfile>script.install.php</scriptfile>
<files>
<file>autoload.php</file>
<file>regularlabs.xml</file>
<folder>layouts</folder>
<folder>src</folder>
<folder>vendor</folder>
</files>
<media folder="media" destination="regularlabs">
<folder>css</folder>
<folder>images</folder>
<folder>js</folder>
<folder>scss</folder>
</media>
</extension>

View File

@ -0,0 +1,30 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
if ( ! class_exists('RegularLabsInstallerScript'))
{
class RegularLabsInstallerScript
{
public function postflight($install_type, $adapter)
{
if ( ! in_array($install_type, ['install', 'update']))
{
return true;
}
JFactory::getCache()->clean('_system');
}
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use Joomla\Component\Actionlogs\Administrator\Plugin\ActionLogPlugin as JActionLogPlugin;
use Joomla\Event\DispatcherInterface as JDispatcherInterface;
use RegularLabs\Library\Language as RL_Language;
class ActionLogPlugin extends JActionLogPlugin
{
static $ids = [];
static $item_titles = [];
static $item_types = [];
public $alias = '';
public $events = [];
public $lang_prefix_change_state = 'PLG_SYSTEM_ACTIONLOGS';
public $lang_prefix_delete = 'PLG_SYSTEM_ACTIONLOGS';
public $lang_prefix_install = 'PLG_ACTIONLOG_JOOMLA';
public $lang_prefix_save = 'PLG_SYSTEM_ACTIONLOGS';
public $lang_prefix_uninstall = 'PLG_ACTIONLOG_JOOMLA';
public $name = '';
public $option = '';
public $table = null;
public function __construct(JDispatcherInterface &$subject, array $config = [])
{
parent::__construct($subject, $config);
\RegularLabs\Library\Language::load('plg_actionlog_joomla', JPATH_ADMINISTRATOR);
\RegularLabs\Library\Language::load('plg_actionlog_' . $this->alias);
$config = \RegularLabs\Library\Parameters::getComponent($this->alias);
$enable_actionlog = $config->enable_actionlog ?? \true;
$this->events = $enable_actionlog ? ['*'] : [];
if ($enable_actionlog && !empty($config->actionlog_events)) {
$this->events = \RegularLabs\Library\ArrayHelper::toArray($config->actionlog_events);
}
$this->name = JText::_($this->name);
$this->option = $this->option ?: 'com_' . $this->alias;
}
public function addItem($extension, $type, $title)
{
self::$item_types[$extension] = $type;
self::$item_titles[$extension] = $title;
}
public function getItem($context)
{
$item = $this->getItemData($context);
if (!isset($item->file)) {
$item->file = JPATH_ADMINISTRATOR . '/components/' . $item->option . '/models/' . $item->type . '.php';
}
if (!isset($item->model)) {
$item->model = $this->alias . 'Model' . ucfirst($item->type);
}
if (!isset($item->url)) {
$item->url = 'index.php?option=' . $item->option . '&view=' . $item->type . '&layout=edit&id={id}';
}
return $item;
}
public function onContentAfterDelete($context, $table)
{
if (!str_contains($context, $this->option)) {
return;
}
if (!\RegularLabs\Library\ArrayHelper::find(['*', 'delete'], $this->events)) {
return;
}
$item = $this->getItem($context);
$title = $table->title ?? $table->name ?? $table->id;
$message = ['action' => 'deleted', 'type' => $item->title, 'id' => $table->id, 'title' => $title];
$this->addLog([$message], $this->lang_prefix_delete . '_CONTENT_DELETED', $context);
}
public function onContentAfterSave($context, $table, $isNew, $data = [])
{
$data = \RegularLabs\Library\ArrayHelper::toArray($data);
if (isset($data['ignore_actionlog']) && $data['ignore_actionlog']) {
return;
}
if (!str_contains($context, $this->option)) {
return;
}
$event = $isNew ? 'create' : 'update';
if (!\RegularLabs\Library\ArrayHelper::find(['*', $event], $this->events)) {
return;
}
$item = $this->getItem($context);
$title = $table->title ?? $table->name ?? $table->id;
$item_url = str_replace('{id}', $table->id, $item->url);
$message = ['action' => $isNew ? 'add' : 'update', 'type' => $item->title, 'id' => $table->id, 'title' => $title, 'itemlink' => $item_url];
$languageKey = $isNew ? $this->lang_prefix_save . '_CONTENT_ADDED' : $this->lang_prefix_save . '_CONTENT_UPDATED';
$this->addLog([$message], $languageKey, $context);
}
public function onContentChangeState($context, $ids, $value)
{
if (!str_contains($context, $this->option)) {
return;
}
if (!\RegularLabs\Library\ArrayHelper::find(['*', 'change_state'], $this->events)) {
return;
}
switch ($value) {
case 0:
$languageKey = $this->lang_prefix_change_state . '_CONTENT_UNPUBLISHED';
$action = 'unpublish';
break;
case 1:
$languageKey = $this->lang_prefix_change_state . '_CONTENT_PUBLISHED';
$action = 'publish';
break;
case 2:
$languageKey = $this->lang_prefix_change_state . '_CONTENT_ARCHIVED';
$action = 'archive';
break;
case -2:
$languageKey = $this->lang_prefix_change_state . '_CONTENT_TRASHED';
$action = 'trash';
break;
default:
return;
}
$item = $this->getItem($context);
if (!$this->table) {
if (!is_file($item->file)) {
return;
}
require_once $item->file;
$this->table = (new $item->model())->getTable();
}
foreach ($ids as $id) {
$this->table->load($id);
$title = $this->table->title ?? $this->table->name ?? $this->table->id;
$itemlink = str_replace('{id}', $this->table->id, $item->url);
$message = ['action' => $action, 'type' => $item->title, 'id' => $id, 'title' => $title, 'itemlink' => $itemlink];
$this->addLog([$message], $languageKey, $context);
}
}
public function onExtensionAfterDelete($context, $table)
{
self::onContentAfterDelete($context, $table);
}
public function onExtensionAfterSave($context, $table, $isNew)
{
self::onContentAfterSave($context, $table, $isNew);
}
public function onExtensionAfterUninstall($installer, $eid, $result)
{
// Prevent duplicate logs
if (in_array('uninstall_' . $eid, self::$ids, \true)) {
return;
}
$context = \RegularLabs\Library\Input::get('option', '');
if (!str_contains($context, $this->option)) {
return;
}
if (!\RegularLabs\Library\ArrayHelper::find(['*', 'uninstall'], $this->events)) {
return;
}
if ($result === \false) {
return;
}
$manifest = $installer->get('manifest');
if ($manifest === null) {
return;
}
self::$ids[] = 'uninstall_' . $eid;
$message = ['action' => 'uninstall', 'type' => $this->lang_prefix_install . '_TYPE_' . strtoupper($manifest->attributes()->type), 'id' => $eid, 'extension_name' => JText::_($manifest->name)];
$languageKey = $this->lang_prefix_uninstall . '_EXTENSION_UNINSTALLED';
$this->addLog([$message], $languageKey, 'com_regularlabsmanager');
}
private function getItemData(string $extension): object
{
if (str_contains($extension, '.')) {
[$extension, $type] = explode('.', $extension);
}
RL_Language::load($extension);
$type ??= self::$item_types[$extension] ?? 'item';
$title = self::$item_titles[$extension] ?? JText::_($extension) . ' ' . JText::_('RL_ITEM');
return (object) ['context' => $extension . '.' . $type, 'option' => $extension, 'type' => $type, 'title' => $title];
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Application\ApplicationHelper as JApplicationHelper;
use Joomla\CMS\Factory as JFactory;
class Alias
{
/**
* Creates an alias from a string
*/
public static function get(string $string = '', bool $unicode = \false): string
{
if (empty($string)) {
return '';
}
$string = \RegularLabs\Library\StringHelper::removeHtml($string);
if ($unicode || JFactory::getApplication()->get('unicodeslugs') == 1) {
return self::stringURLUnicodeSlug($string);
}
// Remove < > html entities
$string = str_replace(['&lt;', '&gt;'], '', $string);
// Convert html entities
$string = \RegularLabs\Library\StringHelper::html_entity_decoder($string);
return JApplicationHelper::stringURLSafe($string);
}
/**
* Creates a unicode alias from a string
* Based on stringURLUnicodeSlug method from the unicode slug plugin by infograf768
*/
private static function stringURLUnicodeSlug(string $string = ''): string
{
if (empty($string)) {
return '';
}
// Remove < > html entities
$string = str_replace(['&lt;', '&gt;'], '', $string);
// Convert html entities
$string = \RegularLabs\Library\StringHelper::html_entity_decoder($string);
// Convert to lowercase
$string = \RegularLabs\Library\StringHelper::strtolower($string);
// remove html tags
$string = \RegularLabs\Library\RegEx::replace('</?[a-z][^>]*>', '', $string);
// remove comments tags
$string = \RegularLabs\Library\RegEx::replace('<\!--.*?-->', '', $string);
// Replace weird whitespace characters like (Â) with spaces
//$string = str_replace(array(chr(160), chr(194)), ' ', $string);
$string = str_replace(" ", ' ', $string);
$string = str_replace("", ' ', $string);
// ascii only
// Replace double byte whitespaces by single byte (East Asian languages)
$string = str_replace(" ", ' ', $string);
// Remove any '-' from the string as they will be used as concatenator.
// Would be great to let the spaces in but only Firefox is friendly with this
$string = str_replace('-', ' ', $string);
// Replace forbidden characters by whitespaces
$string = \RegularLabs\Library\RegEx::replace('[' . \RegularLabs\Library\RegEx::quote(',:#$*"@+=;&.%()[]{}/\'\|') . ']', " ", $string);
// Delete all characters that should not take up any space, like: ?
$string = \RegularLabs\Library\RegEx::replace('[' . \RegularLabs\Library\RegEx::quote('?!¿¡') . ']', '', $string);
// Trim white spaces at beginning and end of alias and make lowercase
$string = trim($string);
// Remove any duplicate whitespace and replace whitespaces by hyphens
$string = \RegularLabs\Library\RegEx::replace('\x20+', '-', $string);
// Remove leading and trailing hyphens
$string = trim($string, '-');
return $string;
}
}

View File

@ -0,0 +1,396 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class ArrayHelper
{
/**
* Add a postfix to all keys in an array
*/
public static function addPostfixToKeys(array|object $array, string $postfix): array|object
{
$pefixed = [];
foreach ($array as $key => $value) {
$pefixed[\RegularLabs\Library\StringHelper::addPostfix($key, $postfix)] = $value;
}
return $pefixed;
}
/**
* Add a postfix to all string values in an array
*/
public static function addPostfixToValues(array|object $array, string $postfix): array|object
{
foreach ($array as &$value) {
$value = \RegularLabs\Library\StringHelper::addPostfix($value, $postfix);
}
return $array;
}
/**
* Add a prefix and postfix to all string values in an array
*/
public static function addPreAndPostfixToValues(array|object $array, string $prefix, string $postfix, bool $keep_leading_slash = \true): array|object
{
foreach ($array as &$value) {
$value = \RegularLabs\Library\StringHelper::addPrefix($value, $prefix, $keep_leading_slash);
$value = \RegularLabs\Library\StringHelper::addPostfix($value, $postfix, $keep_leading_slash);
}
return $array;
}
/**
* Add a prefix to all keys in an array
*/
public static function addPrefixToKeys(array|object $array, string $prefix): array|object
{
$pefixed = [];
foreach ($array as $key => $value) {
$pefixed[\RegularLabs\Library\StringHelper::addPrefix($key, $prefix)] = $value;
}
return $pefixed;
}
/**
* Add a prefix to all string values in an array
*/
public static function addPrefixToValues(array|object $array, string $prefix, bool $keep_leading_slash = \true): array|object
{
foreach ($array as &$value) {
$value = \RegularLabs\Library\StringHelper::addPrefix($value, $prefix, $keep_leading_slash);
}
return $array;
}
/**
* Run a method over all values inside the array or object
*/
public static function applyMethodToKeys(array $attributes, string $class = '', string $method = ''): array|object|null
{
if (!$class || !$method) {
$caller = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
$class = $caller['class'];
$method = $caller['function'];
}
$array = array_shift($attributes);
if (!is_array($array) && !is_object($array)) {
return null;
}
if (empty($array)) {
return $array;
}
$json = json_encode($array);
foreach ($array as $key => $value) {
$value_attributes = [$key, ...$attributes];
$json = str_replace('"' . $key . '":', '"' . $class::$method(...$value_attributes) . '":', $json);
}
return json_decode($json, \true);
}
/**
* Run a method over all values inside the array or object
*/
public static function applyMethodToValues(array $attributes, string $class = '', string $method = '', int $array_number_in_attributes = 0): array|object|null
{
if (!$class || !$method) {
$caller = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
$class = $caller['class'];
$method = $caller['function'];
}
$array = $attributes[$array_number_in_attributes];
if (!is_array($array) && !is_object($array)) {
return null;
}
$as_object = is_object($array);
foreach ($array as &$value) {
if (!is_string($value) && !is_null($value)) {
continue;
}
$value_attributes = array_values($attributes);
$value_attributes[$array_number_in_attributes] = $value;
$value = $class::$method(...$value_attributes);
}
return $as_object ? (object) $array : $array;
}
/**
* Change the case of array keys
*/
public static function changeKeyCase(array|object|null $array, string $format, bool $to_lowercase = \true): array|object
{
if (is_null($array)) {
return [];
}
return self::applyMethodToKeys([$array, $format, $to_lowercase], '\RegularLabs\Library\StringHelper', 'toCase');
}
/**
* Clean array by trimming values and removing empty/false values
*/
public static function clean(?array $array): array
{
if (!is_array($array)) {
return $array;
}
$array = self::trim($array);
$array = self::unique($array);
$array = self::removeEmpty($array);
return $array;
}
/**
* Create a tree from a flat array based on the parent_id value
*/
public static function createTreeArray(array $array, int $level = 0, int|string $parent = 0, string $id_name = 'id', string $parent_id_name = 'parent_id'): array
{
if (empty($array)) {
return $array;
}
$tree = [];
foreach ($array as $item) {
$id = $item->{$id_name} ?? 0;
$parent_id = $item->{$parent_id_name} ?? '';
if ($parent_id !== $parent) {
continue;
}
$item->level = $level;
$tree[$id] = $item;
$children = self::createTreeArray($array, $level + 1, $id, $id_name, $parent_id_name);
if (empty($children)) {
continue;
}
$tree[$id]->children = $children;
}
return $tree;
}
/**
* Check if any of the given values is found in the array
*/
public static function find(array|string|null $needles, array|null $haystack, bool $strict = \true): bool
{
if (!is_array($haystack) || empty($haystack)) {
return \false;
}
$needles = self::toArray($needles);
foreach ($needles as $value) {
if (in_array($value, $haystack, $strict)) {
return \true;
}
}
return \false;
}
/**
* Flatten an array of nested arrays, keeping the order
*/
public static function flatten(array $array): array
{
$flattened = [];
foreach ($array as $nested) {
if (!is_array($nested)) {
$flattened[] = $nested;
continue;
}
$flattened = [...$flattened, ...self::flatten($nested)];
}
return $flattened;
}
/**
* Flatten a tree array into a single dimension array
*/
public static function flattenTreeArray(array $array, string $children_name = 'children'): array
{
$flat = [];
foreach ($array as $key => $item) {
$flat[$key] = $item;
if (!isset($item->{$children_name})) {
continue;
}
$children = $item->{$children_name};
unset($flat[$key]->{$children_name});
$flat = [...$flat, ...self::flattenTreeArray($children, $children_name)];
}
foreach ($flat as $key => $item) {
$check = (array) $item;
unset($check['level']);
if (empty($check)) {
unset($flat[$key]);
}
}
return $flat;
}
/**
* Join array elements with a string
*/
public static function implode(array|object|string|null $pieces, string $glue = '', string $last_glue = null): string
{
if (!is_array($pieces)) {
$pieces = self::toArray($pieces, $glue);
}
if (is_null($last_glue) || $last_glue == $glue || count($pieces) < 2) {
return implode($glue ?? '', $pieces);
}
$last_item = array_pop($pieces);
return implode($glue ?? '', $pieces) . $last_glue . $last_item;
}
/**
* Removes empty values from the array
*/
public static function removeEmpty(array $array): array
{
if (!is_array($array)) {
return $array;
}
foreach ($array as $key => &$value) {
if ($key && !is_numeric($key)) {
continue;
}
if ($value !== '') {
continue;
}
unset($array[$key]);
}
return $array;
}
/**
* Removes the trailing part of all keys in an array
*/
public static function removePostfixFromKeys(array $array, string $postfix): array
{
$pefixed = [];
foreach ($array as $key => $value) {
$pefixed[\RegularLabs\Library\StringHelper::removePostfix($key, $postfix)] = $value;
}
return $pefixed;
}
/**
* Removes the trailing part of all string values in an array
*/
public static function removePostfixFromValues(array $array, string $postfix): array
{
foreach ($array as &$value) {
$value = \RegularLabs\Library\StringHelper::removePostfix($value, $postfix);
}
return $array;
}
/**
* Removes the first part of all keys in an array
*/
public static function removePrefixFromKeys(array $array, string $prefix): array
{
$pefixed = [];
foreach ($array as $key => $value) {
$pefixed[\RegularLabs\Library\StringHelper::removePrefix($key, $prefix)] = $value;
}
return $pefixed;
}
/**
* Removes the first part of all string values in an array
*/
public static function removePrefixFromValues(array $array, string $prefix, bool $keep_leading_slash = \true): array
{
foreach ($array as &$value) {
$value = \RegularLabs\Library\StringHelper::removePrefix($value, $prefix, $keep_leading_slash);
}
return $array;
}
/**
* Set the level on each object based on the parent_id value
*/
public static function setLevelsByParentIds(array $array, int $starting_level = 0, int $parent = 0, string $id_name = 'id', string $parent_id_name = 'parent_id'): array
{
if (empty($array)) {
return $array;
}
$tree = self::createTreeArray($array, $starting_level, $parent, $id_name, $parent_id_name);
return self::flattenTreeArray($tree);
}
/**
* Sorts the array by keys based on the values of another array
*/
public static function sortByOtherArray(array $array, array $order): array
{
if (empty($order)) {
return $array;
}
uksort($array, function ($key1, $key2) use ($order) {
return array_search($key1, $order) > array_search($key2, $order);
});
return $array;
}
/**
* Convert data (string or object) to an array
*/
public static function toArray(mixed $data, string $separator = ',', bool $unique = \false, bool $trim = \true): array
{
if (is_array($data)) {
return $data;
}
if (is_object($data)) {
return (array) $data;
}
if ($data === '' || is_null($data)) {
return [];
}
if ($separator === '') {
return [$data];
}
// explode on separator, but keep escaped separators
$splitter = uniqid('RL_SPLIT');
$data = str_replace($separator, $splitter, $data);
$data = str_replace('\\' . $splitter, $separator, $data);
$array = explode($splitter, $data);
if ($trim) {
$array = self::trim($array);
}
if ($unique) {
$array = array_unique($array);
}
return $array;
}
/**
* Convert an associative array or object to a html style attribute list
*/
public static function toAttributeString(array $array, string $key_prefix = ''): string
{
$array = self::toArray($array);
return implode(' ', array_map(fn($key, $value) => $key_prefix . $key . '="' . htmlspecialchars($value) . '"', array_keys($array), $array));
}
/**
* Clean array by trimming values
*/
public static function trim(array $array): array
{
if (!is_array($array)) {
return $array;
}
foreach ($array as &$value) {
if (!is_string($value)) {
continue;
}
$value = trim($value);
}
return $array;
}
/**
* Removes duplicate values from the array
*/
public static function unique(?array $array): array
{
if (!is_array($array)) {
return $array;
}
$values = [];
foreach ($array as $key => $value) {
if (!is_numeric($key)) {
continue;
}
if (!in_array($value, $values, \true)) {
$values[] = $value;
continue;
}
unset($array[$key]);
}
return $array;
}
}

View File

@ -0,0 +1,212 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\Registry\Registry as JRegistry;
class Article
{
static $articles = [];
/**
* Method to get article data.
*/
public static function get(int|string|null $id = null, bool $get_unpublished = \false, array $selects = []): object|null
{
$id = $id ?? null ?: (int) self::getId();
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$db = \RegularLabs\Library\DB::get();
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$custom_selects = !empty($selects);
$query = \RegularLabs\Library\DB::getQuery()->select($custom_selects ? $selects : [
'a.id',
'a.asset_id',
'a.title',
'a.alias',
'a.introtext',
'a.fulltext',
'a.state',
'a.catid',
'a.created',
'a.created_by',
'a.created_by_alias',
// Use created if modified is 0
'CASE WHEN a.modified = ' . \RegularLabs\Library\DB::quote($db->getNullDate()) . ' THEN a.created ELSE a.modified END as modified',
'a.modified_by',
'a.checked_out',
'a.checked_out_time',
'a.publish_up',
'a.publish_down',
'a.images',
'a.urls',
'a.attribs',
'a.version',
'a.ordering',
'a.metakey',
'a.metadesc',
'a.access',
'a.hits',
'a.metadata',
'a.featured',
'a.language',
])->from(\RegularLabs\Library\DB::quoteName('#__content', 'a'));
if (!is_numeric($id)) {
$query->where('(' . \RegularLabs\Library\DB::is('a.title', $id) . ' OR ' . \RegularLabs\Library\DB::is('a.alias', $id) . ')');
} else {
$query->where(\RegularLabs\Library\DB::is('a.id', (int) $id));
}
// Join on category table.
if (!$custom_selects) {
$query->select([\RegularLabs\Library\DB::quoteName('c.title', 'category_title'), \RegularLabs\Library\DB::quoteName('c.alias', 'category_alias'), \RegularLabs\Library\DB::quoteName('c.access', 'category_access'), \RegularLabs\Library\DB::quoteName('c.lft', 'category_lft'), \RegularLabs\Library\DB::quoteName('c.lft', 'category_ordering')]);
}
$query->innerJoin(\RegularLabs\Library\DB::quoteName('#__categories', 'c') . ' ON ' . \RegularLabs\Library\DB::quoteName('c.id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.catid'))->where(\RegularLabs\Library\DB::is('c.published', '>0'));
// Join on user table.
if (!$custom_selects) {
$query->select(\RegularLabs\Library\DB::quoteName('u.name', 'author'));
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__users', 'u') . ' ON ' . \RegularLabs\Library\DB::quoteName('u.id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.created_by'));
// Join over the categories to get parent category titles
if (!$custom_selects) {
$query->select([\RegularLabs\Library\DB::quoteName('parent.title', 'parent_title'), \RegularLabs\Library\DB::quoteName('parent.id', 'parent_id'), \RegularLabs\Library\DB::quoteName('parent.path', 'parent_route'), \RegularLabs\Library\DB::quoteName('parent.alias', 'parent_alias')]);
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__categories', 'parent') . ' ON ' . \RegularLabs\Library\DB::quoteName('parent.id') . ' = ' . \RegularLabs\Library\DB::quoteName('c.parent_id'));
// Join on voting table
if (!$custom_selects) {
$query->select(['ROUND(v.rating_sum / v.rating_count, 0) AS rating', \RegularLabs\Library\DB::quoteName('v.rating_count', 'rating_count')]);
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__content_rating', 'v') . ' ON ' . \RegularLabs\Library\DB::quoteName('v.content_id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.id'));
if (!$get_unpublished && !$user->authorise('core.edit.state', 'com_content') && !$user->authorise('core.edit', 'com_content')) {
\RegularLabs\Library\DB::addArticleIsPublishedFilters($query);
}
$db->setQuery($query);
$data = $db->loadObject();
if (empty($data)) {
return null;
}
if (isset($data->attribs)) {
// Convert parameter field to object.
$data->params = new JRegistry($data->attribs);
}
if (isset($data->metadata)) {
// Convert metadata field to object.
$data->metadata = new JRegistry($data->metadata);
}
return $cache->set($data);
}
/**
* Gets the current article id based on url data
*/
public static function getId(): int|false
{
$id = \RegularLabs\Library\Input::getInt('id');
if (!$id || !(\RegularLabs\Library\Input::get('option', '') == 'com_content' && \RegularLabs\Library\Input::get('view', '') == 'article' || \RegularLabs\Library\Input::get('option', '') == 'com_flexicontent' && \RegularLabs\Library\Input::get('view', '') == 'item')) {
return \false;
}
return $id;
}
public static function getPageNumber(array|string &$all_pages, string $search_string): int
{
if (is_string($all_pages)) {
$all_pages = self::getPages($all_pages);
}
if (count($all_pages) < 2) {
return 0;
}
foreach ($all_pages as $i => $page_text) {
if ($i % 2) {
continue;
}
if (!str_contains($page_text, $search_string)) {
continue;
}
$all_pages[$i] = \RegularLabs\Library\StringHelper::replaceOnce($search_string, '---', $page_text);
return $i / 2;
}
return 0;
}
public static function getPages(string $string): array
{
if (empty($string)) {
return [''];
}
return preg_split('#(<hr class="system-pagebreak" .*?>)#s', $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
}
/**
* Passes the different article parts through the given plugin method
*/
public static function process(?object &$article, string $context, object &$class, string $method, array $params = [], array $ignore_types = []): void
{
self::processText('title', $article, $class, $method, $params, $ignore_types);
self::processText('created_by_alias', $article, $class, $method, $params, $ignore_types);
$has_text = isset($article->text);
$has_description = isset($article->description);
$has_article_texts = isset($article->introtext) && isset($article->fulltext);
$text_same_as_description = $has_text && $has_description && $article->text == $article->description;
$text_same_as_article_text = \false;
self::processText('description', $article, $class, $method, $params, $ignore_types);
// This is a category page with a category description. So skip the text processing
if ($text_same_as_description) {
$article->text = $article->description;
return;
}
// Don't replace in text fields in the category list view, as they won't get used anyway
if (\RegularLabs\Library\Document::isCategoryList($context)) {
return;
}
// prevent fulltext from being messed with, when it is a json encoded string (Yootheme Pro templates do this for some weird f-ing reason)
if (!empty($article->fulltext) && str_starts_with($article->fulltext, '<!-- {')) {
self::processText('text', $article, $class, $method, $params, $ignore_types);
return;
}
if ($has_text && $has_article_texts) {
$check_text = \RegularLabs\Library\RegEx::replace('\s', '', $article->text);
$check_introtext_fulltext = \RegularLabs\Library\RegEx::replace('\s', '', $article->introtext . ' ' . $article->fulltext);
$text_same_as_article_text = $check_text == $check_introtext_fulltext;
}
if ($has_article_texts && !$has_text) {
self::processText('introtext', $article, $class, $method, $params, $ignore_types);
self::processText('fulltext', $article, $class, $method, $params, $ignore_types);
return;
}
if ($has_article_texts && $text_same_as_article_text) {
$splitter = '͞';
if (str_contains($article->introtext, $splitter) || str_contains($article->fulltext, $splitter)) {
$splitter = 'Ͽ';
}
$article->text = $article->introtext . $splitter . $article->fulltext;
self::processText('text', $article, $class, $method, $params, $ignore_types);
[$article->introtext, $article->fulltext] = explode($splitter, $article->text, 2);
$article->text = str_replace($splitter, ' ', $article->text);
return;
}
self::processText('text', $article, $class, $method, $params, $ignore_types);
self::processText('introtext', $article, $class, $method, $params, $ignore_types);
// Don't handle fulltext on category blog views
if ($context == 'com_content.category' && \RegularLabs\Library\Input::get('view', '') == 'category') {
return;
}
self::processText('fulltext', $article, $class, $method, $params, $ignore_types);
}
public static function processText(string $type, ?object &$article, object &$class, string $method, array $params = [], array $ignore_types = []): void
{
if (empty($article->{$type})) {
return;
}
if (in_array($type, $ignore_types, \true)) {
return;
}
call_user_func_array([$class, $method], [&$article->{$type}, ...$params]);
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Exception;
use Joomla\CMS\Cache\CacheControllerFactoryInterface as JCacheControllerFactoryInterface;
use Joomla\CMS\Cache\Controller\OutputController as JOutputController;
use Joomla\CMS\Factory as JFactory;
class Cache
{
static array $cache = [];
/**
* @var [JOutputController]
*/
private array $file_cache_controllers = [];
private bool $force_caching = \true;
private string $group;
private string $id;
private int $time_to_life_in_seconds = 0;
private bool $use_files = \false;
public function __construct(mixed $id = null, string $group = 'regularlabs', int $class_offset = 1)
{
if (is_null($id)) {
$caller = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 1 + $class_offset)[$class_offset];
$id = [$caller['class'], $caller['function'], $caller['args']];
}
if (!is_string($id)) {
$id = json_encode($id);
}
$this->id = md5($id);
$this->group = $group;
}
public function exists(): bool
{
if (!$this->use_files) {
return $this->existsMemory();
}
return $this->existsMemory() || $this->existsFile();
}
public function get(): mixed
{
return $this->use_files ? $this->getFile() : $this->getMemory();
}
public function reset(): void
{
unset(static::$cache[$this->id]);
if ($this->use_files) {
$this->getFileCache()->remove($this->id);
}
}
public function resetAll(): void
{
static::$cache = [];
if ($this->use_files) {
$this->getFileCache()->clean($this->group);
}
}
public function set(mixed $data): mixed
{
return $this->use_files ? $this->setFile($data) : $this->setMemory($data);
}
public function useFiles(int $time_to_life_in_minutes = 0, bool $force_caching = \true): self
{
$this->use_files = \true;
// convert ttl to minutes
$this->time_to_life_in_seconds = $time_to_life_in_minutes * 60;
$this->force_caching = $force_caching;
return $this;
}
private function existsFile(): bool
{
if (\RegularLabs\Library\Document::isDebug()) {
return \false;
}
return $this->getFileCache()->contains($this->id);
}
private function existsMemory(): bool
{
return isset(static::$cache[$this->id]);
}
/**
* @throws Exception
*/
private function getFile(): mixed
{
if ($this->existsMemory()) {
return $this->getMemory();
}
$data = $this->getFileCache()->get($this->id);
$this->setMemory($data);
return $data;
}
private function getFileCache(): JOutputController
{
$options = ['defaultgroup' => $this->group];
if ($this->time_to_life_in_seconds) {
$options['lifetime'] = $this->time_to_life_in_seconds;
}
if ($this->force_caching) {
$options['caching'] = \true;
}
$id = json_encode($options);
if (isset($this->file_cache_controllers[$id])) {
return $this->file_cache_controllers[$id];
}
$this->file_cache_controllers[$id] = JFactory::getContainer()->get(JCacheControllerFactoryInterface::class)->createCacheController('output', $options);
return $this->file_cache_controllers[$id];
}
private function getMemory(): mixed
{
if (!$this->existsMemory()) {
return null;
}
$data = static::$cache[$this->id];
return is_object($data) ? clone $data : $data;
}
/**
* @throws Exception
*/
private function setFile(mixed $data): mixed
{
$this->setMemory($data);
if (\RegularLabs\Library\Document::isDebug()) {
return $data;
}
$this->getFileCache()->store($data, $this->id);
return $data;
}
private function setMemory(mixed $data): mixed
{
static::$cache[$this->id] = $data;
return $data;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class Color
{
public static function save(string $table, int|string $item_id, ?string $color = null, string $id_column = 'id'): bool
{
if (empty($color)) {
return \true;
}
if (in_array($color, ['none', 'transparent'])) {
$color = '';
}
$db = \RegularLabs\Library\DB::get();
$query = $db->getQuery(\true)->select(\RegularLabs\Library\DB::quoteName($id_column))->from(\RegularLabs\Library\DB::quoteName('#__' . $table))->where(\RegularLabs\Library\DB::quoteName($id_column) . ' = ' . $item_id);
$item_exists = $db->setQuery($query)->loadResult();
if ($item_exists) {
$query = $db->getQuery(\true)->update(\RegularLabs\Library\DB::quoteName('#__' . $table))->set(\RegularLabs\Library\DB::quoteName('color') . ' = ' . \RegularLabs\Library\DB::quote($color))->where(\RegularLabs\Library\DB::quoteName($id_column) . ' = ' . $item_id);
$db->setQuery($query)->execute();
return \true;
}
$query = 'SHOW COLUMNS FROM `#__' . $table . '`';
$db->setQuery($query);
$columns = $db->loadColumn();
$values = array_fill_keys($columns, '');
$values[$id_column] = $item_id;
$values['color'] = $color;
$query = $db->getQuery(\true)->insert(\RegularLabs\Library\DB::quoteName('#__' . $table))->columns(\RegularLabs\Library\DB::quoteName($columns))->values(implode(',', \RegularLabs\Library\DB::quote($values)));
$db->setQuery($query)->execute();
return \true;
}
}

View File

@ -0,0 +1,386 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Uri\Uri as JUri;
use Joomla\Database\DatabaseDriver as JDatabaseDriver;
use Joomla\Database\DatabaseQuery as JDatabaseQuery;
use Joomla\Database\QueryInterface as JQueryInterface;
class DB
{
static $tables = [];
public static function addArticleIsPublishedFilters(JQueryInterface &$query, string $prefix = 'a'): void
{
$filters = self::getArticleIsPublishedFilters($prefix);
$query->where($filters);
}
public static function combine(array $conditions = [], string $glue = 'OR'): string
{
if (empty($conditions)) {
return '';
}
if (!is_array($conditions)) {
return (string) $conditions;
}
if (count($conditions) < 2) {
return reset($conditions);
}
$glue = strtoupper($glue) == 'AND' ? 'AND' : 'OR';
return '(' . implode(' ' . $glue . ' ', $conditions) . ')';
}
/**
* Creat a query dump string
*/
public static function dump(string|JQueryInterface $query, string $class_prefix = '', int $caller_offset = 0): void
{
$string = "\n" . (string) $query;
$string = str_replace('#__', JFactory::getDbo()->getPrefix(), $string);
$bounded = $query->getBounded();
foreach ($bounded as $key => $obj) {
$string = str_replace($key, self::quote($obj->value, \false), $string);
}
\RegularLabs\Library\Protect::protectByRegex($string, ' IN \(.*?\)');
\RegularLabs\Library\Protect::protectByRegex($string, ' FIELD\(.*?\)');
$string = preg_replace('#(\n[A-Z][A-Z ]+) #', "\n\\1\n ", $string);
$string = str_replace(' LIMIT ', "\n\nLIMIT ", $string);
$string = str_replace(' ON ', "\n ON ", $string);
$string = str_replace(' OR ', "\n OR ", $string);
$string = str_replace(' AND ', "\n AND ", $string);
$string = str_replace('`,', "`,\n ", $string);
\RegularLabs\Library\Protect::unprotect($string);
echo "\n<pre>==============================================================================\n";
echo self::getQueryComment($class_prefix, $caller_offset) . "\n";
echo "-----------------------------------------------------------------------------------\n";
echo trim($string);
echo "\n===================================================================================</pre>\n";
}
public static function escape(string $text, bool $extra = \false): string
{
return JFactory::getDbo()->escape($text, $extra);
}
public static function get(): JDatabaseDriver
{
return JFactory::getDbo();
}
public static function getArticleIsPublishedFilters(string $prefix = 'a'): string
{
$nowDate = self::getNowDate();
$nullDate = self::getNullDate();
$wheres = [];
$wheres[] = self::is($prefix . '.state', 1);
$wheres[] = self::combine([self::is($prefix . '.publish_up', 'NULL'), self::is($prefix . '.publish_up', '<=' . $nowDate)], 'OR');
$wheres[] = self::combine([self::is($prefix . '.publish_down', 'NULL'), self::is($prefix . '.publish_down', $nullDate), self::is($prefix . '.publish_down', '>' . $nowDate)], 'OR');
return self::combine($wheres, 'AND');
}
public static function getIncludesExcludes(array|string $values, bool $remove_exclude_operators = \true): array
{
$includes = [];
$excludes = [];
$values = \RegularLabs\Library\ArrayHelper::toArray($values);
if (empty($values)) {
return [$includes, $excludes];
}
foreach ($values as $value) {
if ($value == '') {
$value = '!*';
}
if ($value == '!') {
$value = '+';
}
if (self::isExclude($value)) {
$excludes[] = $remove_exclude_operators ? self::removeOperator($value) : $value;
continue;
}
$includes[] = $value;
}
return [$includes, $excludes];
}
public static function getNowDate(): string
{
return JFactory::getDate()->toSql();
}
public static function getNullDate(): string
{
return JFactory::getDbo()->getNullDate();
}
public static function getOperator(array|string|null $value, string $default = '='): string
{
if (empty($value)) {
return $default;
}
if (is_array($value)) {
$value = array_values($value);
return self::getOperator(reset($value), $default);
}
$regex = '^' . \RegularLabs\Library\RegEx::quote(self::getOperators(), 'operator');
if (!\RegularLabs\Library\RegEx::match($regex, $value, $parts)) {
return $default;
}
$operator = $parts['operator'];
return match ($operator) {
'!', '<>', '!NOT!' => '!=',
'==' => '=',
default => $operator,
};
}
public static function getOperators(): array
{
return ['!NOT!', '!=', '!', '<>', '<=', '<', '>=', '>', '=', '=='];
}
public static function getQuery(): JDatabaseQuery
{
return JFactory::getDbo()->getQuery(\true);
}
public static function getQueryComment(string $class_prefix = '', int $caller_offset = 0): string
{
$callers = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, $caller_offset + 5);
for ($i = 1; $i <= $caller_offset + 2; $i++) {
array_shift($callers);
}
$callers = array_reverse($callers);
$lines = [JUri::getInstance()->toString()];
foreach ($callers as $caller) {
$lines[] = '[' . str_pad($caller['line'] ?? '', 3, ' ', \STR_PAD_LEFT) . '] ' . str_replace('\\', '.', trim(substr($caller['class'] ?? '', strlen($class_prefix)), '\\')) . '.' . $caller['function'];
}
return implode("\n", $lines);
}
public static function getTableColumns(string $table, bool $typeOnly = \true): array
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
return $cache->set(JFactory::getDbo()->getTableColumns($table, $typeOnly));
}
public static function in(array|string $keys, array|string $values, array|object $options = [], bool $quote_key = \true): string
{
$options = (object) \RegularLabs\Library\ArrayHelper::toArray($options);
$glue = $options->glue ?? 'OR';
if (is_array($keys)) {
$wheres = [];
foreach ($keys as $single_key) {
$wheres[] = self::in($single_key, $values, $options, $quote_key);
}
return self::combine($wheres, $glue);
}
if (empty($values)) {
$values = [''];
}
$operator = self::getOperator($values);
$db_key = $keys;
if ($quote_key && !str_starts_with($db_key, '`')) {
$db_key = self::quoteName($db_key);
}
if (!is_array($values) || count($values) == 1) {
$values = self::removeOperator($values);
$value = is_array($values) ? reset($values) : $values;
$value = self::prepareValue($value, $options);
if ($value === 'NULL') {
$operator = $operator == '!=' ? 'IS NOT' : 'IS';
}
return $db_key . ' ' . $operator . ' ' . $value;
}
$values = \RegularLabs\Library\ArrayHelper::clean($values);
$operator = $operator == '!=' ? 'NOT IN' : 'IN';
if ($glue == 'OR') {
$values = self::removeOperator($values);
$values = self::prepareValue($values, $options);
return $db_key . ' ' . $operator . ' (' . implode(',', $values) . ')';
}
$wheres = [];
foreach ($values as $value) {
$wheres[] = self::in($keys, $value, $options, $quote_key);
}
return self::combine($wheres, $glue);
}
public static function is(array|string $keys, array|string $values, array|object $options = [], bool $quote_key = \true): string
{
$options = (object) \RegularLabs\Library\ArrayHelper::toArray($options);
$glue = $options->glue ?? 'OR';
$handle_wildcards = $options->handle_wildcards ?? \true;
if (is_array($keys) && $glue == 'OR') {
$wheres = [];
foreach ($keys as $single_key) {
$wheres[] = self::is($single_key, $values, $options, $quote_key);
}
return self::combine($wheres, $glue);
}
if (is_array($keys) && $glue == 'AND') {
$options->glue = 'OR';
$wheres = [];
foreach ($values as $single_values) {
$wheres[] = self::is($keys, $single_values, $options, $quote_key);
}
return self::combine($wheres, $glue);
}
if (!is_array($values) && $handle_wildcards && str_contains($values, '*')) {
return self::like($keys, $values, $options, $quote_key);
}
if (!is_array($values)) {
return self::in($keys, $values, $options, $quote_key);
}
$includes = [];
$excludes = [];
$wheres = [];
foreach ($values as $value) {
if ($handle_wildcards && str_contains($value, '*')) {
$wheres[] = self::is($keys, $value, $options, $quote_key);
continue;
}
if (self::isExclude($value)) {
$excludes[] = $value;
continue;
}
$includes[] = $value;
}
if (!empty($includes)) {
$wheres[] = self::in($keys, $includes, $options, $quote_key);
}
if (!empty($excludes)) {
$wheres[] = self::in($keys, $excludes, $options, $quote_key);
}
if (empty($wheres)) {
return '0';
}
if (count($wheres) == 1) {
return reset($wheres);
}
return self::combine($wheres, $glue);
}
public static function isExclude(string $string): bool
{
return in_array(self::getOperator($string), ['!=', '<>'], \true);
}
public static function isNot(array|string $key, array|string $value, array|object $options = []): string
{
if (is_array($key)) {
$wheres = [];
foreach ($key as $single_key) {
$wheres[] = self::isNot($single_key, $value, $options);
}
return self::combine($wheres, 'AND');
}
$values = $value;
if (!is_array($values)) {
$values = [$values];
}
foreach ($values as $i => $value) {
$operator = self::isExclude($value) ? '=' : '!=';
$values[$i] = $operator . self::removeOperator($value);
}
return self::is($key, $values, $options);
}
public static function like(string $key, array|string $value, array|object $options = [], $quote_key = \true): string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$key, $value, $options, $quote_key], '', '', 1);
if (!is_null($array)) {
return $array;
}
$operator = self::getOperator($value);
$db_key = $key;
if ($quote_key && !str_starts_with($db_key, '`')) {
$db_key = self::quoteName($db_key);
}
if ($value == '*') {
return $db_key . ' ' . ($operator == '!=' ? 'IS NULL' : 'IS NOT NULL');
}
$db_key = 'LOWER(' . $db_key . ')';
$operator = $operator == '!=' ? 'NOT LIKE' : 'LIKE';
$options = (object) \RegularLabs\Library\ArrayHelper::toArray($options);
$value = self::removeOperator($value);
$value = self::prepareValue($value, $options);
$value = str_replace(['*', '_'], ['%', '\_'], $value);
if (!str_contains($value, '%')) {
$value = 'LOWER(' . $value . ')';
}
return $db_key . ' ' . $operator . ' ' . $value;
}
/**
* Create an NOT IN statement
* Reverts to a simple equals statement if array just has 1 value
*/
public static function notIn(string|array $keys, string|array $values, array|object $options = [], bool $quote_key = \true): string
{
if (is_array($values) && count($values) > 0) {
$values[0] = '!' . $values[0];
}
return self::in($keys, $values, $options, $quote_key);
}
public static function prepareValue(string|array|object $value, array|object $options = []): string|array
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$value, $options]);
if (!is_null($array)) {
return $array;
}
if (!is_array($value) && $value === 'NULL') {
return $value;
}
$options = (object) \RegularLabs\Library\ArrayHelper::toArray($options);
$handle_now = $options->handle_now ?? \true;
$dates = ['now', 'now()', 'date()', 'jfactory::getdate()'];
if ($handle_now && !is_array($value) && in_array(strtolower($value), $dates, \true)) {
return 'NOW()';
}
if ((empty($options->quote) || !$options->quote) && (is_int($value) || ctype_digit($value))) {
return $value;
}
$value = self::quote($value);
return $value;
}
public static function quote(array|string $text, bool $escape = \true): array|string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$text, $escape]);
if (!is_null($array)) {
return $array;
}
if (is_null($text)) {
return 'NULL';
}
return JFactory::getDbo()->quote($text, $escape);
}
public static function quoteName(array|string $name, array|string|null $as = null): array|string
{
return JFactory::getDbo()->quoteName($name, $as);
}
public static function removeOperator(string|array|null $string)
{
if (empty($string)) {
return $string;
}
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
$regex = '^' . \RegularLabs\Library\RegEx::quote(self::getOperators(), 'operator');
return \RegularLabs\Library\RegEx::replace($regex, '', $string);
}
public static function tableExists(string $table): bool
{
if (isset(self::$tables[$table])) {
return self::$tables[$table];
}
$db = JFactory::getDbo();
if (str_starts_with($table, '#__')) {
$table = $db->getPrefix() . substr($table, 3);
}
if (!str_starts_with($table, $db->getPrefix())) {
$table = $db->getPrefix() . $table;
}
$query = 'SHOW TABLES LIKE ' . $db->quote($table);
$db->setQuery($query);
$result = $db->loadResult();
self::$tables[$table] = !empty($result);
return self::$tables[$table];
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use DateTimeZone;
use Joomla\CMS\Factory as JFactory;
class Date
{
/**
* Applies offset to a date
*/
public static function applyTimezone(string &$date, string $timezone = ''): void
{
if ($date <= 0) {
$date = 0;
return;
}
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$timezone = $timezone ?: $user->getParam('timezone', JFactory::getApplication()->get('offset'));
$date = JFactory::getDate($date, $timezone);
$date->setTimezone(new DateTimeZone('UTC'));
$date = $date->format('Y-m-d H:i:s', \true, \false);
}
/**
* Convert string with 'date' format to 'strftime' format
*/
public static function dateToStrftimeFormat(string $format): string
{
return strtr((string) $format, self::getDateToStrftimeFormats());
}
/**
* Convert string to a correct date format ('00-00-00 00:00:00' or '00-00-00') or empty string
*/
public static function fix(string $date): string
{
if (!$date) {
return '';
}
$date = trim($date);
// Check if date has correct syntax: 00-00-00 00:00:00
// If so, the date format is correct
if (\RegularLabs\Library\RegEx::match('^[0-9]+-[0-9]+-[0-9]+( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?$', $date)) {
return $date;
}
// Check if date has syntax: 00-00-00 00:00
// If so, it is missing the seconds, so add :00 (seconds)
if (\RegularLabs\Library\RegEx::match('^[0-9]+-[0-9]+-[0-9]+ [0-9][0-9]:[0-9][0-9]$', $date)) {
return $date . ':00';
}
// Check if date has a prepending date syntax: 00-00-00
// If so, it is missing a correct time time, so add 00:00:00 (hours, minutes, seconds)
if (\RegularLabs\Library\RegEx::match('^([0-9]+-[0-9]+-[0-9]+)$', $date, $match)) {
return $match[1] . ' 00:00:00';
}
// Date format is not correct, so return empty string
return '';
}
/**
* Convert string to a correct time format: 1:23 to 01:23
*/
public static function fixTime(string $time, bool $include_seconds = \true): string
{
[$hours, $minutes, $seconds] = explode(':', $time . '::');
$hours = str_pad($hours, 2, '0', \STR_PAD_LEFT);
$minutes = str_pad($minutes, 2, '0', \STR_PAD_LEFT);
$seconds = str_pad($seconds, 2, '0', \STR_PAD_LEFT);
if (!$include_seconds) {
return $hours . ':' . $minutes;
}
return $hours . ':' . $minutes . ':' . $seconds;
}
/**
* Convert string with 'date' format to 'strftime' format
*/
public static function strftimeToDateFormat(string $format): string
{
if (!str_contains($format, '%')) {
return $format;
}
return strtr((string) $format, self::getStrftimeToDateFormats());
}
private static function getDateToStrftimeFormats(): array
{
return [
// Day - no strf eq : S
'd' => '%d',
'D' => '%a',
'jS' => '%#d[TH]',
'j' => '%#d',
'l' => '%A',
'N' => '%u',
'w' => '%w',
'z' => '%j',
// Week - no date eq : %U, %W
'W' => '%V',
// Month - no strf eq : n, t
'F' => '%B',
'm' => '%m',
'M' => '%b',
// Year - no strf eq : L; no date eq : %C, %g
'o' => '%G',
'Y' => '%Y',
'y' => '%y',
// Time - no strf eq : B, G, u; no date eq : %r, %R, %T, %X
'a' => '%P',
'A' => '%p',
'g' => '%l',
'h' => '%I',
'H' => '%H',
'i' => '%M',
's' => '%S',
// Timezone - no strf eq : e, I, P, Z
'O' => '%z',
'T' => '%Z',
// Full Date / Time - no strf eq : c, r; no date eq : %c, %D, %F, %x
'U' => '%s',
];
}
private static function getStrftimeToDateFormats(): array
{
return [
// Day
'%d' => 'd',
'%a' => 'D',
'%#d' => 'j',
'%A' => 'l',
'%u' => 'N',
'%w' => 'w',
'%j' => 'z',
// Week
'%V' => 'W',
// Month
'%B' => 'F',
'%m' => 'm',
'%b' => 'M',
// Year
'%G' => 'o',
'%Y' => 'Y',
'%y' => 'y',
// Time
'%P' => 'a',
'%p' => 'A',
'%l' => 'g',
'%I' => 'h',
'%H' => 'H',
'%M' => 'i',
'%S' => 's',
// Timezone
'%z' => 'O',
'%Z' => 'T',
// Full Date / Time
'%s' => 'U',
];
}
}

View File

@ -0,0 +1,316 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Document\Document as JDocument;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\WebAsset\WebAssetManager as JWebAssetManager;
class Document
{
public static function adminError(string $message): void
{
self::adminMessage($message, 'error');
}
public static function adminMessage(string $message, string $type = 'message'): void
{
if (!self::isAdmin()) {
return;
}
self::message($message, $type);
}
public static function error(string $message): void
{
self::message($message, 'error');
}
public static function get(): ?JDocument
{
$app = JFactory::getApplication();
if (!method_exists($app, 'getDocument')) {
return null;
}
$document = JFactory::getApplication()->getDocument();
if (!is_null($document)) {
return $document;
}
JFactory::getApplication()->loadDocument();
return JFactory::getApplication()->getDocument();
}
public static function getAssetManager(): ?JWebAssetManager
{
$document = self::get();
if (is_null($document)) {
return null;
}
return $document->getWebAssetManager();
}
public static function getComponentBuffer(): ?string
{
$buffer = self::get()->getBuffer('component') ?? null;
if (empty($buffer) || !is_string($buffer)) {
return null;
}
$buffer = trim($buffer);
if (empty($buffer)) {
return null;
}
return $buffer;
}
public static function isAdmin(bool $exclude_login = \false): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$is_admin = self::isClient('administrator') && (!$exclude_login || !$user->get('guest')) && \RegularLabs\Library\Input::get('task', '') != 'preview' && !(\RegularLabs\Library\Input::get('option', '') == 'com_finder' && \RegularLabs\Library\Input::get('format', '') == 'json');
return $cache->set($is_admin);
}
public static function isCategoryList(string $context): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
// Return false if it is not a category page
if ($context != 'com_content.category' || \RegularLabs\Library\Input::get('view', '') != 'category') {
return $cache->set(\false);
}
// Return false if layout is set and it is not a list layout
if (\RegularLabs\Library\Input::get('layout', '') && \RegularLabs\Library\Input::get('layout', '') != 'list') {
return $cache->set(\false);
}
// Return false if default layout is set to blog
if (JFactory::getApplication()->getParams()->get('category_layout') == '_:blog') {
return $cache->set(\false);
}
// Return true if it IS a list layout
return $cache->set(\true);
}
public static function isCli(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_cli = (new \RegularLabs\Library\MobileDetect())->isCurl();
return $cache->set($is_cli);
}
public static function isClient(string $identifier): bool
{
$identifier = $identifier == 'admin' ? 'administrator' : $identifier;
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
return $cache->set(JFactory::getApplication()->isClient($identifier));
}
public static function isDebug(): bool
{
return JFactory::getApplication()->get('debug') || \RegularLabs\Library\Input::get('debug');
}
public static function isEditPage(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$option = \RegularLabs\Library\Input::get('option', '');
// always return false for these components
if (in_array($option, ['com_rsevents', 'com_rseventspro'], \true)) {
return $cache->set(\false);
}
$task = \RegularLabs\Library\Input::get('task', '');
if (str_contains($task, '.')) {
$task = explode('.', $task);
$task = array_pop($task);
}
$view = \RegularLabs\Library\Input::get('view', '');
if (str_contains($view, '.')) {
$view = explode('.', $view);
$view = array_pop($view);
}
$is_edit_page = in_array($option, ['com_config', 'com_contentsubmit', 'com_cckjseblod'], \true) || $option == 'com_comprofiler' && in_array($task, ['', 'userdetails'], \true) || in_array($task, ['edit', 'form', 'submission'], \true) || in_array($view, ['edit', 'form'], \true) || in_array(\RegularLabs\Library\Input::get('do', ''), ['edit', 'form'], \true) || in_array(\RegularLabs\Library\Input::get('layout', ''), ['edit', 'form', 'write'], \true) || self::isAdmin();
return $cache->set($is_edit_page);
}
public static function isFeed(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_feed = self::get() && ((self::get()->getType() ?? null) == 'feed' || in_array(\RegularLabs\Library\Input::getWord('format'), ['feed', 'xml'], \true) || in_array(\RegularLabs\Library\Input::getWord('type'), ['rss', 'atom'], \true));
return $cache->set($is_feed);
}
public static function isHtml(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_html = self::get() ? self::get()->getType() == 'html' : \false;
return $cache->set($is_html);
}
public static function isHttps(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_https = !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off' || isset($_SERVER['SSL_PROTOCOL']) || isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443 || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https';
return $cache->set($is_https);
}
public static function isJSON(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_json = \RegularLabs\Library\Input::get('format', '') == 'json';
return $cache->set($is_json);
}
/**
* Check if the current setup matches the given main version number
*/
public static function isJoomlaVersion(int $version, string $title = ''): bool
{
$jversion = \RegularLabs\Library\Version::getMajorJoomlaVersion();
if ($jversion == $version) {
return \true;
}
if ($title && self::isAdmin()) {
\RegularLabs\Library\Language::load('plg_system_regularlabs');
JFactory::getApplication()->enqueueMessage(JText::sprintf('RL_NOT_COMPATIBLE_WITH_JOOMLA_VERSION', JText::_($title), $jversion), 'error');
}
return \false;
}
public static function isPDF(): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$is_pdf = self::get() && ((self::get()->getType() ?? null) == 'pdf' || \RegularLabs\Library\Input::getWord('format') == 'pdf' || \RegularLabs\Library\Input::getWord('cAction') == 'pdf');
return $cache->set($is_pdf);
}
public static function message(string $message, string $type = 'message'): void
{
\RegularLabs\Library\Language::load('plg_system_regularlabs');
JFactory::getApplication()->enqueueMessage($message, $type);
}
/**
* @depecated Use RegularLabs\Library\StringHelper::minify()
*/
public static function minify(string $string): string
{
return \RegularLabs\Library\StringHelper::minify($string);
}
public static function removeScriptTag(string &$string, string $folder, string $name): void
{
$regex_name = \RegularLabs\Library\RegEx::quote($name);
$regex_name = str_replace('\*', '[^"]*', $regex_name);
$string = \RegularLabs\Library\RegEx::replace('\s*<script [^>]*href="[^"]*(' . $folder . '/js|js/' . $folder . ')/' . $regex_name . '\.[^>]*( /)?>', '', $string);
}
public static function removeScriptsOptions(string &$string, string $name, string $alias = ''): void
{
\RegularLabs\Library\RegEx::match('(<script type="application/json" class="joomla-script-options new">)(.*?)(</script>)', $string, $match);
if (empty($match)) {
return;
}
$alias = $alias ?: \RegularLabs\Library\Extension::getAliasByName($name);
$scripts = json_decode($match[2]);
if (!isset($scripts->{'rl_' . $alias})) {
return;
}
unset($scripts->{'rl_' . $alias});
$string = str_replace($match[0], $match[1] . json_encode($scripts) . $match[3], $string);
}
public static function removeScriptsStyles(string &$string, string $name, string $alias = ''): void
{
[$start, $end] = \RegularLabs\Library\Protect::getInlineCommentTags($name, null, \true);
$alias = $alias ?: \RegularLabs\Library\Extension::getAliasByName($name);
$string = \RegularLabs\Library\RegEx::replace('((?:;\s*)?)(;?)' . $start . '.*?' . $end . '\s*', '\1', $string);
$string = \RegularLabs\Library\RegEx::replace('\s*<link [^>]*href="[^"]*/(' . $alias . '/css|css/' . $alias . ')/[^"]*\.css[^"]*"[^>]*( /)?>', '', $string);
$string = \RegularLabs\Library\RegEx::replace('\s*<script [^>]*src="[^"]*/(' . $alias . '/js|js/' . $alias . ')/[^"]*\.js[^"]*"[^>]*></script>', '', $string);
$string = \RegularLabs\Library\RegEx::replace('\s*<script></script>', '', $string);
}
public static function removeStyleTag(string &$string, string $folder, string $name): void
{
$name = \RegularLabs\Library\RegEx::quote($name);
$name = str_replace('\*', '[^"]*', $name);
$string = \RegularLabs\Library\RegEx::replace('\s*<link [^>]*href="[^"]*(' . $folder . '/css|css/' . $folder . ')/' . $name . '\.[^>]*( /)?>', '', $string);
}
public static function script(string $name, array $attributes = ['defer' => \true], array $dependencies = [], bool $convert_dots = \true): void
{
$file = $name;
if ($convert_dots) {
$file = str_replace('.', '/', $file) . '.min.js';
}
if ($name == 'regularlabs.regular') {
$attributes['defer'] = \false;
}
self::getAssetManager()->registerAndUseScript($name, $file, [], $attributes, $dependencies);
}
public static function scriptDeclaration(string $content = '', string $name = '', bool $minify = \true, string $position = 'before'): void
{
if ($minify) {
$content = \RegularLabs\Library\StringHelper::minify($content);
}
if (!empty($name)) {
$content = \RegularLabs\Library\Protect::wrapScriptDeclaration($content, $name, $minify);
}
self::getAssetManager()->addInlineScript($content, ['position' => $position]);
}
public static function scriptOptions(array $options = [], string $name = ''): void
{
JHtml::_('behavior.core');
$alias = \RegularLabs\Library\RegEx::replace('[^a-z0-9_-]', '', strtolower($name));
$key = 'rl_' . $alias;
self::get()->addScriptOptions($key, $options);
}
public static function setComponentBuffer(string $buffer = ''): void
{
self::get()->setBuffer($buffer, 'component');
}
public static function style(string $name, array $attributes = [], bool $convert_dots = \true): void
{
$file = $name;
if ($convert_dots) {
$file = str_replace('.', '/', $file) . '.min.css';
}
self::getAssetManager()->registerAndUseStyle($name, $file, [], $attributes);
}
public static function styleDeclaration(string $content = '', string $name = '', bool $minify = \true): void
{
if ($minify) {
$content = \RegularLabs\Library\StringHelper::minify($content);
}
if (!empty($name)) {
$content = \RegularLabs\Library\Protect::wrapStyleDeclaration($content, $name, $minify);
}
self::getAssetManager()->addInlineStyle($content);
}
public static function usePreset(string $name): void
{
self::getAssetManager()->usePreset($name);
}
public static function useScript(string $name): void
{
self::getAssetManager()->useScript($name);
}
public static function useStyle(string $name): void
{
self::getAssetManager()->useStyle($name);
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
class DownloadKey
{
public static function get(bool $update = \true): string
{
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->select('extra_query')->from('#__update_sites')->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('extra_query'), 'k=%'))->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('location'), '%download.regularlabs.com%'));
$db->setQuery($query);
$key = $db->loadResult();
if (!$key) {
return '';
}
\RegularLabs\Library\RegEx::match('#k=([a-zA-Z0-9]{8}[A-Z0-9]{8})#', $key, $match);
if (!$match[1]) {
return '';
}
$key = $match[1];
if ($update) {
self::store($key);
}
return $key;
}
public static function getOutputForComponent(string $extension = 'all', bool $use_modal = \true, bool $hidden = \true, string $callback = ''): string
{
$id = 'downloadkey_' . strtolower($extension);
\RegularLabs\Library\Document::script('regularlabs.script');
\RegularLabs\Library\Document::script('regularlabs.downloadkey');
return (new JFileLayout('regularlabs.form.field.downloadkey', JPATH_SITE . '/libraries/regularlabs/layouts'))->render(['id' => $id, 'extension' => strtolower($extension), 'use_modal' => $use_modal, 'hidden' => $hidden, 'callback' => $callback, 'show_label' => \true]);
}
public static function isValid(string $key, string $extension = 'all'): string
{
$key = trim($key);
if (!self::isValidFormat($key)) {
return json_encode(['valid' => \false, 'active' => \false]);
}
$cache = new \RegularLabs\Library\Cache();
$cache->useFiles(1);
if ($cache->exists()) {
return $cache->get();
}
$result = \RegularLabs\Library\Http::getFromUrl('https://download.regularlabs.com/check_key.php?k=' . $key . '&e=' . $extension);
return $cache->set($result);
}
public static function isValidFormat(string $key): bool
{
$key = trim($key);
if ($key === '') {
return \true;
}
if (strlen($key) != 16) {
return \false;
}
return \RegularLabs\Library\RegEx::match('^[a-zA-Z0-9]{8}[A-Z0-9]{8}$', $key, $match, 's');
}
public static function store(string $key): bool
{
if (!self::isValidFormat($key)) {
return \false;
}
$query = \RegularLabs\Library\DB::getQuery()->update('#__update_sites')->set(\RegularLabs\Library\DB::is('extra_query', ''))->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('location'), '%download.regularlabs.com%'));
\RegularLabs\Library\DB::get()->setQuery($query)->execute();
$extra_query = $key ? 'k=' . $key : '';
$query = \RegularLabs\Library\DB::getQuery()->update('#__update_sites')->set(\RegularLabs\Library\DB::is('extra_query', $extra_query))->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('location'), '%download.regularlabs.com%'))->where(\RegularLabs\Library\DB::combine([\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('location'), '%&pro=%'), \RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('location'), '%e=extensionmanager%')], 'OR'));
$result = \RegularLabs\Library\DB::get()->setQuery($query)->execute();
JFactory::getCache()->clean('_system');
return $result;
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Object\CMSObject as JObject;
use Joomla\CMS\Plugin\CMSPlugin as JCMSPlugin;
use Joomla\CMS\Session\Session;
use Joomla\Event\DispatcherInterface as JDispatcherInterface;
use ReflectionClass;
class EditorButtonPlugin extends JCMSPlugin
{
protected $asset;
protected $author;
protected $button_icon = '';
protected $check_installed;
protected $editor_name = '';
protected $enable_on_acymailing = \false;
protected $folder;
protected $main_type = 'plugin';
protected $popup_class = '';
protected $require_core_auth = \true;
private $_params;
private $_pass;
public function __construct(JDispatcherInterface &$subject, array $config = [])
{
parent::__construct($subject, $config);
$this->popup_class = $this->popup_class ?: 'Plugin.EditorButton.' . $this->getShortName() . '.Popup';
}
public function extraChecks($params)
{
return \true;
}
/**
* Display the button
*
* @param string $name The name of the button to display.
* @param string $asset The name of the asset being edited.
* @param integer $author The id of the author owning the asset being edited.
*
* @return JObject|false
*/
public function onDisplay($editor_name, $asset, $author)
{
$this->editor_name = $editor_name;
$this->asset = $asset;
$this->author = $author;
if (!$this->passChecks()) {
return \false;
}
return $this->render();
}
protected function getButtonText()
{
$params = $this->getParams();
$text_ini = strtoupper(str_replace(' ', '_', $params->button_text ?? $this->_name));
$text = JText::_($text_ini);
if ($text == $text_ini) {
$text = JText::_($params->button_text ?? $this->_name);
}
return trim($text);
}
protected function getParams()
{
if (!is_null($this->_params)) {
return $this->_params;
}
switch ($this->main_type) {
case 'component':
if (\RegularLabs\Library\Protect::isComponentInstalled($this->_name)) {
// Load component parameters
$this->_params = \RegularLabs\Library\Parameters::getComponent($this->_name);
}
break;
case 'plugin':
default:
if (\RegularLabs\Library\Protect::isSystemPluginInstalled($this->_name)) {
// Load plugin parameters
$this->_params = \RegularLabs\Library\Parameters::getPlugin($this->_name);
}
break;
}
return $this->_params;
}
protected function getPopupLink()
{
return 'index.php?rl_qp=1' . '&class=' . $this->popup_class . '&editor=' . $this->editor_name . '&tmpl=component' . '&' . Session::getFormToken() . '=1';
}
protected function getPopupOptions()
{
return ['popupType' => 'iframe', 'height' => '1600px', 'width' => '1200px', 'bodyHeight' => '70', 'modalWidth' => '80'];
}
protected function loadScripts()
{
}
protected function loadStyles()
{
}
protected function render()
{
$this->loadScripts();
$this->loadStyles();
return $this->renderPopupButton();
}
protected function renderPopupButton()
{
$button = new JObject();
$button->setProperties(['modal' => \true, 'action' => 'modal', 'name' => $this->_name, 'text' => $this->getButtonText(), 'icon' => $this->_name . '" aria-hidden="true">' . $this->button_icon . '<span></span class="hidden', 'iconSVG' => $this->button_icon, 'link' => $this->getPopupLink(), 'options' => $this->getPopupOptions()]);
return $button;
}
/**
* Get the short name of the field class
* PlgButtonFoobar => Foobar
*/
private function getShortName(): string
{
return substr((new ReflectionClass($this))->getShortName(), strlen('PlgButton'));
}
private function isInstalled(): bool
{
$extensions = !is_null($this->check_installed) ? $this->check_installed : [$this->main_type];
return \RegularLabs\Library\Extension::areInstalled($this->_name, $extensions);
}
private function passChecks(): bool
{
if (!is_null($this->_pass)) {
return $this->_pass;
}
$this->_pass = \false;
if (!\RegularLabs\Library\Extension::isFrameworkEnabled()) {
return \false;
}
if (!\RegularLabs\Library\Extension::isAuthorised($this->require_core_auth)) {
return \false;
}
if (!$this->isInstalled()) {
return \false;
}
if (!$this->enable_on_acymailing && \RegularLabs\Library\Input::get('option', '') == 'com_acymailing') {
return \false;
}
$params = $this->getParams();
if (!\RegularLabs\Library\Extension::isEnabledInComponent($params)) {
return \false;
}
if (!\RegularLabs\Library\Extension::isEnabledInArea($params)) {
return \false;
}
if (!$this->extraChecks($params)) {
return \false;
}
$this->_pass = \true;
return \true;
}
}

View File

@ -0,0 +1,132 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Exception;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry as JRegistry;
use ReflectionClass;
class EditorButtonPopup
{
public $editor_name = '';
public $form;
public $params;
protected $extension = '';
protected $main_type = 'plugin';
protected $require_core_auth = \true;
private $_params;
public function render()
{
if (!\RegularLabs\Library\Extension::isAuthorised($this->require_core_auth)) {
throw new Exception(JText::_("ALERTNOTAUTH"));
}
$this->params = $this->getParams();
if (!\RegularLabs\Library\Extension::isEnabledInArea($this->params)) {
throw new Exception(JText::_("ALERTNOTAUTH"));
}
$this->loadLanguages();
$doc = \RegularLabs\Library\Document::get();
$asset_manager = \RegularLabs\Library\Document::getAssetManager();
$direction = $doc->getDirection();
$template_params = $this->getTemplateParams();
// Get the hue value
preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $template_params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
// Enable assets
$asset_manager->getRegistry()->addTemplateRegistryFile('atum', 1);
$asset_manager->usePreset('template.atum.' . ($direction === 'rtl' ? 'rtl' : 'ltr'))->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $template_params->get('bg-light', '--template-bg-light') . ';
--template-text-dark: ' . $template_params->get('text-dark', '--template-text-dark') . ';
--template-text-light: ' . $template_params->get('text-light', '--template-text-light') . ';
--template-link-color: ' . $template_params->get('link-color', '--template-link-color') . ';
--template-special-color: ' . $template_params->get('special-color', '--template-special-color') . ';
}');
// No template.js for modals
//$asset_manager->disableScript('template.atum');
// Override 'template.active' asset to set correct ltr/rtl dependency
$asset_manager->registerStyle('template.active', '', [], [], ['template.atum.' . ($direction === 'rtl' ? 'rtl' : 'ltr')]);
// Browsers support SVG favicons
$doc->addHeadLink(JHtml::_('image', 'joomla-favicon.svg', '', [], \true, 1), 'icon', 'rel', ['type' => 'image/svg+xml']);
$doc->addHeadLink(JHtml::_('image', 'favicon.ico', '', [], \true, 1), 'alternate icon', 'rel', ['type' => 'image/vnd.microsoft.icon']);
$doc->addHeadLink(JHtml::_('image', 'joomla-favicon-pinned.svg', '', [], \true, 1), 'mask-icon', 'rel', ['color' => '#000']);
\RegularLabs\Library\Document::script('regularlabs.admin-form');
\RegularLabs\Library\Document::style('regularlabs.admin-form');
\RegularLabs\Library\Document::style('regularlabs.popup');
$this->init();
$this->loadScripts();
$this->loadStyles();
echo $this->renderTemplate();
}
protected function getParams()
{
if (!is_null($this->_params)) {
return $this->_params;
}
switch ($this->main_type) {
case 'component':
if (\RegularLabs\Library\Protect::isComponentInstalled($this->extension)) {
// Load component parameters
$this->_params = \RegularLabs\Library\Parameters::getComponent($this->extension);
}
break;
case 'plugin':
default:
if (\RegularLabs\Library\Protect::isSystemPluginInstalled($this->extension)) {
// Load plugin parameters
$this->_params = \RegularLabs\Library\Parameters::getPlugin($this->extension);
}
break;
}
return $this->_params;
}
protected function getTemplateParams()
{
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->select(\RegularLabs\Library\DB::quoteName('s.params'))->from(\RegularLabs\Library\DB::quoteName('#__template_styles', 's'))->where(\RegularLabs\Library\DB::is('s.template', 'atum'))->order(\RegularLabs\Library\DB::quoteName('s.home'));
$db->setQuery($query, 0, 1);
$template = $db->loadObject();
return new JRegistry($template->params ?? null);
}
protected function init()
{
}
protected function loadLanguages()
{
\RegularLabs\Library\Language::load('joomla', JPATH_ADMINISTRATOR);
\RegularLabs\Library\Language::load('plg_system_regularlabs');
\RegularLabs\Library\Language::load('plg_editors-xtd_' . $this->extension);
\RegularLabs\Library\Language::load('plg_system_' . $this->extension);
}
protected function loadScripts()
{
}
protected function loadStyles()
{
}
private function getDir(): string
{
$rc = new ReflectionClass(static::class);
return dirname($rc->getFileName());
}
private function renderTemplate(): string
{
$layout = \RegularLabs\Library\Input::getString('layout', '');
$file = 'popup' . ($layout ? '.' . $layout : '') . '.php';
ob_start();
include dirname($this->getDir()) . '/tmpl/' . $file;
$html = ob_get_contents();
ob_end_clean();
return $html;
}
}

View File

@ -0,0 +1,327 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Helper\ModuleHelper as JModuleHelper;
use Joomla\CMS\Installer\Installer as JInstaller;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
class Extension
{
/**
* Check if all extension types of a given extension are installed
*/
public static function areInstalled(string $extension, array $types = ['plugin']): bool
{
foreach ($types as $type) {
$folder = 'system';
if (is_array($type)) {
[$type, $folder] = $type;
}
if (!self::isInstalled($extension, $type, $folder)) {
return \false;
}
}
return \true;
}
public static function disable(string $alias, string $type = 'plugin', string $folder = 'system'): void
{
$element = self::getElementByAlias($alias);
$element = match ($element) {
'module' => 'mod_' . $element,
'component' => 'com_' . $element,
default => $element,
};
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->update(\RegularLabs\Library\DB::quoteName('#__extensions'))->set(\RegularLabs\Library\DB::quoteName('enabled') . ' = 0')->where(\RegularLabs\Library\DB::is('element', $element))->where(\RegularLabs\Library\DB::is('type', $type));
if ($type == 'plugin') {
$query->where(\RegularLabs\Library\DB::is('folder', $folder));
}
$db->setQuery($query);
$db->execute();
}
/**
* Return an alias and element name based on the given extension name
*/
public static function getAliasAndElement(string &$name): array
{
$name = self::getNameByAlias($name);
$alias = self::getAliasByName($name);
$element = self::getElementByAlias($alias);
return [$alias, $element];
}
public static function getAliasByName(string $name): string
{
$alias = \RegularLabs\Library\RegEx::replace('[^a-z0-9]', '', strtolower($name));
return match ($alias) {
'advancedmodules' => 'advancedmodulemanager',
'what-nothing' => 'whatnothing',
default => $alias,
};
}
public static function getById(int|string $id): ?object
{
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->select(\RegularLabs\Library\DB::quoteName(['extension_id', 'manifest_cache']))->from(\RegularLabs\Library\DB::quoteName('#__extensions'))->where(\RegularLabs\Library\DB::is('extension_id', (int) $id));
$db->setQuery($query);
return $db->loadObject();
}
/**
* Return an element name based on the given extension alias
*/
public static function getElementByAlias(string $alias): string
{
$alias = self::getAliasByName($alias);
return match ($alias) {
'advancedmodulemanager' => 'advancedmodules',
default => $alias,
};
}
public static function getNameByAlias(string $alias): string
{
// Alias is a language string
if (!str_contains($alias, ' ') && strtoupper($alias) == $alias) {
return JText::_($alias);
}
// Alias has a space and/or capitals, so is already a name
if (str_contains($alias, ' ') || $alias !== strtolower($alias)) {
return $alias;
}
return JText::_(self::getXMLValue('name', $alias));
}
/**
* Get the full path to the extension folder
*/
public static function getPath(string $extension = 'plg_system_regularlabs', string $basePath = JPATH_ADMINISTRATOR, string $check_folder = ''): string
{
$basePath = $basePath ?: JPATH_SITE;
if (!in_array($basePath, [JPATH_ADMINISTRATOR, JPATH_SITE], \true)) {
return $basePath;
}
$extension = str_replace('.sys', '', $extension);
switch (\true) {
case str_starts_with($extension, 'mod_'):
$path = 'modules/' . $extension;
break;
case str_starts_with($extension, 'plg_'):
[$prefix, $folder, $name] = explode('_', $extension, 3);
$path = 'plugins/' . $folder . '/' . $name;
break;
case str_starts_with($extension, 'com_'):
default:
$path = 'components/' . $extension;
break;
}
$check_folder = $check_folder ? '/' . $check_folder : '';
if (is_dir($basePath . '/' . $path . $check_folder)) {
return $basePath . '/' . $path;
}
if (is_dir(JPATH_ADMINISTRATOR . '/' . $path . $check_folder)) {
return JPATH_ADMINISTRATOR . '/' . $path;
}
if (is_dir(JPATH_SITE . '/' . $path . $check_folder)) {
return JPATH_SITE . '/' . $path;
}
return $basePath;
}
/**
* Return an extensions main xml array
*/
public static function getXML(string $alias, string $type = '', string $folder = ''): array|false
{
$file = self::getXMLFile($alias, $type, $folder);
if (!$file) {
return \false;
}
return JInstaller::parseXMLInstallFile($file);
}
/**
* Return an extensions main xml file name (including path)
*/
public static function getXMLFile(string $alias, string $type = '', string $folder = '', bool $get_params = \false): string
{
$element = self::getElementByAlias($alias);
$files = [];
// Components
if (empty($type) || $type == 'component') {
$file = $get_params ? 'config' : $element;
$files[] = JPATH_ADMINISTRATOR . '/components/com_' . $element . '/' . $file . '.xml';
$files[] = JPATH_SITE . '/components/com_' . $element . '/' . $file . '.xml';
if (!$get_params) {
$files[] = JPATH_ADMINISTRATOR . '/components/com_' . $element . '/com_' . $element . '.xml';
$files[] = JPATH_SITE . '/components/com_' . $element . '/com_' . $element . '.xml';
}
}
// Plugins
if (empty($type) || $type == 'plugin') {
if (!empty($folder)) {
$files[] = JPATH_PLUGINS . '/' . $folder . '/' . $element . '/' . $element . '.xml';
}
// System Plugins
$files[] = JPATH_PLUGINS . '/system/' . $element . '/' . $element . '.xml';
// Editor Button Plugins
$files[] = JPATH_PLUGINS . '/editors-xtd/' . $element . '/' . $element . '.xml';
// Field Plugins
$field_name = \RegularLabs\Library\RegEx::replace('field$', '', $element);
$files[] = JPATH_PLUGINS . '/fields/' . $field_name . '/' . $field_name . '.xml';
}
// Modules
if (empty($type) || $type == 'module') {
$files[] = JPATH_ADMINISTRATOR . '/modules/mod_' . $element . '/' . $element . '.xml';
$files[] = JPATH_SITE . '/modules/mod_' . $element . '/' . $element . '.xml';
$files[] = JPATH_ADMINISTRATOR . '/modules/mod_' . $element . '/mod_' . $element . '.xml';
$files[] = JPATH_SITE . '/modules/mod_' . $element . '/mod_' . $element . '.xml';
}
foreach ($files as $file) {
if (!file_exists($file)) {
continue;
}
return $file;
}
return '';
}
/**
* Return a value from an extensions main xml file based on the given key
*/
public static function getXMLValue(string $key, string $alias, string $type = '', string $folder = ''): string
{
$xml = self::getXML($alias, $type, $folder);
if (!$xml) {
return '';
}
if (!isset($xml[$key])) {
return '';
}
return $xml[$key] ?? '';
}
public static function isAuthorised(bool $require_core_auth = \true): bool
{
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
if ($user->get('guest')) {
return \false;
}
if (!$require_core_auth) {
return \true;
}
if (!$user->authorise('core.edit', 'com_content') && !$user->authorise('core.edit.own', 'com_content') && !$user->authorise('core.create', 'com_content')) {
return \false;
}
return \true;
}
/**
* Check if the Regular Labs Library is enabled
*/
public static function isEnabled(string $extension, string $type = 'component', string $folder = 'system'): bool
{
$extension = strtolower($extension);
if (!self::isInstalled($extension, $type, $folder)) {
return \false;
}
switch ($type) {
case 'component':
$extension = str_replace('com_', '', $extension);
return JComponentHelper::isEnabled('com_' . $extension);
case 'module':
$extension = str_replace('mod_', '', $extension);
return JModuleHelper::isEnabled('mod_' . $extension);
case 'plugin':
return JPluginHelper::isEnabled($folder, $extension);
default:
return \false;
}
}
public static function isEnabledInArea(object $params): bool
{
if (!isset($params->enable_frontend)) {
return \true;
}
// Only allow in frontend
if ($params->enable_frontend == 2 && \RegularLabs\Library\Document::isClient('administrator')) {
return \false;
}
// Do not allow in frontend
if (!$params->enable_frontend && \RegularLabs\Library\Document::isClient('site')) {
return \false;
}
return \true;
}
public static function isEnabledInComponent(object $params): bool
{
if (!isset($params->disabled_components)) {
return \true;
}
return !\RegularLabs\Library\Protect::isRestrictedComponent($params->disabled_components);
}
/**
* Check if the Regular Labs Library is enabled
*/
public static function isFrameworkEnabled(): bool
{
return JPluginHelper::isEnabled('system', 'regularlabs');
}
public static function isInstalled(string $extension, string $type = 'component', string $folder = 'system'): bool
{
$extension = strtolower($extension);
switch ($type) {
case 'component':
$extension = str_replace('com_', '', $extension);
return file_exists(JPATH_ADMINISTRATOR . '/components/com_' . $extension . '/' . $extension . '.xml') || file_exists(JPATH_SITE . '/components/com_' . $extension . '/' . $extension . '.xml');
case 'plugin':
return file_exists(JPATH_PLUGINS . '/' . $folder . '/' . $extension . '/' . $extension . '.php');
case 'module':
$extension = str_replace('mod_', '', $extension);
return file_exists(JPATH_ADMINISTRATOR . '/modules/mod_' . $extension . '/' . $extension . '.php') || file_exists(JPATH_ADMINISTRATOR . '/modules/mod_' . $extension . '/mod_' . $extension . '.php') || file_exists(JPATH_SITE . '/modules/mod_' . $extension . '/' . $extension . '.php') || file_exists(JPATH_SITE . '/modules/mod_' . $extension . '/mod_' . $extension . '.php');
case 'library':
$extension = str_replace('lib_', '', $extension);
return JFolder::exists(JPATH_LIBRARIES . '/' . $extension);
default:
return \false;
}
}
public static function orderPluginFirst(string $name, string $folder = 'system'): void
{
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->select(['e.ordering'])->from(\RegularLabs\Library\DB::quoteName('#__extensions', 'e'))->where(\RegularLabs\Library\DB::is('e.type', 'plugin'))->where(\RegularLabs\Library\DB::is('e.folder', $folder))->where(\RegularLabs\Library\DB::is('e.element', $name));
$db->setQuery($query);
$current_ordering = $db->loadResult();
if ($current_ordering == '') {
return;
}
$query = \RegularLabs\Library\DB::getQuery()->select('e.ordering')->from(\RegularLabs\Library\DB::quoteName('#__extensions', 'e'))->where(\RegularLabs\Library\DB::is('e.type', 'plugin'))->where(\RegularLabs\Library\DB::is('e.folder', $folder))->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('e.manifest_cache'), '%"author":"Regular Labs%'))->where(\RegularLabs\Library\DB::isNot('e.element', $name))->order('e.ordering ASC');
$db->setQuery($query);
$min_ordering = $db->loadResult();
if ($min_ordering == '') {
return;
}
if ($current_ordering < $min_ordering) {
return;
}
if ($min_ordering < 1 || $current_ordering == $min_ordering) {
$new_ordering = max($min_ordering, 1);
$query = \RegularLabs\Library\DB::getQuery()->update(\RegularLabs\Library\DB::quoteName('#__extensions'))->set(\RegularLabs\Library\DB::quoteName('ordering') . ' = ' . $new_ordering)->where(\RegularLabs\Library\DB::is('ordering', $min_ordering))->where(\RegularLabs\Library\DB::is('type', 'plugin'))->where(\RegularLabs\Library\DB::is('folder', $folder))->where(\RegularLabs\Library\DB::isNot('element', $name))->where(\RegularLabs\Library\DB::like(\RegularLabs\Library\DB::quoteName('manifest_cache'), '%"author":"Regular Labs%'));
$db->setQuery($query);
$db->execute();
$min_ordering = $new_ordering;
}
if ($current_ordering == $min_ordering) {
return;
}
$new_ordering = $min_ordering - 1;
$query = $db->getQuery(\true)->update(\RegularLabs\Library\DB::quoteName('#__extensions'))->set(\RegularLabs\Library\DB::quoteName('ordering') . ' = ' . $new_ordering)->where(\RegularLabs\Library\DB::is('type', 'plugin'))->where(\RegularLabs\Library\DB::is('folder', $folder))->where(\RegularLabs\Library\DB::is('element', $name));
$db->setQuery($query);
$db->execute();
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class FieldHelper
{
private static $articles_field_names = null;
public static function correctFieldValue(int|string $field_name, mixed &$field_value): void
{
if (is_array($field_value) && (count($field_value) > 1 || !isset($field_value[0]))) {
foreach ($field_value as $key => &$value) {
self::correctFieldValue($key, $value);
}
return;
}
if (!in_array($field_name, self::getArticlesFieldNames())) {
return;
}
$field_value = (array) $field_value;
if (count($field_value) == 1 && str_contains($field_value[0], ',')) {
$field_value = explode(',', $field_value[0]);
}
}
private static function getArticlesFieldNames(): array
{
if (!is_null(self::$articles_field_names)) {
return self::$articles_field_names;
}
self::$articles_field_names = [];
$db = \RegularLabs\Library\DB::get();
$query = \RegularLabs\Library\DB::getQuery()->select([\RegularLabs\Library\DB::quoteName('f.name'), \RegularLabs\Library\DB::quoteName('f.id')])->from(\RegularLabs\Library\DB::quoteName('#__fields', 'f'))->where(\RegularLabs\Library\DB::quoteName('f.type') . ' = ' . $db->quote('articles'));
$db->setQuery($query);
$fields = $db->loadAssocList();
foreach ($fields as $field) {
self::$articles_field_names[] = 'field' . $field['id'];
self::$articles_field_names[] = $field['name'];
}
return self::$articles_field_names;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use JLoader;
use Joomla\Component\Fields\Administrator\Plugin\FieldsPlugin as JFieldsPlugin;
class FieldsPlugin extends JFieldsPlugin
{
public function __construct(&$subject, $config = [])
{
parent::__construct($subject, $config);
$path = JPATH_PLUGINS . '/fields/' . $this->_name . '/src/Form/Field';
if (!file_exists($path)) {
return;
}
$name = str_replace('PlgFields', '', $this::class);
JLoader::registerAlias('JFormField' . $name, '\RegularLabs\Plugin\Fields\\' . $name . '\Form\Field\\' . $name . 'Field');
}
}

View File

@ -0,0 +1,328 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Client\ClientHelper as JClientHelper;
use Joomla\CMS\Client\FtpClient as JFtpClient;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Filesystem\Path as JPath;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Log\Log as JLog;
use Joomla\CMS\Uri\Uri as JUri;
class File
{
static $is_external = [];
/**
* some/url/to/a/file.ext
* > some/url/to/a/file_suffix.ext
*/
public static function addSuffix(string $url, string $suffix): string
{
$url = \RegularLabs\Library\StringHelper::normalize($url);
$info = pathinfo($url);
return ($info['dirname'] ?? '') . '/' . ($info['filename'] ?? '') . $suffix . '.' . ($info['extension'] ?? '');
}
/**
* Delete a file or array of files
*/
public static function delete(string|array $file, bool $show_messages = \false, int $min_age_in_minutes = 0): bool
{
$FTPOptions = JClientHelper::getCredentials('ftp');
$pathObject = new JPath();
$files = is_array($file) ? $file : [$file];
if ($FTPOptions['enabled'] == 1) {
// Connect the FTP client
$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
}
foreach ($files as $file) {
$file = $pathObject->clean($file);
if (!is_file($file)) {
continue;
}
if ($min_age_in_minutes && floor((time() - filemtime($file)) / 60) < $min_age_in_minutes) {
continue;
}
// Try making the file writable first. If it's read-only, it can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
if ($FTPOptions['enabled'] == 1) {
$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
if (!$ftp->delete($file)) {
// FTP connector throws an error
return \false;
}
}
// Try the unlink twice in case something was blocking it on first try
if (!@unlink($file) && !@unlink($file)) {
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), JLog::WARNING, 'jerror');
return \false;
}
}
return \true;
}
/**
* Delete a folder.
*/
public static function deleteFolder(string $path, bool $show_messages = \false, int $min_age_in_minutes = 0): bool
{
@set_time_limit(ini_get('max_execution_time'));
$pathObject = new JPath();
if (!$path) {
$show_messages && JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');
return \false;
}
// Check to make sure the path valid and clean
$path = $pathObject->clean($path);
if (!is_dir($path)) {
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
return \false;
}
// Remove all the files in folder if they exist; disable all filtering
$files = JFolder::files($path, '.', \false, \true, [], []);
if (!empty($files)) {
if (self::delete($files, $show_messages, $min_age_in_minutes) !== \true) {
// JFile::delete throws an error
return \false;
}
}
// Remove sub-folders of folder; disable all filtering
$folders = JFolder::folders($path, '.', \false, \true, [], []);
foreach ($folders as $folder) {
if (is_link($folder)) {
// Don't descend into linked directories, just delete the link.
if (self::delete($folder, $show_messages, $min_age_in_minutes) !== \true) {
return \false;
}
continue;
}
if (!self::deleteFolder($folder, $show_messages, $min_age_in_minutes)) {
return \false;
}
}
// Skip if folder is not empty yet
if (!empty(JFolder::files($path, '.', \false, \true, [], [])) || !empty(JFolder::folders($path, '.', \false, \true, [], []))) {
return \true;
}
if (@rmdir($path)) {
return \true;
}
$FTPOptions = JClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1) {
// Connect the FTP client
$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
// Translate path and delete
$path = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
// FTP connector throws an error
return $ftp->delete($path);
}
if (!@rmdir($path)) {
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');
return \false;
}
return \true;
}
/**
* some/url/to/a/file.ext
* > file.ext
*/
public static function getBaseName(string $url, bool $lowercase = \false): string
{
$url = \RegularLabs\Library\StringHelper::normalize($url);
$basename = ltrim(basename($url), '/');
$parts = explode('?', $basename);
$basename = $parts[0];
if ($lowercase) {
$basename = strtolower($basename);
}
return $basename;
}
/**
* some/url/to/a/file.ext
* > some/url/to/a
*/
public static function getDirName(string $url): string
{
$url = \RegularLabs\Library\StringHelper::normalize($url);
$url = strtok($url, '?');
$url = strtok($url, '#');
return rtrim(dirname($url), '/');
}
/**
* some/url/to/a/file.ext
* > ext
*/
public static function getExtension(string $url): string
{
$info = pathinfo($url);
if (!isset($info['extension'])) {
return '';
}
$ext = explode('?', $info['extension']);
return strtolower($ext[0]);
}
/**
* some/url/to/a/file.ext
* > file
*/
public static function getFileName(string $url, bool $lowercase = \false): string
{
$url = \RegularLabs\Library\StringHelper::normalize($url);
$info = @pathinfo($url);
$filename = $info['filename'] ?? $url;
if ($lowercase) {
$filename = strtolower($filename);
}
return $filename;
}
public static function getFileTypes(string $type = 'images'): array
{
return match ($type) {
'image', 'images' => ['bmp', 'flif', 'gif', 'jpe', 'jpeg', 'jpg', 'png', 'tiff', 'eps', 'webp'],
'audio' => ['aif', 'aiff', 'mp3', 'wav'],
'video', 'videos' => ['3g2', '3gp', 'avi', 'divx', 'f4v', 'flv', 'm4v', 'mov', 'mp4', 'mpe', 'mpeg', 'mpg', 'ogv', 'swf', 'webm', 'wmv'],
'document', 'documents' => ['doc', 'docm', 'docx', 'dotm', 'dotx', 'odb', 'odc', 'odf', 'odg', 'odi', 'odm', 'odp', 'ods', 'odt', 'onepkg', 'onetmp', 'onetoc', 'onetoc2', 'otg', 'oth', 'otp', 'ots', 'ott', 'oxt', 'pdf', 'potm', 'potx', 'ppam', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx', 'rtf', 'sldm', 'sldx', 'thmx', 'xla', 'xlam', 'xlc', 'xld', 'xll', 'xlm', 'xls', 'xlsb', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx', 'xlw'],
'other', 'others' => ['css', 'csv', 'js', 'json', 'tar', 'txt', 'xml', 'zip'],
default => [...self::getFileTypes('images'), ...self::getFileTypes('audio'), ...self::getFileTypes('videos'), ...self::getFileTypes('documents'), ...self::getFileTypes('other')],
};
}
/**
* Find a matching media file in the different possible extension media folders for given type
*/
public static function getMediaFile(string $type, string $file): bool|string
{
// If http is present in filename
if (str_starts_with($file, 'http') || str_starts_with($file, '//')) {
return $file;
}
$files = [];
// Detect debug mode
if (\RegularLabs\Library\Document::isDebug()) {
$files[] = str_replace(['.min.', '-min.'], '.', $file);
}
$files[] = $file;
/**
* Loop on 1 or 2 files and break on first find.
* Add the content of the MD5SUM file located in the same folder to url to ensure cache browser refresh
* This MD5SUM file must represent the signature of the folder content
*/
foreach ($files as $check_file) {
$file_found = self::findMediaFileByFile($check_file, $type);
if (!$file_found) {
continue;
}
return $file_found;
}
return \false;
}
public static function isDocument(string $url): bool
{
return self::isMedia($url, self::getFileTypes('documents'));
}
public static function isExternal(string $url): bool
{
if (isset(static::$is_external[$url])) {
return static::$is_external[$url];
}
$uri = parse_url($url);
if (empty($uri['host'])) {
static::$is_external[$url] = \false;
return static::$is_external[$url];
}
// give preference to SERVER_NAME, because this includes subdomains
$hostname = $_SERVER['SERVER_NAME'] ?: $_SERVER['HTTP_HOST'];
static::$is_external[$url] = !(strcasecmp($hostname, $uri['host']) === 0);
return static::$is_external[$url];
}
public static function isExternalVideo(string $url): bool
{
return str_contains($url, 'youtu.be') || str_contains($url, 'youtube.com') || str_contains($url, 'vimeo.com');
}
public static function isImage($url)
{
return self::isMedia($url, self::getFileTypes('images'));
}
public static function isInternal(string $url): bool
{
return !self::isExternal($url);
}
public static function isMedia(string $url, array|string $filetypes = []): bool
{
$filetype = self::getExtension($url);
if (empty($filetype)) {
return \false;
}
if (!is_array($filetypes)) {
$filetypes = [$filetypes];
}
if (count($filetypes) == 1 && str_contains($filetypes[0], ',')) {
$filetypes = \RegularLabs\Library\ArrayHelper::toArray($filetypes[0]);
}
$filetypes = $filetypes ?? null ?: self::getFileTypes();
return in_array($filetype, $filetypes);
}
public static function isVideo(string $url): bool
{
return self::isMedia($url, self::getFileTypes('videos'));
}
public static function trimFolder(string $folder): string
{
return trim(str_replace(['\\', '//'], '/', $folder), '/');
}
/**
* Find a matching media file in the different possible extension media folders for given type
*/
private static function findMediaFileByFile(string $file, string $type): string|false
{
$template = JFactory::getApplication()->getTemplate();
// If the file is in the template folder
$file_found = self::getFileUrl('/templates/' . $template . '/' . $type . '/' . $file);
if ($file_found) {
return $file_found;
}
// Try to deal with system files in the media folder
if (!str_contains($file, '/')) {
$file_found = self::getFileUrl('/media/system/' . $type . '/' . $file);
if (!$file_found) {
return \false;
}
return $file_found;
}
$paths = [];
// If the file contains any /: it can be in a media extension subfolder
// Divide the file extracting the extension as the first part before /
[$extension, $file] = explode('/', $file, 2);
$paths[] = '/media/' . $extension . '/' . $type;
$paths[] = '/templates/' . $template . '/' . $type . '/system';
$paths[] = '/media/system/' . $type;
$paths[] = '';
foreach ($paths as $path) {
$file_found = self::getFileUrl($path . '/' . $file);
if (!$file_found) {
continue;
}
return $file_found;
}
return \false;
}
/**
* Get the url for the file
*/
private static function getFileUrl(string $path): string|false
{
if (!file_exists(JPATH_ROOT . $path)) {
return \false;
}
return JUri::root(\true) . $path;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\FormField as RL_FormField;
class AccessLevelsField extends RL_FormField
{
static $options;
public bool $is_select_list = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('a.title')->from('#__viewlevels AS a')->where(RL_DB::is('a.id', $values))->order('a.ordering ASC');
$this->db->setQuery($query);
return $this->db->loadColumn();
}
protected function getOptions()
{
if (!is_null(self::$options)) {
return self::$options;
}
$query = $this->db->getQuery(\true)->select('a.id as value, a.title as text')->from('#__viewlevels AS a')->order('a.ordering ASC');
$this->db->setQuery($query);
self::$options = $this->db->loadObjectList();
return self::$options;
}
}

View File

@ -0,0 +1,91 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form\FormField as RL_FormField;
class AgentsField extends RL_FormField
{
public $attributes = ['group' => 'os'];
public bool $is_select_list = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$agents = $this->getAgents($attributes);
$names = [];
foreach ($agents as $agent) {
if (!in_array($agent[1], $values)) {
continue;
}
$names[] = $agent[0];
}
return $names;
}
protected function getListOptions(array $attributes): array|int
{
$agents = $this->getAgents($attributes);
$options = [];
foreach ($agents as $agent) {
$option = JHtml::_('select.option', $agent[1], $agent[0]);
$options[] = $option;
}
return $options;
}
private function getAgents(array $attributes): array
{
$agents = [];
switch ($attributes['group']) {
/* OS */
case 'os':
$agents[] = ['Windows', 'Windows'];
$agents[] = ['Mac OS', '#(Mac OS|Mac_PowerPC|Macintosh)#'];
$agents[] = ['Linux', '#(Linux|X11)#'];
$agents[] = ['Open BSD', 'OpenBSD'];
$agents[] = ['Sun OS', 'SunOS'];
$agents[] = ['QNX', 'QNX'];
$agents[] = ['BeOS', 'BeOS'];
$agents[] = ['OS/2', 'OS/2'];
break;
/* Browsers */
case 'browser':
$agents[] = ['Chrome', 'Chrome'];
$agents[] = ['Firefox', 'Firefox'];
$agents[] = ['Microsoft Edge', 'MSIE Edge'];
// missing MSIE is added to agent string in RegularLabs\Component\Conditions\Administrator\Condition\Agent\Agent
$agents[] = ['Internet Explorer', 'MSIE [0-9]'];
// missing MSIE is added to agent string in RegularLabs\Component\Conditions\Administrator\Condition\Agent\Agent
$agents[] = ['Opera', 'Opera'];
$agents[] = ['Safari', 'Safari'];
break;
/* Mobile browsers */
case 'mobile':
$agents[] = [JText::_('JALL'), 'mobile'];
$agents[] = ['Android', 'Android'];
$agents[] = ['Android Chrome', '#Android.*Chrome#'];
$agents[] = ['Blackberry', 'Blackberry'];
$agents[] = ['IE Mobile', 'IEMobile'];
$agents[] = ['iPad', 'iPad'];
$agents[] = ['iPhone', 'iPhone'];
$agents[] = ['iPod Touch', 'iPod'];
$agents[] = ['NetFront', 'NetFront'];
$agents[] = ['Nokia', 'NokiaBrowser'];
$agents[] = ['Opera Mini', 'Opera Mini'];
$agents[] = ['Opera Mobile', 'Opera Mobi'];
$agents[] = ['UC Browser', 'UC Browser'];
break;
default:
break;
}
return $agents;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Form\FormField as RL_FormField;
class AjaxField extends RL_FormField
{
protected function getInput()
{
$class = $this->get('class', 'btn btn-success');
if ($this->get('disabled')) {
return $this->getButton($class . ' disabled', 'disabled');
}
RL_Document::script('regularlabs.admin-form');
RL_Document::script('regularlabs.regular');
RL_Document::script('regularlabs.script');
$query = '';
$url_query = $this->get('url-query');
if ($url_query) {
$name_prefix = $this->form->getFormControl() . '\\\\[' . $this->group . '\\\\]';
$id_prefix = $this->form->getFormControl() . '_' . $this->group . '_';
$query_parts = [];
$url_query = explode(',', $url_query);
foreach ($url_query as $url_query_part) {
[$key, $id] = explode(':', $url_query_part);
$el_name = 'document.querySelector(`input[name=' . $name_prefix . '\\\\[' . $id . '\\\\]]:checked`)';
$el_id = 'document.querySelector(`#' . $id_prefix . $id . '`)';
$query_parts[] = '`&' . $key . '=`' . ' + encodeURI(' . $el_name . ' ? ' . $el_name . '.value : (' . $el_id . ' ? ' . $el_id . '.value' . ' : ``))';
}
$query = '+' . implode('+', $query_parts);
}
$url = '`' . addslashes($this->get('url')) . '`' . $query;
$attributes = 'onclick="RegularLabs.AdminForm.loadAjaxButton(`' . $this->id . '`, ' . $url . ')"';
return $this->getButton($class, $attributes);
}
private function getButton(string $class = 'btn btn-success', string $attributes_string = ''): string
{
$icon = $this->get('icon', '') ? 'icon-' . $this->get('icon', '') : '';
$attributes_string = $attributes_string ? ' ' . $attributes_string : '';
return '<button type="button" id="' . $this->id . '" class="' . $class . '"' . ' title="' . JText::_($this->get('description')) . '"' . $attributes_string . '>' . '<span class="' . $icon . '"></span> ' . '<span>' . JText::_($this->get('text', $this->get('label'))) . '</span>' . '</button>' . '<div id="message_' . $this->id . '"></div>';
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\FormHelper as JFormHelper;
use RegularLabs\Library\Form\FormField as RL_FormField;
class BlockField extends RL_FormField
{
protected $hiddenDescription = \true;
protected function getInput()
{
if ($this->get('end', 0)) {
return $this->getControlGroupEnd() . '</fieldset>' . $this->getControlGroupStart();
}
$title = $this->get('label');
$description = $this->get('description');
$class = $this->get('class');
$no_default_class = $this->get('no_default_class');
$html = [];
$attributes = 'class="' . ($no_default_class ? '' : 'options-form ') . $class . '"';
if ($this->get('showon')) {
$encodedConditions = json_encode(JFormHelper::parseShowOnConditions($this->get('showon'), $this->formControl, $this->group));
$attributes .= " data-showon='" . $encodedConditions . "'";
}
$html[] = '<fieldset ' . $attributes . '>';
if ($title) {
$html[] = '<legend>' . $this->prepareText($title) . '</legend>';
}
if ($description) {
$html[] = '<div class="form-text mb-3">' . $this->prepareText($description) . '</div>';
}
return $this->getControlGroupEnd() . implode('', $html) . $this->getControlGroupStart();
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\Field\CheckboxesField as JCheckboxesField;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Language\Text as JText;
use SimpleXMLElement;
use UnexpectedValueException;
use function count;
class CheckboxesField extends JCheckboxesField
{
/**
* Name of the layout being used to render the field
*
* @var string
*/
protected $layout = 'regularlabs.form.field.checkboxes';
protected function getLayoutPaths()
{
$paths = parent::getLayoutPaths();
$paths[] = JPATH_LIBRARIES . '/regularlabs/layouts';
return $paths;
}
protected function getOptions()
{
$groups = $this->getGroups();
return self::flattenGroups($groups);
}
private static function flattenGroups(array $groups): array
{
$options = [];
foreach ($groups as $group_name => $group) {
if ($group_name !== 0) {
$options[] = $group_name;
}
foreach ($group as $option) {
$options[] = $option;
}
}
return $options;
}
private function getGroups(): array
{
$fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
$groups = [];
$label = 0;
foreach ($this->element->children() as $element) {
switch ($element->getName()) {
// The element is an <option />
case 'option':
if (!isset($groups[$label])) {
$groups[$label] = [];
}
$groups[$label][] = $this->getOption($element, $fieldname);
break;
// The element is a <group />
case 'group':
// Get the group label.
$groupLabel = (string) $element['label'];
if ($groupLabel) {
$label = JText::_($groupLabel);
}
// Initialize the group if necessary.
if (!isset($groups[$label])) {
$groups[$label] = [];
}
// Iterate through the children and build an array of options.
foreach ($element->children() as $option) {
// Only add <option /> elements.
if ($option->getName() !== 'option') {
continue;
}
$groups[$label][] = $this->getOption($option, $fieldname);
}
if ($groupLabel) {
$label = count($groups);
}
break;
// Unknown element type.
default:
throw new UnexpectedValueException(sprintf('Unsupported element %s in GroupedlistField', $element->getName()), 500);
}
}
reset($groups);
return $groups;
}
private function getOption(SimpleXMLElement $option, string $fieldname): object
{
$value = (string) $option['value'];
$text = trim((string) $option) != '' ? trim((string) $option) : $value;
$disabled = (string) $option['disabled'];
$disabled = $disabled === 'true' || $disabled === 'disabled' || $disabled === '1';
$disabled = $disabled || $this->readonly && $value != $this->value;
$checked = (string) $option['checked'];
$checked = $checked === 'true' || $checked === 'checked' || $checked === '1';
$selected = (string) $option['selected'];
$selected = $selected === 'true' || $selected === 'selected' || $selected === '1';
$tmp = ['value' => $value, 'text' => JText::alt($text, $fieldname), 'disable' => $disabled, 'class' => (string) $option['class'], 'selected' => $checked || $selected, 'checked' => $checked || $selected];
// Set some event handler attributes. But really, should be using unobtrusive js.
$tmp['onclick'] = (string) $option['onclick'];
$tmp['onchange'] = (string) $option['onchange'];
if ((string) $option['showon']) {
$encodedConditions = json_encode(FormHelper::parseShowOnConditions((string) $option['showon'], $this->formControl, $this->group));
$tmp['optionattr'] = " data-showon='" . $encodedConditions . "'";
}
return (object) $tmp;
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\RegEx as RL_RegEx;
class ComponentsField extends RL_FormField
{
static $components;
public $attributes = ['frontend' => \true, 'admin' => \true];
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('e.name, e.element')->from('#__extensions AS e')->where('e.type = ' . $this->db->quote('component'))->where(RL_DB::is('e.element', $values))->order('e.name');
$this->db->setQuery($query);
$components = $this->db->loadObjectList();
$lang = $this->app->getLanguage();
$names = [];
foreach ($components as $component) {
$name = $component->name;
if (!str_contains($component->name, ' ')) {
// Load the core file then
// Load extension-local file.
$lang->load($component->element . '.sys', JPATH_BASE, null, \false, \false) || $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, null, \false, \false) || $lang->load($component->element . '.sys', JPATH_BASE, $lang->getDefault(), \false, \false) || $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, $lang->getDefault(), \false, \false);
$name = JText::_(strtoupper($name));
}
$names[] = $name;
}
return $names;
}
protected function getListOptions(array $attributes): array|int
{
$frontend = $attributes['frontend'];
$admin = $attributes['admin'];
if (!$frontend && !$admin) {
return [];
}
$components = $this->getComponents();
$comps = [];
$lang = $this->app->getLanguage();
foreach ($components as $component) {
if (empty($component->element)) {
continue;
}
$component_folder = ($frontend ? JPATH_SITE : JPATH_ADMINISTRATOR) . '/components/' . $component->element;
if (!JFolder::exists($component_folder) && $admin) {
$component_folder = JPATH_ADMINISTRATOR . '/components/' . $component->element;
}
// return if there is no main component folder
if (!JFolder::exists($component_folder)) {
continue;
}
// return if there is no view(s) folder
if ($component->element !== 'com_ajax' && !JFolder::exists($component_folder . '/src/View') && !JFolder::exists($component_folder . '/views') && !JFolder::exists($component_folder . '/view')) {
continue;
}
if (!str_contains($component->name, ' ')) {
// Load the core file then
// Load extension-local file.
$lang->load($component->element . '.sys', JPATH_BASE, null, \false, \false) || $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, null, \false, \false) || $lang->load($component->element . '.sys', JPATH_BASE, $lang->getDefault(), \false, \false) || $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, $lang->getDefault(), \false, \false);
$component->name = JText::_(strtoupper($component->name));
}
$comps[RL_RegEx::replace('[^a-z0-9_]', '', $component->name . '_' . $component->element)] = $component;
}
ksort($comps);
$options = [];
foreach ($comps as $component) {
$key = $component->element;
if ($this->get('no_com_prefix')) {
$key = RL_RegEx::replace('^com_', '', $key);
}
$options[] = JHtml::_('select.option', $key, $component->name);
}
return $options;
}
private function getComponents(): array
{
if (!is_null(self::$components)) {
return self::$components;
}
$query = $this->db->getQuery(\true)->select('e.name, e.element')->from('#__extensions AS e')->where('e.type = ' . $this->db->quote('component'))->where('e.name != ""')->where('e.element != ""')->group('e.element')->order('e.element, e.name');
$this->db->setQuery($query);
self::$components = $this->db->loadObjectList();
return self::$components;
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\Form;
use RegularLabs\Library\Form\FormField as RL_FormField;
class ContentArticlesField extends RL_FormField
{
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->from('#__content AS i')->select('i.id, i.title as name, i.language, c.title as category, i.state as published')->join('LEFT', '#__categories AS c ON c.id = i.catid')->where(RL_DB::is('i.id', $values))->order('i.title, i.ordering, i.id');
$this->db->setQuery($query);
$articles = $this->db->loadObjectList();
return Form::getNamesWithExtras($articles, ['language', 'category', 'id', 'unpublished']);
}
protected function getOptions()
{
if ($this->max_list_count) {
$query = $this->db->getQuery(\true)->select('COUNT(*)')->from('#__content AS i')->where('i.access > -1')->where('i.state > -1');
$this->db->setQuery($query);
$total = $this->db->loadResult();
if ($total > $this->max_list_count) {
return -1;
}
}
$id = 'i.id';
$extras = ['language', 'category', 'id', 'unpublished'];
if ($this->get('id_alias_name_as_value', 0)) {
$id = 'CONCAT(i.id, "::", i.alias, "::", i.title) AS id';
$extras = ['language', 'category', 'id_number', 'unpublished'];
}
$query->clear('select')->select($id . ', i.id AS id_number, i.title AS name, i.language, c.title AS category, i.state AS published')->join('LEFT', '#__categories AS c ON c.id = i.catid')->order('i.title, i.ordering, i.id');
$this->db->setQuery($query);
$list = $this->db->loadObjectList();
$options = $this->getOptionsByList($list, $extras);
if ($this->get('showselect')) {
array_unshift($options, JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', \true));
array_unshift($options, JHtml::_('select.option', '-', '- ' . JText::_('Select Item') . ' -'));
}
return $options;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\Form as RL_Form;
use RegularLabs\Library\Form\FormField as RL_FormField;
class ContentCategoriesField extends RL_FormField
{
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public bool $use_tree_select = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('c.id, c.title as name, c.published, c.language')->from('#__categories AS c')->where('c.extension = ' . $this->db->quote('com_content'))->where(RL_DB::is('c.id', $values))->order('c.lft');
$this->db->setQuery($query);
$categories = $this->db->loadObjectList();
return RL_Form::getNamesWithExtras($categories, ['language', 'unpublished']);
}
protected function getOptions()
{
if ($this->max_list_count) {
$query = $this->db->getQuery(\true)->select('COUNT(*)')->from('#__categories as c')->where('c.extension = ' . $this->db->quote('com_content'))->where('c.parent_id > 0')->where('c.published > -1');
$this->db->setQuery($query);
$total = $this->db->loadResult();
if ($total > $this->max_list_count) {
return -1;
}
}
$this->value = RL_Array::toArray($this->value);
$query->clear('select')->select('c.id, c.title as name, c.published, c.language, c.level')->order('c.lft');
$this->db->setQuery($query);
$list = $this->db->loadObjectList();
return $this->getOptionsByList($list, ['language', 'unpublished'], -1);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Form\FormField as RL_FormField;
class CustomOptionsField extends RL_FormField
{
protected function getInput()
{
$data = $this->getLayoutData();
$data['options'] = $this->getOptions();
$data['value'] = RL_Array::toArray($this->value);
$data['placeholder'] = JText::_('RL_ENTER_NEW_VALUES');
return (new JFileLayout('regularlabs.form.field.customoptions', JPATH_SITE . '/libraries/regularlabs/layouts'))->render($data);
}
protected function getOptions()
{
$values = RL_Array::toArray($this->value);
$options = [];
foreach ($values as $value) {
$options[] = (object) ['value' => $value, 'text' => $value];
}
return $options;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
class DependencyField extends RL_FormField
{
protected function getInput()
{
$file = $this->get('file', '');
$label = $this->get('label', 'the main extension');
\RegularLabs\Library\Form\Field\DependencyFieldHelper::setMessage($file, $label);
return '';
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
class DependencyFieldHelper
{
public static function setMessage(string $file, string $name): void
{
if (empty($file)) {
return;
}
$file = JPATH_SITE . '/' . trim($file, '/');
if (file_exists($file)) {
return;
}
$msg = JText::sprintf('RL_THIS_EXTENSION_NEEDS_THE_MAIN_EXTENSION_TO_FUNCTION', JText::_($name));
$messageQueue = JFactory::getApplication()->getMessageQueue();
foreach ($messageQueue as $queue_message) {
if ($queue_message['type'] == 'error' && $queue_message['message'] == $msg) {
return;
}
}
JFactory::getApplication()->enqueueMessage($msg, 'error');
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Form\FormField as RL_FormField;
class DownloadKeyField extends RL_FormField
{
protected function getInput()
{
RL_Document::script('regularlabs.script');
RL_Document::script('regularlabs.downloadkey');
return (new JFileLayout('regularlabs.form.field.downloadkey', JPATH_SITE . '/libraries/regularlabs/layouts'))->render(['id' => $this->id, 'extension' => strtolower($this->get('extension', 'all')), 'use_modal' => $this->get('use-modal', \true)]);
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\Form;
use RegularLabs\Library\Form\FormField as RL_FormField;
class FieldField extends RL_FormField
{
static $fields;
public bool $is_select_list = \true;
public function getNameById(string $value, array $attributes): string
{
return RL_Array::implode($this->getNamesByIds([$value], $attributes));
}
public function getNamesByIds(array $values, array $attributes): array
{
$db = RL_DB::get();
$query = RL_DB::getQuery()->select('DISTINCT a.id, a.type, a.title as name')->from('#__fields AS a')->where('a.state = 1')->where(RL_DB::is('a.id', $values))->order('a.title');
$db->setQuery($query);
$fields = $db->loadObjectList();
return Form::getNamesWithExtras($fields, ['type']);
}
protected function getOptions()
{
$fields = $this->getFields();
$options = [];
$options[] = JHtml::_('select.option', '', '- ' . JText::_('RL_SELECT_FIELD') . ' -');
foreach ($fields as $field) {
$key = $field->{$this->get('key', 'id')} ?? $field->id;
$options[] = JHtml::_('select.option', $key, $field->title . ' [' . $field->type . ']');
}
if ($this->get('show_custom')) {
$options[] = JHtml::_('select.option', 'custom', '- ' . JText::_('RL_CUSTOM') . ' -');
}
return $options;
}
private function getFields(): array
{
if (!is_null(self::$fields)) {
return self::$fields;
}
$db = RL_DB::get();
$query = RL_DB::getQuery()->select('DISTINCT a.id, a.type, a.name, a.title')->from('#__fields AS a')->where('a.state = 1')->where('a.only_use_in_subform = 0')->where(RL_DB::isNot('a.type', ['subform', 'repeatable']))->order('a.title');
$db->setQuery($query);
self::$fields = $db->loadObjectList();
return self::$fields;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,42 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\GeoIp\GeoIp as RL_GeoIP;
class GeoInformationField extends RL_FormField
{
protected function getInput()
{
return '';
}
protected function getLabel()
{
if (!class_exists('RegularLabs\Library\GeoIp\GeoIp')) {
return '';
}
$ip = '';
$geo = new RL_GeoIP($ip);
if (empty($geo)) {
return \false;
}
$geo = $geo->get();
if (empty($geo)) {
return \false;
}
$details = [JText::_('CON_CONTINENT') . ': <strong>' . $geo->continent . '</strong>', JText::_('CON_COUNTRY') . ': <strong>' . $geo->country . '</strong>', JText::_('CON_REGION') . ': <strong>' . implode(', ', $geo->regions) . '</strong>', JText::_('CON_POSTAL_CODE') . ': <strong>' . $geo->postalCode . '</strong>'];
$html = '<div class="rl-alert alert alert-info rl-alert-light">' . JText::_('CON_GEO_CURRENT_DETAILS') . '<ul><li>' . implode('</li><li>', $details) . '</li></ul>' . '</div>';
return '</div><div>' . $html;
}
}

View File

@ -0,0 +1,100 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Installer\Installer as JInstaller;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use RegularLabs\Library\Version;
class HeaderField extends RL_FormField
{
protected function getInput()
{
$title = $this->get('label');
$jversion = Version::getMajorJoomlaVersion();
if ($jversion != 4) {
JFactory::getApplication()->enqueueMessage(JText::sprintf('RL_NOT_COMPATIBLE_WITH_JOOMLA_VERSION', JText::_($title), $jversion), 'error');
return '';
}
$description = $this->get('description');
$xml = $this->get('xml');
$url = $this->get('url');
$this->description = '';
if ($description) {
$description = RL_String::html_entity_decoder(trim(JText::_($description)));
}
if ($title) {
$title = JText::_($title);
}
if ($description) {
// Replace inline monospace style with rl_code classname
$description = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $description);
// 'Break' plugin style tags
$description = str_replace(['{', '['], ['<span>{</span>', '<span>[</span>'], $description);
// Wrap in paragraph (if not already starting with an html tag)
if ($description[0] != '<') {
$description = '<p>' . $description . '</p>';
}
}
if (!$xml && $this->form->getValue('element')) {
if ($this->form->getValue('folder')) {
$xml = 'plugins/' . $this->form->getValue('folder') . '/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
} else {
$xml = 'administrator/modules/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
}
}
if ($xml) {
$xml = JInstaller::parseXMLInstallFile(JPATH_SITE . '/' . $xml);
$version = 0;
if ($xml && isset($xml['version'])) {
$version = $xml['version'];
}
if ($version) {
if (str_contains($version, 'PRO')) {
$version = str_replace('PRO', '', $version);
$version .= ' <small style="color:green">[PRO]</small>';
} elseif (str_contains($version, 'FREE')) {
$version = str_replace('FREE', '', $version);
$version .= ' <small style="color:green">[FREE]</small>';
}
if ($title) {
$title .= ' v';
} else {
$title = JText::_('Version') . ' ';
}
$title .= $version;
}
}
$html = [];
if ($title) {
if ($url) {
$title = '<a href="' . $url . '" target="_blank" title="' . RL_RegEx::replace('<[^>]*>', '', $title) . '">' . $title . '</a>';
}
$html[] = '<h4>' . RL_String::html_entity_decoder($title) . '</h4>';
}
if ($description) {
$html[] = $description;
}
if ($url) {
$html[] = '<p><a href="' . $url . '" class="btn btn-outline-info" target="_blank" title="' . JText::_('RL_MORE_INFO') . '">' . JText::_('RL_MORE_INFO') . ' >></a></p>';
}
return $this->getControlGroupEnd() . implode('', $html) . $this->getControlGroupStart();
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
class HeaderLibraryField extends \RegularLabs\Library\Form\Field\HeaderField
{
protected function getInput()
{
$extensions = [
'Advanced Module Manager',
'Articles Anywhere',
'Articles Field',
'Better Frontend Link',
'Cache Cleaner',
'CDN for Joomla!',
'Conditional Content',
// 'Content Templater',
'DB Replacer',
'GeoIP',
'IP Login',
// 'Keyboard Shortcuts',
// 'Modals',
'Modules Anywhere',
'Quick Index',
'Regular Labs Extension Manager',
'ReReplacer',
'Snippets',
'Sourcerer',
// 'Tabs & Accordions',
// 'Tooltips',
'What? Nothing!',
];
$list = '<ul><li>' . implode('</li><li>', $extensions) . '</li></ul>';
$attributes = $this->element->attributes();
$warning = '';
if (isset($attributes['warning'])) {
$warning = '<div class="alert alert-danger">' . JText::_($attributes['warning']) . '</div>';
}
$this->element->attributes()['description'] = JText::sprintf($attributes['description'], $warning, $list);
return parent::getInput();
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
use RegularLabs\Library\Form\FormField as RL_FormField;
class IconToggleField extends RL_FormField
{
protected function getInput()
{
return (new JFileLayout('regularlabs.form.field.icontoggle', JPATH_SITE . '/libraries/regularlabs/layouts'))->render(['id' => $this->id, 'name' => $this->name, 'icon1' => strtolower($this->get('icon1', 'arrow-down')), 'icon2' => $this->get('icon2', 'arrow-up'), 'text1' => $this->get('text1', ''), 'text2' => $this->get('text2', ''), 'class1' => $this->get('class1', ''), 'class2' => $this->get('class2', '')]);
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form\FormField as RL_FormField;
class IconsField extends RL_FormField
{
protected $layout = 'joomla.form.field.radio.buttons';
protected function getInput()
{
$data = $this->getLayoutData();
return $this->getRenderer($this->layout)->render($data);
}
protected function getLayoutData()
{
$data = parent::getLayoutData();
$extraData = ['options' => $this->getOptions(), 'value' => (string) $this->value, 'class' => 'btn-group rl-btn-group rl-btn-group-separate rl-btn-group-min-size'];
return [...$data, ...$extraData];
}
protected function getOptions()
{
$classes = ['address-book', 'address-card', 'align-center', 'align-justify', 'align-left', 'align-right', 'angle-double-left', 'angle-double-right', 'angle-down', 'angle-left', 'angle-right', 'angle-up', 'archive', 'arrow-alt-circle-down', 'arrow-alt-circle-left', 'arrow-alt-circle-right', 'arrow-alt-circle-up', 'arrow-down', 'arrow-left', 'arrow-right', 'arrow-up', 'arrows-alt', 'bars', 'bell', 'bolt', 'bookmark', 'briefcase', 'bullhorn', 'calendar-alt', 'calendar-check', 'camera', 'caret-down', 'caret-left', 'caret-right', 'caret-up', 'chart-area', 'chart-bar', 'chart-pie', 'check-square', 'plus-square', 'minus-square', 'check-circle', 'plus-circle', 'minus-circle', 'times-circle', 'play-circle', 'pause-circle', 'stop-circle', 'chevron-circle-left', 'chevron-circle-right', 'backward', 'forward', 'step-backward', 'fast-backward', 'fast-forward', 'square', 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'circle', 'clipboard', 'clock', 'cloud-download-alt', 'cloud-upload-alt', 'code-branch', 'cogs', 'comment-dots', 'comments', 'compress', 'copy', 'credit-card', 'crop', 'cubes', 'cut', 'database', 'desktop', 'tablet', 'mobile', 'dot-circle', 'download', 'upload', 'edit', 'pen-square', 'pencil-alt', 'ellipsis-h', 'ellipsis-v', 'envelope-open-text', 'exclamation-circle', 'exclamation-triangle', 'info-circle', 'question-circle', 'expand-arrows-alt', 'external-link-alt', 'external-link-square-alt', 'eye-slash', 'fax', 'file-alt', 'filter', 'flag', 'folder-open', 'handshake', 'home', 'image', 'key', 'lock-open', 'unlock-alt', 'language', 'life-ring', 'lightbulb', 'link', 'list-ol', 'list-ul', 'tasks', 'magic', 'compass', 'globe', 'map-marker-alt', 'thumbtack', 'map-signs', 'medkit', 'music', 'paint-brush', 'paperclip', 'phone-square', 'plug', 'power-off', 'print', 'project-diagram', 'puzzle-piece', 'quote-left', 'quote-right', 'random', 'rss-square', 'save', 'search-minus', 'search-plus', 'shield-alt', 'shopping-basket', 'shopping-cart', 'sign-in-alt', 'sign-out-alt', 'sitemap', 'sliders-h', 'smile', 'frown', 'thumbs-down', 'thumbs-up', 'heart', 'star', 'star-half', 'trophy', 'tachometer-alt', 'tags', 'text-width', 'th-large', 'toggle-off', 'toggle-on', 'trash', 'share', 'sync', 'undo', 'universal-access', 'user-circle', 'user-edit', 'user-lock', 'user-tag', 'users-cog', 'video', 'wifi', 'wrench'];
$options = [];
foreach ($classes as $class) {
$options[] = (object) ['value' => $class, 'text' => '<i class="fa fa-' . $class . '"></i>'];
}
if ($this->get('show_none')) {
$options[] = (object) ['value' => '0', 'text' => JText::_('JNONE')];
}
return $options;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\HtmlTag as RL_HtmlTag;
class ImageField extends RL_FormField
{
protected function getInput()
{
$attributes = ['src' => (string) (string) $this->element['src']];
if ($this->element['alt']) {
$attributes['alt'] = (string) $this->element['alt'];
}
if ($this->element['title']) {
$attributes['title'] = (string) $this->element['title'];
}
if ($this->element['height']) {
$attributes['height'] = (string) $this->element['height'];
}
if ($this->element['width']) {
$attributes['width'] = (string) $this->element['width'];
}
$attributes = RL_HtmlTag::combineAttributes($attributes, (string) $this->element['attributes']);
return '<img ' . $attributes . '>';
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Extension as RL_Extension;
use RegularLabs\Library\Form\FormField as RL_FormField;
class IsInstalledField extends RL_FormField
{
protected $layout = 'joomla.form.field.hidden';
protected function getLabel()
{
$this->value = (int) RL_Extension::isInstalled($this->get('extension'), $this->get('extension_type', 'component'), $this->get('folder', 'system'));
return $this->getControlGroupEnd() . rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), \PHP_EOL) . $this->getControlGroupStart();
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\Version;
class JCompatibilityField extends RL_FormField
{
protected function getInput()
{
$extension = $this->get('extension');
if (empty($extension)) {
return '';
}
$jversion = Version::getMajorJoomlaVersion();
if ($jversion == 4) {
return '';
}
RL_Document::useStyle('webcomponent.joomla-alert');
RL_Document::useScript('webcomponent.joomla-alert');
return '<joomla-alert type="danger" dismiss="true" class="joomla-alert--show" role="alert">' . JText::sprintf('RL_NOT_COMPATIBLE_WITH_JOOMLA_VERSION', JText::_($extension), $jversion) . '</joomla-alert>';
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use RegularLabs\Library\Form\FormField as RL_FormField;
class LanguagesField extends RL_FormField
{
public bool $is_select_list = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$languages = JHtml::_('contentlanguage.existing');
$names = [];
foreach ($languages as $language) {
if (empty($language->value)) {
continue;
}
if (!in_array($language->value, $values)) {
continue;
}
$names[] = $language->text . ' [' . $language->value . ']';
}
return $names;
}
protected function getOptions()
{
$languages = JHtml::_('contentlanguage.existing');
$value = $this->get('value', []);
if (!is_array($value)) {
$value = [$value];
}
$options = [];
foreach ($languages as $language) {
if (empty($language->value)) {
continue;
}
$options[] = (object) ['value' => $language->value, 'text' => $language->text . ' [' . $language->value . ']', 'selected' => in_array($language->value, $value, \true)];
}
return $options;
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\License as RL_License;
class LicenseField extends RL_FormField
{
protected function getInput()
{
$extension = $this->get('extension');
if (empty($extension)) {
return '';
}
return RL_License::getMessage($extension, \true);
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\Language as RL_Language;
class LoadLanguageField extends RL_FormField
{
protected function getInput()
{
$extension = $this->get('extension');
$admin = (bool) $this->get('admin', 1);
self::loadLanguage($extension, $admin);
return '';
}
protected function getLabel()
{
return '';
}
private static function loadLanguage(string $extension, bool $admin = \true): void
{
if (!$extension) {
return;
}
RL_Language::load($extension, $admin ? JPATH_ADMINISTRATOR : JPATH_SITE);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Form\FormField as RL_FormField;
class LoadMediaField extends RL_FormField
{
protected function getInput()
{
return '';
}
protected function getLabel()
{
$filetype = $this->get('filetype');
$file = $this->get('file');
switch ($filetype) {
case 'style':
RL_Document::style($file);
break;
case 'script':
RL_Document::script($file);
break;
default:
break;
}
return '';
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Multilanguage as JMultilanguage;
use Joomla\CMS\Language\Text as JText;
use Joomla\Component\Menus\Administrator\Helper\MenusHelper as JMenusHelper;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\Language as RL_Language;
use RegularLabs\Library\RegEx as RL_RegEx;
class MenuItemsField extends RL_FormField
{
public bool $collapse_children = \true;
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public bool $use_tree_select = \true;
public function getNamesByIds(array $ids): array
{
if (empty($ids)) {
return [];
}
RL_Language::load('com_modules', JPATH_ADMINISTRATOR);
$menuTypes = JMenusHelper::getMenuLinks();
$items = array_fill_keys($ids, '');
foreach ($menuTypes as $type) {
if (isset($items['type.' . $type->menutype])) {
$items['type.' . $type->menutype] = $type->title . ' <span class="small">(' . JText::_('JALL') . ')</span>';
}
foreach ($type->links as $link) {
if (!isset($items[$link->value])) {
continue;
}
$text = [];
$text[] = $link->text;
$items[$link->value] = implode(' ', $text);
}
}
return $items;
}
protected function getOptions()
{
RL_Language::load('com_modules', JPATH_ADMINISTRATOR);
$menuTypes = JMenusHelper::getMenuLinks();
$options = [];
foreach ($menuTypes as &$type) {
$option = (object) ['value' => 'type.' . $type->menutype, 'text' => $type->title, 'level' => 0, 'class' => 'hidechildren', 'labelclass' => 'nav-header'];
$options[] = $option;
foreach ($type->links as $link) {
$check1 = RL_RegEx::replace('[^a-z0-9]', '', strtolower($link->text));
$check2 = RL_RegEx::replace('[^a-z0-9]', '', $link->alias);
$text = [];
$text[] = $link->text;
if ($check1 !== $check2) {
$text[] = '<small class="text-muted">[' . $link->alias . ']</small>';
}
if (in_array($link->type, ['separator', 'heading', 'alias', 'url'], \true)) {
$text[] = '<span class="badge bg-secondary">' . JText::_('COM_MODULES_MENU_ITEM_' . strtoupper($link->type)) . '</span>';
// Don't disable, as you need to be able to select the 'Also on Child Items' option
// $link->disable = 1;
}
if (JMultilanguage::isEnabled() && $link->language != '' && $link->language != '*') {
$text[] = $link->language_image ? JHtml::_('image', 'mod_languages/' . $link->language_image . '.gif', $link->language_title, ['title' => $link->language_title], \true) : '<span class="badge bg-secondary" title="' . $link->language_title . '">' . $link->language_sef . '</span>';
}
$link->text = implode(' ', $text);
$options[] = $link;
}
}
return $options;
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Form\FormField as RL_FormField;
class MiniColorField extends RL_FormField
{
public function getInput()
{
$class = trim('rl-mini-colors ' . $this->get('class'));
$table = $this->get('table');
$item_id = $this->get('item_id');
$id_column = $this->get('id_column') ?: 'id';
$disabled = $this->get('disabled') ? ' disabled="disabled"' : '';
$colors = $this->get('colors', 'none,#c0c6cf,#000000,#dc2a28,#fb6b14,#ffa813,#eac90a,#18a047,#0f9aa4,#115dda,#761bda,#d319a4');
$colors = str_replace('none', 'transparent', $colors);
RL_Document::scriptOptions(['swatches' => RL_Array::toArray($colors)], 'minicolors');
RL_Document::script('regularlabs.script');
RL_Document::script('regularlabs.mini-colors');
RL_Document::style('regularlabs.mini-colors');
return '<div class="rl-mini-colors">' . '<input type="text" name="' . $this->name . '" id="' . $this->id . '"' . ' class="' . $class . '" value="' . $this->value . '"' . $disabled . ' data-rl-mini-colors data-table="' . $table . '" data-item_id="' . $item_id . '" data-id_column="' . $id_column . '"' . '>' . '</div>';
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
class NoteField extends RL_FormField
{
protected function getInput()
{
if (empty($this->element['label'])) {
return '';
}
return $this->getNote();
}
protected function getLabel()
{
if (!empty($this->element['label'])) {
return parent::getLabel();
}
$note = $this->getNote();
if (empty($note)) {
return '';
}
return '</div><div>' . $note;
}
protected function getNote()
{
if (empty($this->element['title']) && empty($this->element['text'])) {
return '';
}
$title = $this->prepareText($this->element['title']);
$text = $this->prepareText($this->element['text']);
$heading = $this->element['heading'] ?: 'h4';
$class = !empty($this->element['class']) ? ' class="' . $this->element['class'] . '"' : '';
$html = [];
$html[] = !empty($title) ? '<' . $heading . '>' . $title . '</' . $heading . '>' : '';
$html[] = $text ?: '';
return '<div ' . $class . '>' . implode('', $html) . '</div>';
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Extension as RL_Extension;
use RegularLabs\Library\Form\FormField as RL_FormField;
class OnlyProField extends RL_FormField
{
protected function getExtensionName()
{
$element = $this->form->getValue('element');
if ($element) {
return $element;
}
$component = $this->app->input->get('component', '');
if ($component) {
return str_replace('com_', '', $component);
}
$folder = $this->app->input->get('folder', '');
if ($folder) {
$extension = explode('.', $folder);
return array_pop($extension);
}
$option = $this->app->input->get('option', '');
if ($option) {
return str_replace('com_', '', $option);
}
return \false;
}
protected function getInput()
{
$label = $this->prepareText($this->get('label'));
$description = $this->prepareText($this->get('description'));
if (!$label && !$description) {
return '';
}
return $this->getText();
}
protected function getLabel()
{
$label = $this->prepareText($this->get('label'));
$description = $this->prepareText($this->get('description'));
if (!$label && !$description) {
return '</div><div>' . $this->getText();
}
return parent::getLabel();
}
protected function getText()
{
$text = JText::_('RL_ONLY_AVAILABLE_IN_PRO');
$text = '<em>' . $text . '</em>';
$extension = $this->getExtensionName();
$alias = RL_Extension::getAliasByName($extension);
if (!$alias) {
return $text;
}
return '<a href="https://regularlabs.com/' . $extension . '/features" target="_blank">' . $text . '</a>';
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
class RangeField extends \Joomla\CMS\Form\Field\RangeField
{
/**
* @var string
*/
protected $layout = 'regularlabs.form.field.range';
/**
* @return string The field input markup.
*/
protected function getInput()
{
$this->value = (float) ($this->value ?: $this->default);
if (!empty($this->max)) {
$this->value = min($this->value, $this->max);
}
if (!empty($this->min)) {
$this->value = max($this->value, $this->min);
}
return $this->getRenderer($this->layout)->render($this->getLayoutData());
}
/**
* @return array
*/
protected function getLayoutData()
{
$data = parent::getLayoutData();
// Initialize some field attributes.
$extraData = ['prepend' => (string) ($this->element['prepend'] ?? ''), 'append' => (string) ($this->element['append'] ?? ''), 'class_range' => (string) ($this->element['class_range'] ?? '')];
return [...$data, ...$extraData];
}
protected function getLayoutPaths()
{
$paths = parent::getLayoutPaths();
$paths[] = JPATH_LIBRARIES . '/regularlabs/layouts';
return $paths;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\ShowOn as RL_ShowOn;
class ShowOnField extends RL_FormField
{
protected function getInput()
{
$value = (string) $this->get('value');
$class = $this->get('class', '');
if (!$value) {
return $this->getControlGroupEnd() . RL_ShowOn::close() . $this->getControlGroupStart();
}
$formControl = $this->get('form', $this->formControl);
$formControl = $formControl == 'root' ? '' : $formControl;
while (str_starts_with($value, '../')) {
$value = substr($value, 3);
if (str_contains($formControl, '[')) {
$formControl = RL_RegEx::replace('^(.*)\[.*?\]$', '\1', $formControl);
}
}
return $this->getControlGroupEnd() . RL_ShowOn::open($value, $formControl, $this->group, $class) . $this->getControlGroupStart();
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
use RegularLabs\Library\Form\FormField as RL_FormField;
class SimpleCategoryField extends RL_FormField
{
protected function getInput()
{
$categories = $this->getOptions();
$options = parent::getOptions();
$options = [...$options, ...$categories];
if ($this->get('show_none', \true)) {
$empty_option = JHtml::_('select.option', $this->get('none_value', ''), '- ' . JText::_('JNONE') . ' -');
$empty_option->class = 'hidden';
array_unshift($options, $empty_option);
}
if ($this->get('show_keep_original')) {
$keep_original_option = JHtml::_('select.option', ' ', '- ' . JText::_('RL_KEEP_ORIGINAL_CATEGORY') . ' -');
array_unshift($options, $keep_original_option);
}
$data = $this->getLayoutData();
$data['options'] = $options;
$data['placeholder'] = JText::_($this->get('hint', 'RL_SELECT_OR_CREATE_A_CATEGORY'));
$data['allowCustom'] = $this->get('allow_custom', \true);
return (new JFileLayout('regularlabs.form.field.simplecategory', JPATH_SITE . '/libraries/regularlabs/layouts'))->render($data);
}
protected function getOptions()
{
$table = $this->get('table');
if (!$table) {
return [];
}
// Get the user groups from the database.
$query = $this->db->getQuery(\true)->select([$this->db->quoteName('category', 'value'), $this->db->quoteName('category', 'text')])->from($this->db->quoteName('#__' . $table))->where($this->db->quoteName('category') . ' != ' . $this->db->quote(''))->group($this->db->quoteName('category'))->order($this->db->quoteName('category') . ' ASC');
$this->db->setQuery($query);
$categories = $this->db->loadObjectList();
foreach ($categories as &$category) {
if (!str_contains($category->text, '::')) {
continue;
}
[$text, $icon] = explode('::', $category->text, 2);
$category->text = $text;
}
return $categories;
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
use InvalidArgumentException;
use Joomla\CMS\Form\Field\SubformField as JSubformField;
use Joomla\CMS\Form\Form;
use RuntimeException;
use function count;
defined('_JEXEC') or die;
class SubformField extends JSubformField
{
/**
* @var string
*/
protected $layout = 'regularlabs.form.field.subform.repeatable';
/**
* @param string $name The property name for which to set the value.
* @param mixed $value The value of the property.
*/
public function __set($name, $value)
{
switch ($name) {
case 'layout':
$this->layout = (string) $value;
if (!$this->layout) {
$this->layout = !$this->multiple ? 'joomla.form.field.subform.default' : 'regularlabs.form.field.subform.repeatable';
}
break;
default:
parent::__set($name, $value);
}
}
/**
* Loads the form instance for the subform.
*
* @return Form The form instance.
*
* @throws InvalidArgumentException if no form provided.
* @throws RuntimeException if the form could not be loaded.
*/
public function loadSubForm()
{
$control = $this->name;
if ($this->multiple) {
$control .= '[' . $this->fieldname . 'X]';
}
// Prepare the form template
$formname = 'subform.' . str_replace(['jform[', '[', ']'], ['', '.', ''], $this->name);
return $this->loadSubFormByName($formname, $control);
}
protected function getLayoutPaths()
{
$paths = parent::getLayoutPaths();
$paths[] = JPATH_LIBRARIES . '/regularlabs/layouts';
return $paths;
}
/**
* Loads the form instance for the subform by given name and control.
*
* @param string $name The name of the form.
* @param string $control The control name of the form.
*
* @return Form The form instance.
*
* @throws InvalidArgumentException if no form provided.
* @throws RuntimeException if the form could not be loaded.
*/
protected function loadSubFormByName($name, $control)
{
// Prepare the form template
return Form::getInstance($name, $this->formsource, ['control' => $control]);
}
/**
* Binds given data to the subform and its elements.
*
* @param Form $subForm Form instance of the subform.
*
* @return Form[] Array of Form instances for the rows.
*/
protected function loadSubFormData(Form $subForm)
{
$value = $this->value ? (array) $this->value : [];
// Simple form, just bind the data and return one row.
if (!$this->multiple) {
$subForm->bind($value);
return [$subForm];
}
// Multiple rows possible: Construct array and bind values to their respective forms.
$forms = [];
$value = array_values($value);
// Show as many rows as we have values, but at least min and at most max.
$c = max($this->min, min(count($value), $this->max));
for ($i = 0; $i < $c; $i++) {
$control = $this->name . '[' . $this->fieldname . $i . ']';
$itemForm = $this->loadSubFormByName($subForm->getName() . $i, $control);
if (!empty($value[$i])) {
$itemForm->bind($value[$i]);
}
$forms[] = $itemForm;
}
return $forms;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\FormField as RL_FormField;
class TagsField extends RL_FormField
{
static $options;
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public bool $use_tree_select = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('a.title')->from('#__tags AS a')->where(RL_DB::is('a.id', $values))->order('a.title');
$this->db->setQuery($query);
return $this->db->loadColumn();
}
protected function getOptions()
{
if (!is_null(self::$options)) {
return self::$options;
}
$query = $this->db->getQuery(\true)->select('a.id as value, a.title as text, a.parent_id AS parent')->from('#__tags AS a')->select('COUNT(DISTINCT b.id) - 1 AS level')->join('LEFT', '#__tags AS b ON a.lft > b.lft AND a.rgt < b.rgt')->where('a.alias <> ' . $this->db->quote('root'))->where('a.published IN (0,1)')->group('a.id')->order('a.lft ASC');
$this->db->setQuery($query);
self::$options = $this->db->loadObjectList();
return self::$options;
}
}

View File

@ -0,0 +1,87 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\FormField as RL_FormField;
class TemplatesField extends RL_FormField
{
public bool $collapse_children = \true;
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public bool $use_tree_select = \true;
public function getNamesByIds(array $values, array $attributes): array
{
if (empty($values)) {
return [];
}
$query = $this->db->getQuery(\true)->select('e.name, e.element as template')->from('#__extensions as e')->where('e.enabled=1')->where($this->db->quoteName('e.type') . '=' . $this->db->quote('template'))->where(RL_DB::is('e.name', $values))->order('e.name');
$this->db->setQuery($query);
$templates = $this->db->loadObjectList();
$query = $this->db->getQuery(\true)->select('s.title, e.name as template_name, s.template')->from('#__template_styles as s')->join('LEFT', '#__extensions as e on e.element = s.template')->where(RL_DB::is('s.client_id', 0))->where(RL_DB::is('e.enabled', 1))->where(RL_DB::is('e.type', 'template'))->where(RL_DB::in('CONCAT(e.name, "--", s.id)', $values, [], \false))->order('s.template')->order('s.title');
$this->db->setQuery($query);
$styles = $this->db->loadObjectList();
$lang = $this->app->getLanguage();
$names = [];
foreach ($templates as $template) {
$lang->load('tpl_' . $template->template . '.sys', JPATH_SITE) || $lang->load('tpl_' . $template->template . '.sys', JPATH_SITE . '/templates/' . $template->template);
$names[] = JText::_($template->name);
}
foreach ($styles as $style) {
$lang->load('tpl_' . $style->template . '.sys', JPATH_SITE) || $lang->load('tpl_' . $style->template . '.sys', JPATH_SITE . '/templates/' . $style->template);
$names[] = '[' . JText::_($style->template_name) . '] ' . JText::_($style->title);
}
return $names;
}
protected function getOptions()
{
$options = [];
$templates = $this->getTemplates();
foreach ($templates as $styles) {
$level = 0;
foreach ($styles as $style) {
$style->level = $level;
$options[] = $style;
if (count($styles) <= 2) {
break;
}
$level = 1;
}
}
return $options;
}
protected function getTemplates()
{
$query = $this->db->getQuery(\true)->select('s.id, s.title, e.name as name, s.template')->from('#__template_styles as s')->where('s.client_id = 0')->join('LEFT', '#__extensions as e on e.element=s.template')->where('e.enabled=1')->where($this->db->quoteName('e.type') . '=' . $this->db->quote('template'))->order('s.template')->order('s.title');
$this->db->setQuery($query);
$styles = $this->db->loadObjectList();
if (empty($styles)) {
return [];
}
$lang = $this->app->getLanguage();
$groups = [];
foreach ($styles as $style) {
$template = $style->template;
$lang->load('tpl_' . $template . '.sys', JPATH_SITE) || $lang->load('tpl_' . $template . '.sys', JPATH_SITE . '/templates/' . $template);
$name = JText::_($style->name);
if (!isset($groups[$template])) {
$groups[$template] = [];
$groups[$template][] = JHtml::_('select.option', $template, $name);
}
$groups[$template][] = JHtml::_('select.option', $template . '--' . $style->id, $style->title);
}
return $groups;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\Field\TextareaField as JTextareaField;
use RegularLabs\Library\Document as RL_Document;
class TextAreaField extends JTextareaField
{
protected $layout = 'regularlabs.form.field.textarea';
protected function getLayoutData()
{
RL_Document::script('regularlabs.textarea');
$data = parent::getLayoutData();
$extraData = ['show_insert_date_name' => (bool) $this->element['show_insert_date_name'] ?? \false, 'add_separator' => (bool) $this->element['add_separator'] ?? \true];
return [...$data, ...$extraData];
}
protected function getLayoutPaths()
{
$paths = parent::getLayoutPaths();
$paths[] = JPATH_LIBRARIES . '/regularlabs/layouts';
return $paths;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\FormField as RL_FormField;
class UserGroupsField extends RL_FormField
{
static $options;
public bool $is_select_list = \true;
public bool $use_tree_select = \true;
// public bool $use_ajax = true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('a.title')->from('#__usergroups AS a')->where(RL_DB::is('a.id', $values))->order('a.lft ASC');
$this->db->setQuery($query);
return $this->db->loadColumn();
}
protected function getOptions()
{
if (!empty(self::$options)) {
return self::$options;
}
$query = $this->db->getQuery(\true)->select('a.id as value, a.title as text, a.parent_id AS parent')->from('#__usergroups AS a')->select('COUNT(DISTINCT b.id) AS level')->join('LEFT', '#__usergroups AS b ON a.lft > b.lft AND a.rgt < b.rgt')->group('a.id')->order('a.lft ASC');
$this->db->setQuery($query);
self::$options = $this->db->loadObjectList();
return self::$options;
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Form\Form;
use RegularLabs\Library\Form\FormField as RL_FormField;
class UsersField extends RL_FormField
{
static $users;
static $users_count;
public $attributes = ['show_current' => \false];
public bool $is_select_list = \true;
public bool $use_ajax = \true;
public function getNamesByIds(array $values, array $attributes): array
{
$query = $this->db->getQuery(\true)->select('u.name, u.username, u.id, u.block as disabled')->from('#__users AS u')->where(RL_DB::is('u.id', $values))->order('name');
$this->db->setQuery($query);
$users = $this->db->loadObjectList();
if (in_array('current', $values)) {
array_unshift($users, (object) ['id' => 'current', 'name' => JText::_('RL_CURRENT_USER'), 'add_id' => \false]);
}
return Form::getNamesWithExtras($users, ['username', 'id', 'disabled']);
}
protected function getListOptions(array $attributes): array|int
{
if ($this->max_list_count && $this->getUsersCount() > $this->max_list_count) {
return -1;
}
$users = $this->getUsers();
$options = $this->getOptionsByList($users, ['username', 'id', 'disabled'], 0, $this->get('username_as_value') ? 'username' : 'id');
if (!empty($attributes['show_current'])) {
array_unshift($options, JHtml::_('select.option', 'current', '- ' . JText::_('RL_CURRENT_USER') . ' -'));
}
return $options;
}
private function getUsers(): array
{
if (!is_null(self::$users)) {
return self::$users;
}
$query = $this->db->getQuery(\true)->select('u.name, u.username, u.id, u.block as disabled')->from('#__users AS u')->order('name');
$this->db->setQuery($query);
self::$users = $this->db->loadObjectList();
return self::$users;
}
private function getUsersCount(): int
{
if (!is_null(self::$users_count)) {
return self::$users_count;
}
$query = $this->db->getQuery(\true)->select('COUNT(*)')->from('#__users AS u');
$this->db->setQuery($query);
self::$users_count = $this->db->loadResult();
return self::$users_count;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\Form\FormField as RL_FormField;
use RegularLabs\Library\Version as RL_Version;
class VersionField extends RL_FormField
{
protected function getInput()
{
$extension = $this->get('extension');
$xml = $this->get('xml');
if (!$xml && $this->form->getValue('element')) {
if ($this->form->getValue('folder')) {
$xml = 'plugins/' . $this->form->getValue('folder') . '/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
} else {
$xml = 'administrator/modules/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
}
if (!file_exists(JPATH_SITE . '/' . $xml)) {
return '';
}
}
if (empty($extension) || empty($xml)) {
return '';
}
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$authorise = $user->authorise('core.manage', 'com_installer');
if (!$authorise) {
return '';
}
return RL_Version::getMessage($extension);
}
protected function getLabel()
{
return '';
}
}

View File

@ -0,0 +1,169 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Input as RL_Input;
use RegularLabs\Library\Parameters as RL_Parameters;
use RegularLabs\Library\RegEx as RL_RegEx;
class Form
{
public static function getLayout(array $options, bool $treeselect = \false): string
{
if ($treeselect) {
return 'regularlabs.form.field.treeselect';
}
if (is_array(reset($options))) {
return 'joomla.form.field.groupedlist-fancy-select';
}
return 'joomla.form.field.list-fancy-select';
}
/**
* Return a name with added extras and formatting
*/
public static function getNameWithExtras(object $item, array $extras = []): string
{
$name = trim($item->name);
foreach ($extras as $extra) {
if ($extra == 'language' && $item->{$extra} == '*') {
continue;
}
if (in_array($extra, ['id', 'alias'], \true) && $item->{$extra} == $item->name) {
continue;
}
if ($extra == 'unpublished') {
$name .= isset($item->published) && !$item->published ? ' (' . JText::_('JUNPUBLISHED') . ')' : '';
continue;
}
if ($extra == 'disabled') {
$name .= isset($item->disabled) && $item->disabled ? ' (' . JText::_('JDISABLED') . ')' : '';
continue;
}
if (empty($item->{$extra})) {
continue;
}
if (isset($item->{'add_' . $extra}) && !$item->{'add_' . $extra}) {
continue;
}
$name .= ' [' . $item->{$extra} . ']';
}
return self::prepareSelectItem($name);
}
/**
* Return an array with names with added extras and formatting
*/
public static function getNamesWithExtras(array $items, array $extras = []): array
{
$names = [];
foreach ($items as $item) {
$names[] = self::getNameWithExtras($item, $extras);
}
return $names;
}
public static function prepareSelectItem(string $string, int $remove_first = 0): string
{
if (empty($string)) {
return '';
}
$string = str_replace(['&nbsp;', '&#160;'], ' ', $string);
$string = RL_RegEx::replace('^(- )+', ' ', $string);
for ($i = 0; $remove_first > $i; $i++) {
$string = RL_RegEx::replace('^ ', '', $string, '');
}
if (RL_RegEx::match('^( *)(.*)$', $string, $match, '')) {
[$string, $pre, $name] = $match;
$pre = str_replace(' ', ' · ', $pre);
$pre = RL_RegEx::replace('(( · )*) · ', '\1 » ', $pre);
$pre = str_replace(' ', ' &nbsp; ', $pre);
$string = $pre . $name;
}
return $string;
}
/**
* Render a full select list
*/
public static function selectList(int|array $options, string $name, mixed $value, string $id, array $attributes = [], bool $treeselect = \false, bool $collapse_children = \false, null|int $max_list_count = null): string
{
if (empty($options)) {
return '<fieldset class="radio">' . JText::_('RL_NO_ITEMS_FOUND') . '</fieldset>';
}
$params = RL_Parameters::getPlugin('regularlabs');
$max_list_count = $max_list_count ?? $params->max_list_count;
if (!is_array($value)) {
$value = explode(',', $value);
}
if (count($value) === 1 && str_contains($value[0], ',')) {
$value = explode(',', $value[0]);
}
$count = 0;
if ($max_list_count && $options != -1) {
foreach ($options as $option) {
$count++;
if (isset($option->links)) {
$count += count($option->links);
}
if ($count > $params->max_list_count) {
break;
}
}
}
if ($options == -1 || $max_list_count && $count > $max_list_count) {
if (is_array($value)) {
$value = implode(',', $value);
}
if (!$value) {
$input = '<textarea name="' . $name . '" id="' . $id . '" cols="40" rows="5">' . $value . '</textarea>';
} else {
$input = '<input type="text" name="' . $name . '" id="' . $id . '" value="' . $value . '" size="60">';
}
$plugin = JPluginHelper::getPlugin('system', 'regularlabs');
$url = !empty($plugin->id) ? 'index.php?option=com_plugins&task=plugin.edit&extension_id=' . $plugin->id : 'index.php?option=com_plugins&&filter[folder]=&filter[search]=Regular%20Labs%20Library';
$label = JText::_('RL_ITEM_IDS');
$text = JText::_('RL_MAX_LIST_COUNT_INCREASE');
$tooltip = JText::_('RL_MAX_LIST_COUNT_INCREASE_DESC,' . $max_list_count . ',RL_MAX_LIST_COUNT');
$link = '<a href="' . $url . '" target="_blank" id="' . $id . '_msg"' . ' class="hasPopover" title="' . $text . '" data-content="' . htmlentities($tooltip) . '">' . '<span class="icon icon-cog"></span>' . $text . '</a>';
$script = 'jQuery("#' . $id . '_msg").popover({"html": true,"trigger": "hover focus","container": "body"})';
return '<fieldset class="radio">' . '<label for="' . $id . '">' . $label . ':</label>' . $input . '<br><small>' . $link . '</small>' . '</fieldset>' . '<script>' . $script . '</script>';
}
$layout = self::getLayout($options, $treeselect);
$path = $treeselect ? JPATH_SITE . '/libraries/regularlabs/layouts' : null;
$data = [...compact('id', 'name', 'value', 'options'), 'multiple' => \false, 'autofocus' => \false, 'onchange' => '', 'dataAttribute' => '', 'readonly' => \false, 'disabled' => '', 'hint' => \false, 'required' => \false, 'collapse_children' => $collapse_children, 'groups' => $options, ...$attributes];
$renderer = new JFileLayout($layout, $path);
return $renderer->render($data);
}
/**
* Render a select list loaded via Ajax
*/
public static function selectListAjax(string $field_class, string $name, mixed $value, string $id, array $attributes = [], bool $treeselect = \false, bool $collapse_children = \false): string
{
RL_Document::style('regularlabs.admin-form');
RL_Document::script('regularlabs.admin-form');
RL_Document::script('regularlabs.regular');
RL_Document::script('regularlabs.script');
if ($treeselect) {
RL_Document::script('regularlabs.treeselect');
RL_Document::useScript('bootstrap.dropdown');
} else {
RL_Document::usePreset('choicesjs');
RL_Document::useScript('webcomponent.field-fancy-select');
}
if (is_array($value)) {
$value = implode(',', $value);
}
$ajax_data = ['parent_request' => ['option' => RL_Input::getCmd('option'), 'view' => RL_Input::getCmd('view'), 'id' => RL_Input::getInt('id')], 'field_class' => $field_class, 'value' => $value, 'attributes' => $attributes, 'treeselect' => $treeselect, 'collapse_children' => $collapse_children];
return '<div class="rl-ajax-wrapper">' . '<textarea name="' . $name . '" id="' . $id . '" cols="40" rows="1" class="form-control rl-ajax-field"' . ' data-rl-ajax="' . htmlspecialchars(json_encode($ajax_data)) . '">' . $value . '</textarea>' . '<div class="rl-spinner"></div>' . '</div>';
}
}

View File

@ -0,0 +1,321 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form;
defined('_JEXEC') or die;
use DateTimeZone;
use Joomla\CMS\Application\CMSApplicationInterface as JCMSApplicationInterface;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Form\Form as JForm;
use Joomla\CMS\Form\FormField as JFormField;
use Joomla\CMS\Form\FormHelper as JFormHelper;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Database\DatabaseDriver as JDatabaseDriver;
use Joomla\Registry\Registry;
use Joomla\Registry\Registry as JRegistry;
use ReflectionClass;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Parameters as RL_Parameters;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use SimpleXMLElement;
use function explode;
use function is_array;
class FormField extends JFormField
{
public JCMSApplicationInterface $app;
/* @var object|JRegistry $attributes */
public $attributes;
public bool $collapse_children = \false;
public JDatabaseDriver $db;
public bool $is_select_list = \false;
public null|int $max_list_count = null;
public $params;
public array $parent_request = [];
public bool $use_ajax = \false;
public bool $use_tree_select = \false;
/**
* @param JForm $form
*/
public function __construct($form = null)
{
$this->type ??= $this->getShortFieldName();
parent::__construct($form);
$this->db = JFactory::getDbo();
$this->app = JFactory::getApplication();
$params = RL_Parameters::getPlugin('regularlabs');
$this->max_list_count = $this->max_list_count ?? $params->max_list_count;
RL_Document::style('regularlabs.admin-form');
}
/**
* Get a value from the field params
*/
public function get(string $key, mixed $default = ''): mixed
{
$value = $default;
if (isset($this->params[$key]) && (string) $this->params[$key] != '') {
$value = (string) $this->params[$key];
}
return $this->sanitizeValue($value);
}
public function getAjaxRaw(JRegistry|\RegularLabs\Library\Form\FormField $attributes): string
{
return $this->selectListForAjax($attributes);
}
public function getControlGroupEnd(): string
{
return '</div></div>';
}
public function getControlGroupStart(): string
{
return '<div class="control-group"><div class="control-label">';
}
/**
* Return a list option using the custom prepare methods
*/
public function getOptionByListItem(object $item, array $extras = [], int $levelOffset = 0, string $key = 'id'): object
{
$name = \RegularLabs\Library\Form\Form::getNameWithExtras($item, $extras);
$option = JHtml::_('select.option', $item->{$key}, $name, 'value', 'text', 0);
if (isset($item->level)) {
$option->level = $item->level + $levelOffset;
}
return $option;
}
/**
* Return an array of options using the custom prepare methods
*/
public function getOptionsByList(array $list, array $extras = [], int $levelOffset = 0, string $key = 'id'): array
{
$options = [];
foreach ($list as $id => $item) {
$options[$id] = $this->getOptionByListItem($item, $extras, $levelOffset, $key);
}
return $options;
}
/**
* Method to post-process a field value.
*
* @param mixed $value The optional value to use as the default for the field.
* @param string $group The optional dot-separated form group path on which to find the field.
* @param ?Registry $input An optional Registry object with the entire data set to filter
* against the entire form.
*
* @return mixed The processed value.
*/
public function postProcess($value, $group = null, Registry $input = null)
{
if (!$this->multiple) {
return $value;
}
if (!is_array($value)) {
$value = [$value];
}
if (count($value) == 1) {
$value = explode(',', $value[0]);
}
return $value;
}
/**
* Prepare the option string, handling language strings
*/
public function prepareText(?string $string = ''): string
{
$string = trim((string) $string);
if ($string == '') {
return '';
}
$string = JText::_($string);
$string = $this->replaceDateTags($string);
$string = $this->fixLanguageStringSyntax($string);
return $string;
}
public function replaceDateTags(string $string): string
{
if (!RL_RegEx::matchAll('\[date:(?<format>.*?)\]', $string, $matches)) {
return $string;
}
$date = JFactory::getDate();
$tz = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));
$date->setTimeZone($tz);
foreach ($matches as $match) {
$replace = $date->format($match['format'], \true);
$string = str_replace($match[0], $replace, $string);
}
return $string;
}
public function sanitizeValue(mixed $value): mixed
{
if (is_bool($value) || is_array($value) || is_object($value)) {
return $value;
}
if ($value === 'true') {
return \true;
}
if ($value === 'false') {
return \false;
}
return (string) $value;
}
public function selectList(): string
{
return $this->selectListFromData($this);
}
public function selectListAjax(): string
{
$class = $this->get('class', '');
$multiple = $this->get('multiple', \false);
$attributes = compact('class', 'multiple');
if (!empty($this->attributes)) {
foreach ($this->attributes as $key => $default) {
$attributes[$key] = $this->get($key, $default);
}
}
if (!empty($this->params)) {
foreach ($this->params as $key => $value) {
$attributes[$key] = (string) $value;
}
}
$tree_select = $this->use_tree_select && $multiple;
return \RegularLabs\Library\Form\Form::selectListAjax($this::class, $this->name, $this->value, $this->id, $attributes, $tree_select, $tree_select && $this->collapse_children);
}
public function selectListForAjax(JRegistry|\RegularLabs\Library\Form\FormField $data): string
{
return $this->selectListFromData($data);
}
public function selectListFromData(JRegistry|\RegularLabs\Library\Form\FormField $data): string
{
$data_attributes = $data->get('attributes', []);
$this->parent_request = (array) $data->get('parent_request', []);
$name = $this->name ?: $data->get('name', $this->type);
$id = $this->id ?: $data->get('id', strtolower($name));
$value = $this->value ?: $data->get('value', []);
$class = $data->get('class', $data_attributes->class ?? '');
$multiple = $data->get('multiple', $data_attributes->multiple ?? 0);
$tree_select = $data->get('treeselect', $this->use_tree_select);
$collapse_children = $data->get('collapse_children', $this->collapse_children);
if (!is_array($value)) {
$value = explode(',', $value);
}
$this->value = $value;
$attributes = compact('class', 'multiple');
if (!empty($this->attributes)) {
foreach ($this->attributes as $key => $default) {
$attributes[$key] = $data->get($key, $this->get($key, $default));
}
}
foreach ($data_attributes as $key => $val) {
$this->params[$key] = $this->sanitizeValue($val);
$attributes[$key] = $this->sanitizeValue($val);
}
$attributes = array_diff_key($attributes, ['name' => '', 'type' => '']);
$options = $this->getListOptions($attributes);
if ($this->get('text_as_value')) {
$this->setTextAsValue($options);
}
return \RegularLabs\Library\Form\Form::selectList($options, $name, $value, $id, $attributes, $tree_select && $multiple, $tree_select && $multiple && $collapse_children, $this->max_list_count);
}
/**
* Method declaration must be compatible with JFormField::setup()
*/
public function setup(SimpleXMLElement $element, $value, $group = null)
{
$this->params = $element->attributes();
return parent::setup($element, $value, $group);
}
/**
* Method declaration must be compatible with JFormField::getInput()
*/
protected function getInput()
{
if (!$this->is_select_list) {
return '';
}
if (!$this->use_ajax && !$this->use_tree_select) {
return $this->selectList();
}
return $this->selectListAjax();
}
/**
* Method declaration must be compatible with JFormField::getLabel()
*/
protected function getLabel()
{
$this->element['label'] = $this->prepareText($this->element['label']);
return $this->element['label'] == '---' ? '&nbsp;' : parent::getLabel();
}
protected function getListOptions(array $attributes): array|int
{
return $this->getOptions();
}
/**
* Return the field options (array)
* Overrules the Joomla core functionality
* Method declaration must be compatible with JFormField::getOptions()
*/
protected function getOptions()
{
if (empty($this->element->option)) {
return [];
}
$fieldname = RL_RegEx::replace('[^a-z0-9_\-]', '_', $this->fieldname);
$options = [];
foreach ($this->element->option as $option) {
$value = (string) $option['value'];
$text = trim((string) $option) != '' ? trim((string) $option) : $value;
$disabled = (string) $option['disabled'];
$disabled = $disabled === 'true' || $disabled === 'disabled' || $disabled === '1';
$disabled = $disabled || $this->readonly && $value != $this->value;
$checked = (string) $option['checked'];
$checked = $checked === 'true' || $checked === 'checked' || $checked === '1';
$selected = (string) $option['selected'];
$selected = $selected === 'true' || $selected === 'selected' || $selected === '1';
$attributes = '';
if ((string) $option['showon']) {
$encodedConditions = json_encode(JFormHelper::parseShowOnConditions((string) $option['showon'], $this->formControl, $this->group));
$attributes .= ' data-showon="' . $encodedConditions . '"';
}
// Add the option object to the result set.
$options[] = ['value' => $value, 'text' => '- ' . JText::alt($text, $fieldname) . ' -', 'disable' => $disabled, 'class' => (string) $option['class'], 'selected' => $checked || $selected, 'checked' => $checked || $selected, 'onclick' => (string) $option['onclick'], 'onchange' => (string) $option['onchange'], 'optionattr' => $attributes];
}
return $options;
}
/**
* Fix some syntax/encoding issues in option text strings
*/
private function fixLanguageStringSyntax(string $string = ''): string
{
$string = str_replace('[:COMMA:]', ',', $string);
$string = trim(RL_String::html_entity_decoder($string));
$string = str_replace('&quot;', '"', $string);
$string = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $string);
return $string;
}
/**
* Get the short name of the field class
* FoobarField => Foobar
*/
private function getShortFieldName(): string
{
return substr((new ReflectionClass($this))->getShortName(), 0, -strlen('Field'));
}
private function setTextAsValue(array &$options): void
{
if (empty($options)) {
return;
}
foreach ($options as &$option) {
$option->value = $option->text;
}
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library\Form;
defined('_JEXEC') or die;
/**
* @depracated No longer used
*/
class FormFieldGroup extends \RegularLabs\Library\Form\FormField
{
public $default_group = 'Categories';
public $type = 'Field';
}

View File

@ -0,0 +1,508 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use DOMDocument;
class Html
{
/**
* Removes complete html tag pairs from the concatenated parts
*/
public static function cleanSurroundingTags(array $parts, array $elements = ['p', 'span', 'strong', 'b', 'em', 'i']): array
{
$breaks = '(?:(?:<br ?/?>|<\!--[^>]*-->|:\|:)\s*)*';
$keys = array_keys($parts);
$string = implode(':|:', $parts);
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
// Remove empty tags
$regex = '<(?<tag>' . implode('|', $elements) . ')(?: [^>]*)?>\s*(?<breaks>' . $breaks . ')<\/\1>\s*';
while (\RegularLabs\Library\RegEx::match($regex, $string, $match)) {
$string = str_replace($match[0], $match['breaks'], $string);
}
// Remove paragraphs around block elements
$block_elements = ['p', 'div', 'table', 'tr', 'td', 'thead', 'tfoot', 'h[1-6]'];
$block_elements = '(?<element>' . implode('|', $block_elements) . ')';
$regex = '(?<p_tag><p(?: [^>]*)?>)(?<breaks>\s*' . $breaks . ')(?<block_tag><' . $block_elements . '(?: [^>]*)?>)';
while (\RegularLabs\Library\RegEx::match($regex, $string, $match)) {
$tags = $match['block_tag'];
if ($match['element'] == 'p') {
$tags = $match['p_tag'] . $tags;
self::combinePTags($tags);
}
$string = str_replace($match[0], $match['breaks'] . $tags, $string);
}
$regex = '(</' . $block_elements . '>\s*' . $breaks . ')</p>';
while (\RegularLabs\Library\RegEx::match($regex, $string, $match)) {
$string = str_replace($match[0], $match[1], $string);
}
\RegularLabs\Library\Protect::unprotect($string);
$parts = explode(':|:', $string);
$new_tags = [];
foreach ($parts as $key => $val) {
$key = $keys[$key] ?? $key;
$new_tags[$key] = $val;
}
return $new_tags;
}
/**
* Combine duplicate <p> tags
* input: <p class="aaa" a="1"><!-- ... --><p class="bbb" b="2">
* output: <p class="aaa bbb" a="1" b="2"><!-- ... -->
*/
public static function combinePTags(string &$string): void
{
if (empty($string)) {
return;
}
$p_start_tag = '<p(?: [^>]*)?>';
$optional_tags = '\s*(?:<\!--[^>]*-->|&nbsp;|&\#160;)*\s*';
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
\RegularLabs\Library\RegEx::matchAll('(' . $p_start_tag . ')(' . $optional_tags . ')(' . $p_start_tag . ')', $string, $tags);
if (empty($tags)) {
\RegularLabs\Library\Protect::unprotect($string);
return;
}
foreach ($tags as $tag) {
$string = str_replace($tag[0], $tag[2] . \RegularLabs\Library\HtmlTag::combine($tag[1], $tag[3]), $string);
}
\RegularLabs\Library\Protect::unprotect($string);
}
/**
* Check if string contains block elements
*/
public static function containsBlockElements(string $string): string
{
return \RegularLabs\Library\RegEx::match('</?(' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>', $string);
}
/**
* Convert content saved in a WYSIWYG editor to plain text (like removing html tags)
*/
public static function convertWysiwygToPlainText(string $string): string
{
// replace chr style enters with normal enters
$string = str_replace([chr(194) . chr(160), '&#160;', '&nbsp;'], ' ', $string);
// replace linebreak tags with normal linebreaks (paragraphs, enters, etc).
$enter_tags = ['p', 'br'];
$regex = '</?((' . implode(')|(', $enter_tags) . '))+[^>]*?>\n?';
$string = \RegularLabs\Library\RegEx::replace($regex, " \n", $string);
// replace indent characters with spaces
$string = \RegularLabs\Library\RegEx::replace('<img [^>]*/sourcerer/images/tab\.png[^>]*>', ' ', $string);
// strip all other tags
$regex = '<(/?\w+((\s+\w+(\s*=\s*(?:".*?"|\'.*?\'|[^\'">\s]+))?)+\s*|\s*)/?)>';
$string = \RegularLabs\Library\RegEx::replace($regex, '', $string);
// reset htmlentities
$string = \RegularLabs\Library\StringHelper::html_entity_decoder($string);
// convert protected html entities &_...; -> &...;
$string = \RegularLabs\Library\RegEx::replace('&_([a-z0-9\#]+?);', '&\1;', $string);
return $string;
}
/**
* Fix broken/invalid html syntax in a string
*/
public static function fix(string $string): string
{
if (!self::containsBlockElements($string)) {
return $string;
}
// Convert utf8 characters to html entities
if (function_exists('mb_decode_numericentity')) {
$string = mb_encode_numericentity($string, [0x80, 0xffff, 0, ~0], 'UTF-8');
}
$string = self::protectSpecialCode($string);
$string = self::convertDivsInsideInlineElementsToSpans($string);
$string = self::removeParagraphsAroundBlockElements($string);
$string = self::removeInlineElementsAroundBlockElements($string);
$string = self::fixParagraphsAroundParagraphElements($string);
$string = class_exists('DOMDocument') ? self::fixUsingDOMDocument($string) : self::fixUsingCustomFixer($string);
$string = self::unprotectSpecialCode($string);
// Convert html entities back to utf8 characters
if (function_exists('mb_decode_numericentity')) {
$string = mb_decode_numericentity($string, [0x80, 0xffff, 0, ~0], 'UTF-8');
}
$string = self::removeParagraphsAroundComments($string);
return $string;
}
/**
* Fix broken/invalid html syntax in an array of strings
*/
public static function fixArray(array $array): array
{
$splitter = ':|:';
$string = self::fix(implode($splitter, $array));
$parts = self::removeEmptyTags(explode($splitter, $string));
// use original keys but new values
return array_combine(array_keys($array), $parts);
}
/**
* Return an array of block element names, optionally without any of the names given $exclude
*/
public static function getBlockElements(array $exclude = []): array
{
if (!is_array($exclude)) {
$exclude = [$exclude];
}
$elements = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
$elements = array_diff($elements, $exclude);
$elements = implode(',', $elements);
$elements = str_replace('h1,h2,h3,h4,h5,h6', 'h[1-6]', $elements);
$elements = explode(',', $elements);
return $elements;
}
/**
* Return an array of block element names, without divs and any of the names given $exclude
*/
public static function getBlockElementsNoDiv(array $exclude = []): array
{
return array_diff(self::getBlockElements($exclude), ['div']);
}
/**
* Extract the <body>...</body> part from an entire html output string
*/
public static function getBody(string $html, bool $include_body_tag = \true): array
{
if (!str_contains($html, '<body') || !str_contains($html, '</body>')) {
return ['', $html, ''];
}
// Force string to UTF-8
$html = \RegularLabs\Library\StringHelper::convertToUtf8($html);
$split = explode('<body', $html, 2);
$pre = $split[0];
$split = explode('>', $split[1], 2);
$body_start = '<body' . $split[0] . '>';
$body_end = '</body>';
$split = explode('</body>', $split[1]);
$post = array_pop($split);
$body = implode('</body>', $split);
if (!$include_body_tag) {
return [$pre . $body_start, $body, $body_end . $post];
}
return [$pre, $body_start . $body . $body_end, $post];
}
/**
* Search the string for the start and end searches and split the string in a pre, body and post part
* This is used to be able to do replacements on the body part, which will be lighter than doing it on the entire string
*/
public static function getContentContainingSearches(string $string, array $start_searches = [], array $end_searches = [], int $start_offset = 1000, ?int $end_offset = null): array
{
// String is too short to split and search through
if (strlen($string) < 2000) {
return ['', $string, ''];
}
$end_offset = is_null($end_offset) ? $start_offset : $end_offset;
$found = \false;
$start_split = strlen($string);
foreach ($start_searches as $search) {
$pos = strpos($string, $search);
if ($pos === \false) {
continue;
}
$start_split = min($start_split, $pos);
$found = \true;
}
// No searches are found
if (!$found) {
return [$string, '', ''];
}
// String is too short to split
if (strlen($string) < $start_offset + $end_offset + 1000) {
return ['', $string, ''];
}
$start_split = max($start_split - $start_offset, 0);
$pre = substr($string, 0, $start_split);
$string = substr($string, $start_split);
self::fixBrokenTagsByPreString($pre, $string);
if (empty($end_searches)) {
$end_searches = $start_searches;
}
$end_split = 0;
$found = \false;
foreach ($end_searches as $search) {
$pos = strrpos($string, $search);
if ($pos === \false) {
continue;
}
$end_split = max($end_split, $pos + strlen($search));
$found = \true;
}
// No end split is found, so don't split remainder
if (!$found) {
return [$pre, $string, ''];
}
$end_split = min($end_split + $end_offset, strlen($string));
$post = substr($string, $end_split);
$string = substr($string, 0, $end_split);
self::fixBrokenTagsByPostString($post, $string);
return [$pre, $string, $post];
}
/**
* Return an array of inline element names, optionally without any of the names given $exclude
*/
public static function getInlineElements(array $exclude = []): array
{
if (!is_array($exclude)) {
$exclude = [$exclude];
}
$elements = ['span', 'code', 'a', 'strong', 'b', 'em', 'i', 'u', 'big', 'small', 'font', 'sup', 'sub'];
return array_diff($elements, $exclude);
}
/**
* Return an array of block element names, without anchors (a) and any of the names given $exclude
*/
public static function getInlineElementsNoAnchor(array $exclude = []): array
{
return array_diff(self::getInlineElements($exclude), ['a']);
}
/**
* Remove empty tags
*/
public static function removeEmptyTagPairs(string $string, array $elements = ['p', 'span']): string
{
$breaks = '(?:(?:<br ?/?>|<\!--[^>]*-->)\s*)*';
$regex = '<(' . implode('|', $elements) . ')(?: [^>]*)?>\s*(' . $breaks . ')<\/\1>\s*';
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
while (\RegularLabs\Library\RegEx::match($regex, $string, $match)) {
$string = str_replace($match[0], $match[2], $string);
}
\RegularLabs\Library\Protect::unprotect($string);
return $string;
}
/**
* Removes empty tags which span concatenating parts in the array
*/
public static function removeEmptyTags(array $array): array
{
$splitter = ':|:';
$comments = '(?:\s*<\!--[^>]*-->\s*)*';
$string = implode($splitter, $array);
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
$string = \RegularLabs\Library\RegEx::replace('<([a-z][a-z0-9]*)(?: [^>]*)?>\s*(' . $comments . \RegularLabs\Library\RegEx::quote($splitter) . $comments . ')\s*</\1>', '\2', $string);
\RegularLabs\Library\Protect::unprotect($string);
return explode($splitter, $string);
}
/**
* Removes html tags from string
*/
public static function removeHtmlTags(string $string, bool $remove_comments = \false): string
{
// remove pagenavcounter
$string = \RegularLabs\Library\RegEx::replace('<div class="pagenavcounter">.*?</div>', ' ', $string);
// remove pagenavbar
$string = \RegularLabs\Library\RegEx::replace('<div class="pagenavbar">(<div>.*?</div>)*</div>', ' ', $string);
// remove inline scripts
$string = \RegularLabs\Library\RegEx::replace('<script[^a-z0-9].*?</script>', '', $string);
$string = \RegularLabs\Library\RegEx::replace('<noscript[^a-z0-9].*?</noscript>', '', $string);
// remove inline styles
$string = \RegularLabs\Library\RegEx::replace('<style[^a-z0-9].*?</style>', '', $string);
// remove inline html tags
$string = \RegularLabs\Library\RegEx::replace('</?(' . implode('|', self::getInlineElements()) . ')( [^>]*)?>', '', $string);
if ($remove_comments) {
// remove html comments
$string = \RegularLabs\Library\RegEx::replace('<!--.*?-->', ' ', $string);
}
// replace other tags with a space
$string = \RegularLabs\Library\RegEx::replace('</?[a-z].*?>', ' ', $string);
// remove double whitespace
$string = trim(\RegularLabs\Library\RegEx::replace('(\s)[ ]+', '\1', $string));
return $string;
}
/**
* Remove inline elements around block elements
*/
public static function removeInlineElementsAroundBlockElements(string $string): string
{
$string = \RegularLabs\Library\RegEx::replace('(?:<(?:' . implode('|', self::getInlineElementsNoAnchor()) . ')(?: [^>]*)?>\s*)' . '(</?(?:' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>)', '\1', $string);
$string = \RegularLabs\Library\RegEx::replace('(</?(?:' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>)' . '(?:\s*</(?:' . implode('|', self::getInlineElementsNoAnchor()) . ')>)', '\1', $string);
return $string;
}
/**
* Convert <div> tags inside inline elements to <span> tags
*/
private static function convertDivsInsideInlineElementsToSpans(string $string): string
{
if (!str_contains($string, '</div>')) {
return $string;
}
// Ignore block elements inside anchors
$regex = '<(' . implode('|', self::getInlineElementsNoAnchor()) . ')(?: [^>]*)?>.*?</\1>';
\RegularLabs\Library\RegEx::matchAll($regex, $string, $matches, '', \PREG_PATTERN_ORDER);
if (empty($matches)) {
return $string;
}
$matches = array_unique($matches[0]);
$searches = [];
$replacements = [];
foreach ($matches as $match) {
if (!str_contains($match, '</div>')) {
continue;
}
$searches[] = $match;
$replacements[] = str_replace(['<div>', '<div ', '</div>'], ['<span>', '<span ', '</span>'], $match);
}
if (empty($searches)) {
return $string;
}
return str_replace($searches, $replacements, $string);
}
/**
* Prevents broken html tags at the beginning of $pre (other half at end of $string)
* It will move the broken part to the end of $string to complete it
*/
private static function fixBrokenTagsByPostString(string &$post, string &$string): void
{
if (!\RegularLabs\Library\RegEx::match('<(\![^>]*|/?[a-z][^>]*(="[^"]*)?)$', $string, $match)) {
return;
}
if (!\RegularLabs\Library\RegEx::match('^[^>]*>', $post, $match)) {
return;
}
$post = substr($post, strlen($match[0]));
$string .= $match[0];
}
/**
* Prevents broken html tags at the end of $pre (other half at beginning of $string)
* It will move the broken part to the beginning of $string to complete it
*/
private static function fixBrokenTagsByPreString(string &$pre, string &$string): void
{
if (!\RegularLabs\Library\RegEx::match('<(\![^>]*|/?[a-z][^>]*(="[^"]*)?)$', $pre, $match)) {
return;
}
$pre = substr($pre, 0, strlen($pre) - strlen($match[0]));
$string = $match[0] . $string;
}
/**
* Fix <p> tags around other <p> elements
*/
private static function fixParagraphsAroundParagraphElements(string $string): string
{
if (!str_contains($string, '</p>')) {
return $string;
}
$parts = explode('</p>', $string);
$ending = '</p>' . array_pop($parts);
foreach ($parts as &$part) {
if (!str_contains($part, '<p>') && !str_contains($part, '<p ')) {
$part = '<p>' . $part;
continue;
}
$part = \RegularLabs\Library\RegEx::replace('(<p(?: [^>]*)?>.*?)(<p(?: [^>]*)?>)', '\1</p>\2', $part);
}
return implode('</p>', $parts) . $ending;
}
/**
* Fix broken/invalid html syntax in a string using custom code as an alternative to php DOMDocument functionality
*/
private static function fixUsingCustomFixer(string $string): string
{
$block_regex = '<(' . implode('|', self::getBlockElementsNoDiv()) . ')[\s>]';
$string = \RegularLabs\Library\RegEx::replace('(' . $block_regex . ')', '[:SPLIT-BLOCK:]\1', $string);
$parts = explode('[:SPLIT-BLOCK:]', $string);
foreach ($parts as $i => &$part) {
if (!\RegularLabs\Library\RegEx::match('^' . $block_regex, $part, $type)) {
continue;
}
$type = strtolower($type[1]);
// remove endings of other block elements
$part = \RegularLabs\Library\RegEx::replace('</(?:' . implode('|', self::getBlockElementsNoDiv($type)) . ')>', '', $part);
if (str_contains($part, '</' . $type . '>')) {
continue;
}
// Add ending tag once
$part = \RegularLabs\Library\RegEx::replaceOnce('(\s*)$', '</' . $type . '>\1', $part);
// Remove empty block tags
$part = \RegularLabs\Library\RegEx::replace('^<' . $type . '(?: [^>]*)?>\s*</' . $type . '>', '', $part);
}
return implode('', $parts);
}
/**
* Fix broken/invalid html syntax in a string using php DOMDocument functionality
*/
private static function fixUsingDOMDocument(string $string): string
{
$doc = new DOMDocument();
$doc->substituteEntities = \false;
[$pre, $body, $post] = \RegularLabs\Library\Html::getBody($string, \false);
// Add temporary document structures
$body = '<html><body><div>' . $body . '</div></body></html>';
@$doc->loadHTML($body);
$body = $doc->saveHTML();
if (str_contains($doc->documentElement->textContent, 'Ã')) {
// Need to do this utf8 workaround to deal with special characters
// DOMDocument doesn't seem to deal with them very well
// See: https://stackoverflow.com/questions/8218230/php-domdocument-loadhtml-not-encoding-utf-8-correctly/47396055#47396055
$body = \RegularLabs\Library\StringHelper::utf8_decode($doc->saveHTML($doc->documentElement));
}
// Remove temporary document structures and surrounding div
$body = \RegularLabs\Library\RegEx::replace('^.*?<html>.*?(?:<head>(.*)</head>.*?)?<body>\s*<div>(.*)</div>\s*</body>.*?$', '\1\2', $body);
// Remove leading/trailing empty paragraph
$body = \RegularLabs\Library\RegEx::replace('(^\s*<div>\s*</div>|<div>\s*</div>\s*$)', '', $body);
// Remove leading/trailing empty paragraph
$body = \RegularLabs\Library\RegEx::replace('(^\s*<div>\s*</div>|<div>\s*</div>\s*$)', '', $body);
// Remove leading/trailing empty paragraph
$body = \RegularLabs\Library\RegEx::replace('(^\s*<p(?: [^>]*)?>\s*</p>|<p(?: [^>]*)?>\s*</p>\s*$)', '', $body);
return $pre . $body . $post;
}
/**
* Protect plugin style tags and php
*/
private static function protectSpecialCode(string $string): string
{
// Protect PHP code
\RegularLabs\Library\Protect::protectByRegex($string, '(<|&lt;)\?php\s.*?\?(>|&gt;)');
// Protect {...} tags
\RegularLabs\Library\Protect::protectByRegex($string, '\{[a-z0-9].*?\}');
// Protect [...] tags
\RegularLabs\Library\Protect::protectByRegex($string, '\[[a-z0-9].*?\]');
// Protect scripts
\RegularLabs\Library\Protect::protectByRegex($string, '<script[^>]*>.*?</script>');
// Protect css
\RegularLabs\Library\Protect::protectByRegex($string, '<style[^>]*>.*?</style>');
\RegularLabs\Library\Protect::convertProtectionToHtmlSafe($string);
return $string;
}
/**
* Remove <p> tags around block elements
*/
private static function removeParagraphsAroundBlockElements(string $string): string
{
if (!str_contains($string, '</p>')) {
return $string;
}
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
$string = \RegularLabs\Library\RegEx::replace('<p(?: [^>]*)?>\s*' . '((?:<\!--[^>]*-->\s*)*</?(?:' . implode('|', self::getBlockElements()) . ')' . '(?: [^>]*)?>)', '\1', $string);
$string = \RegularLabs\Library\RegEx::replace('(</?(?:' . implode('|', self::getBlockElements()) . ')' . '(?: [^>]*)?>(?:\s*<\!--[^>]*-->)*)' . '(?:\s*</p>)', '\1', $string);
\RegularLabs\Library\Protect::unprotect($string);
return $string;
}
/**
* Remove <p> tags around comments
*/
private static function removeParagraphsAroundComments(string $string): string
{
if (!str_contains($string, '</p>')) {
return $string;
}
\RegularLabs\Library\Protect::protectHtmlCommentTags($string);
$string = \RegularLabs\Library\RegEx::replace('(?:<p(?: [^>]*)?>\s*)' . '(<\!--[^>]*-->)' . '(?:\s*</p>)', '\1', $string);
\RegularLabs\Library\Protect::unprotect($string);
return $string;
}
/**
* Unprotect protected tags
*/
private static function unprotectSpecialCode(string $string): string
{
\RegularLabs\Library\Protect::unprotectHtmlSafe($string);
return $string;
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class HtmlTag
{
/**
* Combine 2 opening html tags into one
*/
public static function combine(string $tag1, string $tag2): string
{
// Return if tags are the same
if ($tag1 == $tag2) {
return $tag1;
}
if (!\RegularLabs\Library\RegEx::match('<([a-z][a-z0-9]*)', $tag1, $tag_type)) {
return $tag2;
}
$tag_type = $tag_type[1];
$attribs = self::combineAttributes($tag1, $tag2);
if (!$attribs) {
return '<' . $tag_type . '>';
}
return '<' . $tag_type . ' ' . $attribs . '>';
}
/**
* Combine attribute values from 2 given html tag strings (or arrays of attributes)
* And return as a string of attributes (if $flatten = true)
*/
public static function combineAttributes(string|array $string1, string|array $string2, bool $flatten = \true): string|array
{
$attribsutes1 = is_array($string1) ? $string1 : self::getAttributes($string1);
$attribsutes2 = is_array($string2) ? $string2 : self::getAttributes($string2);
$duplicate_attributes = array_intersect_key($attribsutes1, $attribsutes2);
// Fill $attributes with the unique ids
$attributes = array_diff_key($attribsutes1, $attribsutes2) + array_diff_key($attribsutes2, $attribsutes1);
// List of attrubute types that can only contain one value
$single_value_attributes = ['id', 'href'];
// Add/combine the duplicate ids
foreach ($duplicate_attributes as $key => $val) {
if (in_array($key, $single_value_attributes, \true)) {
$attributes[$key] = $attribsutes2[$key];
continue;
}
// Combine strings, but remove duplicates
// "aaa bbb" + "aaa ccc" = "aaa bbb ccc"
// use a ';' as a concatenated for javascript values (keys beginning with 'on')
// Otherwise use a space (like for classes)
$glue = str_starts_with($key, 'on') ? ';' : ' ';
$attributes[$key] = implode($glue, [...explode($glue, $attribsutes1[$key]), ...explode($glue, $attribsutes2[$key])]);
}
return $flatten ? self::flattenAttributes($attributes) : $attributes;
}
/**
* Convert array or object of attributes to a html style string
*/
public static function flattenAttributes(array|object $attributes, string $prefix = ''): string
{
$output = [];
foreach ($attributes as $key => $val) {
if (is_null($val) || $val === '') {
continue;
}
if ($val === \false) {
$val = 'false';
}
if ($val === \true) {
$val = 'true';
}
$val = str_replace('"', '&quot;', $val);
$output[] = $prefix . $key . '="' . $val . '"';
}
return implode(' ', $output);
}
/**
* Extract attribute value from a html tag string by given attribute key
*/
public static function getAttributeValue(string $key, string $string): string
{
if (empty($key) || empty($string)) {
return '';
}
\RegularLabs\Library\RegEx::match(\RegularLabs\Library\RegEx::quote($key) . '="([^"]*)"', $string, $match);
if (empty($match)) {
return '';
}
return $match[1];
}
/**
* Extract all attributes from a html tag string
*/
public static function getAttributes(string $string): array
{
if (empty($string)) {
return [];
}
\RegularLabs\Library\RegEx::matchAll('([a-z0-9-_]+)="([^"]*)"', $string, $matches);
if (empty($matches)) {
return [];
}
$attribs = [];
foreach ($matches as $match) {
$attribs[$match[1]] = $match[2];
}
return $attribs;
}
/**
* Returns true/false based on whether the html tag type is a single tag
*/
public static function isSelfClosingTag(string $type): bool
{
return in_array($type, ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'], \true);
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Http\HttpFactory as JHttpFactory;
use Joomla\Registry\Registry;
use RuntimeException;
/**
* Class Http
*
* @package RegularLabs\Library
*/
class Http
{
/**
* Get the contents of the given internal url
*/
public static function get(string $url, int $timeout = 20, string $default = ''): string
{
if (\RegularLabs\Library\Uri::isExternal($url)) {
return $default;
}
return @file_get_contents($url, \false, stream_context_create(['http' => ['timeout' => $timeout]])) || self::getFromUrl($url, $timeout, $default);
}
/**
* Get the contents of the given external url from the Regular Labs server
*/
public static function getFromServer(string $url, int $timeout = 20, string $default = ''): string
{
$cache = new \RegularLabs\Library\Cache();
$cache_ttl = \RegularLabs\Library\Input::getInt('cache', 0);
if ($cache_ttl) {
$cache->useFiles($cache_ttl > 1 ? $cache_ttl : null);
}
if ($cache->exists()) {
return $cache->get();
}
// only allow url calls from administrator
if (!\RegularLabs\Library\Document::isClient('administrator')) {
die;
}
// only allow when logged in
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
if (!$user->id) {
die;
}
if (!str_starts_with($url, 'http')) {
$url = 'http://' . $url;
}
// only allow url calls to regularlabs.com domain
if (!\RegularLabs\Library\RegEx::match('^https?://([^/]+\.)?regularlabs\.com/', $url)) {
die;
}
// only allow url calls to certain files
if (!str_contains($url, 'download.regularlabs.com/extensions.php') && !str_contains($url, 'download.regularlabs.com/extensions.json') && !str_contains($url, 'download.regularlabs.com/extensions.xml') && !str_contains($url, 'download.regularlabs.com/check_key.php')) {
die;
}
$content = self::getContents($url, $timeout);
$format = str_contains($url, '.json') || str_contains($url, 'format=json') ? 'application/json' : 'text/xml';
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-type: " . $format);
if (empty($content)) {
return $default;
}
return $cache->set($content ?: $default);
}
/**
* Get the contents of the given url
*/
public static function getFromUrl(string $url, int $timeout = 20, string $default = ''): string
{
$cache = new \RegularLabs\Library\Cache();
$cache_ttl = \RegularLabs\Library\Input::getInt('cache', 0);
if ($cache_ttl) {
$cache->useFiles($cache_ttl > 1 ? $cache_ttl : null);
}
if ($cache->exists()) {
return $cache->get();
}
$content = self::getContents($url, $timeout);
if (empty($content)) {
return $default;
}
return $cache->set($content ?: $default);
}
/**
* Load the contents of the given url
*/
private static function getContents(string $url, int $timeout = 20, string $default = ''): string
{
try {
// Adding a valid user agent string, otherwise some feed-servers returning an error
$options = new Registry(['userAgent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0']);
$response = JHttpFactory::getHttp($options)->get($url, [], $timeout);
$content = $response->body ?? $default;
} catch (RuntimeException $e) {
return $default;
}
// Remove prefix and postfix stuff added by SocketTransport
$content = preg_replace('#^\s*1c\s*(\{.*\})\s*0\s*$#s', '$1', $content);
return $content;
}
}

View File

@ -0,0 +1,760 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
require_once dirname(__FILE__, 2) . '/vendor/autoload.php';
use Exception;
use RegularLabs\Scoped\Intervention\Image\Exception\NotReadableException as NotReadableException;
use RegularLabs\Scoped\Intervention\Image\ImageManagerStatic as ImageManager;
use Joomla\CMS\Filesystem\File as JFile;
use Joomla\CMS\Uri\Uri as JUri;
class Image
{
static $data_files = [];
private $attributes;
private $description;
private $file;
private $input;
private $is_resized;
private $output;
private $settings;
/**
* @param object $attributes @deprecated use SET methods instead
*/
public function __construct(string $file = null, object $attributes = null)
{
$this->settings = (object) ['resize' => (object) ['enabled' => \true, 'quality' => 70, 'method' => 'crop', 'folder' => 'resized', 'max_age' => 0, 'force_overwrite' => \false, 'use_retina' => \true, 'retina_pixel_density' => 1.5], 'title' => (object) ['enabled' => \true, 'format' => 'uppercase_first', 'lowercase_words' => 'a,the,to,at,in,with,and,but,or'], 'lazy_loading' => \false];
if ($file) {
$this->setFile($file);
}
if ($attributes) {
$this->setFromOldAttributes($attributes);
}
$this->resetOutput();
}
public static function cleanPath(string $path): string
{
$path = str_replace('\\', '/', $path);
$path_site = str_replace('\\', '/', JPATH_SITE) . '/';
if (str_starts_with($path, $path_site)) {
$path = substr($path, strlen($path_site));
}
$path = ltrim(str_replace(JUri::root(), '', $path), '/');
$path = strtok($path, '?');
$path = strtok($path, '#');
return $path;
}
public function createResizeFolder(): self
{
$path = $this->getResizeFolderPath();
if (is_dir($path)) {
return $this;
}
if (!@mkdir($path, 0755, \true)) {
$this->settings->resize->folder = '';
}
return $this;
}
public function exists(?string $file = null): bool
{
$file = urldecode($file ?: $this->getFilePath());
return $file && file_exists($file) && is_file($file);
}
public function getAlt(): string
{
if (isset($this->attributes->alt)) {
return $this->attributes->alt;
}
$alt = $this->getDataFileDataByType('alt');
if (!is_null($alt)) {
return htmlentities($alt);
}
return $this->getTitle(\true);
}
public function getDataFileData(): mixed
{
$image_data = $this->getFolderFileData();
return $image_data[$this->getFileStem()] ?? null;
}
public function getDataFileDataByType(string $type = 'title'): mixed
{
$image_data = $this->getDataFileData();
if (isset($image_data[$type])) {
return $image_data[$type];
}
$default_data = $this->getDefaultDataFileData();
return $default_data[$type] ?? null;
}
public function getDefaultDataFileData(): mixed
{
$image_data = $this->getFolderFileData();
return $image_data['*'] ?? null;
}
public function getDescription(): string
{
if (!is_null($this->description)) {
return $this->description;
}
return $this->getDataFileDataByType('description') ?? '';
}
public function getFile(): string
{
$this->prepareInput();
return $this->input->file;
}
public function getFileExtension(): string
{
$this->prepareInput();
return $this->input->file_extension;
}
public function getFileInfo(string $file, string $file_path): object
{
$file_path = urldecode($file_path);
$path_info = @pathinfo($file_path);
if (\RegularLabs\Library\File::isInternal($file_path)) {
$size_info = @getimagesize($file_path);
}
$file_name = $path_info['basename'] ?? basename($file_path);
$file_stem = $path_info['filename'] ?? JFile::stripExt($file_name);
$file_extension = $path_info['extension'] ?? JFile::getExt($file_name);
return (object) ['folder' => \RegularLabs\Library\File::getDirName($file), 'folder_path' => $path_info['dirname'] ?? null, 'file' => $file, 'file_path' => $file_path, 'file_name' => $file_name, 'file_stem' => $file_stem, 'file_extension' => $file_extension, 'mime' => $size_info['mime'] ?? null, 'width' => $size_info[0] ?? null, 'height' => $size_info[1] ?? null];
}
public function getFileName(): string
{
$this->prepareInput();
return $this->input->file_name;
}
public function getFilePath(): string
{
$this->prepareInput();
return $this->input->file_path;
}
public function getFileStem(): string
{
$this->prepareInput();
return $this->input->file_stem;
}
public function getFolder(): string
{
$this->prepareInput();
return $this->input->folder;
}
public function getFolderPath(): string
{
$this->prepareInput();
return $this->input->folder_path;
}
public function getHeight(): int
{
$this->prepareOutput();
return $this->output->height;
}
public function getOriginalHeight(): int
{
$this->prepareInput();
return $this->input->height;
}
public function getOriginalWidth(): int
{
$this->prepareInput();
return $this->input->width;
}
public function getOutputFile(): string
{
$this->prepareInput();
if ($this->isExternal() || !$this->exists()) {
return $this->input->file;
}
$this->prepareOutput();
return $this->output->file;
}
public function getOutputFilePath(): string
{
$this->prepareOutput();
return $this->output->file_path;
}
/**
* @depecated Use getOutputFile() instead
*/
public function getPath(): string
{
return $this->getOutputFile();
}
public function getSrcSet(?string $pixel_density = null): ?string
{
if ($this->isExternal()) {
return null;
}
if (!$this->settings->resize->use_retina) {
return null;
}
if ($this->getFilePath() == $this->getOutputFilePath()) {
return null;
}
$pixel_density = $pixel_density ?: $this->settings->resize->retina_pixel_density;
$single = $this->getOutputFile();
$double = $this->getOutputFile2x();
if ($double == $single) {
return null;
}
return $double . ' ' . (float) $pixel_density . 'x, ' . $single . ' 1x';
}
public function getTagAttributes(): object
{
$ordered_keys = ['src', 'srcset', 'alt', 'title', 'width', 'height', 'class', 'loading'];
krsort($ordered_keys);
$tag_width = $this->output->width;
$tag_height = $this->output->height;
$this->attributes = $this->attributes ?: (object) [];
$this->attributes->src = $this->getOutputFile();
$this->attributes->srcset = $this->getSrcset();
$this->attributes->alt = $this->getAlt();
$this->attributes->title = $this->getTitle(\true);
$this->attributes->width = $this->output->width ?: $this->attributes->width ?? 0;
$this->attributes->height = $this->output->height ?: $this->attributes->height ?? 0;
if ($this->attributes->width < $tag_width) {
$this->attributes->width = $tag_width;
$this->attributes->height = round($this->attributes->height * ($tag_width / $this->attributes->width));
}
if ($this->attributes->height < $tag_height) {
$this->attributes->height = $tag_height;
$this->attributes->width = round($this->attributes->width * ($tag_height / $this->attributes->height));
}
$this->attributes->width = $this->attributes->width ?: null;
$this->attributes->height = $this->attributes->height ?: null;
$attributes = (array) $this->attributes;
foreach ($ordered_keys as $key) {
if (!key_exists($key, $attributes)) {
continue;
}
$value = $attributes[$key];
if (is_null($value)) {
continue;
}
unset($attributes[$key]);
$attributes = [$key => $value, ...$attributes];
}
return (object) $attributes;
}
public function getTitle(bool $force = \false): string
{
if (isset($this->attributes->title)) {
return $this->attributes->title;
}
$title = $this->getDataFileDataByType('title');
if (!is_null($title)) {
// Remove HTML tags
$title = strip_tags($title);
return htmlentities($title);
}
return $this->getTitleFromName($force);
}
public function getWidth(): int
{
$this->prepareOutput();
return $this->output->width;
}
public function isResized(): bool
{
if (!is_null($this->is_resized)) {
return $this->is_resized;
}
$this->is_resized = \false;
$this->prepareInput();
$parent_folder_name = \RegularLabs\Library\File::getBaseName($this->input->folder_path);
$resize_folder_name = $this->settings->resize->folder;
// Image is not inside the resize folder
if ($parent_folder_name != $resize_folder_name) {
return \false;
}
$parent_folder = \RegularLabs\Library\File::getDirName($this->input->folder_path);
$file_name = $this->input->file_name;
// Check if image with same name exists in parent folder
if ($this->exists($parent_folder . '/' . \RegularLabs\Library\StringHelper::utf8_decode($file_name))) {
$this->is_resized = \true;
return \true;
}
// Remove any dimensions from the file
// image_300x200.jpg => image.jpg
$file_name = \RegularLabs\Library\RegEx::replace('_[0-9]+x[0-9]*(\.[^.]+)$', '\1', $file_name);
// Check again if image with same name (but without dimensions) exists in parent folder
if ($this->exists($parent_folder . '/' . \RegularLabs\Library\StringHelper::utf8_decode($file_name))) {
$this->is_resized = \true;
return \true;
}
return \false;
}
public function outputExists(): bool
{
$file = $this->getOutputFilePath();
return $file && file_exists($file) && is_file($file);
}
public function render(string $outer_class = ''): string
{
$attributes = (array) $this->getTagAttributes();
$image_tag = '<img ' . \RegularLabs\Library\HtmlTag::flattenAttributes($attributes) . ' />';
if (!$outer_class) {
return $image_tag;
}
return '<div class="' . htmlspecialchars($outer_class) . '">' . $image_tag . '</div>';
}
/**
* @depecated Use render() instead
*/
public function renderTag(): string
{
return $this->render($this->attributes->{'outer-class'} ?? '');
}
public function setAlt($alt): self
{
return $this->setTagAttribute('alt', $alt);
}
public function setAutoTitles(bool $enabled, string $title_format = 'titlecase', string $lowercase_words = 'a,the,to,at,in,with,and,but,or'): self
{
$this->settings->title->enabled = $enabled;
$this->settings->title->format = $title_format;
$this->settings->title->lowercase_words = $lowercase_words;
return $this;
}
public function setDescription($description): self
{
$this->description = $description;
return $this;
}
public function setDimensions(int|float|string $width, int|float|string $height): self
{
$this->setResizeMethod(empty($width) || empty($height) ? 'scale' : 'crop');
$this->setOutputSetting('width', (int) $width);
$this->setOutputSetting('height', (int) $height);
return $this;
}
public function setEnableResize(bool $enabled): self
{
$this->settings->resize->enabled = $enabled;
return $this;
}
public function setFile($file): self
{
$this->file = $file;
$this->resetInput();
return $this;
}
public function setHeight(int|float|string $height): self
{
return $this->setOutputSetting('height', (int) $height);
}
public function setItemProp(?string $itemprop): self
{
$itemprop = $itemprop ? 'image' : null;
$this->setTagAttribute('itemprop', $itemprop);
return $this;
}
public function setLazyLoading(?bool $enabled): self
{
return $this->setTagAttribute('loading', $enabled ? 'lazy' : null);
}
public function setLowerCaseWords(array $words): self
{
$this->settings->title->lowercase_words = $words;
return $this;
}
public function setOutputFileData(): self
{
if (!empty($this->output->file)) {
return $this;
}
$this->prepareInput();
$output = clone $this->input;
if (!empty($this->output->width) || !empty($this->output->height)) {
$output->width = $this->output->width;
$output->height = $this->output->height;
}
$this->output = $output;
return $this;
}
public function setOutputSetting(string $key, mixed $value): self
{
if (is_null($this->output)) {
$this->output = (object) ['width' => 0, 'height' => 0];
}
if ($this->output->{$key} == $value) {
return $this;
}
$this->output->{$key} = $value;
$this->resetOutput();
return $this;
}
public function setResizeAttribute(string $key, mixed $value): self
{
if ($this->settings->resize->{$key} == $value) {
return $this;
}
$this->settings->resize->{$key} = $value;
$this->resetOutput();
return $this;
}
public function setResizeFolder(string $folder = 'resized'): self
{
return $this->setResizeAttribute('folder', $folder);
}
public function setResizeMaxAge(int $age_in_days): self
{
return $this->setResizeAttribute('max_age', $age_in_days);
}
public function forceOverwriteResized(): self
{
return $this->setResizeAttribute('force_overwrite', 1.0E-5);
}
public function setResizeMethod(string $method): self
{
$this->settings->resize->method = $method;
return $this;
}
public function setResizeQuality(string|int $quality): self
{
return $this->setResizeAttribute('quality', $this->parseQuality($quality));
}
public function setRetinaPixelDensity(string $pixel_density): self
{
return $this->setResizeAttribute('retina_pixel_density', $pixel_density);
}
public function setTagAttribute(string $key, mixed $value): self
{
if (is_null($this->attributes)) {
$this->attributes = (object) [];
}
$this->attributes->{$key} = $value;
return $this;
}
public function setTagAttributes(object $attributes): self
{
foreach ($attributes as $key => $value) {
$this->setTagAttribute($key, $value);
}
return $this;
}
public function setTitle(string $title): self
{
return $this->setTagAttribute('title', $title);
}
public function setUseRetina(bool $use_retina): self
{
return $this->setResizeAttribute('use_retina', $use_retina);
}
public function setWidth(int|float|string $width): self
{
return $this->setOutputSetting('width', (int) $width);
}
private function getFolderFileData(): array
{
$folder = $this->getFolderPath();
if (isset(self::$data_files[$folder])) {
return self::$data_files[$folder];
}
self::$data_files[$folder] = [];
if (!$this->exists($folder . '/data.txt')) {
return self::$data_files[$folder];
}
$data = file_get_contents($folder . '/data.txt');
$data = str_replace("\r", '', $data);
$data = explode("\n", $data);
foreach ($data as $data_line) {
if (empty($data_line) || $data_line[0] == '#' || !str_contains($data_line, '=')) {
continue;
}
[$key, $val] = explode('=', $data_line, 2);
if (!\RegularLabs\Library\RegEx::match('^(?<file>.*?)\[(?<type>.*)\]$', $key, $match)) {
continue;
}
$file = $match['file'];
$type = $match['type'];
if (!isset(self::$data_files[$folder][$file])) {
self::$data_files[$folder][$file] = [];
}
self::$data_files[$folder][$file][$type] = $val;
}
return self::$data_files[$folder];
}
private function getLowercaseWords(): array
{
$words = $this->settings->title->lowercase_words;
$words = \RegularLabs\Library\ArrayHelper::implode($words, ',');
$words = \RegularLabs\Library\StringHelper::strtolower($words);
return explode(',', ' ' . str_replace(',', ' , ', $words . ' '));
}
private function getOutputFile2x(): string
{
if ($this->isExternal()) {
return $this->getOutputFile();
}
$double_width = $this->output->width * 2;
$double_height = $this->output->height * 2;
if ($double_width == $this->input->width && $double_height == $this->input->height) {
return $this->getOutputFile();
}
$double_size = \RegularLabs\Library\ObjectHelper::clone($this);
$double_size->setDimensions($double_width, $double_height);
return $double_size->getOutputFile();
}
private function getResizeBoundry(): string
{
if ($this->input->width / $this->output->width > $this->input->height / $this->output->height) {
return 'width';
}
return 'height';
}
private function getResizeDimensions(): array
{
if (!$this->output->width) {
return [null, $this->output->height];
}
if (!$this->output->height) {
return [$this->output->width, null];
}
if ($this->input->width / $this->output->width > $this->input->height / $this->output->height) {
return [null, $this->output->height];
}
return [$this->output->width, null];
}
private function getResizeFolder(): string
{
if (!$this->settings->resize->folder) {
$this->setResizeFolder();
}
return $this->getFolder() . '/' . $this->settings->resize->folder;
}
private function getResizeFolderPath(): string
{
if (!$this->settings->resize->folder) {
$this->setResizeFolder();
}
return $this->getFolderPath() . '/' . $this->settings->resize->folder;
}
private function getResizedFileName(): string
{
$this->prepareInput();
return $this->input->file_stem . '_' . $this->output->width . 'x' . $this->output->height . '.' . $this->input->file_extension;
}
private function getTitleFromName(bool $force = \false): string
{
if (!$force && !$this->settings->title->enabled) {
return '';
}
$title = \RegularLabs\Library\StringHelper::toSpaceSeparated($this->input->file_stem);
switch ($this->settings->title->format) {
case 'lowercase':
return \RegularLabs\Library\StringHelper::strtolower($title);
case 'uppercase':
return \RegularLabs\Library\StringHelper::strtoupper($title);
case 'uppercase_first':
return \RegularLabs\Library\StringHelper::strtoupper(\RegularLabs\Library\StringHelper::substr($title, 0, 1)) . \RegularLabs\Library\StringHelper::strtolower(\RegularLabs\Library\StringHelper::substr($title, 1));
case 'titlecase':
return function_exists('mb_convert_case') ? mb_convert_case(\RegularLabs\Library\StringHelper::strtolower($title), \MB_CASE_TITLE) : ucwords(strtolower($title));
case 'titlecase_smart':
$title = function_exists('mb_convert_case') ? mb_convert_case(\RegularLabs\Library\StringHelper::strtolower($title), \MB_CASE_TITLE) : ucwords(strtolower($title));
$lowercase_words = $this->getLowercaseWords();
return str_ireplace($lowercase_words, $lowercase_words, $title);
default:
return $title;
}
}
private function handleDimensions(): self
{
if (!$this->input->width || !$this->input->height) {
return $this;
}
// Width and height are both not set, so revert to original dimensions
if (!$this->output->height && !$this->output->width) {
$this->output->width = $this->input->width;
$this->output->height = $this->input->height;
return $this;
}
if (!$this->outputExists()) {
return $this;
}
if ($this->settings->resize->method == 'crop') {
$this->output->width = $this->output->width ?: $this->output->height;
$this->output->height = $this->output->height ?: $this->output->width;
return $this->resize();
}
// Width and height are both set
if ($this->output->width && $this->output->height) {
$boundry = $this->getResizeBoundry();
$this->output->width = $boundry == 'width' ? $this->output->width : round($this->output->height / $this->input->height * $this->input->width);
$this->output->height = $boundry == 'height' ? $this->output->height : round($this->output->width / $this->input->width * $this->input->height);
return $this->resize();
}
$this->output->width = $this->output->width ?: round($this->output->height / $this->input->height * $this->input->width);
$this->output->height = $this->output->height ?: round($this->output->width / $this->input->width * $this->input->height);
return $this->resize();
}
private function isExternal(): bool
{
$this->prepareInput();
return \RegularLabs\Library\File::isExternal($this->input->file);
}
private function limitDimensions(): self
{
if ($this->output->width <= $this->input->width && $this->output->height <= $this->input->height) {
return $this;
}
if ($this->output->width > $this->input->width) {
$this->output->height = $this->output->height / $this->output->width * $this->input->width;
$this->output->width = $this->input->width;
}
if ($this->output->height > $this->input->height) {
$this->output->width = $this->output->width / $this->output->height * $this->input->height;
$this->output->height = $this->input->height;
}
$this->output->width = round($this->output->width);
$this->output->height = round($this->output->height);
return $this;
}
private function parseQuality(string|int $quality = 90): int
{
if (is_int($quality)) {
return $quality;
}
return match ($quality) {
'low' => 30,
'medium' => 60,
default => 90,
};
}
private function prepareInput(): self
{
if (!is_null($this->input)) {
return $this;
}
if (!$this->file) {
throw new Exception('No file set');
}
if (\RegularLabs\Library\File::isExternal($this->file)) {
$this->input = $this->getFileInfo($this->file, $this->file);
return $this;
}
$file = self::cleanPath($this->file);
$this->input = $this->getFileInfo($file, JPATH_ROOT . '/' . ltrim($file, '/'));
return $this;
}
private function prepareOutput(): self
{
if (!empty($this->output->file)) {
return $this;
}
$this->prepareInput();
if (empty($this->output->width) && empty($this->output->height)) {
$this->output = clone $this->input;
return $this;
}
$this->setOutputFileData();
$this->handleDimensions();
return $this;
}
private function resetInput(): self
{
$this->input = null;
$this->resetOutput();
return $this;
}
private function resetOutput(): self
{
if (is_null($this->output)) {
$this->output = (object) ['width' => 0, 'height' => 0];
}
unset($this->output->file);
return $this;
}
/**
* Method to create a resized version of the current image and save them to disk
*/
private function resize(?string $width = null, ?string $height = null, ?int $quality = null): self
{
if (!$this->settings->resize->enabled) {
return $this;
}
if ($this->isResized()) {
return $this;
}
if ($this->isExternal()) {
return $this;
}
if (!is_null($width) || !is_null($height)) {
$this->setDimensions($width, $height);
}
if (!is_null($quality)) {
$this->setResizeQuality($quality);
}
if ($this->output->width == $this->input->width && $this->output->height == $this->input->height) {
$this->output = clone $this->input;
return $this;
}
$this->limitDimensions();
$file = $this->getResizedFileName();
$this->output->file = $this->getResizeFolder() . '/' . $file;
$this->output->file_path = $this->getResizeFolderPath() . '/' . $file;
$file_exists = $this->outputExists();
$file_is_outdated = $this->settings->resize->force_overwrite;
if (!$file_is_outdated && $file_exists && $this->settings->resize->max_age > 0) {
$max_age_in_seconds = ceil($this->settings->resize->max_age * 60 * 60 * 24);
$min_time = time() - $max_age_in_seconds;
$file_is_outdated = filemtime($this->output->file_path) < $min_time;
}
if ($file_exists && !$file_is_outdated) {
//$this->output = $this->getFileInfo($this->output->file, $this->output->file_path);
return $this;
}
[$resize_width, $resize_height] = $this->getResizeDimensions();
try {
$resized = ImageManager::make($this->getFilePath())->orientate()->resize($resize_width, $resize_height, function ($constraint) {
$constraint->aspectRatio();
});
if ($this->settings->resize->method == 'crop' || $this->output->width && $this->output->height) {
$resized->crop($this->output->width, $this->output->height);
}
$this->createResizeFolder();
$resized->save($this->output->file_path, $this->settings->resize->quality);
} catch (NotReadableException $exception) {
$resized = null;
}
if (!$resized) {
$this->output = clone $this->input;
$this->is_resized = \false;
}
$this->output = $this->getFileInfo($this->output->file, $this->output->file_path);
return $this;
}
private function setFromOldAttributes(object $attributes): void
{
if (isset($attributes->alt)) {
$this->setAlt($attributes->alt);
}
if (isset($attributes->title)) {
$this->setTitle($attributes->title);
}
if (isset($attributes->class)) {
$this->setTagAttribute('class', $attributes->class);
}
if (isset($attributes->{'outer-class'})) {
$this->setTagAttribute('outer-class', $attributes->{'outer-class'});
}
if (isset($attributes->{'resize-folder'})) {
$folder = \RegularLabs\Library\File::getBaseName($attributes->{'resize-folder'});
$this->setResizeFolder($folder);
}
if (isset($attributes->quality)) {
$this->setResizeQuality($attributes->quality);
}
$this->setDimensions($attributes->width ?? 0, $attributes->height ?? 0);
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
/**
* string getAlphaNumeric($name, $default = null) Get an alphanumeric string.
* string getBase64($name, $default = null) Get a base64 encoded string.
* boolean getBool($name, $default = null) Get a boolean value.
* string getCmd($name, $default = null) Get a CMD filtered string.
* float getFloat($name, $default = null) Get a floating-point number.
* string getHtml($name, $default = null) Get a HTML string.
* integer getInt($name, $default = null) Get a signed integer.
* string getPath($name, $default = null) Get a file path.
* mixed getRaw($name, $default = null) Get an unfiltered value.
* string getString($name, $default = null) Get a string.
* integer getUint($name, $default = null) Get an unsigned integer.
* string getUsername($name, $default = null) Get a username.
* string getWord($name, $default = null) Get a word.
*/
class Input
{
public static function get(string $name, mixed $default = null, string $filter = 'none'): mixed
{
return JFactory::getApplication()->getInput()->get($name, $default, $filter);
}
public static function getAlphaNumeric(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getAlnum($name, $default));
}
public static function getArray(array $vars = [], mixed $datasource = null): array
{
return JFactory::getApplication()->getInput()->getArray($vars, $datasource);
}
public static function getAsArray(string $name, ?array $default = []): array
{
return (array) JFactory::getApplication()->getInput()->get($name, $default ?? [], 'array');
}
public static function getBase64(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getBase64($name, $default));
}
public static function getBool(string $name, ?bool $default = \false): bool
{
return (bool) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getBool($name, $default));
}
public static function getCmd(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getCmd($name, $default));
}
public static function getFloat(string $name, mixed $default = null): float
{
return (float) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getFloat($name, $default));
}
public static function getHtml(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getHtml($name, $default));
}
public static function getInt(string $name, mixed $default = null): int
{
return (int) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getInt($name, $default));
}
public static function getPath(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getPath($name, $default));
}
public static function getRaw(string $name, mixed $default = null): mixed
{
return JFactory::getApplication()->getInput()->getRaw($name, $default);
}
public static function getString(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getString($name, $default));
}
public static function getUint(string $name, mixed $default = null): int
{
return (int) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getUint($name, $default));
}
public static function getUsername(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getUsername($name, $default));
}
public static function getWord(string $name, mixed $default = null): string
{
return (string) (self::convertFromArray($name) ?? JFactory::getApplication()->getInput()->getWord($name, $default));
}
public static function set(string $name, mixed $value): void
{
JFactory::getApplication()->getInput()->set($name, $value);
}
public static function setCookie(string $name, mixed $value, array $options = []): void
{
JFactory::getApplication()->getInput()->cookie->set($name, $value, $options);
}
private static function convertFromArray(string $name): mixed
{
$value = JFactory::getApplication()->getInput()->get($name, null);
if (is_array($value)) {
return $value[0] ?? null;
}
return null;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Factory as JFactory;
class Language
{
/**
* Load the language of the given extension
*/
public static function load(string $extension = 'plg_system_regularlabs', string $basePath = '', bool $reload = \false): bool
{
if ($basePath && JFactory::getApplication()->getLanguage()->load($extension, $basePath, null, $reload)) {
return \true;
}
$basePath = \RegularLabs\Library\Extension::getPath($extension, $basePath, 'language');
return JFactory::getApplication()->getLanguage()->load($extension, $basePath, null, $reload);
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Layout\FileLayout as JFileLayout;
class Layout
{
static $layouts = [];
public static function get($layout_id, $layout_path, $extension)
{
$key = $extension . '.' . $layout_id;
if (isset(self::$layouts[$key])) {
return self::$layouts[$key];
}
$layout = new JFileLayout($layout_id);
$default_paths = $layout->getDefaultIncludePaths();
$default_paths = array_reverse($default_paths);
$layout->addIncludePath($layout_path);
foreach ($default_paths as $path) {
$layout->addIncludePath($path . '/' . $extension);
}
self::$layouts[$key] = $layout;
return self::$layouts[$key];
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text as JText;
class License
{
/**
* Render the license message for Free versions
*/
public static function getMessage(string $name, bool $check_pro = \false): string
{
if (!$name) {
return '';
}
$alias = \RegularLabs\Library\Extension::getAliasByName($name);
$name = \RegularLabs\Library\Extension::getNameByAlias($name);
if ($check_pro && self::isPro($alias)) {
return '';
}
return '<div class="rl-license rl-alert alert alert-warning rl-alert-light">' . '<div>' . JText::sprintf('RL_IS_FREE_VERSION', $name) . '</div>' . '<div>' . JText::_('RL_FOR_MORE_GO_PRO') . '</div>' . '<div>' . '<a href="https://regularlabs.com/purchase/cart/add/' . $alias . '" target="_blank" class="btn btn-sm btn-primary">' . '<span class="icon-basket"></span>&nbsp;&nbsp;' . \RegularLabs\Library\StringHelper::html_entity_decoder(JText::_('RL_GO_PRO')) . '</a>' . '</div>' . '</div>';
}
/**
* Check if the installed version of the extension is a Pro version
*/
private static function isPro(string $element_name): bool
{
$version = \RegularLabs\Library\Extension::getXMLValue('version', $element_name);
if (!$version) {
return \false;
}
return stripos($version, 'PRO') !== \false;
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
require_once dirname(__FILE__, 2) . '/vendor/autoload.php';
class MobileDetect extends \RegularLabs\Scoped\Detection\MobileDetect
{
public function isCurl(): bool
{
return $this->match('curl', $this->getUserAgent());
}
public function isMac(): bool
{
return $this->match('(Mac OS|Mac_PowerPC|Macintosh)', $this->getUserAgent());
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use RegularLabs\Scoped\DeepCopy\DeepCopy;
require_once dirname(__FILE__, 2) . '/vendor/autoload.php';
class ObjectHelper
{
/**
* Change the case of object keys
* $key_format: 'camel', 'dash', 'dot', 'underscore'
*/
public static function changeKeyCase(object|array|null $object, $format, bool $to_lowercase = \true): object
{
return (object) \RegularLabs\Library\ArrayHelper::applyMethodToKeys([$object, $format, $to_lowercase], '\RegularLabs\Library\StringHelper', 'toCase');
}
/**
* Deep clone an object
*/
public static function clone(object $object): object
{
return (new DeepCopy())->copy($object);
}
/**
* Return the value by the object property key
* A list of keys can be given. The first one that is not empty will get returned
*/
public static function getValue(object $object, string|array $keys, mixed $default = null): mixed
{
$keys = \RegularLabs\Library\ArrayHelper::toArray($keys);
foreach ($keys as $key) {
if (empty($object->{$key})) {
continue;
}
return $object->{$key};
}
return $default;
}
/**
* Merge 2 objects
*/
public static function merge(object $object1, object $object2): object
{
return (object) [...(array) $object1, ...(array) $object2];
}
/**
* Replace key names
*/
public static function replaceKeys(string|object $object, array $replacements, bool $include_prefixes = \false, string $prefix_delimiter = '_'): string|object
{
$json = json_encode($object);
foreach ($replacements as $to => $froms) {
if (!is_array($froms)) {
$froms = [$froms];
}
foreach ($froms as $from) {
$json = str_replace('"' . $from . '":', '"' . $to . '":', $json);
if (!$include_prefixes) {
continue;
}
$json = \RegularLabs\Library\RegEx::replace('"' . \RegularLabs\Library\RegEx::quote($from . $prefix_delimiter) . '([^"]+":)', '"' . $to . $prefix_delimiter . '\1', $json);
}
}
return json_decode($json);
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use RegularLabs\Library\ObjectHelper as RL_Object;
class Parameters
{
/**
* Get a usable parameter object for the component
*/
public static function getComponent(string $name, object|array|string|null $params = null, bool $use_cache = \true): object
{
$name = 'com_' . \RegularLabs\Library\RegEx::replace('^com_', '', $name);
$cache = new \RegularLabs\Library\Cache();
if ($use_cache && $cache->exists()) {
return $cache->get();
}
if (empty($params) && JComponentHelper::isInstalled($name)) {
$params = JComponentHelper::getParams($name);
}
return $cache->set(self::getObjectFromRegistry($params, JPATH_ADMINISTRATOR . '/components/' . $name . '/config.xml'));
}
/**
* Get a usable parameter object for the module
*/
public static function getModule(string $name, bool $admin = \true, object|array|string|null $params = null, bool $use_cache = \true): object
{
$name = 'mod_' . \RegularLabs\Library\RegEx::replace('^mod_', '', $name);
$cache = new \RegularLabs\Library\Cache();
if ($use_cache && $cache->exists()) {
return $cache->get();
}
if (empty($params)) {
$params = null;
}
return $cache->set(self::getObjectFromRegistry($params, ($admin ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $name . '/' . $name . '.xml'));
}
/**
* Get a usable parameter object based on the Joomla Registry object
* The object will have all the available parameters with their value (default value if none is set)
*/
public static function getObjectFromRegistry(object|array|string|null $params, string $path = '', string $default = '', bool $use_cache = \true): object
{
$cache = new \RegularLabs\Library\Cache();
if ($use_cache && $cache->exists()) {
return $cache->get();
}
$xml = self::loadXML($path, $default);
if (empty($params)) {
return $cache->set((object) $xml);
}
if (is_array($params)) {
$params = (object) $params;
}
if (is_string($params)) {
$params = json_decode($params);
}
if (is_object($params) && method_exists($params, 'toObject')) {
$params = $params->toObject();
}
if (is_null($xml)) {
$xml = (object) [];
}
if (!$params) {
return $cache->set((object) $xml);
}
if (empty($xml)) {
return $cache->set($params);
}
foreach ($xml as $key => $val) {
if (isset($params->{$key}) && $params->{$key} != '') {
continue;
}
$params->{$key} = $val;
}
return $cache->set($params);
}
/**
* Get a usable parameter object for the plugin
*/
public static function getPlugin(string $name, string $type = 'system', object|array|string|null $params = null, bool $use_cache = \true): object
{
$cache = new \RegularLabs\Library\Cache();
if ($use_cache && $cache->exists()) {
return $cache->get();
}
if (empty($params)) {
$plugin = JPluginHelper::getPlugin($type, $name);
$params = is_object($plugin) && isset($plugin->params) ? $plugin->params : null;
}
return $cache->set(self::getObjectFromRegistry($params, JPATH_PLUGINS . '/' . $type . '/' . $name . '/' . $name . '.xml'));
}
/**
* Returns an array based on the data in a given xml file
*/
public static function loadXML(string $path, ?string $default = '', bool $use_cache = \true, bool $full_info = \false): array
{
$cache = new \RegularLabs\Library\Cache();
if ($use_cache && $cache->exists()) {
return $cache->get();
}
if (!$path || !file_exists($path)) {
return $cache->set([]);
}
$file = file_get_contents($path);
if (!$file) {
return $cache->set([]);
}
$xml = [];
$xml_parser = xml_parser_create();
xml_parse_into_struct($xml_parser, $file, $fields);
xml_parser_free($xml_parser);
$default = $default ? strtoupper($default) : 'DEFAULT';
foreach ($fields as $field) {
if ($field['tag'] != 'FIELD' || !isset($field['attributes']) || !isset($field['attributes']['NAME']) || $field['attributes']['NAME'] == '' || $field['attributes']['NAME'][0] == '@' || !isset($field['attributes']['TYPE']) || $field['attributes']['TYPE'] == 'spacer') {
continue;
}
if ($full_info) {
$full_object = $xml[$field['attributes']['NAME']] = RL_Object::changeKeyCase($field['attributes'], 'lower');
$full_object->multiple ??= 'false';
}
if (isset($field['attributes'][$default])) {
$field['attributes']['DEFAULT'] = $field['attributes'][$default];
}
if (!isset($field['attributes']['DEFAULT'])) {
$field['attributes']['DEFAULT'] = '';
}
if ($field['attributes']['TYPE'] == 'textarea') {
$field['attributes']['DEFAULT'] = str_replace('<br>', "\n", $field['attributes']['DEFAULT']);
}
if ($full_info) {
$full_object->value = $field['attributes']['DEFAULT'];
continue;
}
$xml[$field['attributes']['NAME']] = $field['attributes']['DEFAULT'];
}
return $cache->set($xml);
}
public static function overrideFromObject(object $params, ?object $object = null): object
{
if (empty($object)) {
return $params;
}
foreach ($params as $key => $value) {
if (!isset($object->{$key})) {
continue;
}
$params->{$key} = $object->{$key};
}
return $params;
}
}

View File

@ -0,0 +1,143 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Application\CMSApplication as JCMSApplication;
use Joomla\CMS\Application\CMSApplicationInterface as JCMSApplicationInterface;
use Joomla\CMS\Document\Document as JDocument;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\File as JFile;
use Joomla\CMS\Version as JVersion;
class Php
{
static $rl_variables;
public static function execute(string $rl_string, object|false|null $rl_article = null, object|false|null $rl_module = null, bool $has_own_return = \false): mixed
{
self::prepareString($rl_string);
$function_name = self::getFunctionName($rl_string);
if (!$function_name) {
// Something went wrong!
return \true;
}
if (!$rl_article && str_contains($rl_string, '$article')) {
if (\RegularLabs\Library\Input::get('option', '') == 'com_content' && \RegularLabs\Library\Input::get('view', '') == 'article') {
$rl_article = \RegularLabs\Library\Article::get(\RegularLabs\Library\Input::getInt('id'));
}
}
$rl_pre_variables = array_keys(get_defined_vars());
ob_start();
$rl_post_variables = $function_name(self::$rl_variables, $rl_article, $rl_module);
$rl_output = ob_get_contents();
ob_end_clean();
if ($has_own_return) {
return $rl_post_variables;
}
if (!is_array($rl_post_variables)) {
return $rl_output;
}
$rl_diff_variables = array_diff(array_keys($rl_post_variables), $rl_pre_variables);
foreach ($rl_diff_variables as $rl_diff_key) {
if (in_array($rl_diff_key, ['Itemid', 'mainframe', 'app', 'document', 'doc', 'database', 'db', 'user', 'article', 'module'], \true) || substr($rl_diff_key, 0, 4) == 'rl_') {
continue;
}
self::$rl_variables[$rl_diff_key] = $rl_post_variables[$rl_diff_key];
}
return $rl_output;
}
public static function getApplication(): JCMSApplicationInterface
{
if (\RegularLabs\Library\Input::get('option', '') != 'com_finder') {
return JFactory::getApplication();
}
return JCMSApplication::getInstance('site');
}
public static function getDocument(): JDocument
{
if (\RegularLabs\Library\Input::get('option', '') != 'com_finder') {
return \RegularLabs\Library\Document::get();
}
$lang = JFactory::getApplication()->getLanguage();
$version = new JVersion();
$attributes = ['charset' => 'utf-8', 'lineend' => 'unix', 'tab' => "\t", 'language' => $lang->getTag(), 'direction' => $lang->isRtl() ? 'rtl' : 'ltr', 'mediaversion' => $version->getMediaVersion()];
return JDocument::getInstance('html', $attributes);
}
private static function createFunctionInMemory(string $string): void
{
$file_name = getmypid() . '_' . md5($string);
$tmp_path = JFactory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp');
$temp_file = $tmp_path . '/regularlabs/custom_php/' . $file_name;
// Write file
if (!file_exists($temp_file) || is_writable($temp_file)) {
JFile::write($temp_file, $string);
}
// Include file
include_once $temp_file;
// Delete file
if (!JFactory::getApplication()->get('debug')) {
@chmod($temp_file, 0777);
@unlink($temp_file);
}
}
private static function extractUseStatements(string &$string): string
{
$use_statements = [];
$string = trim($string);
\RegularLabs\Library\RegEx::matchAll('^use\s+[^\s;]+\s*;', $string, $matches, 'm');
foreach ($matches as $match) {
$use_statements[] = $match[0];
$string = str_replace($match[0], '', $string);
}
$string = trim($string);
return implode("\n", $use_statements);
}
private static function generateFileContents(string $function_name = 'rl_function', string $string = ''): string
{
$use_statements = self::extractUseStatements($string);
$init_variables = self::getVarInits();
$init_variables[] = 'if (is_array($rl_variables)) {' . 'foreach ($rl_variables as $rl_key => $rl_value) {' . '${$rl_key} = $rl_value;' . '}' . '}';
$contents = ['<?php', 'defined(\'_JEXEC\') or die;', $use_statements, 'function ' . $function_name . '($rl_variables, $article, $module){', implode("\n", $init_variables), $string . ';', 'return get_defined_vars();', ';}'];
$contents = implode("\n", $contents);
// Remove Zero Width spaces / (non-)joiners
$contents = str_replace(["", "", ""], '', $contents);
return $contents;
}
private static function getFunctionName(string $string): string|false
{
$function_name = 'regularlabs_php_' . md5($string);
if (function_exists($function_name)) {
return $function_name;
}
$contents = self::generateFileContents($function_name, $string);
self::createFunctionInMemory($contents);
if (!function_exists($function_name)) {
// Something went wrong!
return \false;
}
return $function_name;
}
private static function getVarInits(): array
{
return ['$app = $mainframe = RegularLabs\Library\Php::getApplication();', '$document = $doc = RegularLabs\Library\Php::getDocument();', '$database = $db = Joomla\CMS\Factory::getDbo();', '$user = $app->getIdentity() ?: Joomla\CMS\Factory::getUser();', '$Itemid = $app->getInput()->getInt(\'Itemid\');'];
}
private static function prepareString(string &$string): void
{
$string = trim($string);
$string = str_replace('?><?php', '', $string . '<?php ;');
if (!str_starts_with($string, '<?php')) {
$string = '?>' . $string;
return;
}
$string = substr($string, 5);
$string = trim($string);
}
}

View File

@ -0,0 +1,400 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class PluginTag
{
/**
* @var array
*/
static $protected_characters = ['=' => '[[:EQUAL:]]', '"' => '[[:QUOTE:]]', ',' => '[[:COMMA:]]', '|' => '[[:BAR:]]', ':' => '[[:COLON:]]'];
/**
* Cleans the given tag word
*
* @param string $string
*
* @return string
*/
public static function clean($string = '')
{
return \RegularLabs\Library\RegEx::replace('[^a-z0-9-_]', '', $string);
}
/**
* Get the attributes from plugin style string
*
* @param string $string
* @param string $main_key
* @param array $known_boolean_keys
* @param string $key_format (empty, 'underscore', 'dash')
* @param array $keep_escaped_chars
*
* @return object
*/
public static function getAttributesFromString($string = '', $main_key = 'title', $known_boolean_keys = [], $key_format = '', $keep_escaped_chars = null, $convert_numerals = null)
{
if (empty($string)) {
return (object) [];
}
if (is_object($string)) {
return $string;
}
if (is_array($string)) {
return (object) $string;
}
// Replace html entity quotes to normal quotes
if (!str_contains($string, '"')) {
$string = str_replace('&quot;', '"', $string);
}
self::protectSpecialChars($string);
// replace weird whitespace
$string = str_replace(chr(194) . chr(160), ' ', $string);
// Replace html entity spaces between attributes to normal spaces
$string = \RegularLabs\Library\RegEx::replace('((?:^|")\s*)&nbsp;(\s*(?:[a-z]|$))', '\1 \2', $string);
// Only one value, so return simple key/value object
if (!str_contains($string, '|') && !\RegularLabs\Library\RegEx::match('=\s*["\']', $string)) {
self::unprotectSpecialChars($string, $keep_escaped_chars);
return (object) [$main_key => $string];
}
// Cannot find right syntax, so return simple key/value object
if (!\RegularLabs\Library\RegEx::matchAll('(?:^|\s)(?<key>[a-z0-9-_\:]+)\s*(?<not>\!?)=\s*(["\'])(?<value>.*?)\3', $string, $matches)) {
self::unprotectSpecialChars($string, $keep_escaped_chars);
return (object) [$main_key => $string];
}
$tag = (object) [];
foreach ($matches as $match) {
$key = \RegularLabs\Library\StringHelper::toCase($match['key'], $key_format);
$tag->{$key} = self::getAttributeValueFromMatch($match, $known_boolean_keys, $keep_escaped_chars, $convert_numerals);
}
return $tag;
}
/**
* Extract the plugin style div tags with the possible attributes. like:
* {div width:100|float:left}...{/div}
*
* @param string $start_tag
* @param string $end_tag
* @param string $tag_start
* @param string $tag_end
*
* @return array
*/
public static function getDivTags($start_tag = '', $end_tag = '', $tag_start = '{', $tag_end = '}')
{
$tag_start = \RegularLabs\Library\RegEx::quote($tag_start);
$tag_end = \RegularLabs\Library\RegEx::quote($tag_end);
$start_div = ['pre' => '', 'tag' => '', 'post' => ''];
$end_div = ['pre' => '', 'tag' => '', 'post' => ''];
if (!empty($start_tag) && \RegularLabs\Library\RegEx::match('^(?<pre>.*?)(?<tag>' . $tag_start . 'div(?: .*?)?' . $tag_end . ')(?<post>.*)$', $start_tag, $match)) {
$start_div = $match;
}
if (!empty($end_tag) && \RegularLabs\Library\RegEx::match('^(?<pre>.*?)(?<tag>' . $tag_start . '/div' . $tag_end . ')(?<post>.*)$', $end_tag, $match)) {
$end_div = $match;
}
if (empty($start_div['tag']) || empty($end_div['tag'])) {
return [$start_div, $end_div];
}
$attribs = trim(\RegularLabs\Library\RegEx::replace($tag_start . 'div(.*)' . $tag_end, '\1', $start_div['tag']));
$start_div['tag'] = '<div>';
$end_div['tag'] = '</div>';
if (empty($attribs)) {
return [$start_div, $end_div];
}
$attribs = self::getDivAttributes($attribs);
$style = [];
if (isset($attribs->width)) {
if (is_numeric($attribs->width)) {
$attribs->width .= 'px';
}
$style[] = 'width:' . $attribs->width;
}
if (isset($attribs->height)) {
if (is_numeric($attribs->height)) {
$attribs->height .= 'px';
}
$style[] = 'height:' . $attribs->height;
}
if (isset($attribs->align)) {
$style[] = 'float:' . $attribs->align;
}
if (!isset($attribs->align) && isset($attribs->float)) {
$style[] = 'float:' . $attribs->float;
}
$attribs = isset($attribs->class) ? 'class="' . $attribs->class . '"' : '';
if (!empty($style)) {
$attribs .= ' style="' . implode(';', $style) . ';"';
}
$start_div['tag'] = trim('<div ' . trim($attribs)) . '>';
return [$start_div, $end_div];
}
/**
* Return the Regular Expressions string to match:
* Plugin type tags inside others
*
* @return string
*/
public static function getRegexInsideTag($start_character = '{', $end_character = '}')
{
$s = \RegularLabs\Library\RegEx::quote($start_character);
$e = \RegularLabs\Library\RegEx::quote($end_character);
return '(?:[^' . $s . $e . ']*' . $s . '[^' . $e . ']*' . $e . ')*.*?';
}
/**
* Return the Regular Expressions string to match:
* html before plugin tag
*
* @param string $group_id
*
* @return string
*/
public static function getRegexLeadingHtml($group_id = '')
{
$group = 'leading_block_element';
$html_tag_group = 'html_tag';
if ($group_id) {
$group .= '_' . $group_id;
$html_tag_group .= '_' . $group_id;
}
$block_elements = \RegularLabs\Library\Html::getBlockElements(['div']);
$block_element = '(?<' . $group . '>' . implode('|', $block_elements) . ')';
$other_html = '[^<]*(<(?<' . $html_tag_group . '>[a-z][a-z0-9_-]*)[\s>]([^<]*</(?P=' . $html_tag_group . ')>)?[^<]*)*';
// Grab starting block element tag and any html after it (that is not the same block element starting/ending tag).
return '(?:' . '<' . $block_element . '(?: [^>]*)?>' . $other_html . ')?';
}
/**
* Return the Regular Expressions string to match:
* Different types of spaces
*
* @param string $modifier
*
* @return string
*/
public static function getRegexSpaces($modifier = '+')
{
return '(?:\s|&nbsp;|&\#160;)' . $modifier;
}
/**
* Return the Regular Expressions string to match:
* Trailing html tag
*
* @param array $elements
*
* @return string
*/
public static function getRegexSurroundingTagPost($elements = [])
{
$elements = $elements ?? null ?: [...\RegularLabs\Library\Html::getBlockElements(), 'span'];
return '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $elements) . ')>)?';
}
/**
* Return the Regular Expressions string to match:
* Leading html tag
*
* @param array $elements
*
* @return string
*/
public static function getRegexSurroundingTagPre($elements = [])
{
$elements = $elements ?? null ?: [...\RegularLabs\Library\Html::getBlockElements(), 'span'];
return '(?:<(?:' . implode('|', $elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*)?';
}
/**
* Return the Regular Expressions string to match:
* Closing html tags
*
* @param array $block_elements
* @param array $inline_elements
* @param array $excluded_block_elements
*
* @return string
*/
public static function getRegexSurroundingTagsPost($block_elements = [], $inline_elements = ['span', 'strong', 'b', 'em', 'i'], $excluded_block_elements = [])
{
$block_elements = $block_elements ?? null ?: \RegularLabs\Library\Html::getBlockElements($excluded_block_elements);
$regex = '';
if (!empty($inline_elements)) {
$regex .= '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $inline_elements) . ')>){0,3}';
}
$regex .= '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $block_elements) . ')>)?';
return $regex;
}
/**
* Return the Regular Expressions string to match:
* Opening html tags
*
* @param array $block_elements
* @param array $inline_elements
* @param array $excluded_block_elements
*
* @return string
*/
public static function getRegexSurroundingTagsPre($block_elements = [], $inline_elements = ['span', 'strong', 'b', 'em', 'i'], $excluded_block_elements = [])
{
$block_elements = $block_elements ?? null ?: \RegularLabs\Library\Html::getBlockElements($excluded_block_elements);
$regex = '(?:<(?:' . implode('|', $block_elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*)?';
if (!empty($inline_elements)) {
$regex .= '(?:<(?:' . implode('|', $inline_elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*){0,3}';
}
return $regex;
}
/**
* Return the Regular Expressions string to match:
* Plugin style tags
*
* @param array|string $tags
* @param bool $include_no_attributes
* @param bool $include_ending
* @param array $required_attributes
*
* @return string
*/
public static function getRegexTags($tags, $include_no_attributes = \true, $include_ending = \true, $required_attributes = [])
{
$tags = \RegularLabs\Library\ArrayHelper::toArray($tags);
$tags = count($tags) > 1 ? '(?:' . implode('|', $tags) . ')' : $tags[0];
$value = '(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|[a-z0-9-_]+))?';
$attributes = '(?:\s+[a-z0-9-_]+' . $value . ')+';
$required_attributes = \RegularLabs\Library\ArrayHelper::toArray($required_attributes);
if (!empty($required_attributes)) {
$attributes = '(?:' . $attributes . ')?' . '(?:\s+' . implode('|', $required_attributes) . ')' . $value . '(?:' . $attributes . ')?';
}
if ($include_no_attributes) {
$attributes = '\s*(?:' . $attributes . ')?';
}
if (!$include_ending) {
return '<' . $tags . $attributes . '\s*/?>';
}
return '<(?:\/' . $tags . '|' . $tags . $attributes . '\s*/?)\s*/?>';
}
/**
* Return the Regular Expressions string to match:
* html after plugin tag
*
* @param string $group_id
*
* @return string
*/
public static function getRegexTrailingHtml($group_id = '')
{
$group = 'leading_block_element';
if ($group_id) {
$group .= '_' . $group_id;
}
// If the grouped name is found, then grab all content till ending html tag is found. Otherwise grab nothing.
return '(?(<' . $group . '>)' . '(?:.*?</(?P=' . $group . ')>)?' . ')';
}
/**
* Replace special characters in the string with the protected versions
*
* @param string $string
*/
public static function protectSpecialChars(&$string)
{
$unescaped_chars = array_keys(self::$protected_characters);
array_walk($unescaped_chars, function (&$char) {
$char = '\\' . $char;
});
// replace escaped characters with special markup
$string = str_replace($unescaped_chars, array_values(self::$protected_characters), $string);
if (!\RegularLabs\Library\RegEx::matchAll('(<[a-z][a-z0-9-_]*(?: [a-z0-9-_]*=".*?")* ?/?>|{.*?}|\[.*?\])', $string, $tags, null, \PREG_PATTERN_ORDER)) {
return;
}
foreach ($tags[0] as $tag) {
// replace unescaped characters with special markup
$protected = str_replace(['=', '"'], [self::$protected_characters['='], self::$protected_characters['"']], $tag);
$string = str_replace($tag, $protected, $string);
}
}
/**
* Replace keys aliases with the main key names in an object
*
* @param object|string $attributes
* @param array $key_aliases
* @param bool $handle_plurals
*
* @deprecated Use ObjectHelper::replaceKeys()
*/
public static function replaceKeyAliases(&$attributes, $key_aliases = [], $handle_plurals = \false)
{
return \RegularLabs\Library\ObjectHelper::replaceKeys($attributes, $key_aliases);
}
/**
* Replace protected characters in the string with the original special versions
*
* @param string $string
* @param array $keep_escaped_chars
*/
public static function unprotectSpecialChars(&$string, $keep_escaped_chars = null)
{
$unescaped_chars = array_keys(self::$protected_characters);
$keep_escaped_chars = !is_null($keep_escaped_chars) ? \RegularLabs\Library\ArrayHelper::toArray($keep_escaped_chars) : [];
if (!empty($keep_escaped_chars)) {
array_walk($unescaped_chars, function (&$char, $key, $keep_escaped_chars) {
if (is_array($keep_escaped_chars) && !in_array($char, $keep_escaped_chars, \true)) {
return;
}
$char = '\\' . $char;
}, $keep_escaped_chars);
}
// replace special markup with unescaped characters
$string = str_replace(array_values(self::$protected_characters), $unescaped_chars, $string);
}
/**
* Get the value from a found attribute match
*
* @param array $match
* @param array $known_boolean_keys
* @param array $keep_escaped_chars
*
* @return bool|int|string
*/
private static function getAttributeValueFromMatch($match, $known_boolean_keys = [], $keep_escaped_chars = [','], $convert_numerals = \true)
{
$value = $match['value'];
self::unprotectSpecialChars($value, $keep_escaped_chars);
if (is_numeric($value) && (in_array($match['key'], $known_boolean_keys, \true) || in_array(strtolower($match['key']), $known_boolean_keys, \true))) {
$value = $value ? 'true' : 'false';
}
// Convert numeric values to ints/floats
if ($convert_numerals && is_numeric($value) && \RegularLabs\Library\RegEx::match('^[0-9\.]+$', $value)) {
$value = $value + 0;
}
// Convert boolean values to actual booleans
if ($value === 'true' || $value === \true) {
return $match['not'] ? \false : \true;
}
if ($value === 'false' || $value === \false) {
return $match['not'] ? \true : \false;
}
return $match['not'] ? '!NOT!' . $value : $value;
}
/**
* Get the attributes from a plugin style div tag
*/
private static function getDivAttributes(string $string): object
{
if (str_contains($string, '="')) {
return self::getAttributesFromString($string);
}
$parts = explode('|', $string);
$attributes = (object) [];
foreach ($parts as $e) {
if (!str_contains($e, ':')) {
continue;
}
[$key, $val] = explode(':', $e, 2);
$attributes->{$key} = $val;
}
return $attributes;
}
}

View File

@ -0,0 +1,640 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Access\Access as JAccess;
class Protect
{
static $html_safe_end = '___/RL_PROTECTED___';
static $html_safe_start = '___RL_PROTECTED___';
static $html_safe_tags_end = '___/RL_PROTECTED_TAGS___';
static $html_safe_tags_start = '___RL_PROTECTED_TAGS___';
static $protect_end = '___RL_PROTECTED___ -->';
static $protect_start = '<!-- ___RL_PROTECTED___';
static $protect_tags_end = '___RL_PROTECTED_TAGS___ -->';
static $protect_tags_start = '<!-- ___RL_PROTECTED_TAGS___';
static $sourcerer_characters = '{.}';
static $sourcerer_tag;
/**
* Check if article passes security levels
*/
public static function articlePassesSecurity(?object &$article, array|string $securtiy_levels = []): bool
{
if (!isset($article->created_by)) {
return \true;
}
if (empty($securtiy_levels)) {
return \true;
}
if (is_string($securtiy_levels)) {
$securtiy_levels = [$securtiy_levels];
}
if (!is_array($securtiy_levels) || in_array('-1', $securtiy_levels)) {
return \true;
}
// Lookup group level of creator
$user_groups = new JAccess();
$user_groups = $user_groups->getGroupsByUser($article->created_by);
// Return true if any of the security levels are found in the users groups
return count(array_intersect($user_groups, $securtiy_levels)) > 0;
}
/**
* Replace any protected text to original
*/
public static function convertProtectionToHtmlSafe(string &$string): void
{
$string = str_replace([self::$protect_start, self::$protect_end, self::$protect_tags_start, self::$protect_tags_end], [self::$html_safe_start, self::$html_safe_end, self::$html_safe_tags_start, self::$html_safe_tags_end], $string);
}
/**
* Get the html end comment tags
*/
public static function getCommentEndTag(string $name = ''): string
{
return '<!-- END: ' . $name . ' -->';
}
/**
* Get the html start comment tags
*/
public static function getCommentStartTag(string $name = ''): string
{
return '<!-- START: ' . $name . ' -->';
}
/**
* Get the html comment tags
*/
public static function getCommentTags(string $name = ''): array
{
return [self::getCommentStartTag($name), self::getCommentEndTag($name)];
}
/**
* Return the Regular Expressions string to match:
* The edit form
*/
public static function getFormRegex(array|string $form_classes = []): string
{
$form_classes = \RegularLabs\Library\ArrayHelper::toArray($form_classes);
return '(<form\s[^>]*(' . '(id|name)="(adminForm|postform|submissionForm|default_action_user|seblod_form|spEntryForm)"' . '|action="[^"]*option=com_myjspace&(amp;)?view=see"' . (!empty($form_classes) ? '|class="([^"]* )?(' . implode('|', $form_classes) . ')( [^"]*)?"' : '') . '))';
}
/**
* Get the start and end parts for the inline comment tags for scripts/styles
*/
public static function getInlineCommentTags(string $name = '', ?string $type = '', bool $regex = \false): array
{
if ($regex) {
return self::getInlineCommentTagsRegEx($name, $type);
}
if ($type) {
$type = ': ' . $type;
}
$start = '/* START: ' . $name . $type . ' */';
$end = '/* END: ' . $name . $type . ' */';
return [$start, $end];
}
/**
* Get the start and end parts for the inline comment tags for scripts/styles
*/
public static function getInlineCommentTagsRegEx(string $name = '', ?string $type = ''): array
{
$name = str_replace(' ', ' ?', \RegularLabs\Library\RegEx::quote($name));
$type = $type ? ':? ' . \RegularLabs\Library\RegEx::quote($type) : '(:? [a-z0-9]*)?';
$start = '/\* START: ' . $name . $type . ' \*/';
$end = '/\* END: ' . $name . $type . ' \*/';
return [$start, $end];
}
/**
* Create a html comment from given comment string
*/
public static function getMessageCommentTag(string $name, string $comment): string
{
[$start, $end] = self::getMessageCommentTags($name);
return $start . $comment . $end;
}
/**
* Get the start and end parts for the html message comment tag
*/
public static function getMessageCommentTags(string $name = ''): array
{
return ['<!-- ' . $name . ' Message: ', ' -->'];
}
/**
* Return the sourcerer tag name and characters
*/
public static function getSourcererTag(): array
{
if (!is_null(self::$sourcerer_tag)) {
return [self::$sourcerer_tag, self::$sourcerer_characters];
}
$parameters = \RegularLabs\Library\Parameters::getPlugin('sourcerer');
self::$sourcerer_tag = $parameters->syntax_word ?? '';
self::$sourcerer_characters = $parameters->tag_characters ?? '{.}';
return [self::$sourcerer_tag, self::$sourcerer_characters];
}
/**
* Check if the component is installed
*/
public static function isComponentInstalled(string $extension_alias): bool
{
return file_exists(JPATH_ADMINISTRATOR . '/components/com_' . $extension_alias . '/' . $extension_alias . '.xml');
}
/**
* Check if page should be protected for given extension
*/
public static function isDisabledByUrl(string $extension_alias = ''): bool
{
// return if disabled via url
return $extension_alias && \RegularLabs\Library\Input::get('disable_' . $extension_alias);
}
/**
* @deprecated Use isDisabledByUrl() and isRestrictedPage()
*/
public static function isProtectedPage(string $extension_alias = '', bool $hastags = \false, array $exclude_formats = []): bool
{
if (self::isDisabledByUrl($extension_alias)) {
return \true;
}
return self::isRestrictedPage($hastags, $exclude_formats);
}
/**
* Check if the page is a restricted component
*/
public static function isRestrictedComponent(array|string $restricted_components, string $area = 'component'): bool
{
if ($area != 'component' && !($area == 'article' && \RegularLabs\Library\Input::get('option', '') == 'com_content')) {
return \false;
}
$restricted_components = \RegularLabs\Library\ArrayHelper::toArray(str_replace('|', ',', $restricted_components));
$restricted_components = \RegularLabs\Library\ArrayHelper::clean($restricted_components);
if (!empty($restricted_components) && in_array(\RegularLabs\Library\Input::get('option', ''), $restricted_components, \true)) {
return \true;
}
if (\RegularLabs\Library\Input::get('option', '') == 'com_acymailing' && !in_array(\RegularLabs\Library\Input::get('ctrl', ''), ['user', 'archive'], \true) && !in_array(\RegularLabs\Library\Input::get('view', ''), ['user', 'archive'], \true)) {
return \true;
}
return \false;
}
/**
* Check if page should be protected for given extension
*/
public static function isRestrictedPage(bool $hastags = \false, array $restricted_formats = []): bool
{
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
// return if current page is in protected formats
// return if current page is an image
// return if current page is an installation page
// return if current page is Regular Labs QuickPage
// return if current page is a JoomFish or Josetta page
$is_restricted = in_array(\RegularLabs\Library\Input::get('format', ''), $restricted_formats, \true) || in_array(\RegularLabs\Library\Input::get('type', ''), ['image', 'img'], \true) || in_array(\RegularLabs\Library\Input::get('task', ''), ['install.install', 'install.ajax_upload'], \true) || $hastags && \RegularLabs\Library\Input::getInt('rl_qp', 0) || $hastags && in_array(\RegularLabs\Library\Input::get('option', ''), ['com_joomfishplus', 'com_josetta'], \true) || \RegularLabs\Library\Document::isClient('administrator') && in_array(\RegularLabs\Library\Input::get('option', ''), ['com_jdownloads'], \true);
return $cache->set($is_restricted);
}
/**
* Check if the component is installed
*/
public static function isSystemPluginInstalled(string $extension_alias): bool
{
return file_exists(JPATH_PLUGINS . '/system/' . $extension_alias . '/' . $extension_alias . '.xml');
}
/**
* Protect text by given regex
*/
public static function protectByRegex(string &$string, string $regex, int|string $group = 0): void
{
\RegularLabs\Library\RegEx::matchAll($regex, $string, $matches);
if (empty($matches)) {
return;
}
$replacements = [];
foreach ($matches as $match) {
if (isset($replacements[$match[0]])) {
continue;
}
$replacements[$match[0]] = self::protectString($match[$group] ?? $match[0]);
}
$string = str_replace(array_keys($replacements), $replacements, $string);
}
/**
* Protect all text based form fields
*/
public static function protectFields(string &$string, array $search_strings = []): void
{
// No specified strings tags found in the string
if (!self::containsStringsToProtect($string, $search_strings)) {
return;
}
$parts = \RegularLabs\Library\StringHelper::split($string, ['</label>', '</select>']);
foreach ($parts as &$part) {
if (!self::containsStringsToProtect($part, $search_strings)) {
continue;
}
self::protectFieldsPart($part);
}
$string = implode('', $parts);
}
/**
* Protect complete AdminForm
*/
public static function protectForm(string &$string, array $tags = [], bool $include_closing_tags = \true, array|string $form_classes = []): void
{
if (!\RegularLabs\Library\Document::isEditPage()) {
return;
}
[$tags, $protected_tags] = self::prepareTags($tags, $include_closing_tags);
$string = \RegularLabs\Library\RegEx::replace(self::getFormRegex($form_classes), '<!-- TMP_START_EDITOR -->\1', $string);
$string = explode('<!-- TMP_START_EDITOR -->', $string);
foreach ($string as $i => &$string_part) {
if (empty($string_part) || !fmod($i, 2)) {
continue;
}
self::protectFormPart($string_part, $tags, $protected_tags);
}
$string = implode('', $string);
}
/**
* Protect all html comment tags
*/
public static function protectHtmlCommentTags(string &$string, array $ignores = []): void
{
$regex = '<\!--.*?-->';
if (!empty($ignores) && \RegularLabs\Library\StringHelper::contains($string, $ignores)) {
$regex = '<\!--((?!' . \RegularLabs\Library\RegEx::quote($ignores) . ').)*-->';
}
self::protectByRegex($string, $regex);
}
/**
* Protect all html tags with some type of attributes/content
*/
public static function protectHtmlTags(string &$string): void
{
// protect comment tags
self::protectHtmlCommentTags($string);
// protect html tags
self::protectByRegex($string, '<[a-z][^>]*(?:="[^"]*"|=\'[^\']*\')+[^>]*>');
}
/**
* Protect the script tags
*/
public static function protectScripts(string &$string): void
{
if (!str_contains($string, '</script>')) {
return;
}
self::protectByRegex($string, '<script[\s>].*?</script>');
}
/**
* Protect all Sourcerer blocks
*/
public static function protectSourcerer(string &$string): void
{
[$tag, $characters] = self::getSourcererTag();
if (empty($tag)) {
return;
}
[$start, $end] = explode('.', $characters);
if (!str_contains($string, $start . '/' . $tag . $end)) {
return;
}
$regex = \RegularLabs\Library\RegEx::quote($start . $tag) . '[\s\}].*?' . \RegularLabs\Library\RegEx::quote($start . '/' . $tag . $end);
\RegularLabs\Library\RegEx::matchAll($regex, $string, $matches, null, \PREG_PATTERN_ORDER);
if (empty($matches)) {
return;
}
$matches = array_unique($matches[0]);
foreach ($matches as $match) {
$string = str_replace($match, self::protectString($match), $string);
}
}
/**
* Encode string
*/
public static function protectString(string $string, bool $is_tag = \false): string
{
if ($is_tag) {
return self::$protect_tags_start . base64_encode($string) . self::$protect_tags_end;
}
return self::$protect_start . base64_encode($string) . self::$protect_end;
}
/**
* Protect given plugin style tags
*/
public static function protectTags(string &$string, array $tags = [], bool $include_closing_tags = \true): void
{
[$tags, $protected] = self::prepareTags($tags, $include_closing_tags);
$string = str_replace($tags, $protected, $string);
}
/**
* Remove area comments in html
*/
public static function removeAreaTags(string &$string, string $prefix = ''): void
{
$string = \RegularLabs\Library\RegEx::replace('<!-- (START|END): ' . $prefix . '_[A-Z]+ -->', '', $string, 's');
}
/**
* Remove comments in html
*/
public static function removeCommentTags(string &$string, string $name = ''): void
{
[$start, $end] = self::getCommentTags($name);
$string = str_replace([$start, $end, htmlentities($start), htmlentities($end), urlencode($start), urlencode($end)], '', $string);
$start = str_replace(' -->', 'REGEX_PLACEHOLDER -->', $start);
$end = str_replace(' -->', 'REGEX_PLACEHOLDER -->', $end);
$regex = '(' . \RegularLabs\Library\RegEx::quote($start) . '|' . \RegularLabs\Library\RegEx::quote($end) . ')';
$regex = str_replace('REGEX_PLACEHOLDER', '(:? [a-z0-9]*)?', $regex);
$string = \RegularLabs\Library\RegEx::replace($regex, '', $string);
[$start, $end] = self::getMessageCommentTags($name);
$string = \RegularLabs\Library\RegEx::replace(\RegularLabs\Library\RegEx::quote($start) . '.*?' . \RegularLabs\Library\RegEx::quote($end), '', $string);
}
/**
* Remove tags from tag attributes
*/
public static function removeFromHtmlTagAttributes(string &$string, array $tags, string $attributes = 'ALL', bool $include_closing_tags = \true): void
{
[$tags, $protected] = self::prepareTags($tags, $include_closing_tags);
if ($attributes == 'ALL') {
$attributes = ['[a-z][a-z0-9-_]*'];
}
if (!is_array($attributes)) {
$attributes = [$attributes];
}
\RegularLabs\Library\RegEx::matchAll('\s(?:' . implode('|', $attributes) . ')\s*=\s*".*?"', $string, $matches, null, \PREG_PATTERN_ORDER);
if (empty($matches) || empty($matches[0])) {
return;
}
$matches = array_unique($matches[0]);
// preg_quote all tags
$tags_regex = \RegularLabs\Library\RegEx::quote($tags) . '.*?\}';
foreach ($matches as $match) {
if (!\RegularLabs\Library\StringHelper::contains($match, $tags)) {
continue;
}
$title = $match;
$title = \RegularLabs\Library\RegEx::replace($tags_regex, '', $title);
$string = \RegularLabs\Library\StringHelper::replaceOnce($match, $title, $string);
}
}
/**
* Remove tags from title tags
*/
public static function removeFromHtmlTagContent(string &$string, array $tags, bool $include_closing_tags = \true, array $html_tags = ['title']): void
{
[$tags, $protected] = self::prepareTags($tags, $include_closing_tags);
if (!is_array($html_tags)) {
$html_tags = [$html_tags];
}
\RegularLabs\Library\RegEx::matchAll('(<(' . implode('|', $html_tags) . ')(?:\s[^>]*?)>)(.*?)(</\2>)', $string, $matches);
if (empty($matches)) {
return;
}
foreach ($matches as $match) {
$content = $match[3];
foreach ($tags as $tag) {
$content = \RegularLabs\Library\RegEx::replace(\RegularLabs\Library\RegEx::quote($tag) . '.*?\}', '', $content);
}
$string = str_replace($match[0], $match[1] . $content . $match[4], $string);
}
}
/**
* Remove inline comments in scrips and styles
*/
public static function removeInlineComments(string &$string, string $name): void
{
[$start, $end] = \RegularLabs\Library\Protect::getInlineCommentTags($name, '', \true);
$string = \RegularLabs\Library\RegEx::replace('(' . $start . '|' . $end . ')', "\n", $string);
}
/**
* Remove leftover plugin tags
*/
public static function removePluginTags(string &$string, array $tags, string $character_start = '{', string $character_end = '}', bool $keep_content = \true): void
{
$regex_character_start = \RegularLabs\Library\RegEx::quote($character_start);
$regex_character_end = \RegularLabs\Library\RegEx::quote($character_end);
foreach ($tags as $tag) {
if (!is_array($tag)) {
$tag = [$tag, $tag];
}
if (count($tag) < 2) {
$tag = [$tag[0], $tag[0]];
}
if (!\RegularLabs\Library\StringHelper::contains($string, $character_start . '/' . $tag[1] . $character_end)) {
continue;
}
$regex = $regex_character_start . \RegularLabs\Library\RegEx::quote($tag[0]) . '(?:\s.*?)?' . $regex_character_end . '(.*?)' . $regex_character_start . '/' . \RegularLabs\Library\RegEx::quote($tag[1]) . $regex_character_end;
$replace = $keep_content ? '\1' : '';
$string = \RegularLabs\Library\RegEx::replace($regex, $replace, $string);
}
}
/**
* Replace any protected text to original
*/
public static function unprotect(string|array &$string): void
{
if (is_array($string)) {
foreach ($string as &$part) {
self::unprotect($part);
}
return;
}
self::unprotectByDelimiters($string, [self::$protect_tags_start, self::$protect_tags_end]);
self::unprotectByDelimiters($string, [self::$protect_start, self::$protect_end]);
if (\RegularLabs\Library\StringHelper::contains($string, [self::$protect_tags_start, self::$protect_tags_end, self::$protect_start, self::$protect_end])) {
self::unprotect($string);
}
}
/**
* Replace any protected text to original
*/
public static function unprotectHtmlSafe(string &$string): void
{
$string = str_replace([self::$html_safe_start, self::$html_safe_end, self::$html_safe_tags_start, self::$html_safe_tags_end], [self::$protect_start, self::$protect_end, self::$protect_tags_start, self::$protect_tags_end], $string);
self::unprotect($string);
}
/**
* Replace any protected tags to original
*/
public static function unprotectTags(string &$string, array $tags = [], bool $include_closing_tags = \true): void
{
[$tags, $protected] = self::prepareTags($tags, $include_closing_tags);
$string = str_replace($protected, $tags, $string);
}
/**
* Wraps a style or javascript declaration with comment tags
*/
public static function wrapDeclaration(string $content = '', string $name = '', string $type = 'styles', bool $minify = \true): string
{
if (empty($name)) {
return $content;
}
[$start, $end] = self::getInlineCommentTags($name, $type);
$spacer = $minify ? ' ' : "\n";
return $start . $spacer . $content . $spacer . $end;
}
/**
* Wrap string in comment tags
*/
public static function wrapInCommentTags(string $name, string $string): string
{
[$start, $end] = self::getCommentTags($name);
return $start . $string . $end;
}
/**
* Wraps a javascript declaration with comment tags
*/
public static function wrapScriptDeclaration(string $content = '', string $name = '', bool $minify = \true): string
{
return self::wrapDeclaration($content, $name, 'scripts', $minify);
}
/**
* Wraps a stylesheet declaration with comment tags
*/
public static function wrapStyleDeclaration(string $content = '', string $name = '', bool $minify = \true): string
{
return self::wrapDeclaration($content, $name, 'styles', $minify);
}
/**
* Check if the string contains certain substrings to protect
*/
private static function containsStringsToProtect(string $string, array $search_strings = []): bool
{
if (empty($string) || !str_contains($string, '<input') && !str_contains($string, '<textarea') && !str_contains($string, '<select')) {
return \false;
}
// No specified strings tags found in the string
if (!empty($search_strings) && !\RegularLabs\Library\StringHelper::contains($string, $search_strings)) {
return \false;
}
return \true;
}
/**
* Prepare the tags and protected tags array
*/
private static function prepareTags(array|string $tags, $include_closing_tags = \true): array
{
if (!is_array($tags)) {
$tags = [$tags];
}
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
foreach ($tags as $i => $tag) {
if (\RegularLabs\Library\StringHelper::is_alphanumeric($tag[0])) {
$tag = '{' . $tag;
}
$tags[$i] = $tag;
if ($include_closing_tags) {
$tags[] = \RegularLabs\Library\RegEx::replace('^([^a-z0-9]+)', '\1/', $tag);
}
}
return $cache->set([$tags, self::protectArray($tags, \true)]);
}
/**
* Encode array of strings
*/
private static function protectArray(array $array, bool $is_tag = \false): array
{
foreach ($array as &$string) {
$string = self::protectString($string, $is_tag);
}
return $array;
}
/**
* Protect the input fields in the string
*/
private static function protectFieldsInputFields(string &$string): void
{
if (!str_contains($string, '<input')) {
return;
}
$type_values = '(?:text|email|hidden)';
// must be of certain type
$param_type = '\s+type\s*=\s*(?:"' . $type_values . '"|\'' . $type_values . '\'])';
// must have a non-empty value or placeholder attribute
$param_value = '\s+(?:value|placeholder)\s*=\s*(?:"[^"]+"|\'[^\']+\'])';
// Regex to match any other parameter
$params = '(?:\s+[a-z][a-z0-9-_]*(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|[0-9]+))?)*';
self::protectByRegex($string, '(?:(?:' . '<input' . $params . $param_type . $params . $param_value . $params . '\s*/?>' . '|<input' . $params . $param_value . $params . $param_type . $params . '\s*/?>' . ')\s*)+');
}
/**
* Protect the fields in the string
*/
private static function protectFieldsPart(string &$string): void
{
self::protectFieldsTextAreas($string);
self::protectFieldsInputFields($string);
}
/**
* Protect the textarea fields in the string
*/
private static function protectFieldsTextAreas(string &$string): void
{
if (!str_contains($string, '<textarea')) {
return;
}
// Only replace non-empty textareas
// Todo: maybe also prevent empty textareas but with a non-empty placeholder attribute
// Temporarily replace empty textareas
$temp_tag = '___TEMP_TEXTAREA___';
$string = \RegularLabs\Library\RegEx::replace('<textarea((?:\s[^>]*)?)>(\s*)</textarea>', '<' . $temp_tag . '\1>\2</' . $temp_tag . '>', $string);
self::protectByRegex($string, '(?:' . '<textarea.*?</textarea>' . '\s*)+');
// Replace back the temporarily replaced empty textareas
$string = str_replace($temp_tag, 'textarea', $string);
}
/**
* Protect part of the AdminForm
*/
private static function protectFormPart(string &$string, array $tags = [], array $protected_tags = []): void
{
if (!str_contains($string, '</form>')) {
return;
}
// Protect entire form
if (empty($tags)) {
$form_parts = explode('</form>', $string, 2);
$form_parts[0] = self::protectString($form_parts[0] . '</form>');
$string = implode('', $form_parts);
return;
}
$regex_tags = \RegularLabs\Library\RegEx::quote($tags);
if (!\RegularLabs\Library\RegEx::match($regex_tags, $string)) {
return;
}
$form_parts = explode('</form>', $string, 2);
// protect tags only inside form fields
\RegularLabs\Library\RegEx::matchAll('(?:<textarea[^>]*>.*?<\/textarea>|<input[^>]*>)', $form_parts[0], $matches, null, \PREG_PATTERN_ORDER);
if (empty($matches)) {
return;
}
$matches = array_unique($matches[0]);
foreach ($matches as $match) {
$field = str_replace($tags, $protected_tags, $match);
$form_parts[0] = str_replace($match, $field, $form_parts[0]);
}
$string = implode('</form>', $form_parts);
}
private static function unprotectByDelimiters(string &$string, array $delimiters): void
{
if (!\RegularLabs\Library\StringHelper::contains($string, $delimiters)) {
return;
}
$regex = \RegularLabs\Library\RegEx::preparePattern(\RegularLabs\Library\RegEx::quote($delimiters), 's', $string);
$parts = preg_split($regex, $string);
foreach ($parts as $i => &$part) {
if ($i % 2 == 0) {
continue;
}
$part = base64_decode($part);
}
$string = implode('', $parts);
}
}

View File

@ -0,0 +1,166 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class RegEx
{
/**
* Perform a regular expression match
*/
public static function match(string $pattern, string $string, &$match = null, ?string $options = null, int $flags = 0): int
{
if (empty($string) || empty($pattern)) {
return \false;
}
$pattern = self::preparePattern($pattern, $options, $string);
$result = preg_match($pattern, $string, $match, $flags);
// Remove all numeric keys except 0
$no_numeric_values = array_filter($match, fn($key) => !is_int($key) || $key === 0, \ARRAY_FILTER_USE_KEY);
// If the leftover array counts more than 2 (so contains named groups), replace $match
if (count($no_numeric_values) > 1) {
$match = $no_numeric_values;
}
return $result;
}
/**
* Perform a global regular expression match
*/
public static function matchAll(string $pattern, string $string, &$matches = null, ?string $options = null, int $flags = \PREG_SET_ORDER): int
{
if (empty($string) || empty($pattern)) {
$matches = [];
return \false;
}
$pattern = self::preparePattern($pattern, $options, $string);
$result = preg_match_all($pattern, $string, $matches, $flags);
if (!$result) {
return \false;
}
if ($flags == \PREG_OFFSET_CAPTURE) {
// Remove all numeric keys except 0
$no_numeric_values = array_filter($matches, fn($key) => !is_int($key) || $key === 0, \ARRAY_FILTER_USE_KEY);
// If the leftover array counts less than 2 (so no named groups), don't continue
if (count($no_numeric_values) < 2) {
return $result;
}
$matches = $no_numeric_values;
return $result;
}
if ($flags != \PREG_SET_ORDER) {
return $result;
}
foreach ($matches as &$match) {
// Remove all numeric keys except 0
$no_numeric_values = array_filter($match, fn($key) => !is_int($key) || $key === 0, \ARRAY_FILTER_USE_KEY);
// If the leftover array counts less than 2 (so no named groups), don't continue
if (count($no_numeric_values) < 2) {
break;
}
$match = $no_numeric_values;
}
return $result;
}
/**
* preg_quote the given string or array of strings
*/
public static function nameGroup(string $data, string $name = ''): string
{
return '(?<' . $name . '>' . $data . ')';
}
/**
* Make a string a valid regular expression pattern
*/
public static function preparePattern(string|array $pattern, ?string $options = null, string $string = ''): string|array
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$pattern, $options, $string]);
if (!is_null($array)) {
return $array;
}
if (!str_starts_with($pattern, '#')) {
$options = !is_null($options) ? $options : 'si';
$pattern = '#' . $pattern . '#' . $options;
}
if (\RegularLabs\Library\StringHelper::detectUTF8($string)) {
// use utf-8
return $pattern . 'u';
}
return $pattern;
}
/**
* preg_quote the given string or array of strings
*/
public static function quote(string|array $data, string $name = '', string $delimiter = '#'): string
{
if (is_array($data)) {
if (count($data) === 1) {
return self::quote(array_pop($data), $name, $delimiter);
}
$array = self::quoteArray($data, $delimiter);
$prefix = '?:';
if (!empty($name)) {
$prefix = $name ? '?<' . $name . '>' : '';
}
return '(' . $prefix . implode('|', $array) . ')';
}
if (!empty($name)) {
return '(?<' . $name . '>' . preg_quote($data, $delimiter) . ')';
}
return preg_quote($data, $delimiter);
}
/**
* preg_quote the given array of strings
*/
public static function quoteArray(array $array, string $delimiter = '#'): array
{
array_walk($array, function (&$part, $key, $delimiter) {
$part = self::quote($part, '', $delimiter);
}, $delimiter);
return $array;
}
/**
* Perform a regular expression search and replace
*/
public static function replace(string $pattern, string $replacement, string $string, ?string $options = null, int $limit = -1, ?int &$count = null): string|null
{
if (empty($string) || empty($pattern)) {
return $string;
}
$pattern = self::preparePattern($pattern, $options, $string);
return preg_replace($pattern, $replacement, $string, $limit, $count);
}
/**
* Perform a regular expression search and replace once
*/
public static function replaceOnce(string $pattern, string $replacement, string $string, ?string $options = null): string
{
return self::replace($pattern, $replacement, $string, $options, 1);
}
/**
* Perform a regular expression split
*/
public static function split(string $pattern, string $string, ?string $options = null, int $limit = -1, int $flags = \PREG_SPLIT_DELIM_CAPTURE): array
{
if (empty($string) || empty($pattern)) {
return [$string];
}
$pattern = self::preparePattern($pattern, $options, $string);
return preg_split($pattern, $string, $limit, $flags);
}
/**
* reverse preg_quote the given string
*/
public static function unquote(string $string, string $delimiter = '#'): string
{
return strtr($string, ['\\' . $delimiter => $delimiter, '\.' => '.', '\\\\' => '\\', '\+' => '+', '\*' => '*', '\?' => '?', '\[' => '[', '\^' => '^', '\]' => ']', '\$' => '$', '\(' => '(', '\)' => ')', '\{' => '{', '\}' => '}', '\=' => '=', '\!' => '!', '\<' => '<', '\>' => '>', '\|' => '|', '\:' => ':', '\-' => '-']);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Form\FormHelper as JFormHelper;
class ShowOn
{
public static function close()
{
return '</div>';
}
public static function open(string $condition = '', string $formControl = '', string $group = '', string $class = ''): string
{
if (!$condition) {
return self::close();
}
\RegularLabs\Library\Document::useScript('showon');
$json = json_encode(JFormHelper::parseShowOnConditions($condition, $formControl, $group));
return '<div data-showon=\'' . $json . '\' class="hidden ' . $class . '"">';
}
public static function show(string $string = '', string $condition = '', string $formControl = '', string $group = '', bool $animate = \true, string $class = ''): string
{
if (!$condition || !$string) {
return $string;
}
return self::open($condition, $formControl, $group, $animate, $class) . $string . self::close();
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class SimpleCategory
{
public static function save(string $table, int $item_id, string $category, string $id_column = 'id'): void
{
$db = \RegularLabs\Library\DB::get();
$query = $db->getQuery(\true)->select(\RegularLabs\Library\DB::quoteName($id_column))->from(\RegularLabs\Library\DB::quoteName('#__' . $table))->where(\RegularLabs\Library\DB::quoteName($id_column) . ' = ' . $item_id);
$item_exists = $db->setQuery($query)->loadResult();
if ($item_exists) {
$query = $db->getQuery(\true)->update(\RegularLabs\Library\DB::quoteName('#__' . $table))->set(\RegularLabs\Library\DB::quoteName('category') . ' = ' . \RegularLabs\Library\DB::quote($category))->where(\RegularLabs\Library\DB::quoteName($id_column) . ' = ' . $item_id);
$db->setQuery($query)->execute();
return;
}
$query = 'SHOW COLUMNS FROM `#__' . $table . '`';
$db->setQuery($query);
$columns = $db->loadColumn();
$values = array_fill_keys($columns, '');
$values[$id_column] = $item_id;
$values['category'] = $category;
$query = $db->getQuery(\true)->insert(\RegularLabs\Library\DB::quoteName('#__' . $table))->columns(\RegularLabs\Library\DB::quoteName($columns))->values(implode(',', \RegularLabs\Library\DB::quote($values)));
$db->setQuery($query)->execute();
}
}

View File

@ -0,0 +1,615 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\String\Normalise as JNormalise;
use Normalizer;
class StringHelper extends \Joomla\String\StringHelper
{
/**
* Adds postfix to a string
*/
public static function addPostfix(string $string, string $postfix): string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $postfix]);
if (!is_null($array)) {
return $array;
}
if (empty($postfix)) {
return $string;
}
if (!is_string($string) && !is_numeric($string)) {
return $string;
}
return $string . $postfix;
}
/**
* Adds prefix to a string
*/
public static function addPrefix(string $string, string $prefix, bool $keep_leading_slash = \true): string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $prefix, $keep_leading_slash]);
if (!is_null($array)) {
return $array;
}
if (empty($prefix)) {
return $string;
}
if (!is_string($string) && !is_numeric($string)) {
return $string;
}
if ($keep_leading_slash && !empty($string) && $string[0] == '/') {
return $string[0] . $prefix . substr($string, 1);
}
return $prefix . $string;
}
public static function applyConversion(string $type, string $string, ?object $attributes): string
{
switch ($type) {
case 'escape':
return addslashes($string);
case 'lowercase':
return self::toLowerCase($string);
case 'uppercase':
return self::toUpperCase($string);
case 'notags':
return strip_tags($string);
case 'nowhitespace':
return str_replace(' ', '', strip_tags($string));
case 'toalias':
return \RegularLabs\Library\Alias::get($string);
case 'replace':
if (!isset($attributes->from)) {
return $string;
}
$case_insensitive = isset($attributes->{'case-insensitive'}) && $attributes->{'case-insensitive'} == 'true';
return \RegularLabs\Library\RegEx::replace($attributes->from, $attributes->to ?? '', $string, $case_insensitive ? 'is' : 's');
default:
return $string;
}
}
/**
* Check if any of the needles are found in any of the haystacks
*/
public static function contains(string|array $haystacks, string|array $needles): bool
{
$haystacks = \RegularLabs\Library\ArrayHelper::toArray($haystacks);
$needles = \RegularLabs\Library\ArrayHelper::toArray($needles);
if (empty($haystacks) || empty($needles)) {
return \false;
}
foreach ($haystacks as $haystack) {
foreach ($needles as $needle) {
if (!str_contains($haystack, $needle)) {
continue;
}
return \true;
}
}
return \false;
}
/**
* Converts a string to a UTF-8 encoded string
*/
public static function convertToUtf8(string $string = ''): string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
if (self::detectUTF8($string)) {
// Already UTF-8, so skip
return $string;
}
if (!function_exists('iconv')) {
// Still need to find a stable fallback
return $string;
}
$utf8_string = @iconv('UTF8', 'UTF-8//IGNORE', $string);
if (empty($utf8_string)) {
return $string;
}
return $utf8_string;
}
public static function countWords(string $string, int|string $format = 0): array|int
{
$format = match ($format) {
'array', 1 => 'array',
'numbered', 2 => 'numbered',
default => 'number',
};
$words = preg_split('#[^\p{L}\p{N}\']+#u', $string, -1, $format == 'numbered' ? \PREG_SPLIT_OFFSET_CAPTURE : null);
switch ($format) {
case 'array':
return $words;
case 'numbered':
$numbered = [];
foreach ($words as $word) {
$numbered[$word[1]] = $word[0];
}
return $numbered;
case 'number':
default:
return count($words);
}
}
/**
* Check whether string is a UTF-8 encoded string
*/
public static function detectUTF8(string $string = ''): bool
{
// Try to check the string via the mb_check_encoding function
if (function_exists('mb_check_encoding')) {
return mb_check_encoding($string, 'UTF-8');
}
// Otherwise: Try to check the string via the iconv function
if (function_exists('iconv')) {
$converted = iconv('UTF-8', 'UTF-8//IGNORE', $string);
return md5($converted) == md5($string);
}
// As last fallback, check if the preg_match finds anything using the unicode flag
return preg_match('#.#u', $string);
}
public static function escape(string $string): string
{
return htmlspecialchars($string, \ENT_QUOTES, 'UTF-8');
}
/**
* Converts a camelcased string to a space separated string
* eg: FooBar => Foo Bar
*/
public static function fromCamelCase(string $string): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
$parts = JNormalise::fromCamelCase($string, \true);
$parts = \RegularLabs\Library\ArrayHelper::trim($parts);
return implode(' ', $parts);
}
/**
* Decode html entities in string (or array of strings)
*/
public static function html_entity_decoder(string $string, int $quote_style = \ENT_QUOTES, string $encoding = 'UTF-8'): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $quote_style, $encoding]);
if (!is_null($array)) {
return $array;
}
if (!is_string($string)) {
return $string;
}
$string = html_entity_decode($string, $quote_style | \ENT_HTML5, $encoding);
$string = str_replace(chr(194) . chr(160), ' ', $string);
return $string;
}
/**
* Check if string is alphanumerical
*/
public static function is_alphanumeric(string $string): bool
{
if (function_exists('ctype_alnum')) {
return (bool) ctype_alnum($string);
}
return (bool) \RegularLabs\Library\RegEx::match('^[a-z0-9]+$', $string);
}
/**
* Check if string is a valid key / alias (alphanumeric with optional _ or - chars)
*/
public static function is_key(string $string): bool
{
return \RegularLabs\Library\RegEx::match('^[a-z][a-z0-9-_]*$', trim($string));
}
/**
* UTF-8 aware alternative to lcfirst
*/
public static function lcfirst(string $string): string
{
switch (utf8_strlen($string)) {
case 0:
return '';
case 1:
return utf8_strtolower($string);
default:
preg_match('/^(.{1})(.*)$/us', $string, $matches);
return utf8_strtolower($matches[1]) . $matches[2];
}
}
/**
* Converts the first letter to lowercase
* eg: FooBar => fooBar
* eg: Foo bar => foo bar
* eg: FOO_BAR => fOO_BAR
*/
public static function lowerCaseFirst(string|array|object $string): string|array|null
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_string($string)) {
return $array;
}
return self::lcfirst($string);
}
public static function minify(string $string): string
{
// place new lines around string to make regex searching easier
$string = "\n" . $string . "\n";
// Remove comment lines
$string = \RegularLabs\Library\RegEx::replace('\n\s*//.*?\n', '', $string);
// Remove comment blocks
$string = \RegularLabs\Library\RegEx::replace('/\*.*?\*/', '', $string);
// Remove enters
$string = \RegularLabs\Library\RegEx::replace('\n\s*', ' ', $string);
// Remove surrounding whitespace
$string = trim($string);
return $string;
}
/**
* Normalizes the input provided and returns the normalized string
*/
public static function normalize(string $string, bool $to_lowercase = \false): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $to_lowercase]);
if (!is_null($array)) {
return $array;
}
// Normalizer-class missing!
if (class_exists('Normalizer', \false)) {
$string = Normalizer::normalize($string);
}
return $to_lowercase ? self::toLowerCase($string) : $string;
}
/**
* Removes html tags from string
*/
public static function removeHtml(string $string, bool $remove_comments = \false): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $remove_comments]);
if (!is_null($array)) {
return $array;
}
return \RegularLabs\Library\Html::removeHtmlTags($string, $remove_comments);
}
/**
* Removes the trailing part of a string if it matches the given $postfix
*/
public static function removePostfix(string $string, string $postfix): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $postfix]);
if (!is_null($array)) {
return $array;
}
if (empty($string) || empty($postfix)) {
return $string;
}
if (!is_string($string) && !is_numeric($string)) {
return $string;
}
$string_length = strlen($string);
$postfix_length = strlen($postfix);
$start = $string_length - $postfix_length;
if (substr($string, $start) !== $postfix) {
return $string;
}
return substr($string, 0, $start);
}
/**
* Removes the first part of a string if it matches the given $prefix
*/
public static function removePrefix(string $string, string $prefix, bool $keep_leading_slash = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $prefix, $keep_leading_slash]);
if (!is_null($array)) {
return $array;
}
if (empty($string) || empty($prefix)) {
return $string;
}
if (!is_string($string) && !is_numeric($string)) {
return $string;
}
$prefix_length = strlen($prefix);
$start = 0;
if ($keep_leading_slash && $prefix[0] !== '/' && $string[0] == '/') {
$start = 1;
}
if (substr($string, $start, $prefix_length) !== $prefix) {
return $string;
}
return substr($string, 0, $start) . substr($string, $start + $prefix_length);
}
/**
* Replace the given replace string once in the main string
*/
public static function replaceOnce(?string $search, ?string $replace, string $string): string
{
if (empty($search) || empty($string)) {
return $string;
}
if (!str_contains($string, $search)) {
return $string;
}
if (empty($replace)) {
$replace = '';
}
return substr_replace($string, $replace, strpos($string, $search), strlen($search));
}
/**
* Split a long string into parts (array)
*
* @param array $delimiters Array of strings to split the string on
* @param int $max_length Maximum length of each part
* @param bool $maximize_parts If true, the different parts will be made as large as possible (combining consecutive short string elements)
*/
public static function split(string $string, array $delimiters = [], int $max_length = 10000, bool $maximize_parts = \true): array
{
// String is too short to split
if (strlen($string) < $max_length) {
return [$string];
}
// No delimiters given or found
if (empty($delimiters) || !self::contains($string, $delimiters)) {
return [$string];
}
// preg_quote all delimiters
$array = preg_split('#(' . \RegularLabs\Library\RegEx::quote($delimiters) . ')#s', $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
if (!$maximize_parts) {
return $array;
}
$new_array = [];
foreach ($array as $i => $part) {
// First element, add to new array
if (!count($new_array)) {
$new_array[] = $part;
continue;
}
$last_part = end($new_array);
$last_key = key($new_array);
// This is the delimiter so add to previous part
if ($i % 2) {
// Concatenate part to previous part
$new_array[$last_key] .= $part;
continue;
}
// If last and current parts are shorter than or same as max_length, then add to previous part
if (strlen($last_part) + strlen($part) <= $max_length) {
$new_array[$last_key] .= $part;
continue;
}
$new_array[] = $part;
}
return $new_array;
}
/**
* Converts a string to a camel case
* eg: foo bar => fooBar
* eg: foo_bar => fooBar
* eg: foo-bar => fooBar
*/
public static function toCamelCase(string $string, bool $keep_duplicate_separators = \true): string
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
if (empty($string)) {
return $string;
}
return JNormalise::toVariable(self::toSpaceSeparated($string, $keep_duplicate_separators));
}
/**
* Converts a string to a certain case
*/
public static function toCase(string $string, string $format, bool $to_lowercase = \true): string
{
$format = strtolower(str_replace('case', '', $format));
return match ($format) {
'lower' => self::toLowerCase($string),
'upper' => self::toUpperCase($string),
'lcfirst', 'lower-first' => self::lowerCaseFirst($string),
'ucfirst', 'upper-first' => self::upperCaseFirst($string),
'title' => self::toTitleCase($string),
'camel' => self::toCamelCase($string),
'dash' => self::toDashCase($string, $to_lowercase),
'dot' => self::toDotCase($string, $to_lowercase),
'pascal' => self::toPascalCase($string),
'underscore' => self::toUnderscoreCase($string, $to_lowercase),
default => $to_lowercase ? self::toLowerCase($string) : $string,
};
}
/**
* Converts a string to a camel case
* eg: FooBar => foo-bar
* eg: foo_bar => foo-bar
*/
public static function toDashCase(string|array|object $string, bool $to_lowercase = \true, bool $keep_duplicate_separators = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $to_lowercase]);
if (!is_string($string)) {
return $array;
}
$string = preg_replace(self::getSeparatorRegex($keep_duplicate_separators), '-', self::toSpaceSeparated($string, $keep_duplicate_separators));
return $to_lowercase ? self::toLowerCase($string) : $string;
}
/**
* Converts a string to a camel case
* eg: FooBar => foo.bar
* eg: foo_bar => foo.bar
*/
public static function toDotCase(string|array|object $string, bool $to_lowercase = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $to_lowercase]);
if (!is_string($string)) {
return $array;
}
$string = self::toDashCase($string, $to_lowercase);
return str_replace('-', '.', $string);
}
/**
* Converts a string to a lower case
* eg: FooBar => foobar
* eg: foo_bar => foo_bar
*/
public static function toLowerCase(string|array|object $string): string|array
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_string($string)) {
return $array;
}
return self::strtolower($string);
}
/**
* Converts a string to a camel case
* eg: foo bar => FooBar
* eg: foo_bar => FooBar
* eg: foo-bar => FooBar
*/
public static function toPascalCase(string $string, bool $keep_duplicate_separators = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
return JNormalise::toCamelCase(self::toSpaceSeparated($string, $keep_duplicate_separators));
}
/**
* Converts a string into space separated form
* eg: FooBar => Foo Bar
* eg: foo-bar => foo bar
*/
public static function toSpaceSeparated(string $string, bool $keep_duplicate_separators = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
return preg_replace(self::getSeparatorRegex($keep_duplicate_separators), ' ', self::fromCamelCase($string));
}
/**
* Converts an object or array to a single string
*/
public static function toString(string|array|object $string): string
{
if (is_string($string)) {
return $string;
}
foreach ($string as &$part) {
$part = self::toString($part);
}
return \RegularLabs\Library\ArrayHelper::implode((array) $string);
}
/**
* Converts a string to a camel case
* eg: foo bar => Foo Bar
* eg: foo_bar => Foo Bar
* eg: foo-bar => Foo Bar
*/
public static function toTitleCase(string $string, bool $keep_duplicate_separators = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
return self::ucwords(self::toSpaceSeparated($string, $keep_duplicate_separators));
}
/**
* Converts a string to a underscore separated string
* eg: FooBar => foo_bar
* eg: foo-bar => foo_bar
*/
public static function toUnderscoreCase(string $string, bool $to_lowercase = \true, bool $keep_duplicate_separators = \true): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string, $to_lowercase]);
if (!is_null($array)) {
return $array;
}
$string = preg_replace(self::getSeparatorRegex($keep_duplicate_separators), '_', self::toSpaceSeparated($string, $keep_duplicate_separators));
return $to_lowercase ? self::toLowerCase($string) : $string;
}
/**
* Converts a string to a lower case
* eg: FooBar => FOOBAR
* eg: foo_bar => FOO_BAR
*/
public static function toUpperCase(string|array|object $string): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_string($string)) {
return $array;
}
return self::strtoupper($string);
}
public static function truncate(string $string, int $maxlen): string
{
if (self::strlen($string) <= $maxlen) {
return $string;
}
return self::substr($string, 0, $maxlen - 3) . '…';
}
/**
* Converts the first letter to uppercase
* eg: fooBar => FooBar
* eg: foo bar => Foo bar
* eg: foo_bar => Foo_bar
*/
public static function upperCaseFirst(string|array|object $string): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_string($string)) {
return $array;
}
return self::ucfirst($string);
}
/**
* utf8 decode a string (or array of strings)
*/
public static function utf8_decode(string $string): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
if (!is_string($string)) {
return $string;
}
if (!function_exists('mb_decode_numericentity')) {
return $string;
}
return mb_decode_numericentity($string, [0x80, 0xffff, 0, ~0], 'UTF-8');
}
/**
* utf8 encode a string (or array of strings)
*/
public static function utf8_encode(string $string): string|array|object
{
$array = \RegularLabs\Library\ArrayHelper::applyMethodToValues([$string]);
if (!is_null($array)) {
return $array;
}
if (!is_string($string)) {
return $string;
}
if (!function_exists('mb_decode_numericentity')) {
return $string;
}
return mb_encode_numericentity($string, [0x80, 0xffff, 0, ~0], 'UTF-8');
}
private static function getSeparatorRegex(bool $keep_duplicate_separators = \true): string
{
$regex = '[ \-_]';
if (!$keep_duplicate_separators) {
$regex .= '+';
}
return '#' . $regex . '#';
}
}

View File

@ -0,0 +1,380 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
/**
* Class StringReplacer
* Handles string replacement operations with the ability to exclude certain parts of the string
*/
class StringReplacer
{
private bool $enable_clean = \true;
private array $parts = [];
public function __construct(string $string = '')
{
$this->set($string ?? '');
}
public function clean(): self
{
$this->enable_clean = \true;
$this->cleanParts();
return $this;
}
public function contains(string $string): bool
{
return str_contains($this->toString(), $string);
}
public function disableCleaning(): self
{
$this->enable_clean = \false;
return $this;
}
public function excludeExceptHtmlTags(array $tags = ['*']): self
{
$regex = $this->getHtmlTagsRegex();
$this->excludeExceptRegex($regex);
if (in_array('*', $tags)) {
return $this;
}
return $this->excludeHtmlTags($tags);
}
public function excludeExceptRegex(string $regex): self
{
$all_parts = [];
foreach ($this->parts as $key => &$string) {
if ($this->shouldSkip($key, $string)) {
$all_parts[] = $string;
continue;
}
$parts = \RegularLabs\Library\RegEx::split($regex, $string);
$parts = ['', ...$parts, ''];
array_push($all_parts, ...$parts);
}
$this->setParts($all_parts);
return $this;
}
public function excludeExceptStrings(array $strings = []): self
{
$regex = \RegularLabs\Library\RegEx::quote($strings);
return $this->excludeExceptRegex($regex);
}
public function excludeForm(array $form_classes = []): self
{
// Exclude the complete adminForm (to prevent replacements messing stuff up when editing articles and such)
$regexes = $this->getFormRegexes($form_classes);
return $this->excludeRegexBetween($regexes->start, $regexes->end, \true);
}
public function excludeHtmlTags(array $except_tags = []): self
{
$regex = $this->getHtmlTagsRegex();
if (in_array('*', $except_tags)) {
return $this;
}
$this->disableCleaning();
$this->excludeRegex($regex);
if (empty($except_tags)) {
$this->clean();
return $this;
}
$this->includeHtmlTags($except_tags);
$this->clean();
return $this;
}
public function excludeOutsideStrings(string $start, string $end, $exclude_strings = \false): self
{
if ($start == '' && $end == '') {
return $this;
}
$start = $start ?: '^';
$end = $end ?: '$';
$regex = $exclude_strings ? '()(' . \RegularLabs\Library\RegEx::quote($start) . ')(.*?)(' . \RegularLabs\Library\RegEx::quote($end) . ')()' : '(' . \RegularLabs\Library\RegEx::quote($start) . '.*?' . \RegularLabs\Library\RegEx::quote($end) . ')';
return $this->excludeExceptRegex($regex);
}
public function excludeRegex(string $regex): self
{
$all_parts = [];
foreach ($this->parts as $key => &$string) {
if ($this->shouldSkip($key, $string)) {
$all_parts[] = $string;
continue;
}
$parts = \RegularLabs\Library\RegEx::split($regex, $string);
if (empty($parts)) {
$all_parts[] = $string;
continue;
}
array_push($all_parts, ...$parts);
}
$this->setParts($all_parts);
return $this;
}
public function excludeRegexBetween(string $start, string $end, $exclude_matches = \false): self
{
$all_parts = [];
foreach ($this->parts as $key => &$string) {
if ($this->shouldSkip($key, $string)) {
$all_parts[] = $string;
continue;
}
$start_parts = \RegularLabs\Library\RegEx::split($start, $string);
if (count($start_parts) < 2) {
$all_parts[] = $string;
continue;
}
$first_part = array_shift($start_parts);
if (!$exclude_matches) {
$first_part .= array_shift($start_parts);
}
$search_part = implode($start_parts);
$end_parts = (new \RegularLabs\Library\StringReplacer($search_part))->excludeRegex($end)->getParts();
if (count($end_parts) < 2) {
$all_parts[] = $string;
continue;
}
$protected_part = array_shift($end_parts);
if ($exclude_matches) {
$protected_part .= array_shift($end_parts);
}
$last_part = implode($end_parts);
array_push($all_parts, $first_part, $protected_part, $last_part);
}
$this->setParts($all_parts);
return $this;
}
public function excludeRegexNested(string $regex_outer, string $regex_inner): self
{
$all_parts = [];
foreach ($this->parts as $key => $string) {
if (trim($string) == '' || $this->rowIsExcluded($key)) {
$all_parts[] = $string;
continue;
}
if (!\RegularLabs\Library\RegEx::match($regex_outer, $string) || !\RegularLabs\Library\RegEx::match($regex_inner, $string)) {
$all_parts[] = $string;
continue;
}
$nested = (new \RegularLabs\Library\StringReplacer($string))->excludeRegex($regex_inner);
array_push($all_parts, ...$nested->getParts());
}
$this->setParts($all_parts);
return $this;
}
public function excludeStrings(array $strings = []): self
{
$regex = \RegularLabs\Library\RegEx::quote($strings);
return $this->excludeRegex($regex);
}
public function getHtmlTagsRegex(): string
{
return '(</?[a-zA-Z][^>]*>)';
}
public function getParts(): array
{
return $this->parts;
}
public function includeRegex(string $regex): self
{
$all_parts = [];
foreach ($this->parts as $key => $string) {
if (trim($string) == '' || !$this->rowIsExcluded($key)) {
$all_parts[] = $string;
continue;
}
if (!\RegularLabs\Library\RegEx::match($regex, $string)) {
$all_parts[] = $string;
continue;
}
$parts = \RegularLabs\Library\RegEx::split($regex, $string);
array_push($all_parts, ...$parts);
}
$this->setParts($all_parts);
return $this;
}
public function includeRegexNested(string $regex_outer, string $regex_inner): self
{
$all_parts = [];
foreach ($this->parts as $key => $string) {
if (trim($string) == '' || !$this->rowIsExcluded($key)) {
$all_parts[] = $string;
continue;
}
if (!\RegularLabs\Library\RegEx::match($regex_outer, $string) || !\RegularLabs\Library\RegEx::match($regex_inner, $string)) {
$all_parts[] = $string;
continue;
}
// using exclude on this excluded row to get the reverse result
$nested = (new \RegularLabs\Library\StringReplacer($string))->excludeRegex($regex_inner);
array_push($all_parts, ...$nested->getParts());
}
$this->setParts($all_parts);
return $this;
}
public function replace($search, $replace): self
{
foreach ($this->parts as $key => &$string) {
if ($this->shouldSkip($key, $string)) {
continue;
}
$string = str_replace($search, $replace, $string);
}
return $this;
}
public function replaceRegex(string $search, string $replace): self
{
foreach ($this->parts as $key => &$string) {
if ($this->shouldSkip($key, $string)) {
continue;
}
$string = \RegularLabs\Library\RegEx::replace($search, $replace, $string);
}
return $this;
}
public function run($callback, $on_excluded = \false): self
{
foreach ($this->parts as $key => &$string) {
if (trim($string) == '' || $this->rowIsExcluded($key) && !$on_excluded) {
continue;
}
$callback($string);
}
$this->flattenParts();
return $this;
}
public function set(string $string): void
{
$this->parts = [$string];
}
public function stillContains(string $string): bool
{
foreach ($this->parts as $key => $value) {
if (trim($value) == '' || $this->rowIsExcluded($key)) {
continue;
}
if (str_contains($this->toString(), $string)) {
return \true;
}
}
return \false;
}
public function toString(): string
{
return implode('', $this->parts);
}
private static function includeHtmlTagsOnString(string &$string, array $tags): void
{
$replacer = new \RegularLabs\Library\StringReplacer($string);
foreach ($tags as $tag_name => $tag_params) {
self::includeSingleHtmlTag($replacer, $tag_name, $tag_params);
}
$string = $replacer->getParts();
}
private static function includeSingleHtmlTag(\RegularLabs\Library\StringReplacer &$replacer, $tag_name, $tag_params): void
{
if ($tag_name == '*') {
$tag_name = '[a-zA-Z][^> ]*';
}
$regex_tag = '(</?' . $tag_name . '(?: [^>]*)?>)';
if (!count($tag_params)) {
// include the whole tag (exclude on an excluded row)
$replacer->excludeRegex($regex_tag);
return;
}
// only include the parameter values
$regex_params = '()(' . \RegularLabs\Library\RegEx::quote($tag_params) . '=")([^"]*)';
$replacer->excludeRegexNested($regex_tag, $regex_params);
}
private function cleanParts(): void
{
if (!$this->enable_clean) {
return;
}
$delimiter = '<!-- ___RL_DELIMITER___ -->';
$temp_string = implode($delimiter, $this->parts);
$temp_string = str_replace($delimiter . $delimiter, '', $temp_string);
$this->parts = explode($delimiter, $temp_string);
}
private function flattenParts(): void
{
// move any nested parts to the parent
$all_parts = [];
foreach ($this->parts as $string) {
if (!is_array($string)) {
$all_parts[] = $string;
continue;
}
array_push($all_parts, ...$string);
}
$this->setParts($all_parts);
}
private function getFormRegexes(array $form_classes = []): object
{
$form_classes = \RegularLabs\Library\ArrayHelper::toArray($form_classes);
$start = '(<form\s[^>]*(?:' . '(?:id|name)="(?:adminForm|postform|submissionForm|default_action_user|seblod_form|spEntryForm)"' . '|action="[^"]*option=com_myjspace&(?:amp;)?view=see"' . (!empty($form_classes) ? '|class="(?:[^"]* )?(?:' . implode('|', $form_classes) . ')(?: [^"]*)?"' : '') . '))';
$end = '(</form>)';
return (object) compact('start', 'end');
}
private function getHtmlTagArray(array $tags = []): array
{
$search_tags = [];
foreach ($tags as $tag) {
if (!strlen($tag)) {
continue;
}
$tag = trim($tag, ']');
$tag_parts = explode('[', $tag);
$tag_name = trim($tag_parts[0]);
if ($tag_name == '*') {
return [];
}
if (count($tag_parts) < 2) {
$search_tags[$tag_name] = [];
continue;
}
$tag_params = $tag_parts[1];
// Trim and remove empty values
$tag_params = array_diff(array_map('trim', explode(',', $tag_params)), ['']);
if (in_array('*', $tag_params)) {
// Make array empty if asterisk is found
// (the whole tag should be allowed)
$search_tags[$tag_name] = [];
continue;
}
$search_tags[$tag_name] = $tag_params;
}
return $search_tags;
}
private function includeHtmlTags(array $tags = []): void
{
$tags = $this->getHtmlTagArray($tags);
if (!count($tags)) {
return;
}
$this->run(function (&$string) use ($tags) {
self::includeHtmlTagsOnString($string, $tags);
}, \true);
}
private function rowIsExcluded(int $key): bool
{
// uneven count = excluded
return fmod($key, 2);
}
private function setParts(array $parts): void
{
$this->parts = $parts;
$this->cleanParts();
}
private function shouldSkip(int $key, string $string): bool
{
return trim($string) == '' || $this->rowIsExcluded($key);
}
}

View File

@ -0,0 +1,519 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Application\CMSApplication as JCMSApplication;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Form\Form as JForm;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Plugin\CMSPlugin as JCMSPlugin;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use Joomla\Component\Finder\Administrator\Indexer\Helper as JIndexerHelper;
use Joomla\Component\Finder\Administrator\Indexer\Result as JIndexerResult;
use Joomla\Database\DatabaseDriver as JDatabaseDriver;
use Joomla\Event\DispatcherInterface as JDispatcherInterface;
use Joomla\Registry\Registry as JRegistry;
use stdClass;
class SystemPlugin extends JCMSPlugin
{
public $_alias = '';
public $_lang_prefix = '';
public $_title = '';
protected $_can_disable_by_url = \true;
protected $_doc_ready = \false;
protected $_enable_in_admin = \false;
protected $_enable_in_frontend = \true;
protected $_enable_in_indexer = \true;
protected $_id = 0;
protected $_jversion = 4;
protected $_page_types = ['html', 'feed', 'pdf', 'xml', 'ajax', 'json', 'raw'];
protected $_pass;
/**
* @var JCMSApplication
*/
protected $app;
protected $autoloadLanguage = \true;
/**
* @var JDatabaseDriver
*/
protected $db;
public function __construct(JDispatcherInterface &$subject, array $config = [])
{
if (isset($config['id'])) {
$this->_id = $config['id'];
}
parent::__construct($subject, $config);
$this->app = JFactory::getApplication();
$this->db = JFactory::getDbo();
if (empty($this->_alias)) {
$this->_alias = $this->_name;
}
if (empty($this->_title)) {
$this->_title = strtoupper($this->_alias);
}
}
/**
* @param JIndexerResult $item The search result
* @param array $query The search query of this result
*
* @return void
*/
public function handleOnFinderResult(JIndexerResult $item, $query)
{
$description = $item->description ?? '';
$summary = $item->getElement('summary') ?? '';
if (empty($description) && empty($summary)) {
return;
}
$article = (object) ['id' => $item->getElement('id')];
if (!empty($description)) {
$article->fulltext = $description;
\RegularLabs\Library\Article::processText('fulltext', $article, $this, 'processArticle', ['article', 'com_finder.index', $article]);
$item->description = JIndexerHelper::parse($article->fulltext);
}
if ($description == $summary) {
$item->setElement('summary', $item->description);
return;
}
if (!empty($summary)) {
$article->fulltext = $summary;
\RegularLabs\Library\Article::processText('fulltext', $article, $this, 'processArticle', ['article', 'com_finder.index', $article]);
$item->setElement('summary', $article->fulltext);
}
}
/**
* @param string $extension The extension for which a language file should be loaded
* @param string $basePath The basepath to use
*
* @return bool True, if the file has successfully loaded.
*/
public function loadLanguage($extension = '', $basePath = JPATH_ADMINISTRATOR)
{
parent::loadLanguage('plg_system_regularlabs', JPATH_LIBRARIES . '/regularlabs');
return parent::loadLanguage();
}
public function onAfterDispatch(): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterDispatch();
$buffer = \RegularLabs\Library\Document::getComponentBuffer();
$this->loadStylesAndScripts($buffer);
if (!$buffer) {
return;
}
$this->changeDocumentBuffer($buffer);
\RegularLabs\Library\Document::setComponentBuffer($buffer);
}
/**
* @return void
*/
public function onAfterInitialise(): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterInitialise();
}
public function onAfterRender(): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterRender();
$html = $this->app->getBody();
if ($html == '') {
return;
}
if (!$this->changeFinalHtmlOutput($html)) {
return;
}
$this->cleanFinalHtmlOutput($html);
$this->app->setBody($html);
}
/**
* @param object $module
* @param array $params
*/
public function onAfterRenderModule(&$module, &$params): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterRenderModule($module, $params);
}
/**
* @param string $buffer
* @param array $params
*/
public function onAfterRenderModules(&$buffer, &$params): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterRenderModules($buffer, $params);
if (empty($buffer)) {
return;
}
$this->changeModulePositionOutput($buffer, $params);
}
public function onAfterRoute(): void
{
$this->_doc_ready = \true;
if (!$this->passChecks()) {
return;
}
$this->handleOnAfterRoute();
}
public function onBeforeCompileHead(): void
{
if (!$this->passChecks()) {
return;
}
$this->handleOnBeforeCompileHead();
}
/**
* @param string $context The context of the content being passed to the plugin.
* @param mixed &$row An object with a "text" property
* @param mixed &$params Additional parameters. See {@see PlgContentContent()}.
* @param integer $page Optional page number. Unused. Defaults to zero.
*/
public function onContentPrepare($context, &$article, &$params, $page = 0): void
{
if (!$this->passChecks()) {
return;
}
$area = isset($article->created_by) ? 'article' : 'other';
$context = $params instanceof JRegistry && $params->get('rl_search') ? 'com_search.' . $params->get('readmore_limit') : $context;
if (!$this->handleOnContentPrepare($area, $context, $article, $params, $page)) {
return;
}
\RegularLabs\Library\Article::process($article, $context, $this, 'processArticle', [$area, $context, $article, $page]);
}
/**
* @param JForm $form The form
* @param stdClass $data The data
*/
public function onContentPrepareForm(JForm $form, $data): bool
{
if (!$this->passChecks()) {
return \true;
}
return $this->handleOnContentPrepareForm($form, $data);
}
/**
* @param JIndexerResult $item The search result
* @param array $query The search query of this result
*/
public function onFinderResult(JIndexerResult $item, $query)
{
if (!$this->passChecks()) {
return;
}
$this->handleOnFinderResult($item, $query);
}
/**
* @param string &$string
* @param string $area
* @param string $context The context of the content being passed to the plugin.
* @param mixed $article An object with a "text" property
* @param int $page Optional page number. Unused. Defaults to zero.
*
* @return void
*/
public function processArticle(&$string, $area = 'article', $context = '', $article = null, $page = 0)
{
}
/**
* @param string $buffer
*
* @return bool
*/
protected function changeDocumentBuffer(&$buffer)
{
return \false;
}
/**
* @param string $html
*
* @return bool
*/
protected function changeFinalHtmlOutput(&$html)
{
return \false;
}
/**
* @param string $buffer
* @param string $params
*
* @return void
*/
protected function changeModulePositionOutput(&$buffer, &$params)
{
}
/**
* @param string $html
*
* @return void
*/
protected function cleanFinalHtmlOutput(&$html)
{
}
protected function extraChecks()
{
return \true;
}
/**
* @return void
*/
protected function handleFeedArticles()
{
if (!empty($this->_page_types) && !in_array('feed', $this->_page_types, \true)) {
return;
}
if (!\RegularLabs\Library\Document::isFeed() && \RegularLabs\Library\Input::get('option', '') != 'com_acymailing') {
return;
}
if (!isset(\RegularLabs\Library\Document::get()->items)) {
return;
}
$context = 'feed';
$items = \RegularLabs\Library\Document::get()->items;
$params = null;
foreach ($items as $item) {
$this->handleOnContentPrepare('article', $context, $item, $params);
}
}
/**
* @return void
*/
protected function handleOnAfterDispatch()
{
$this->handleFeedArticles();
}
/**
* @return void
*/
protected function handleOnAfterInitialise()
{
}
/**
* @return void
*
* Consider using changeFinalHtmlOutput instead
*/
protected function handleOnAfterRender()
{
}
/**
* @param object $module
* @param array $params
*
* @return void
*/
protected function handleOnAfterRenderModule(&$module, &$params)
{
}
/**
* @param string $buffer
* @param array $params
*
* @return void
*/
protected function handleOnAfterRenderModules(&$buffer, &$params)
{
}
/**
* @return void
*/
protected function handleOnAfterRoute()
{
}
/**
* @return void
*/
protected function handleOnBeforeCompileHead()
{
}
/**
* @param string $area
* @param string $context The context of the content being passed to the plugin.
* @param mixed $article An object with a "text" property
* @param mixed &$params Additional parameters. See {@see PlgContentContent()}.
* @param int $page Optional page number. Unused. Defaults to zero.
*
* @return bool
*/
protected function handleOnContentPrepare($area, $context, &$article, &$params, $page = 0)
{
return \true;
}
/**
* @param JForm $form The form
* @param stdClass $data The data
*
* @return bool
*/
protected function handleOnContentPrepareForm(JForm $form, $data)
{
return \true;
}
/**
* @return bool
*/
protected function is3rdPartyEditPage()
{
// // Disable on Gridbox edit form: option=com_gridbox&view=gridbox
// if (Input::get('option', '') == 'com_gridbox' && Input::get('view', '') == 'gridbox')
// {
// return false;
// }
// Disable on SP PageBuilder edit form: option=com_sppagebuilder&view=form
if (\RegularLabs\Library\Input::get('option', '') == 'com_sppagebuilder' && \RegularLabs\Library\Input::get('view', '') == 'form') {
return \true;
}
return \false;
}
/**
* @param string $buffer
*
* @return void
*/
protected function loadStylesAndScripts(&$buffer)
{
}
/**
* @return bool
*/
protected function passChecks()
{
if (!is_null($this->_pass)) {
return $this->_pass;
}
$this->setPass(\false);
if (!$this->isFrameworkEnabled()) {
return \false;
}
if ($this->is3rdPartyEditPage()) {
return \false;
}
if ($this->_doc_ready && !$this->passPageTypes()) {
return \false;
}
if (!$this->_enable_in_frontend && $this->app->isClient('site')) {
return \false;
}
$is_indexer = $this->app->input->get('option') == 'com_finder' && $this->app->input->get('task') == 'batch';
if ($this->app->input->get('option')) {
$this->resetPass();
}
if (!$this->_enable_in_indexer && $is_indexer) {
return \false;
}
$is_admin = !$this->app->isClient('site') && !$is_indexer;
if (!$this->_enable_in_admin && $is_admin) {
return \false;
}
// disabled by url?
if ($this->_can_disable_by_url && \RegularLabs\Library\Protect::isDisabledByUrl($this->_alias)) {
return \false;
}
if (!$this->extraChecks()) {
return \false;
}
$this->setPass(\true);
return \true;
}
protected function passPageTypes()
{
if (empty($this->_page_types)) {
return \true;
}
if (in_array('*', $this->_page_types, \true)) {
return \true;
}
if (\RegularLabs\Library\Document::isFeed()) {
return in_array('feed', $this->_page_types, \true);
}
if (\RegularLabs\Library\Document::isPDF()) {
return in_array('pdf', $this->_page_types, \true);
}
$page_type = \RegularLabs\Library\Document::get()->getType();
return in_array($page_type, $this->_page_types, \true);
}
/**
* Place an error in the message queue
*/
protected function throwError($error)
{
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
// Return if page is not an admin page or the admin login page
if (!JFactory::getApplication()->isClient('administrator') || $user->get('guest')) {
return;
}
// load the admin language file
JFactory::getApplication()->getLanguage()->load('plg_' . $this->_type . '_' . $this->_name, JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name);
$text = JText::sprintf($this->_lang_prefix . '_' . $error, JText::_($this->_title));
$text = JText::_($text) . ' ' . JText::sprintf($this->_lang_prefix . '_EXTENSION_CAN_NOT_FUNCTION', JText::_($this->_title));
// Check if message is not already in queue
$messagequeue = JFactory::getApplication()->getMessageQueue();
foreach ($messagequeue as $message) {
if ($message['message'] == $text) {
return;
}
}
JFactory::getApplication()->enqueueMessage($text, 'error');
}
/**
* Check if the Regular Labs Library is enabled
*
* @return bool
*/
private function isFrameworkEnabled(): bool
{
if (!defined('REGULAR_LABS_LIBRARY_ENABLED')) {
$this->setIsFrameworkEnabled();
}
if (!REGULAR_LABS_LIBRARY_ENABLED) {
$this->throwError('REGULAR_LABS_LIBRARY_NOT_ENABLED');
}
return REGULAR_LABS_LIBRARY_ENABLED;
}
/**
* @return void
*/
private function resetPass(): void
{
$this->_pass = null;
}
/**
* Set the define with whether the Regular Labs Library is enabled
*/
private function setIsFrameworkEnabled(): void
{
if (!JPluginHelper::isEnabled('system', 'regularlabs')) {
$this->throwError('REGULAR_LABS_LIBRARY_NOT_ENABLED');
define('REGULAR_LABS_LIBRARY_ENABLED', \false);
return;
}
define('REGULAR_LABS_LIBRARY_ENABLED', \true);
}
private function setPass(bool $pass): void
{
if (!$this->_doc_ready) {
return;
}
$this->_pass = (bool) $pass;
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
class Title
{
/**
* Cleans the string to make it usable as a title
*/
public static function clean(string $string = '', bool $strip_tags = \false, bool $strip_spaces = \true): string
{
if (empty($string)) {
return '';
}
// remove comment tags
$string = \RegularLabs\Library\RegEx::replace('<\!--.*?-->', '', $string);
// replace weird whitespace
$string = str_replace(chr(194) . chr(160), ' ', $string);
if ($strip_tags) {
// remove svgs
$string = \RegularLabs\Library\RegEx::replace('<svg.*?</svg>', '', $string);
// remove html tags
$string = \RegularLabs\Library\RegEx::replace('</?[a-z][^>]*>', '', $string);
// remove comments tags
$string = \RegularLabs\Library\RegEx::replace('<\!--.*?-->', '', $string);
}
if ($strip_spaces) {
// Replace html spaces
$string = str_replace(['&nbsp;', '&#160;'], ' ', $string);
// Remove duplicate whitespace
$string = \RegularLabs\Library\RegEx::replace('[ \n\r\t]+', ' ', $string);
}
return trim($string);
}
/**
* Creates an array of different syntaxes of titles to match against a url variable
*/
public static function getUrlMatches(array $titles = []): array
{
$matches = [];
foreach ($titles as $title) {
$matches[] = $title;
$matches[] = \RegularLabs\Library\StringHelper::strtolower($title);
}
$matches = array_unique($matches);
foreach ($matches as $title) {
$matches[] = htmlspecialchars(\RegularLabs\Library\StringHelper::html_entity_decoder($title));
}
$matches = array_unique($matches);
foreach ($matches as $title) {
$matches[] = urlencode($title);
if (function_exists('mb_convert_encoding')) {
$matches[] = mb_convert_encoding($title, 'ISO-8859-1', 'UTF-8');
}
$matches[] = str_replace(' ', '', $title);
$matches[] = trim(\RegularLabs\Library\RegEx::replace('[^a-z0-9]', '', $title));
$matches[] = trim(\RegularLabs\Library\RegEx::replace('[^a-z]', '', $title));
}
$matches = array_unique($matches);
foreach ($matches as $i => $title) {
$matches[$i] = trim(str_replace('?', '', $title));
}
$matches = array_diff(array_unique($matches), ['', '-']);
return $matches;
}
}

View File

@ -0,0 +1,178 @@
<?php
/**
* @package Regular Labs Library
* @version 24.11.1459
*
* @author Peter van Westen <info@regularlabs.com>
* @link https://regularlabs.com
* @copyright Copyright © 2024 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Uri\Uri as JUri;
class Uri
{
/**
* Adds the given url parameter (key + value) to the url or replaces it already exists
*/
public static function addParameter(string $url, string $key, string $value = '', bool $replace = \true): string
{
if (empty($key)) {
return $url;
}
$uri = parse_url($url);
$query = self::parse_query($uri['query'] ?? '');
if (!$replace && isset($query[$key])) {
return $url;
}
$query[$key] = $value;
$uri['query'] = http_build_query($query);
return self::createUrlFromArray($uri);
}
/**
* Appends the given hash to the url or replaces it if there is already one
*/
public static function appendHash(string $url = '', string $hash = ''): string
{
if (empty($hash)) {
return $url;
}
$uri = parse_url($url);
$uri['fragment'] = $hash;
return self::createUrlFromArray($uri);
}
/**
* Converts an array of url parts (like made by parse_url) to a string
*/
public static function createUrlFromArray(array $uri): string
{
$user = $uri['user'] ?? '';
$pass = !empty($uri['pass']) ? ':' . $uri['pass'] : '';
return (!empty($uri['scheme']) ? $uri['scheme'] . '://' : '') . ($user || $pass ? $user . $pass . '@' : '') . (!empty($uri['host']) ? $uri['host'] : '') . (!empty($uri['port']) ? ':' . $uri['port'] : '') . (!empty($uri['path']) ? $uri['path'] : '') . (!empty($uri['query']) ? '?' . $uri['query'] : '') . (!empty($uri['fragment']) ? '#' . $uri['fragment'] : '');
}
public static function decode(string $string, bool $urldecode = \true): string
{
if ($urldecode) {
$string = urldecode($string);
}
$string = base64_decode($string);
$deflated = @gzinflate($string);
if ($string === $deflated || !$deflated) {
return $string;
}
return $deflated;
}
public static function encode(string $string, bool $urlencode = \true): string
{
$string = base64_encode(gzdeflate($string));
if ($urlencode) {
$string = urlencode($string);
}
return $string;
}
/**
* Returns the full uri and optionally adds/replaces the hash
*/
public static function get(string $hash = ''): string
{
$url = JUri::getInstance()->toString();
if ($hash == '') {
return $url;
}
return self::appendHash($url, $hash);
}
public static function getCompressedAttributes(): string
{
$compressed = '';
for ($i = 0; $i < 10; $i++) {
$compressed .= \RegularLabs\Library\Input::getString('rlatt_' . $i, '');
}
return self::decode($compressed, \false);
}
/**
* Get the value of a given url parameter from the url
*/
public static function getParameter(string $url, string $key): mixed
{
if (empty($key)) {
return '';
}
$uri = parse_url($url);
if (!isset($uri['query'])) {
return '';
}
$query = self::parse_query($uri['query']);
return $query[$key] ?? '';
}
/**
* Get all url parameters from the url
*/
public static function getParameters(string $url): object
{
$uri = parse_url($url);
if (!isset($uri['query'])) {
return (object) [];
}
$query = self::parse_query($uri['query']);
return (object) $query;
}
public static function isExternal(string $url): bool
{
if (!str_contains($url, '://')) {
return \false;
}
// hostname: give preference to SERVER_NAME, because this includes subdomains
$hostname = $_SERVER['SERVER_NAME'] ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
return !str_starts_with(\RegularLabs\Library\RegEx::replace('^.*?://', '', $url), $hostname);
}
/**
* removes the given url parameter from the url
*/
public static function removeParameter(string $url, string $key): string
{
if (empty($key)) {
return $url;
}
$uri = parse_url($url);
if (!isset($uri['query'])) {
return $url;
}
$query = self::parse_query($uri['query']);
unset($query[$key]);
$uri['query'] = http_build_query($query);
return self::createUrlFromArray($uri);
}
public static function route(string $url): string
{
return JRoute::_(JUri::root(\true) . '/' . $url);
}
/**
* Parse a query string into an associative array.
*/
private static function parse_query(string $string): array
{
$result = [];
if ($string === '') {
return $result;
}
$decoder = fn($value) => rawurldecode(str_replace('+', ' ', $value));
foreach (explode('&', $string) as $kvp) {
$parts = explode('=', $kvp, 2);
$key = $decoder($parts[0]);
$value = isset($parts[1]) ? $decoder($parts[1]) : null;
if (!isset($result[$key])) {
$result[$key] = $value;
continue;
}
if (!is_array($result[$key])) {
$result[$key] = [$result[$key]];
}
$result[$key][] = $value;
}
return $result;
}
}

Some files were not shown because too many files have changed in this diff Show More