first commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2018 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* Color picker
|
||||
*/
|
||||
class ColorPicker extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
// The default value of the widget.
|
||||
'value' => '#dedede',
|
||||
|
||||
// The input border color
|
||||
'input_border_color' => '#dedede',
|
||||
|
||||
// The input border color on focus
|
||||
'input_border_color_focus' => '#dedede',
|
||||
|
||||
// The input background color
|
||||
'input_bg_color' => '#fff',
|
||||
|
||||
// Input text color
|
||||
'input_text_color' => '#333'
|
||||
];
|
||||
}
|
||||
536
plugins/system/nrframework/NRFramework/Widgets/Countdown.php
Normal file
536
plugins/system/nrframework/NRFramework/Widgets/Countdown.php
Normal file
@@ -0,0 +1,536 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2021 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* Countdown
|
||||
*/
|
||||
class Countdown extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
/**
|
||||
* The Countdown type:
|
||||
*
|
||||
* - static: Counts down to a specific date and time. Universal deadline for all visitors.
|
||||
* - evergreen: Set-and-forget solution. The countdown starts when your visitor sees the offer.
|
||||
*/
|
||||
'countdown_type' => 'static',
|
||||
|
||||
// The Static Countdown Date
|
||||
'value' => '',
|
||||
|
||||
/**
|
||||
* The timezone that will be used.
|
||||
*
|
||||
* - server - Use server's timezone
|
||||
* - client - Use client's timezone
|
||||
*/
|
||||
'timezone' => 'server',
|
||||
|
||||
// Dynamic Days
|
||||
'dynamic_days' => 0,
|
||||
|
||||
// Dynamic Hours
|
||||
'dynamic_hours' => 0,
|
||||
|
||||
// Dynamic Minutes
|
||||
'dynamic_minutes' => 0,
|
||||
|
||||
// Dynamic Seconds
|
||||
'dynamic_seconds' => 0,
|
||||
|
||||
/**
|
||||
* The countdown format.
|
||||
*
|
||||
* Available tags:
|
||||
* {years}
|
||||
* {months}
|
||||
* {days}
|
||||
* {hours}
|
||||
* {minutes}
|
||||
* {seconds}
|
||||
*/
|
||||
'format' => '{days} days, {hours} hours, {minutes} minutes and {seconds} seconds',
|
||||
|
||||
/**
|
||||
* The countdown theme.
|
||||
*
|
||||
* Available themes:
|
||||
* default
|
||||
* oneline
|
||||
* custom
|
||||
*/
|
||||
'theme' => 'default',
|
||||
|
||||
/**
|
||||
* Set the action once countdown finishes.
|
||||
*
|
||||
* Available values:
|
||||
* keep - Keep the countdown visible
|
||||
* hide - Hide the countdown
|
||||
* restart - Restart the countdown
|
||||
* message - Show a message
|
||||
* redirect - Redirect to a URL
|
||||
*/
|
||||
'countdown_action' => 'keep',
|
||||
|
||||
/**
|
||||
* The message appearing after the countdown has finished.
|
||||
*
|
||||
* Requires `countdown_action` to be set to `message`
|
||||
*
|
||||
* Example: Countdown finished.
|
||||
*/
|
||||
'finish_text' => '',
|
||||
|
||||
/**
|
||||
* The redirect URL once the countdown expires.
|
||||
*
|
||||
* Requires `countdown_action` to be set to `redirect`
|
||||
*/
|
||||
'redirect_url' => '',
|
||||
|
||||
/**
|
||||
* Widget Settings
|
||||
*/
|
||||
// Alignment
|
||||
'align' => '',
|
||||
|
||||
// Padding
|
||||
'padding' => null,
|
||||
|
||||
// Margin
|
||||
'margin' => null,
|
||||
|
||||
// Gap
|
||||
'gap' => 20,
|
||||
|
||||
// Background Color
|
||||
'background_color' => '',
|
||||
|
||||
/**
|
||||
* Unit Display Settings
|
||||
*/
|
||||
// Whether to display Days
|
||||
'days' => true,
|
||||
|
||||
// Days Label
|
||||
'days_label' => 'Days',
|
||||
|
||||
// Whether to display Hours
|
||||
'hours' => true,
|
||||
|
||||
// Hours Label
|
||||
'hours_label' => 'Hrs',
|
||||
|
||||
// Whether to display Minutes
|
||||
'minutes' => true,
|
||||
|
||||
// Minutes Label
|
||||
'minutes_label' => 'Mins',
|
||||
|
||||
// Whether to display Seconds
|
||||
'seconds' => true,
|
||||
|
||||
// Seconds Label
|
||||
'seconds_label' => 'Secs',
|
||||
|
||||
// Whether to display a separator between the units
|
||||
'separator' => false,
|
||||
|
||||
// Whether to display numbers in 00 or 0 format
|
||||
'double_zeroes_format' => true,
|
||||
|
||||
/**
|
||||
* Unit Item Settings
|
||||
*/
|
||||
// The size (width, height) of the unit item in pixels
|
||||
'item_size' => null,
|
||||
|
||||
// The unit item border width
|
||||
'item_border_width' => '',
|
||||
|
||||
// The unit item border style
|
||||
'item_border_style' => '',
|
||||
|
||||
// The unit item border color
|
||||
'item_border_color' => '',
|
||||
|
||||
// The unit item border radius
|
||||
'item_border_radius' => null,
|
||||
|
||||
// Item Background Color
|
||||
'item_background_color' => '',
|
||||
|
||||
/**
|
||||
* Unit Digits Container Settings
|
||||
*/
|
||||
// Digits wrapper Min Width
|
||||
'digits_wrapper_min_width' => 0,
|
||||
|
||||
// The digits wrapper padding
|
||||
'digits_wrapper_padding' => null,
|
||||
|
||||
// The digits wrapper border radius
|
||||
'digits_wrapper_border_radius' => null,
|
||||
|
||||
// The digits wrapper background color.
|
||||
'digits_wrapper_background_color' => '',
|
||||
|
||||
/**
|
||||
* Unit Digit Settings
|
||||
*/
|
||||
// Digits Font Size
|
||||
'digits_font_size' => 25,
|
||||
|
||||
// Digits Font Weight
|
||||
'digits_font_weight' => '400',
|
||||
|
||||
// Digit Min Width
|
||||
'digit_min_width' => 0,
|
||||
|
||||
// The digits padding
|
||||
'digits_padding' => null,
|
||||
|
||||
// The digits border radius
|
||||
'digit_border_radius' => null,
|
||||
|
||||
// Digits Gap
|
||||
'digits_gap' => null,
|
||||
|
||||
// Digit Item Background Color. This applies for each of the 2 digits on a unit.
|
||||
'digit_background_color' => '',
|
||||
|
||||
// Digit Item Text Color
|
||||
'digit_text_color' => '',
|
||||
|
||||
/**
|
||||
* Unit Label Settings
|
||||
*/
|
||||
// Label Font Size
|
||||
'label_font_size' => 13,
|
||||
|
||||
// Label Font Weight
|
||||
'label_font_weight' => '400',
|
||||
|
||||
// Unit Label Margin Top. The spacing between the unit and its label.
|
||||
'unit_label_margin_top' => 5,
|
||||
|
||||
// Unit Label Color
|
||||
'unit_label_text_color' => '',
|
||||
|
||||
// Extra attributes added to the widget
|
||||
'atts' => '',
|
||||
|
||||
// Custom CSS printed after the widget assets
|
||||
'custom_css' => '.foo {}',
|
||||
|
||||
// Preview HTML used prior to JS initializing the Countdown
|
||||
'preview_html' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
\JText::script('NR_AND_LC');
|
||||
|
||||
$this->prepare();
|
||||
|
||||
if ($this->options['theme'] !== 'custom')
|
||||
{
|
||||
$this->setCSSVars();
|
||||
$this->setResponsiveCSS();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the countdown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepare()
|
||||
{
|
||||
$this->setCSSVars();
|
||||
|
||||
$this->options['css_class'] .= ' is-preview ' . $this->options['theme'] . ' ' . $this->options['align'];
|
||||
|
||||
if (!empty($this->options['value']) && $this->options['value'] !== '0000-00-00 00:00:00')
|
||||
{
|
||||
if ($this->options['countdown_type'] === 'static' && $this->options['timezone'] === 'server')
|
||||
{
|
||||
// Get timezone
|
||||
$tz = new \DateTimeZone(\JFactory::getApplication()->getCfg('offset', 'UTC'));
|
||||
|
||||
// Convert given date time to UTC
|
||||
$this->options['value'] = date_create($this->options['value'], $tz)->setTimezone(new \DateTimeZone('UTC'))->format('c');
|
||||
|
||||
// Apply server timezone
|
||||
$this->options['value'] = (new \DateTime($this->options['value']))->setTimezone($tz)->format('c');
|
||||
}
|
||||
}
|
||||
|
||||
$this->options['preview_html'] = $this->getPreviewHTML();
|
||||
|
||||
// Set countdown payload
|
||||
$payload = [
|
||||
'data-countdown-type="' . $this->options['countdown_type'] . '"',
|
||||
'data-value="' . $this->options['value'] . '"',
|
||||
'data-timezone="' . $this->options['timezone'] . '"',
|
||||
'data-separator="' . (json_decode($this->options['separator']) ? 'true' : 'false') . '"',
|
||||
'data-double-zeroes-format="' . (json_decode($this->options['double_zeroes_format']) ? 'true' : 'false') . '"',
|
||||
'data-dynamic-days="' . $this->options['dynamic_days'] . '"',
|
||||
'data-dynamic-hours="' . $this->options['dynamic_hours'] . '"',
|
||||
'data-dynamic-minutes="' . $this->options['dynamic_minutes'] . '"',
|
||||
'data-dynamic-seconds="' . $this->options['dynamic_seconds'] . '"',
|
||||
'data-finish-text="' . htmlspecialchars($this->options['finish_text']) . '"',
|
||||
'data-redirect-url="' . $this->options['redirect_url'] . '"',
|
||||
'data-theme="' . $this->options['theme'] . '"',
|
||||
'data-countdown-action="' . $this->options['countdown_action'] . '"',
|
||||
'data-days="' . (json_decode($this->options['days']) ? 'true' : 'false') . '"',
|
||||
'data-days-label="' . $this->options['days_label'] . '"',
|
||||
'data-hours="' . (json_decode($this->options['hours']) ? 'true' : 'false') . '"',
|
||||
'data-hours-label="' . $this->options['hours_label'] . '"',
|
||||
'data-minutes="' . (json_decode($this->options['minutes']) ? 'true' : 'false') . '"',
|
||||
'data-minutes-label="' . $this->options['minutes_label'] . '"',
|
||||
'data-seconds="' . (json_decode($this->options['seconds']) ? 'true' : 'false') . '"',
|
||||
'data-seconds-label="' . $this->options['seconds_label'] . '"'
|
||||
];
|
||||
|
||||
// Only set the format for custom-themed countdown instances
|
||||
if ($this->options['theme'] === 'custom')
|
||||
{
|
||||
$payload[] = 'data-format="' . htmlspecialchars($this->options['format']) . '"';
|
||||
}
|
||||
|
||||
$this->options['atts'] = implode(' ', $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set widget CSS vars
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function setCSSVars()
|
||||
{
|
||||
if (!$this->options['load_css_vars'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$atts = [];
|
||||
|
||||
if (!empty($this->options['digits_wrapper_background_color']))
|
||||
{
|
||||
$atts['digits-background-color'] = $this->options['digits_wrapper_background_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['background_color']))
|
||||
{
|
||||
$atts['background-color'] = $this->options['background_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['item_background_color']))
|
||||
{
|
||||
$atts['item-background-color'] = $this->options['item_background_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['unit_label_text_color']))
|
||||
{
|
||||
$atts['unit-label-text-color'] = $this->options['unit_label_text_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['digit_background_color']))
|
||||
{
|
||||
$atts['digit-background-color'] = $this->options['digit_background_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['digit_text_color']))
|
||||
{
|
||||
$atts['digit-text-color'] = $this->options['digit_text_color'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['unit_label_margin_top']))
|
||||
{
|
||||
$atts['unit-label-margin-top'] = $this->options['unit_label_margin_top'] . 'px';
|
||||
}
|
||||
|
||||
if (!empty($this->options['digits_wrapper_min_width']))
|
||||
{
|
||||
$atts['digits-wrapper-min-width'] = $this->options['digits_wrapper_min_width'] . 'px';
|
||||
}
|
||||
|
||||
if (!empty($this->options['digit_min_width']))
|
||||
{
|
||||
$atts['digit-min-width'] = $this->options['digit_min_width'] . 'px';
|
||||
}
|
||||
|
||||
if (!empty($this->options['digits_font_weight']))
|
||||
{
|
||||
$atts['digits-font-weight'] = $this->options['digits_font_weight'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['label_font_weight']))
|
||||
{
|
||||
$atts['label-font-weight'] = $this->options['label_font_weight'];
|
||||
}
|
||||
|
||||
if (!empty($this->options['item_border_width']) && !empty($this->options['item_border_style']) && !empty($this->options['item_border_color']))
|
||||
{
|
||||
$atts['item-border'] = $this->options['item_border_width'] . 'px ' . $this->options['item_border_style'] . ' ' . $this->options['item_border_color'];
|
||||
}
|
||||
|
||||
if (empty($atts))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$css = \NRFramework\Helpers\CSS::cssVarsToString($atts, '.nrf-countdown.' . $this->options['id']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->options['custom_css'] = $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS for the responsive settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setResponsiveCSS()
|
||||
{
|
||||
$initial_breakpoints = [
|
||||
'desktop' => [],
|
||||
'tablet' => [],
|
||||
'mobile' => []
|
||||
];
|
||||
$responsive_css = $initial_breakpoints;
|
||||
|
||||
// Add digits wrapper padding
|
||||
if ($digits_wrapper_padding = \NRFramework\Helpers\Controls\Spacing::getResponsiveSpacingControlValue($this->options['digits_wrapper_padding'], '--digits-padding', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $digits_wrapper_padding);
|
||||
}
|
||||
|
||||
// Add widget padding
|
||||
if ($padding = \NRFramework\Helpers\Controls\Spacing::getResponsiveSpacingControlValue($this->options['padding'], 'padding', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $padding);
|
||||
}
|
||||
|
||||
// Add widget margin
|
||||
if ($margin = \NRFramework\Helpers\Controls\Spacing::getResponsiveSpacingControlValue($this->options['margin'], 'margin', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $margin);
|
||||
}
|
||||
|
||||
// Add gap
|
||||
if ($gap = \NRFramework\Helpers\Controls\Responsive::getResponsiveControlValue($this->options['gap'], '--gap', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $gap);
|
||||
}
|
||||
|
||||
// Add digits gap
|
||||
if ($gap = \NRFramework\Helpers\Controls\Responsive::getResponsiveControlValue($this->options['digits_gap'], '--digits-gap', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $gap);
|
||||
}
|
||||
|
||||
// Add Item Size
|
||||
if ($item_size = \NRFramework\Helpers\Controls\Responsive::getResponsiveControlValue($this->options['item_size'], '--item-size', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $item_size);
|
||||
}
|
||||
|
||||
// Add Digits Font Size
|
||||
if ($digits_font_size = \NRFramework\Helpers\Controls\Responsive::getResponsiveControlValue($this->options['digits_font_size'], '--digits-font-size', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $digits_font_size);
|
||||
}
|
||||
|
||||
// Add Label Font Size
|
||||
if ($label_font_size = \NRFramework\Helpers\Controls\Responsive::getResponsiveControlValue($this->options['label_font_size'], '--label-font-size', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $label_font_size);
|
||||
}
|
||||
|
||||
// Add Digits Padding
|
||||
if ($digitsPadding = \NRFramework\Helpers\Controls\Spacing::getResponsiveSpacingControlValue($this->options['digits_padding'], '--digit-padding', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $digitsPadding);
|
||||
}
|
||||
|
||||
// Add item border radius
|
||||
if ($itemBorderRadius = \NRFramework\Helpers\Controls\BorderRadius::getResponsiveSpacingControlValue($this->options['item_border_radius'], '--item-border-radius', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $itemBorderRadius);
|
||||
}
|
||||
|
||||
// Add digits wrapper border radius
|
||||
if ($borderRadius = \NRFramework\Helpers\Controls\BorderRadius::getResponsiveSpacingControlValue($this->options['digits_wrapper_border_radius'], '--digits-border-radius', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $borderRadius);
|
||||
}
|
||||
|
||||
// Add digits border radius
|
||||
if ($borderRadius = \NRFramework\Helpers\Controls\BorderRadius::getResponsiveSpacingControlValue($this->options['digit_border_radius'], '--digit-border-radius', 'px'))
|
||||
{
|
||||
$responsive_css = array_merge_recursive($responsive_css, $borderRadius);
|
||||
}
|
||||
|
||||
if ($css = \NRFramework\Helpers\Responsive::renderResponsiveCSS($responsive_css, '.nrf-countdown.' . $this->options['id']))
|
||||
{
|
||||
$this->options['custom_css'] .= $css;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns preview HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPreviewHTML()
|
||||
{
|
||||
if ($this->options['theme'] === 'custom')
|
||||
{
|
||||
return $this->options['format'];
|
||||
}
|
||||
|
||||
$format_items = [
|
||||
'days' => $this->options['days'],
|
||||
'hours' => $this->options['hours'],
|
||||
'minutes' => $this->options['minutes'],
|
||||
'seconds' => $this->options['seconds']
|
||||
];
|
||||
|
||||
$html = '';
|
||||
|
||||
foreach ($format_items as $key => $value)
|
||||
{
|
||||
$labelStr = !empty($this->options[$key . '_label']) ? '<span class="countdown-digit-label">' . $this->options[$key . '_label'] . '</span>' : '';
|
||||
$html .= '<span class="countdown-item"><span class="countdown-digit ' . $key . '"><span class="digit-number digit-1">0</span><span class="digit-number digit-2">0</span></span>' . $labelStr . '</span>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
973
plugins/system/nrframework/NRFramework/Widgets/Gallery.php
Normal file
973
plugins/system/nrframework/NRFramework/Widgets/Gallery.php
Normal file
@@ -0,0 +1,973 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2021 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use \NRFramework\Helpers\Widgets\Gallery as GalleryHelper;
|
||||
use NRFramework\Mimes;
|
||||
use NRFramework\File;
|
||||
use NRFramework\Image;
|
||||
|
||||
/**
|
||||
* Gallery
|
||||
*/
|
||||
class Gallery extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
/**
|
||||
* The gallery items source.
|
||||
*
|
||||
* This can be one or combination of the following:
|
||||
*
|
||||
* - Path to a relative folder (String)
|
||||
* /path/to/folder
|
||||
* - Path to a relative image (String)
|
||||
* /path/to/folder/image.png
|
||||
* - URL of an image (String)
|
||||
* https://example.com/path/to/image.png
|
||||
* - Array of images (Array)
|
||||
* [
|
||||
* 'url' => 'https://example.com/path/to/image.png',
|
||||
* 'thumbnail_url' => 'https://example.com/path/to/image_thumb.png',
|
||||
* 'caption' => 'This is a caption',
|
||||
* 'thumbnail_size' => [
|
||||
* 'width' => '200',
|
||||
* 'height' => '200'
|
||||
* ],
|
||||
* 'module' => 'position-2'
|
||||
* ]
|
||||
*
|
||||
* - The `url` property is required.
|
||||
* - All other properties are optional.
|
||||
*/
|
||||
'items' => [],
|
||||
|
||||
/**
|
||||
* Set the ordering.
|
||||
*
|
||||
* Available values:
|
||||
* - default
|
||||
* - alphabetical
|
||||
* - reverse_alphabetical
|
||||
* - random
|
||||
*/
|
||||
'ordering' => 'default',
|
||||
|
||||
// Set the module key to display whenever we are viewing a single item's lightbox, appearing after the image
|
||||
'module' => '',
|
||||
|
||||
// Set the style of the gallery (masonry, grid)
|
||||
'style' => 'masonry',
|
||||
|
||||
/**
|
||||
* Define the columns per supported device.
|
||||
*
|
||||
* Example value:
|
||||
* - An integer representing the columns for all devices: 3
|
||||
* - A value for each device:
|
||||
* [
|
||||
* 'desktop' => 3,
|
||||
* 'tablet' => 2,
|
||||
* 'mobile' => 1
|
||||
* ]
|
||||
*/
|
||||
'columns' => 4,
|
||||
|
||||
/**
|
||||
* Define the gap per gallery item per supported device.
|
||||
*
|
||||
* Example value:
|
||||
* - An integer representing the gap for all devices: 30
|
||||
* - A value for each device:
|
||||
* [
|
||||
* 'desktop' => 30,
|
||||
* 'tablet' => 20,
|
||||
* 'mobile' => 10
|
||||
* ]
|
||||
*/
|
||||
'gap' => 15,
|
||||
|
||||
/**
|
||||
* Set the allowed file types.
|
||||
*
|
||||
* This is used to validate the files loaded via a directory or a fixed path to an image.
|
||||
*
|
||||
* Given URLs are not validated by this setting.
|
||||
*/
|
||||
'allowed_file_types' => '.jpg, .jpeg, .png',
|
||||
|
||||
// Gallery Items wrapper CSS classes
|
||||
'gallery_items_css' => '',
|
||||
|
||||
// Set whether to display a lightbox
|
||||
'lightbox' => true,
|
||||
|
||||
/**
|
||||
* Source Image
|
||||
*/
|
||||
/**
|
||||
* Should the source image be resized?
|
||||
*
|
||||
* If `original_image_resize` is false, then the source image will appear
|
||||
* in the lightbox (also if `thumbnails` is false, the source image will also appear as the thumbnail)
|
||||
*
|
||||
* Issue: if this image is a raw photo, there are chances it will increase the page load in order for the browser to display the image.
|
||||
*
|
||||
* By enabling this, we resize the source image to our desired dimensions and reduce the page load in the above scenario.
|
||||
*
|
||||
* Note: Always ensure the source image is backed up to a safe place.
|
||||
* Note 2: We require thumbnails or original image resize to be enabled for this to work.
|
||||
* Reason: The above options if enabled generate the gallery_info.txt file in the /cache folder which helps us
|
||||
* generate the source images only if necessary(image has been edited), otherwise, the source image would
|
||||
* be generated on each page refresh.
|
||||
*/
|
||||
'source_image_resize' => false,
|
||||
|
||||
// Source image resize width
|
||||
'source_image_resize_width' => 1920,
|
||||
|
||||
// Source image resize height
|
||||
'source_image_resize_height' => null,
|
||||
|
||||
// Source image resize method (crop, stretch, fit)
|
||||
'source_image_resize_method' => 'crop',
|
||||
|
||||
// Source image resize quality
|
||||
'source_image_resize_image_quality' => 80,
|
||||
|
||||
/**
|
||||
* Original Image
|
||||
*/
|
||||
// Should the original uploaded image be resized?
|
||||
'original_image_resize' => false,
|
||||
|
||||
// Resize method (crop, stretch, fit)
|
||||
'original_image_resize_method' => 'crop',
|
||||
|
||||
/**
|
||||
* Original Image Resize Width.
|
||||
*
|
||||
* If `original_image_resize_height` is null, resizes via the width to keep the aspect ratio.
|
||||
*/
|
||||
'original_image_resize_width' => 1920,
|
||||
|
||||
// Original Image Resize Height
|
||||
'original_image_resize_height' => null,
|
||||
|
||||
// Original Image Resize Quality
|
||||
'original_image_resize_image_quality' => 80,
|
||||
|
||||
/**
|
||||
* Thumbnails
|
||||
*/
|
||||
// Set whether to generate thumbnails on-the-fly
|
||||
'thumbnails' => false,
|
||||
|
||||
// Resize method (crop, stretch, fit)
|
||||
'thumb_resize_method' => 'crop',
|
||||
|
||||
// Resize quality
|
||||
'thumb_resize_quality' => 80,
|
||||
|
||||
// Thumbnails width
|
||||
'thumb_width' => 300,
|
||||
|
||||
// Thumbnails height
|
||||
'thumb_height' => null,
|
||||
|
||||
// The CSS class of the thumbnail
|
||||
'thumb_class' => '',
|
||||
|
||||
/**
|
||||
* Set whether to resize the images whenever their source file changes.
|
||||
*
|
||||
* i.e. If we edit the source image and also need to recreate the resized original image or thumbnail.
|
||||
* This is rather useful otherwise we would have to delete the resized image or thumbnail in order for it to be recreated.
|
||||
*/
|
||||
'force_resizing' => false,
|
||||
|
||||
// Destination folder
|
||||
'destination_folder' => 'cache/tassos/gallery',
|
||||
|
||||
// The unique hash of this gallery based on its options
|
||||
'hash' => null,
|
||||
|
||||
// Set whether to show warnings when an image that has been set to appear does not exist.
|
||||
'show_warnings' => true
|
||||
];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
$this->prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the Gallery.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepare()
|
||||
{
|
||||
$this->options['hash'] = $this->getHash();
|
||||
|
||||
$this->options['destination_folder'] = JPATH_ROOT . DIRECTORY_SEPARATOR . $this->options['destination_folder'] . DIRECTORY_SEPARATOR . $this->options['hash'] . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->parseGalleryItems();
|
||||
|
||||
$this->cleanDestinationFolder();
|
||||
|
||||
$this->resizeSourceImages();
|
||||
$this->resizeOriginalImages();
|
||||
$this->createThumbnails();
|
||||
|
||||
// Set style on the gallery items container.
|
||||
$this->options['gallery_items_css'] .= ' ' . $this->getStyle();
|
||||
|
||||
// Set class to trigger lightbox.
|
||||
if ($this->options['lightbox'])
|
||||
{
|
||||
$this->options['css_class'] .= ' lightbox';
|
||||
}
|
||||
|
||||
$this->prepareItems();
|
||||
|
||||
$this->setCSSVariables();
|
||||
|
||||
$this->setOrdering();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ordering of the gallery.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setOrdering()
|
||||
{
|
||||
switch ($this->options['ordering']) {
|
||||
case 'random':
|
||||
shuffle($this->options['items']);
|
||||
break;
|
||||
case 'alphabetical':
|
||||
usort($this->options['items'], [$this, 'compareByThumbnailASC']);
|
||||
break;
|
||||
case 'reverse_alphabetical':
|
||||
usort($this->options['items'], [$this, 'compareByThumbnailDESC']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares thumbnail file names in ASC order
|
||||
*
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function compareByThumbnailASC($a, $b)
|
||||
{
|
||||
return strcmp(basename($a['thumbnail']), basename($b['thumbnail']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares thumbnail file names in DESC order
|
||||
*
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function compareByThumbnailDESC($a, $b)
|
||||
{
|
||||
return strcmp(basename($b['thumbnail']), basename($a['thumbnail']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of this gallery.
|
||||
*
|
||||
* Generate the hash with only the essential options of the Gallery widget.
|
||||
* i.e. with the data that are related to the images.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getHash()
|
||||
{
|
||||
$opts = [
|
||||
'items',
|
||||
'style',
|
||||
'allowed_file_types',
|
||||
'source_image_resize',
|
||||
'source_image_resize_width',
|
||||
'source_image_resize_height',
|
||||
'source_image_resize_method',
|
||||
'source_image_resize_image_quality',
|
||||
'original_image_resize',
|
||||
'original_image_resize_method',
|
||||
'original_image_resize_width',
|
||||
'original_image_resize_height',
|
||||
'original_image_resize_image_quality',
|
||||
'thumbnails',
|
||||
'thumb_resize_method',
|
||||
'thumb_resize_quality',
|
||||
'thumb_width',
|
||||
'thumb_height',
|
||||
'force_resizing',
|
||||
'destination_folder'
|
||||
];
|
||||
|
||||
$payload = [];
|
||||
|
||||
foreach ($opts as $opt)
|
||||
{
|
||||
$payload[$opt] = $this->options[$opt];
|
||||
}
|
||||
|
||||
return md5(serialize($payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the source folder.
|
||||
*
|
||||
* If an image from the source folder is removed, we also remove the
|
||||
* original image/thumbnail from the destination folder as well as
|
||||
* from the gallery info file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanDestinationFolder()
|
||||
{
|
||||
if (!$this->options['original_image_resize'] && !$this->options['thumbnails'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all folders that we need to search
|
||||
$dirs_to_search = [];
|
||||
|
||||
// Store all source files
|
||||
$source_files = [];
|
||||
|
||||
foreach ($this->options['items'] as $key => $item)
|
||||
{
|
||||
if (!isset($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$source_files[] = pathinfo($item['path'], PATHINFO_BASENAME);
|
||||
|
||||
$directory = is_dir($item['path']) ? $item['path'] : dirname($item['path']);
|
||||
|
||||
if (in_array($directory, $dirs_to_search))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$dirs_to_search[] = $directory;
|
||||
}
|
||||
|
||||
if (empty($dirs_to_search))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop each directory found and check which files we need to delete
|
||||
foreach ($dirs_to_search as $dir)
|
||||
{
|
||||
$source_folder_info_file = GalleryHelper::getGalleryInfoFileData($dir);
|
||||
|
||||
// Find all soon to be deleted files
|
||||
$to_be_deleted = array_diff(array_keys($source_folder_info_file), $source_files);
|
||||
|
||||
if (!count($to_be_deleted))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($to_be_deleted as $source)
|
||||
{
|
||||
// Original image delete
|
||||
if (isset($source_folder_info_file[$source]))
|
||||
{
|
||||
$file = $this->options['destination_folder'] . $source_folder_info_file[$source]['filename'];
|
||||
if (file_exists($file))
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail delete
|
||||
$parts = pathinfo($file);
|
||||
$thumbnail = $this->options['destination_folder'] . $parts['filename'] . '_thumb.' . $parts['extension'];
|
||||
if (file_exists($thumbnail))
|
||||
{
|
||||
unlink($thumbnail);
|
||||
}
|
||||
|
||||
// Also remove the image from the gallery info file.
|
||||
GalleryHelper::removeImageFromGalleryInfoFile(rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gallery style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getStyle()
|
||||
{
|
||||
$style = $this->options['style'];
|
||||
|
||||
// Get aspect ratio for source image, original image resized and thumbnail
|
||||
$thumb_height = intval($this->options['thumb_height']);
|
||||
$thumb_aspect_ratio = $thumb_height ? intval($this->options['thumb_width']) / $thumb_height : 0;
|
||||
|
||||
$source_image_height = intval($this->options['source_image_resize_height']);
|
||||
$source_image_aspect_ratio = $source_image_height ? intval($this->options['source_image_resize_width']) / $source_image_height : 0;
|
||||
|
||||
$original_image_height = intval($this->options['original_image_resize_height']);
|
||||
$original_image_aspect_ratio = $original_image_height ? intval($this->options['original_image_resize_width']) / $original_image_height : 0;
|
||||
|
||||
// Check whether the aspect ratio for thumb and lightbox image are the same and use `masonry` style
|
||||
$checking_aspect_ratio = $this->options['original_image_resize'] ? $original_image_aspect_ratio : $source_image_aspect_ratio;
|
||||
if ($thumb_aspect_ratio && $checking_aspect_ratio && $thumb_aspect_ratio === $checking_aspect_ratio)
|
||||
{
|
||||
return 'masonry';
|
||||
}
|
||||
|
||||
/**
|
||||
* If both thumbnail width & height are equal we use the `grid` style.
|
||||
*/
|
||||
if ($this->options['thumb_width'] === $this->options['thumb_height'])
|
||||
{
|
||||
$style = 'grid';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the style is grid and we do not have a null or 0 thumb_height set the fade lightbox CSS Class.
|
||||
*
|
||||
* This CSS Class tells PhotoSwipe to use the fade transition.
|
||||
*/
|
||||
if ($style === 'grid' && (!is_null($this->options['thumb_height']) && $this->options['thumb_height'] !== '0'))
|
||||
{
|
||||
$this->options['css_class'] .= ' lightbox-fade';
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the gallery items by finding all iamges to display from all
|
||||
* different sources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function parseGalleryItems()
|
||||
{
|
||||
// If it's a string, we assume its a path to a folder and we convert it to an array.
|
||||
$this->options['items'] = (array) $this->options['items'];
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($this->options['items'] as $key => $value)
|
||||
{
|
||||
if (!$data = GalleryHelper::parseGalleryItems($value, $this->getAllowedFileTypes()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$items = array_merge($items, $data);
|
||||
}
|
||||
|
||||
// Ensure only unique image paths are used
|
||||
$items = array_unique($items, SORT_REGULAR);
|
||||
|
||||
$this->options['items'] = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed file types in an array format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedFileTypes()
|
||||
{
|
||||
$types = explode(',', $this->options['allowed_file_types']);
|
||||
$types = array_filter(array_map('trim', array_map('strtolower', $types)));
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the source images.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function resizeSourceImages()
|
||||
{
|
||||
if (!$this->options['source_image_resize'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We require either original image resize or thumbnails to be enabled
|
||||
if (!$this->options['original_image_resize'] && !$this->options['thumbnails'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->options['items'] as $key => &$item)
|
||||
{
|
||||
if (!isset($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if source does not exist
|
||||
if (!is_file($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$source = $item['path'];
|
||||
|
||||
// Find source image in the destination folder
|
||||
if ($image_data = GalleryHelper::findSourceImageDetails($source, $this->options['destination_folder']))
|
||||
{
|
||||
// If force resizing is disabled, continue
|
||||
if (!$this->options['force_resizing'])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the destination image has not been edited and exists, abort
|
||||
if (!$image_data['edited'] && file_exists($image_data['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($this->options['source_image_resize_height']))
|
||||
{
|
||||
Image::resizeAndKeepAspectRatio(
|
||||
$source,
|
||||
$this->options['source_image_resize_width'],
|
||||
$this->options['source_image_resize_image_quality']
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Image::resize(
|
||||
$source,
|
||||
$this->options['source_image_resize_width'],
|
||||
$this->options['source_image_resize_height'],
|
||||
$this->options['source_image_resize_image_quality'],
|
||||
$this->options['source_image_resize_method']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the original images.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function resizeOriginalImages()
|
||||
{
|
||||
if (!$this->options['original_image_resize'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create destination folder if missing
|
||||
File::createDirs($this->options['destination_folder']);
|
||||
|
||||
foreach ($this->options['items'] as $key => &$item)
|
||||
{
|
||||
if (!isset($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if source does not exist
|
||||
if (!is_file($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$source = $item['path'];
|
||||
|
||||
$unique = true;
|
||||
|
||||
// Path to resized image in destination folder
|
||||
$destination = $this->options['destination_folder'] . basename($source);
|
||||
|
||||
// Find source image in the destination folder
|
||||
if ($image_data = GalleryHelper::findSourceImageDetails($source, $this->options['destination_folder']))
|
||||
{
|
||||
// If force resizing is disabled and the original image exists, set the URL of the destination image
|
||||
if (!$this->options['force_resizing'] && file_exists($image_data['path']))
|
||||
{
|
||||
$item['url'] = GalleryHelper::directoryImageToURL($image_data['path']);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the destination image has not been edited and exists, abort
|
||||
if (!$image_data['edited'] && file_exists($image_data['path']))
|
||||
{
|
||||
$item['url'] = GalleryHelper::directoryImageToURL($image_data['path']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since we are forcing resizing, overwrite the existing image, do not create a new unique image
|
||||
$unique = false;
|
||||
|
||||
// The destination path is the same resized image
|
||||
$destination = $image_data['path'];
|
||||
}
|
||||
}
|
||||
|
||||
$original_image_file = is_null($this->options['original_image_resize_height'])
|
||||
?
|
||||
Image::resizeAndKeepAspectRatio(
|
||||
$source,
|
||||
$this->options['original_image_resize_width'],
|
||||
$this->options['original_image_resize_image_quality'],
|
||||
$destination,
|
||||
$unique
|
||||
)
|
||||
:
|
||||
Image::resize(
|
||||
$source,
|
||||
$this->options['original_image_resize_width'],
|
||||
$this->options['original_image_resize_height'],
|
||||
$this->options['original_image_resize_image_quality'],
|
||||
$this->options['original_image_resize_method'],
|
||||
$destination,
|
||||
$unique
|
||||
);
|
||||
|
||||
if (!$original_image_file)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set image URL
|
||||
$item = array_merge($item, [
|
||||
'url' => GalleryHelper::directoryImageToURL($original_image_file)
|
||||
]);
|
||||
|
||||
// Update image data in Gallery Info File
|
||||
GalleryHelper::updateImageDataInGalleryInfoFile($source, $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates thumbnails.
|
||||
*
|
||||
* If `force_resizing` is enabled, it will re-generate thumbsnails under the following cases:
|
||||
*
|
||||
* - If a thumbnail does not exist.
|
||||
* - If the original image has been edited.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function createThumbnails()
|
||||
{
|
||||
if (!$this->options['thumbnails'])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create destination folder if missing
|
||||
File::createDirs($this->options['destination_folder']);
|
||||
|
||||
foreach ($this->options['items'] as $key => &$item)
|
||||
{
|
||||
// Skip items that do not have a path set
|
||||
if (!isset($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if source does not exist
|
||||
if (!is_file($item['path']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$source = $item['path'];
|
||||
|
||||
$unique = true;
|
||||
|
||||
$parts = pathinfo($source);
|
||||
$destination = $this->options['destination_folder'] . $parts['filename'] . '_thumb.' . $parts['extension'];
|
||||
|
||||
// Find source image in the destination folder
|
||||
if ($image_data = GalleryHelper::findSourceImageDetails($source, $this->options['destination_folder']))
|
||||
{
|
||||
/**
|
||||
* Use the found original image path to produce the thumb file path.
|
||||
*
|
||||
* This is used as we have multiple files with the same which produce file names of _copy_X
|
||||
* and thus the above $destination will not be valid. Instead, we use the original file name
|
||||
* to find the thumbnail file.
|
||||
*/
|
||||
if ($this->options['original_image_resize'])
|
||||
{
|
||||
$parts = pathinfo($image_data['path']);
|
||||
$destination = $this->options['destination_folder'] . $parts['filename'] . '_thumb.' . $parts['extension'];
|
||||
}
|
||||
|
||||
// If force resizing is disabled and the thumbnail exists, set the URL of the destination image
|
||||
if (!$this->options['force_resizing'] && file_exists($destination))
|
||||
{
|
||||
$item['thumbnail_url'] = GalleryHelper::directoryImageToURL($destination);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the destination image has not been edited and exists, abort
|
||||
if (!$image_data['edited'] && file_exists($destination))
|
||||
{
|
||||
$item['thumbnail_url'] = GalleryHelper::directoryImageToURL($destination);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since we are forcing resizing, overwrite the existing image, do not create a new unique image
|
||||
$unique = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate thumbnails
|
||||
$thumb_file = is_null($this->options['thumb_height'])
|
||||
?
|
||||
Image::resizeAndKeepAspectRatio(
|
||||
$source,
|
||||
$this->options['thumb_width'],
|
||||
$this->options['thumb_resize_quality'],
|
||||
$destination,
|
||||
$unique
|
||||
)
|
||||
:
|
||||
Image::resize(
|
||||
$source,
|
||||
$this->options['thumb_width'],
|
||||
$this->options['thumb_height'],
|
||||
$this->options['thumb_resize_quality'],
|
||||
$this->options['thumb_resize_method'],
|
||||
$destination,
|
||||
$unique
|
||||
);
|
||||
|
||||
if (!$thumb_file)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set image thumbnail URL
|
||||
$item = array_merge($item, [
|
||||
'thumbnail_url' => GalleryHelper::directoryImageToURL($thumb_file)
|
||||
]);
|
||||
|
||||
// Update image data in Gallery Info File
|
||||
GalleryHelper::updateImageDataInGalleryInfoFile($source, $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the items.
|
||||
*
|
||||
* - Sets the thumbnails image dimensions.
|
||||
* - Assures caption property exist.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function prepareItems()
|
||||
{
|
||||
if (!is_array($this->options['items']) || !count($this->options['items']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->options['items'] as $key => &$item)
|
||||
{
|
||||
// Initialize image atts
|
||||
$item['img_atts'] = '';
|
||||
|
||||
// Initializes caption if none given
|
||||
if (!isset($item['caption']))
|
||||
{
|
||||
$item['caption'] = '';
|
||||
}
|
||||
|
||||
$item['alt'] = !empty($item['caption']) ? mb_substr($item['caption'], 0, 100) : pathinfo($item['url'], PATHINFO_FILENAME);
|
||||
|
||||
// Ensure a thumbnail is given
|
||||
if (!isset($item['thumbnail_url']))
|
||||
{
|
||||
// If no thumbnail is given, set it to the full image
|
||||
$item['thumbnail_url'] = $item['url'];
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the thumbnail size for this item is given, set the image attributes
|
||||
if (isset($item['thumbnail_size']))
|
||||
{
|
||||
$item['img_atts'] = 'width="' . $item['thumbnail_size']['width'] . '" height="' . $item['thumbnail_size']['height'] . '"';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSS variables for the widget.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setCSSVariables()
|
||||
{
|
||||
$data = array_merge(
|
||||
$this->getColumns(),
|
||||
$this->getGap()
|
||||
);
|
||||
|
||||
$css = '';
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
$css .= '--' . $key . ':' . $value . ';';
|
||||
}
|
||||
|
||||
\JFactory::getDocument()->addStyleDeclaration('
|
||||
.nrf-widget.' . $this->options['id'] . ' { ' . $css . ' }');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns of the gallery.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getColumns()
|
||||
{
|
||||
if (is_string($this->options['columns']))
|
||||
{
|
||||
$this->options['columns'] = (int) $this->options['columns'];
|
||||
}
|
||||
|
||||
if (!is_int($this->options['columns']) && !is_array($this->options['columns']))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$columns_defaults = [
|
||||
'columns' => 4,
|
||||
'tablet-columns' => 2,
|
||||
'mobile-columns' => 1
|
||||
];
|
||||
|
||||
$columns = [];
|
||||
|
||||
// String
|
||||
if (is_int($this->options['columns']))
|
||||
{
|
||||
$columns = [
|
||||
'columns' => $this->options['columns'],
|
||||
'tablet-columns' => $this->options['columns'] !== 1 ? 2 : 1,
|
||||
'mobile-columns' => 1
|
||||
];
|
||||
}
|
||||
|
||||
// Array
|
||||
if (is_array($this->options['columns']))
|
||||
{
|
||||
if (isset($this->options['columns']['desktop']))
|
||||
{
|
||||
$columns['columns'] = $this->options['columns']['desktop'];
|
||||
}
|
||||
if (isset($this->options['columns']['tablet']))
|
||||
{
|
||||
$columns['tablet-columns'] = $this->options['columns']['tablet'];
|
||||
}
|
||||
if (isset($this->options['columns']['mobile']))
|
||||
{
|
||||
$columns['mobile-columns'] = $this->options['columns']['mobile'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($columns_defaults, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gaps of the gallery.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getGap()
|
||||
{
|
||||
if (is_string($this->options['gap']))
|
||||
{
|
||||
$this->options['gap'] = (int) $this->options['gap'];
|
||||
}
|
||||
|
||||
if (!is_int($this->options['gap']) && !is_array($this->options['gap']))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$gap_defaults = [
|
||||
'gap' => 0,
|
||||
'tablet-gap' => 0,
|
||||
'mobile-gap' => 0
|
||||
];
|
||||
|
||||
$gaps = [];
|
||||
|
||||
// String
|
||||
if (is_int($this->options['gap']))
|
||||
{
|
||||
$gaps = [
|
||||
'gap' => $this->options['gap'] . 'px',
|
||||
'tablet-gap' => $this->options['gap'] . 'px',
|
||||
'mobile-gap' => $this->options['gap'] . 'px'
|
||||
];
|
||||
}
|
||||
|
||||
// Array
|
||||
if (is_array($this->options['gap']))
|
||||
{
|
||||
if (isset($this->options['gap']['desktop']))
|
||||
{
|
||||
$gaps['gap'] = $this->options['gap']['desktop'] . 'px';
|
||||
}
|
||||
if (isset($this->options['gap']['tablet']))
|
||||
{
|
||||
$gaps['tablet-gap'] = $this->options['gap']['tablet'] . 'px';
|
||||
}
|
||||
if (isset($this->options['gap']['mobile']))
|
||||
{
|
||||
$gaps['mobile-gap'] = $this->options['gap']['mobile'] . 'px';
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($gap_defaults, $gaps);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2021 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\Registry\Registry;
|
||||
use NRFramework\Helpers\Widgets\GalleryManager as GalleryManagerHelper;
|
||||
|
||||
/**
|
||||
* Gallery Manager
|
||||
*/
|
||||
class GalleryManager extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
// The input name
|
||||
'name' => '',
|
||||
|
||||
// The field ID associated to this Gallery Manager, used to retrieve the field settings on AJAX actions
|
||||
'field_id' => null,
|
||||
|
||||
/**
|
||||
* Max file size in MB.
|
||||
*
|
||||
* Defults to 0 (no limit).
|
||||
*/
|
||||
'max_file_size' => 0,
|
||||
|
||||
/**
|
||||
* How many files we can upload.
|
||||
*
|
||||
* Defaults to 0 (no limit).
|
||||
*/
|
||||
'limit_files' => 0,
|
||||
|
||||
// Allowed upload file types
|
||||
'allowed_file_types' => '.jpg, .jpeg, .png, .gif',
|
||||
|
||||
/**
|
||||
* Original Image
|
||||
*/
|
||||
// Should the original uploaded image be resized?
|
||||
'original_image_resize' => false,
|
||||
|
||||
// Original Image Resize Quality
|
||||
'original_image_resize_quality' => 80,
|
||||
|
||||
// Main image width
|
||||
'original_image_resize_width' => 1920,
|
||||
|
||||
/**
|
||||
* Thumbnails
|
||||
*/
|
||||
// Thumbnails width
|
||||
'thumb_width' => 300,
|
||||
|
||||
// Thumbnails height
|
||||
'thumb_height' => null,
|
||||
|
||||
// Thumbnails resize method (crop, stretch, fit)
|
||||
'thumb_resize_method' => 'crop',
|
||||
|
||||
// Thumbnails resize quality
|
||||
'thumb_resize_quality' => 80
|
||||
];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
// Set gallery items
|
||||
$this->options['gallery_items'] = is_array($this->options['value']) ? $this->options['value'] : [];
|
||||
|
||||
// Set css class for readonly state
|
||||
if ($this->options['readonly'])
|
||||
{
|
||||
$this->options['css_class'] .= ' readonly';
|
||||
}
|
||||
|
||||
// Adds a css class when the gallery contains at least one item
|
||||
if (count($this->options['gallery_items']))
|
||||
{
|
||||
$this->options['css_class'] .= ' dz-has-items';
|
||||
}
|
||||
|
||||
// Load translation strings
|
||||
\JText::script('NR_GALLERY_MANAGER_CONFIRM_REGENERATE_THUMBNAILS');
|
||||
\JText::script('NR_GALLERY_MANAGER_CONFIRM_DELETE_ALL_SELECTED');
|
||||
\JText::script('NR_GALLERY_MANAGER_CONFIRM_DELETE_ALL');
|
||||
\JText::script('NR_GALLERY_MANAGER_CONFIRM_DELETE');
|
||||
\JText::script('NR_GALLERY_MANAGER_FILE_MISSING');
|
||||
\JText::script('NR_GALLERY_MANAGER_REACHED_FILES_LIMIT');
|
||||
}
|
||||
|
||||
/**
|
||||
* The upload task called by the AJAX hanler
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ajax_upload()
|
||||
{
|
||||
$input = \JFactory::getApplication()->input;
|
||||
|
||||
// Make sure we have a valid field id
|
||||
if (!$field_id = $input->getInt('field_id'))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_FIELD_ID_ERROR');
|
||||
}
|
||||
|
||||
// Make sure we have a valid file passed
|
||||
if (!$file = $input->files->get('file'))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_ERROR_INVALID_FILE');
|
||||
}
|
||||
|
||||
if (!$field_data = \NRFramework\Helpers\CustomField::getData($field_id))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_INVALID_FIELD_DATA');
|
||||
}
|
||||
|
||||
// get the media uploader file data, values are passed when we upload a file using the Media Uploader
|
||||
$media_uploader_file_data = [
|
||||
'is_media_uploader_file' => $input->get('media_uploader', false) == '1',
|
||||
'media_uploader_filename' => $input->getString('media_uploader_filename', '')
|
||||
];
|
||||
|
||||
// In case we allow multiple uploads the file parameter is a 2 levels array.
|
||||
$first_property = array_pop($file);
|
||||
if (is_array($first_property))
|
||||
{
|
||||
$file = $first_property;
|
||||
}
|
||||
|
||||
$uploadSettings = [
|
||||
'allow_unsafe' => false,
|
||||
'allowed_types' => $field_data->get('allowed_file_types', $this->widget_options['allowed_file_types'])
|
||||
];
|
||||
|
||||
// resize image settings
|
||||
$resizeSettings = [
|
||||
'thumb_width' => $field_data->get('thumb_width', 300),
|
||||
// Send the height only if grid is selected. We do not need it for masonry style
|
||||
'thumb_height' => $field_data->get('style', 'masonry') === 'grid' ? $field_data->get('thumb_height', null) : null,
|
||||
'thumb_resize_method' => $field_data->get('thumb_resize_method', 'crop'),
|
||||
'thumb_resize_quality' => $field_data->get('thumb_resize_quality', 80),
|
||||
'original_image_resize' => ($field_data->get('original_image_resize', false)),
|
||||
'original_image_resize_width' => $field_data->get('original_image_resize_width', 1920),
|
||||
'original_image_resize_quality' => $field_data->get('original_image_resize_quality', 80)
|
||||
];
|
||||
|
||||
// Upload the file and resize the image if needed
|
||||
if (!$uploaded_filenames = GalleryManagerHelper::upload($file, $uploadSettings, $media_uploader_file_data, $resizeSettings))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_ERROR_CANNOT_UPLOAD_FILE');
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'filename' => $uploaded_filenames['filename'],
|
||||
'thumbnail' => $uploaded_filenames['thumbnail'],
|
||||
'is_media_uploader_file' => $media_uploader_file_data['is_media_uploader_file']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The delete task called by the AJAX hanlder
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ajax_delete()
|
||||
{
|
||||
$input = \JFactory::getApplication()->input;
|
||||
|
||||
// Make sure we have a valid file passed
|
||||
if (!$filename = $input->getString('filename'))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_ERROR_INVALID_FILE');
|
||||
}
|
||||
|
||||
// Make sure we have a valid field id
|
||||
if (!$field_id = $input->getInt('field_id'))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_FIELD_ID_ERROR');
|
||||
}
|
||||
|
||||
if (!$field_data = \NRFramework\Helpers\CustomField::getData($field_id))
|
||||
{
|
||||
$this->exitWithMessage('NR_GALLERY_MANAGER_INVALID_FIELD_DATA');
|
||||
}
|
||||
|
||||
// Delete the uploaded file
|
||||
$deleted = GalleryManagerHelper::deleteFile($filename, $input->getString('thumbnail'));
|
||||
|
||||
echo json_encode(['success' => $deleted]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This task allows us to regenerate the thumbnails.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ajax_regenerate_thumbs()
|
||||
{
|
||||
$input = \JFactory::getApplication()->input;
|
||||
|
||||
// Make sure we have a valid field id
|
||||
if (!$field_id = $input->getInt('field_id'))
|
||||
{
|
||||
echo json_encode(['success' => false, 'message' => \JText::_('NR_GALLERY_MANAGER_FIELD_ID_ERROR')]);
|
||||
die();
|
||||
}
|
||||
|
||||
if (!$field_data = \NRFramework\Helpers\CustomField::getData($field_id))
|
||||
{
|
||||
echo json_encode(['success' => false, 'message' => \JText::_('NR_GALLERY_MANAGER_INVALID_FIELD_DATA')]);
|
||||
die();
|
||||
}
|
||||
|
||||
$resizeSettings = [
|
||||
'thumb_width' => $field_data->get('thumb_width', 300),
|
||||
// Send the height only if grid is selected. We do not need it for masonry style
|
||||
'thumb_height' => $field_data->get('style', 'masonry') === 'grid' ? $field_data->get('thumb_height', null) : null,
|
||||
'thumb_resize_method' => $field_data->get('thumb_resize_method', 'crop'),
|
||||
'thumb_resize_quality' => $field_data->get('thumb_resize_quality', 80),
|
||||
];
|
||||
|
||||
$existing = $input->get('existing', null, 'ARRAY');
|
||||
$existing = json_decode($existing[0], true);
|
||||
|
||||
$new = $input->get('new', null, 'ARRAY');
|
||||
$new = json_decode($new[0], true);
|
||||
|
||||
$ds = DIRECTORY_SEPARATOR;
|
||||
|
||||
// Check each existing file and re-create thumbs
|
||||
if (is_array($existing) && count($existing))
|
||||
{
|
||||
foreach ($existing as $path)
|
||||
{
|
||||
$_path = implode($ds, [JPATH_ROOT, $path]);
|
||||
|
||||
if (!file_exists($_path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
\NRFramework\Helpers\Widgets\GalleryManager::generateThumbnail($_path, $resizeSettings, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Check each newly added file in temp folder and re-create thumbs
|
||||
if (is_array($new) && count($new))
|
||||
{
|
||||
foreach ($new as $path)
|
||||
{
|
||||
$_path = implode($ds, [\NRFramework\File::getTempFolder(), $path]);
|
||||
|
||||
if (!file_exists($_path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
\NRFramework\Helpers\Widgets\GalleryManager::generateThumbnail($_path, $resizeSettings, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => \JText::_('NR_GALLERY_MANAGER_THUMBS_REGENERATED')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the page with given message.
|
||||
*
|
||||
* @param string $translation_string
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function exitWithMessage($translation_string)
|
||||
{
|
||||
http_response_code('500');
|
||||
die(\JText::_($translation_string));
|
||||
}
|
||||
}
|
||||
77
plugins/system/nrframework/NRFramework/Widgets/Helper.php
Normal file
77
plugins/system/nrframework/NRFramework/Widgets/Helper.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2021 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
class Helper
|
||||
{
|
||||
/**
|
||||
* This is a map with all widgets used for caching the widget's class name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $widgets_map = [];
|
||||
|
||||
/**
|
||||
* Renders a Widget and returns
|
||||
*
|
||||
* @param array $options A list of attributes passed to the layout
|
||||
*
|
||||
* @return string The widget's final HTML layout
|
||||
*/
|
||||
public static function render($widget_name, $options = [])
|
||||
{
|
||||
if (!$widgetClass = self::find($widget_name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$class = __NAMESPACE__ . '\\' . $widgetClass;
|
||||
|
||||
// ensure class exists
|
||||
if (!class_exists($class))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
return (new $class($options))->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the real class name of a widget by a case-insensitive name.
|
||||
*
|
||||
* @param string $name The widget's name
|
||||
*
|
||||
* @return mixed Null when the class name is not found, string when the class name is found.
|
||||
*/
|
||||
public static function find($name)
|
||||
{
|
||||
if (!$name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
if (empty(self::$widgets_map) || !isset(self::$widgets_map[$name]))
|
||||
{
|
||||
$widgetClasses = \JFolder::files(__DIR__);
|
||||
foreach ($widgetClasses as $widgetClass)
|
||||
{
|
||||
$widgetClass = str_replace('.php', '', $widgetClass);
|
||||
|
||||
self::$widgets_map[strtolower($widgetClass)] = $widgetClass;
|
||||
}
|
||||
}
|
||||
|
||||
return isset(self::$widgets_map[$name]) ? self::$widgets_map[$name] : null;
|
||||
}
|
||||
}
|
||||
155
plugins/system/nrframework/NRFramework/Widgets/OpenStreetMap.php
Normal file
155
plugins/system/nrframework/NRFramework/Widgets/OpenStreetMap.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* OpenStreetMap
|
||||
*/
|
||||
class OpenStreetMap extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
/**
|
||||
* The value of the widget.
|
||||
* Format: latitude,longitude
|
||||
*
|
||||
* i.e. 36.891319,27.283480
|
||||
*/
|
||||
'value' => '',
|
||||
|
||||
// Default map width
|
||||
'width' => '500px',
|
||||
|
||||
// Default map height
|
||||
'height' => '400px',
|
||||
|
||||
// Default map zoon
|
||||
'zoom' => 4,
|
||||
|
||||
// Map scale. Values: metric, imperial, false
|
||||
'scale' => false,
|
||||
|
||||
// View mode of the map. Values: road, aerial.
|
||||
'view' => 'road',
|
||||
|
||||
/**
|
||||
* Address input above map
|
||||
*/
|
||||
// Whether to show the address input above the map
|
||||
'showAddressInput' => false,
|
||||
|
||||
/**
|
||||
* Map Marker
|
||||
*/
|
||||
// Whether to show the marker
|
||||
'showMarker' => true,
|
||||
|
||||
// Marker image relative to Joomla installation
|
||||
'markerImage' => 'media/plg_system_nrframework/img/marker.png',
|
||||
|
||||
// Allows marker to be dragged
|
||||
'allowMarkerDrag' => false,
|
||||
|
||||
// Allows map to be clicked and thus allows us to select a new location
|
||||
'allowMapClick' => false,
|
||||
|
||||
// Whether to show the marker tooltip
|
||||
'showMarkerTooltip' => false,
|
||||
|
||||
// Set whether to display the marker tooltip textarea.
|
||||
'showMarkerTooltipInput' => false,
|
||||
|
||||
// Marker Tooltip Textarea Name. If a value is given, then the tooltip textarea field appears below the map
|
||||
'markerTooltipName' => '',
|
||||
|
||||
// Marker tooltip value
|
||||
'markerTooltipValue' => '',
|
||||
|
||||
/**
|
||||
* Coordinates input below map
|
||||
*/
|
||||
// Whether to show the coordinates input
|
||||
'showCoordsInput' => false,
|
||||
|
||||
// Coordinates input name. If a value is given, then the coordinates input field appears below the map
|
||||
'coordsInputName' => ''
|
||||
];
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
$this->options['markerImage'] = \JURI::root() . ltrim($this->options['markerImage'], DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
self::loadMedia();
|
||||
|
||||
return parent::render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads media files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadMedia()
|
||||
{
|
||||
\JHtml::stylesheet('https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.9.0/css/ol.css');
|
||||
\JHtml::script('https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.9.0/build/ol.js');
|
||||
|
||||
$this->load_geocoder();
|
||||
|
||||
if ($this->options['load_stylesheet'])
|
||||
{
|
||||
\JHtml::stylesheet('plg_system_nrframework/widgets/openstreetmap.css', ['relative' => true, 'version' => 'auto']);
|
||||
}
|
||||
|
||||
\JHtml::script('plg_system_nrframework/widgets/openstreetmap.js', ['relative' => true, 'version' => 'auto']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether geocoder is enabled and loads it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_geocoder()
|
||||
{
|
||||
if (!$this->options['showAddressInput'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$lang = \JFactory::getLanguage();
|
||||
$lang_tag = $lang->getTag();
|
||||
$doc = \JFactory::getDocument();
|
||||
$doc->addScriptOptions('nrf_osm_settings', [
|
||||
'lang_tag' => $lang_tag
|
||||
]);
|
||||
\JText::script('NR_OSM_ADDRESS_DESC');
|
||||
|
||||
\JHtml::stylesheet('https://unpkg.com/ol-geocoder/dist/ol-geocoder.min.css');
|
||||
\JHtml::script('https://unpkg.com/ol-geocoder');
|
||||
|
||||
$this->options['css_class'] .= ' geocoder';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* The Range Slider widget
|
||||
*/
|
||||
class RangeSlider extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
// The default value of the widget.
|
||||
'value' => 0,
|
||||
|
||||
// The minimum value of the slider
|
||||
'min' => 0,
|
||||
|
||||
// The maximum value of the slider
|
||||
'max' => 100,
|
||||
|
||||
// The step of the slider
|
||||
'step' => 1,
|
||||
|
||||
// The main slider color
|
||||
'color' => '#1976d2',
|
||||
|
||||
// The input border color of the slider inputs
|
||||
'input_border_color' => '#bdbdbd',
|
||||
|
||||
// The input background color of the slider inputs
|
||||
'input_bg_color' => 'transparent'
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
// Base color is 20% of given color
|
||||
$this->options['base_color'] = $this->options['color'] . '33';
|
||||
|
||||
// Calculate value
|
||||
$this->options['value'] = (int) $this->options['value'] < $this->options['min'] ? $this->options['min'] : ((int) $this->options['value'] > $this->options['max'] ? $this->options['max'] : (int) $this->options['value']);
|
||||
|
||||
// Calculate bar percentage
|
||||
$this->options['bar_percentage'] = $this->options['max'] ? ~~(100 * ($this->options['value'] - $this->options['min']) / ($this->options['max'] - $this->options['min'])) : $this->options['value'];
|
||||
}
|
||||
}
|
||||
60
plugins/system/nrframework/NRFramework/Widgets/Rating.php
Normal file
60
plugins/system/nrframework/NRFramework/Widgets/Rating.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* The Rating Widget
|
||||
*/
|
||||
class Rating extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
// The SVG icon representing the rating icon. Available values: check, circle, flag, heart, smiley, square, star, thumbs_up
|
||||
'icon' => 'star',
|
||||
|
||||
// The default value of the widget.
|
||||
'value' => 0,
|
||||
|
||||
// How many stars to show?
|
||||
'max_rating' => 5,
|
||||
|
||||
// Whether to show half ratings
|
||||
'half_ratings' => false,
|
||||
|
||||
// The size of the rating icon in pixels.
|
||||
'size' => 24,
|
||||
|
||||
// The color of the icon in the default state
|
||||
'selected_color' => '#f6cc01',
|
||||
|
||||
// The color of the icon in the selected and hover state
|
||||
'unselected_color' => '#bdbdbd'
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
$this->options['value'] = $this->options['value'] > $this->options['max_rating'] ? $this->options['max_rating'] : $this->options['value'];
|
||||
$this->options['icon_url'] = \JURI::root() . 'media/plg_system_nrframework/svg/rating/' . $this->options['icon'] . '.svg';
|
||||
$this->options['max_rating'] = $this->options['half_ratings'] ? 2 * $this->options['max_rating'] : $this->options['max_rating'];
|
||||
}
|
||||
}
|
||||
92
plugins/system/nrframework/NRFramework/Widgets/Signature.php
Normal file
92
plugins/system/nrframework/NRFramework/Widgets/Signature.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2018 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* Signature
|
||||
*/
|
||||
class Signature extends Widget
|
||||
{
|
||||
/**
|
||||
* Widget default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $widget_options = [
|
||||
// The base64 image data of the signature.
|
||||
'value' => '',
|
||||
|
||||
// The width of the signature in pixels or empty for auto width. The width will be taken from the signature container.
|
||||
'width' => '',
|
||||
|
||||
// The height of the signature in pixels.
|
||||
'height' => '300px',
|
||||
|
||||
// The background color of the signature.
|
||||
'background_color' => '#ffffff',
|
||||
|
||||
// The border color of the canvas.
|
||||
'border_color' => '#dedede',
|
||||
|
||||
/**
|
||||
* The border radius of the canvas.
|
||||
*
|
||||
* Example values: 0, 0px, 50px, 50%
|
||||
*/
|
||||
'border_radius' => 0,
|
||||
|
||||
/**
|
||||
* The border width of the canvas.
|
||||
*
|
||||
* Example values: 0, 1px, 5px
|
||||
*/
|
||||
'border_width' => '1px',
|
||||
|
||||
// Whether to show the horizontal line within the canvas
|
||||
'show_line' => true,
|
||||
|
||||
/**
|
||||
* The line color.
|
||||
*
|
||||
* If `null`, retrieves the value from `border_color`
|
||||
*/
|
||||
'line_color' => null,
|
||||
|
||||
// The pen color
|
||||
'pen_color' => '#000'
|
||||
];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
if ($this->options['readonly'])
|
||||
{
|
||||
$this->options['css_class'] .= ' readonly';
|
||||
}
|
||||
|
||||
if (!empty($this->options['value']))
|
||||
{
|
||||
$this->options['css_class'] .= ' painted has-value';
|
||||
}
|
||||
|
||||
if ($this->options['show_line'])
|
||||
{
|
||||
$this->options['css_class'] .= ' show-line';
|
||||
}
|
||||
}
|
||||
}
|
||||
155
plugins/system/nrframework/NRFramework/Widgets/Widget.php
Normal file
155
plugins/system/nrframework/NRFramework/Widgets/Widget.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link http://www.tassos.gr
|
||||
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
|
||||
namespace NRFramework\Widgets;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
class Widget
|
||||
{
|
||||
/**
|
||||
* Widget's default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [
|
||||
// Set whether to load the CSS variables
|
||||
'load_css_vars' => true,
|
||||
|
||||
// Set whether to load the default stylesheet
|
||||
'load_stylesheet' => true,
|
||||
|
||||
// If true, the widget will be rended in read-only mode.
|
||||
'readonly' => false,
|
||||
|
||||
// If true, the widget will be rended in disabled mode.
|
||||
'disabled' => false,
|
||||
|
||||
// Indicates the widget's input field must be filled out before submitting the form.
|
||||
'required' => false,
|
||||
|
||||
// The CSS class to be used on the widget's wrapper
|
||||
'css_class' => '',
|
||||
|
||||
// The CSS class to be used on the input
|
||||
'input_class' => '',
|
||||
|
||||
// The default widget value
|
||||
'value' => '',
|
||||
|
||||
// Extra attributes
|
||||
'atts' => '',
|
||||
|
||||
// A short hint that describes the expected value
|
||||
'placeholder' => '',
|
||||
|
||||
// The name of the layout to be used to render the widget
|
||||
'layout' => 'default',
|
||||
|
||||
// Whether we are rendering the Pro version of the widget
|
||||
'pro' => false
|
||||
];
|
||||
|
||||
/**
|
||||
* If no name is provided, this counter is appended to the widget's name to prevent name conflicts
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $counter = 0;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
// Merge Widget class default options with given Widget default options
|
||||
$this->options = array_merge($this->options, $this->widget_options, $options);
|
||||
|
||||
// Set ID if none given
|
||||
if (!isset($this->options['id']))
|
||||
{
|
||||
$this->options['id'] = $this->getName() . self::$counter;
|
||||
}
|
||||
|
||||
// Help developers target the whole widget by applying the widget's ID to the CSS class list.
|
||||
// Do not use the id="xx" attribute in the HTML to prevent conflicts with the input's ID.
|
||||
$this->options['css_class'] .= ' ' . $this->options['id'];
|
||||
|
||||
// Set name if none given
|
||||
if (!isset($this->options['name']))
|
||||
{
|
||||
$this->options['name'] = $this->options['id'];
|
||||
}
|
||||
|
||||
// Set disabled class if widget is disabled
|
||||
if ($this->options['disabled'])
|
||||
{
|
||||
$this->options['css_class'] .= ' disabled';
|
||||
}
|
||||
|
||||
self::$counter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget with the given layout
|
||||
*
|
||||
* Layouts can be overriden in the following folder: /templates/TEMPLATE_NAME/html/tassos/WIDGET_NAME/LAYOUT_NAME.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$defaultPath = implode(DIRECTORY_SEPARATOR, [JPATH_PLUGINS, 'system', 'nrframework', 'layouts']);
|
||||
$overridePath = implode(DIRECTORY_SEPARATOR, [JPATH_THEMES, \JFactory::getApplication()->getTemplate(), 'html', 'tassos']);
|
||||
|
||||
$layout = new \JLayoutFile('widgets.' . $this->getName() . '.' . $this->options['layout'], null, ['debug' => false]);
|
||||
$layout->addIncludePaths($defaultPath);
|
||||
$layout->addIncludePaths($overridePath);
|
||||
|
||||
return $layout->render($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the widget
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return strtolower((new \ReflectionClass($this))->getShortName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages ajax requests for the widget.
|
||||
*
|
||||
* @param string $task
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAjax($task)
|
||||
{
|
||||
\JSession::checkToken('request') or die('Invalid Token');
|
||||
|
||||
if (!$task || !is_string($task))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$method = 'ajax_' . $task;
|
||||
|
||||
if (!method_exists($this, $method))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->$method();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user