diff --git a/administrator/components/com_media/resources/scripts/components/browser/items/item.es6.js b/administrator/components/com_media/resources/scripts/components/browser/items/item.es6.js
index b52bbd233180e..85975fee55e97 100644
--- a/administrator/components/com_media/resources/scripts/components/browser/items/item.es6.js
+++ b/administrator/components/com_media/resources/scripts/components/browser/items/item.es6.js
@@ -94,6 +94,8 @@ export default {
thumb: this.item.thumb,
fileType: this.item.mime_type ? this.item.mime_type : false,
extension: this.item.extension ? this.item.extension : false,
+ width: this.item.width ? this.item.width : 0,
+ height: this.item.height ? this.item.height : 0,
},
},
),
diff --git a/administrator/components/com_media/resources/scripts/components/browser/items/row.vue b/administrator/components/com_media/resources/scripts/components/browser/items/row.vue
index b7165fa0548f6..2870763f8584d 100644
--- a/administrator/components/com_media/resources/scripts/components/browser/items/row.vue
+++ b/administrator/components/com_media/resources/scripts/components/browser/items/row.vue
@@ -104,6 +104,8 @@ export default {
if (this.item.type === 'file') {
data.path = this.item.path;
data.thumb = this.item.thumb ? this.item.thumb : false;
+ data.width = this.item.width ? this.item.width : 0;
+ data.height = this.item.height ? this.item.height : 0;
const ev = new CustomEvent('onMediaFileSelected', {
bubbles: true,
diff --git a/administrator/language/en-GB/joomla.ini b/administrator/language/en-GB/joomla.ini
index 06aa152e0d29a..91642dd0a9bef 100644
--- a/administrator/language/en-GB/joomla.ini
+++ b/administrator/language/en-GB/joomla.ini
@@ -241,6 +241,8 @@ JFIELD_LOGOUT_REDIRECT_PAGE_DESC="Select or create the page the user will be red
JFIELD_LOGOUT_REDIRECT_PAGE_LABEL="Logout Redirection Page"
JFIELD_LOGOUT_REDIRECT_URL_DESC="If a URL is entered here, users will be redirected to it after logout.
The URL must be internal (eg: index.php?Itemid=999)."
JFIELD_LOGOUT_REDIRECT_URL_LABEL="Logout Redirect"
+JFIELD_MEDIA_ALT_LABEL="Alt Text"
+JFIELD_MEDIA_LAZY_LABEL="Image will be lazyloaded"
; Do not translate the text between the {}
JFIELD_META_DESCRIPTION_COUNTER="{remaining} characters remaining of {maxlength} characters."
JFIELD_META_DESCRIPTION_DESC="An optional paragraph to be used as the description of the page in the HTML output. This will generally display in the results of search engines."
diff --git a/administrator/language/en-GB/plg_editors_tinymce.ini b/administrator/language/en-GB/plg_editors_tinymce.ini
index 3ab4bfae8a35c..ff7512c18296b 100644
--- a/administrator/language/en-GB/plg_editors_tinymce.ini
+++ b/administrator/language/en-GB/plg_editors_tinymce.ini
@@ -8,6 +8,9 @@ PLG_TINY_BUILDER="TinyMCE Configuration Builder"
PLG_TINY_BUTTON_TOGGLE_EDITOR="Toggle Editor"
PLG_TINY_CONFIG_TEXTFILTER_ACL_LABEL="Use Joomla Text Filter"
PLG_TINY_CORE_BUTTONS="CMS Content"
+PLG_TINY_DND_ADDITIONALDATA="Additional Data"
+PLG_TINY_DND_ALTTEXT="Alt Text"
+PLG_TINY_DND_LAZYLOADED="Image will be lazyloaded"
PLG_TINY_ERR_CUSTOMCSSFILENOTPRESENT="The file name %s was entered in the TinyMCE Custom CSS field. This file could not be found in the default template folder. No styles are available."
PLG_TINY_ERR_EDITORCSSFILENOTPRESENT="Could not find the file 'editor.css' in the template or templates/system folder. No styles are available."
PLG_TINY_ERR_UNSUPPORTEDBROWSER="Drag and drop image upload is not available for your browser. Please consider using a fully HTML5 compatible browser."
diff --git a/build/media_source/plg_editors_tinymce/js/plugins/dragdrop/plugin.es6.js b/build/media_source/plg_editors_tinymce/js/plugins/dragdrop/plugin.es6.js
index 73bd140e69a15..6f434591622ef 100644
--- a/build/media_source/plg_editors_tinymce/js/plugins/dragdrop/plugin.es6.js
+++ b/build/media_source/plg_editors_tinymce/js/plugins/dragdrop/plugin.es6.js
@@ -1,10 +1,11 @@
/* eslint-disable no-undef */
tinymce.PluginManager.add('jdragndrop', (editor) => {
+ let responseData;
// Reset the drop area border
tinyMCE.DOM.bind(document, 'dragleave', (e) => {
e.stopPropagation();
e.preventDefault();
- tinyMCE.activeEditor.contentAreaContainer.style.borderWidth = '1px 0 0';
+ editor.contentAreaContainer.style.borderWidth = '1px 0 0';
return false;
});
@@ -26,12 +27,12 @@ tinymce.PluginManager.add('jdragndrop', (editor) => {
});
function uploadFile(name, content) {
- const url = `${tinyMCE.activeEditor.settings.uploadUri}&path=${tinyMCE.activeEditor.settings.comMediaAdapter}`;
+ const url = `${editor.settings.uploadUri}&path=${editor.settings.comMediaAdapter}`;
const data = {
- [tinyMCE.activeEditor.settings.csrfToken]: '1',
+ [editor.settings.csrfToken]: '1',
name,
content,
- parent: tinyMCE.activeEditor.settings.parentUploadFolder,
+ parent: editor.settings.parentUploadFolder,
};
Joomla.request({
@@ -49,18 +50,68 @@ tinymce.PluginManager.add('jdragndrop', (editor) => {
}
if (response.data && response.data.path) {
+ responseData = response.data;
let urlPath;
// For local adapters use relative paths
- if (/local-/.test(response.data.adapter)) {
+ if (/local-/.test(responseData.adapter)) {
const { rootFull } = Joomla.getOptions('system.paths');
urlPath = `${response.data.thumb_path.split(rootFull)[1]}`;
- } else if (response.data.thumb_path) {
+ } else if (responseData.thumb_path) {
// Absolute path for different domain
- urlPath = response.data.thumb_path;
+ urlPath = responseData.thumb_path;
}
- tinyMCE.activeEditor.execCommand('mceInsertContent', false, ``);
+ const dialogClose = (api) => {
+ const dialogData = api.getData();
+ const altValue = dialogData.altText ? ` alt="${dialogData.altText}"` : '';
+ const lazyValue = dialogData.isLazy ? ' loading="lazy"' : '';
+ const width = dialogData.isLazy ? ` width="${responseData.width}"` : '';
+ const height = dialogData.isLazy ? ` height="${responseData.height}"` : '';
+ editor.execCommand('mceInsertContent', false, ``);
+ };
+
+ editor.windowManager.open({
+ title: Joomla.Text._('PLG_TINY_DND_ADDITIONALDATA'),
+ body: {
+ type: 'panel',
+ items: [
+ {
+ type: 'input',
+ name: 'altText',
+ label: Joomla.Text._('PLG_TINY_DND_ALTTEXT'),
+ },
+ {
+ type: 'checkbox',
+ name: 'isLazy',
+ label: Joomla.Text._('PLG_TINY_DND_LAZYLOADED'),
+ },
+ ],
+ },
+ buttons: [
+ {
+ type: 'cancel',
+ text: 'Cancel',
+ },
+ {
+ type: 'submit',
+ name: 'submitButton',
+ text: 'Save',
+ primary: true,
+ },
+ ],
+ initialData: {
+ altText: '',
+ isLazy: true,
+ },
+ onSubmit(api) {
+ dialogClose(api);
+ api.close();
+ },
+ onCancel(api) {
+ dialogClose(api);
+ },
+ });
}
},
onError: (xhr) => {
diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json
index f78516af1d90b..7ada4f2bc66af 100644
--- a/build/media_source/system/joomla.asset.json
+++ b/build/media_source/system/joomla.asset.json
@@ -210,6 +210,12 @@
"uri": "system/fields/joomla-field-media.min.css",
"webcomponent": true
},
+ {
+ "name": "webcomponent.image-select",
+ "type": "script",
+ "uri": "system/fields/joomla-image-select.min.js",
+ "webcomponent": true
+ },
{
"name": "webcomponent.field-media",
"type": "script",
diff --git a/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js b/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js
index cc2a33054cff8..a9ac24d0a9c1b 100644
--- a/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js
+++ b/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js
@@ -1,87 +1,10 @@
((customElements, Joomla) => {
+ 'use strict';
+
if (!Joomla) {
throw new Error('Joomla API is not properly initiated');
}
- Joomla.selectedFile = {};
-
- window.document.addEventListener('onMediaFileSelected', (e) => {
- Joomla.selectedFile = e.detail;
- });
-
- const execTransform = (resp, editor, fieldClass) => {
- if (resp.success === true) {
- if (resp.data[0].url) {
- if (/local-/.test(resp.data[0].adapter)) {
- const { rootFull } = Joomla.getOptions('system.paths');
-
- // eslint-disable-next-line prefer-destructuring
- Joomla.selectedFile.url = resp.data[0].url.split(rootFull)[1];
- if (resp.data[0].thumb_path) {
- Joomla.selectedFile.thumb = resp.data[0].thumb_path;
- } else {
- Joomla.selectedFile.thumb = false;
- }
- } else if (resp.data[0].thumb_path) {
- Joomla.selectedFile.thumb = resp.data[0].thumb_path;
- }
- } else {
- Joomla.selectedFile.url = false;
- }
-
- const isElement = (o) => (
- typeof HTMLElement === 'object' ? o instanceof HTMLElement
- : o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
- );
-
- if (Joomla.selectedFile.url) {
- if (!isElement(editor) && (typeof editor !== 'object')) {
- Joomla.editors.instances[editor].replaceSelection(``);
- } else if (!isElement(editor) && (typeof editor === 'object' && editor.id)) {
- window.parent.Joomla.editors.instances[editor.id].replaceSelection(``);
- } else {
- editor.value = Joomla.selectedFile.url;
- fieldClass.updatePreview();
- }
- }
- }
- };
-
- /**
- * Create and dispatch onMediaFileSelected Event
- *
- * @param {object} data The data for the detail
- *
- * @returns {void}
- */
- Joomla.getImage = (data, editor, fieldClass) => new Promise((resolve, reject) => {
- if (!data || (typeof data === 'object' && (!data.path || data.path === ''))) {
- Joomla.selectedFile = {};
- resolve({
- resp: {
- success: false,
- },
- });
- return;
- }
-
- const apiBaseUrl = `${Joomla.getOptions('system.paths').baseFull}index.php?option=com_media&format=json`;
-
- Joomla.request({
- url: `${apiBaseUrl}&task=api.files&url=true&path=${data.path}&${Joomla.getOptions('csrf.token')}=1&format=json`,
- method: 'GET',
- perform: true,
- headers: { 'Content-Type': 'application/json' },
- onSuccess: (response) => {
- const resp = JSON.parse(response);
- resolve(execTransform(resp, editor, fieldClass));
- },
- onError: (err) => {
- reject(err);
- },
- });
- });
-
class JoomlaFieldMedia extends HTMLElement {
constructor() {
super();
@@ -204,20 +127,20 @@
show() {
this.modalElement.open();
- Joomla.selectedFile = {};
+ Joomla.selectedMediaFile = {};
this.buttonSaveSelectedElement.addEventListener('click', this.onSelected);
}
modalClose() {
- Joomla.getImage(Joomla.selectedFile, this.inputElement, this)
+ Joomla.getImage(Joomla.selectedMediaFile, this.inputElement, this)
.then(() => {
Joomla.Modal.getCurrent().close();
- Joomla.selectedFile = {};
+ Joomla.selectedMediaFile = {};
})
.catch(() => {
Joomla.Modal.getCurrent().close();
- Joomla.selectedFile = {};
+ Joomla.selectedMediaFile = {};
Joomla.renderMessages({
error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')],
});
@@ -248,15 +171,14 @@
this.previewElement.innerHTML = '';
const imgPreview = new Image();
- switch (this.type) {
- case 'image':
+ const mediaType = {
+ image() {
imgPreview.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
imgPreview.setAttribute('alt', '');
- break;
- default:
- // imgPreview.src = dummy image path;
- break;
- }
+ },
+ };
+
+ mediaType[this.type]();
this.previewElement.style.width = this.previewWidth;
this.previewElement.appendChild(imgPreview);
diff --git a/build/media_source/system/js/fields/joomla-image-select.w-c.es6.js b/build/media_source/system/js/fields/joomla-image-select.w-c.es6.js
new file mode 100644
index 0000000000000..fe300904c064d
--- /dev/null
+++ b/build/media_source/system/js/fields/joomla-image-select.w-c.es6.js
@@ -0,0 +1,270 @@
+((customElements, Joomla) => {
+ 'use strict';
+
+ if (!Joomla) {
+ throw new Error('Joomla API is not properly initiated');
+ }
+
+ /**
+ * An object holding all the information of the selected image in media manager
+ * eg:
+ * {
+ * extension: "png"
+ * fileType: "image/png"
+ * height: 44
+ * path: "local-0:/powered_by.png"
+ * thumb: undefined
+ * width: 294
+ * }
+ */
+ Joomla.selectedMediaFile = {};
+
+ /**
+ * Event Listener that updates the Joomla.selectedMediaFile
+ * to the selected file in the media manager
+ */
+ window.document.addEventListener('onMediaFileSelected', (e) => {
+ Joomla.selectedMediaFile = e.detail;
+
+ const currentModal = Joomla.Modal.getCurrent();
+ const container = currentModal.querySelector('.modal-body');
+
+ // No extra attributes (lazy, alt) for fields
+ if (container.closest('joomla-field-media')) {
+ return;
+ }
+
+ const optionsEl = container.querySelector('joomla-field-mediamore');
+ if (optionsEl) {
+ optionsEl.parentNode.removeChild(optionsEl);
+ }
+
+ if (Joomla.selectedMediaFile.path) {
+ container.insertAdjacentHTML('afterbegin', ``);
+ }
+ });
+
+ /**
+ * Method to check if passed param is HTMLElement
+ *
+ * @param o {string|HTMLElement} Element to be checked
+ *
+ * @returns {boolean}
+ */
+ const isElement = (o) => (
+ typeof HTMLElement === 'object' ? o instanceof HTMLElement
+ : o && typeof o === 'object' && o.nodeType === 1 && typeof o.nodeName === 'string'
+ );
+
+ /**
+ * Method to safely append parameters to a URL string
+ *
+ * @param url {string} The URL
+ * @param key {string} The key of the parameter
+ * @param value {string} The value of the parameter
+ *
+ * @returns {string}
+ */
+ const appendParam = (url, key, value) => {
+ const newKey = encodeURIComponent(key);
+ const newValue = encodeURIComponent(value);
+ const r = new RegExp(`(&|\\?)${key}=[^&]*`);
+ let s = url;
+ const param = `${newKey}=${newValue}`;
+
+ s = s.replace(r, `$1${param}`);
+
+ if (!RegExp.$1 && s.includes('?')) {
+ return `${s}&${param}`;
+ }
+
+ if (!RegExp.$1 && !s.includes('?')) {
+ return `${s}?${param}`;
+ }
+
+ return s;
+ };
+
+ /**
+ * Method to append the image in an editor or a field
+ *
+ * @param resp
+ * @param editor
+ * @param fieldClass
+ */
+ const execTransform = (resp, editor, fieldClass) => {
+ if (resp.success === true) {
+ if (resp.data[0].url) {
+ if (/local-/.test(resp.data[0].adapter)) {
+ const { rootFull } = Joomla.getOptions('system.paths');
+
+ // eslint-disable-next-line prefer-destructuring
+ Joomla.selectedMediaFile.url = resp.data[0].url.split(rootFull)[1];
+ if (resp.data[0].thumb_path) {
+ Joomla.selectedMediaFile.thumb = resp.data[0].thumb_path;
+ } else {
+ Joomla.selectedMediaFile.thumb = false;
+ }
+ } else if (resp.data[0].thumb_path) {
+ Joomla.selectedMediaFile.thumb = resp.data[0].thumb_path;
+ }
+ } else {
+ Joomla.selectedMediaFile.url = false;
+ }
+
+ if (Joomla.selectedMediaFile.url) {
+ let isLazy = '';
+ let alt = '';
+
+ if (!isElement(editor)) {
+ const currentModal = fieldClass.closest('.modal-content');
+ const attribs = currentModal.querySelector('joomla-field-mediamore');
+ if (attribs) {
+ alt = attribs.getAttribute('alt-value') ? ` alt="${attribs.getAttribute('alt-value')}"` : '';
+ if (attribs.getAttribute('is-lazy') === 'true') {
+ isLazy = ` loading="lazy" width="${Joomla.selectedMediaFile.width}" height="${Joomla.selectedMediaFile.height}"`;
+ }
+ attribs.parentNode.removeChild(attribs);
+ }
+
+ Joomla.editors.instances[editor].replaceSelection(``);
+ } else {
+ const val = appendParam(Joomla.selectedMediaFile.url, 'joomla_image_width', Joomla.selectedMediaFile.width);
+ editor.value = appendParam(val, 'joomla_image_height', Joomla.selectedMediaFile.height);
+ fieldClass.updatePreview();
+ }
+ }
+ }
+ };
+
+ /**
+ * Method that resolves the real url for the selected image
+ *
+ * @param data {object} The data for the detail
+ * @param editor {string|object} The data for the detail
+ * @param fieldClass {HTMLElement} The fieldClass for the detail
+ *
+ * @returns {void}
+ */
+ Joomla.getImage = (data, editor, fieldClass) => new Promise((resolve, reject) => {
+ if (!data || (typeof data === 'object' && (!data.path || data.path === ''))) {
+ Joomla.selectedMediaFile = {};
+ resolve({
+ resp: {
+ success: false,
+ },
+ });
+ return;
+ }
+
+ const apiBaseUrl = `${Joomla.getOptions('system.paths').rootFull}administrator/index.php?option=com_media&format=json`;
+
+ Joomla.request({
+ url: `${apiBaseUrl}&task=api.files&url=true&path=${data.path}&${Joomla.getOptions('csrf.token')}=1&format=json`,
+ method: 'GET',
+ perform: true,
+ headers: { 'Content-Type': 'application/json' },
+ onSuccess: (response) => {
+ const resp = JSON.parse(response);
+ resolve(execTransform(resp, editor, fieldClass));
+ },
+ onError: (err) => {
+ reject(err);
+ },
+ });
+ });
+
+ /**
+ * A sipmle Custom Element for adding alt text and controlling
+ * the lazy loading on a selected image
+ *
+ * Will be rendered only for editor content images
+ * Attributes:
+ * - parent-id: the id of the parent media field {string}
+ * - lazy-label: The text for the checkbox label {string}
+ * - alt-label: The text for the alt label {string}
+ * - is-lazy: The value for the lazyloading (calculated, defaults to 'true') {string}
+ * - alt-value: The value for the alt text (calculated, defaults to '') {string}
+ */
+ class JoomlaFieldMediaOptions extends HTMLElement {
+ constructor() {
+ super();
+
+ this.adjustedHeight = false;
+ this.lazyInputFn = this.lazyInputFn.bind(this);
+ this.altInputFn = this.altInputFn.bind(this);
+ this.adjustHeight = this.adjustHeight.bind(this);
+ }
+
+ get parentId() { return this.getAttribute('parent-id'); }
+
+ get lazytext() { return this.getAttribute('lazy-label'); }
+
+ get alttext() { return this.getAttribute('alt-label'); }
+
+ connectedCallback() {
+ this.innerHTML = `
+
`;
+
+ // Add event listeners
+ this.lazyInput = this.querySelector(`#${this.parentId}-lazy`);
+ this.lazyInput.addEventListener('change', this.lazyInputFn);
+ this.altInput = this.querySelector(`#${this.parentId}-alt`);
+ this.altInput.addEventListener('input', this.altInputFn);
+
+ // Set initial values
+ this.setAttribute('is-lazy', !!this.lazyInput.checked);
+ this.setAttribute('alt-value', '');
+
+ // Reduce iframe height
+ if (!this.adjustedHeight) {
+ requestAnimationFrame(this.adjustHeight);
+ }
+ }
+
+ disconnectedCallback() {
+ this.lazyInput.removeEventListener('click', this.lazyInputFn);
+ if (this.enableAltField) {
+ this.altInput.removeEventListener('click', this.altInputFn);
+ }
+
+ this.innerHTML = '';
+ }
+
+ lazyInputFn(e) {
+ this.setAttribute('is-lazy', !!e.target.checked);
+ }
+
+ altInputFn(e) {
+ this.setAttribute('alt-value', e.target.value.replace(/"/g, '"'));
+ }
+
+ adjustHeight() {
+ const that = this;
+ const parentEl = this.parentNode;
+ const nextEl = this.nextElementSibling;
+ requestAnimationFrame(() => {
+ const height = `${parentEl.getBoundingClientRect().height - (that.getBoundingClientRect().height + 40)}`;
+ nextEl.style.height = `${height}px`;
+ that.adjustedHeight = true;
+ });
+ }
+ }
+
+ customElements.define('joomla-field-mediamore', JoomlaFieldMediaOptions);
+})(customElements, Joomla);
diff --git a/language/en-GB/joomla.ini b/language/en-GB/joomla.ini
index 98cbf8927ed52..a807a853a8eed 100644
--- a/language/en-GB/joomla.ini
+++ b/language/en-GB/joomla.ini
@@ -182,6 +182,8 @@ JFIELD_COLOR_VALUE="Colour with hexadecimal value of"
JFIELD_FIELDS_CATEGORY_DESC="Select the category that this field is assigned to."
JFIELD_LANGUAGE_DESC="Assign a language to this article."
JFIELD_LANGUAGE_LABEL="Language"
+JFIELD_MEDIA_ALT_LABEL="Alt Text"
+JFIELD_MEDIA_LAZY_LABEL="Image will be lazyloaded"
; Do not translate the text between the {}
JFIELD_META_DESCRIPTION_COUNTER="{remaining} characters remaining of {maxlength} characters."
JFIELD_META_DESCRIPTION_DESC="Metadata description."
diff --git a/layouts/joomla/content/full_image.php b/layouts/joomla/content/full_image.php
index 6bc95b309004a..f4f08b6f87ce6 100644
--- a/layouts/joomla/content/full_image.php
+++ b/layouts/joomla/content/full_image.php
@@ -9,18 +9,36 @@
defined('_JEXEC') or die;
+use Joomla\CMS\HTML\HTMLHelper;
+use Joomla\Utilities\ArrayHelper;
+
$params = $displayData->params;
$images = json_decode($displayData->images);
+
+if (empty($images->image_fulltext))
+{
+ return;
+}
+
+$imgclass = empty($images->float_fulltext) ? $params->get('float_fulltext') : $images->float_fulltext;
+$extraAttr = '';
+$img = HTMLHelper::cleanImageURL($images->image_fulltext);
+$alt = empty($images->image_fulltext_alt) && empty($images->image_fulltext_alt_empty) ? '' : 'alt="' . htmlspecialchars($images->image_fulltext_alt, ENT_COMPAT, 'UTF-8') . '"';
+
+// Set lazyloading only for images which have width and height attributes
+if ((isset($img->attributes['width']) && (int) $img->attributes['width'] > 0)
+&& (isset($img->attributes['height']) && (int) $img->attributes['height'] > 0))
+{
+ $extraAttr = ArrayHelper::toString($img->attributes) . ' loading="lazy"';
+}
?>
-image_fulltext)) : ?>
- float_fulltext) ? $params->get('float_fulltext') : $images->float_fulltext; ?>
- image_fulltext_alt) && empty($images->image_fulltext_alt_empty) ? '' : 'alt="' . htmlspecialchars($images->image_fulltext_alt, ENT_COMPAT, 'UTF-8') . '"'; ?>
-
-
+
diff --git a/layouts/joomla/content/intro_image.php b/layouts/joomla/content/intro_image.php
index be5e685022a89..f4b36a0721c91 100644
--- a/layouts/joomla/content/intro_image.php
+++ b/layouts/joomla/content/intro_image.php
@@ -9,34 +9,48 @@
defined('_JEXEC') or die;
+use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
use Joomla\Component\Content\Site\Helper\RouteHelper;
+use Joomla\Utilities\ArrayHelper;
$params = $displayData->params;
$images = json_decode($displayData->images);
+if (empty($images->image_intro))
+{
+ return;
+}
+
+$imgclass = empty($images->float_intro) ? $params->get('float_intro') : $images->float_intro;
+$extraAttr = '';
+$img = HTMLHelper::cleanImageURL($images->image_intro);
+$alt = empty($images->image_intro_alt) && empty($images->image_intro_alt_empty) ? '' : 'alt="'. htmlspecialchars($images->image_intro_alt, ENT_COMPAT, 'UTF-8') .'"';
+
+// Set lazyloading only for images which have width and height attributes
+if ((isset($img->attributes['width']) && (int) $img->attributes['width'] > 0)
+&& (isset($img->attributes['height']) && (int) $img->attributes['height'] > 0))
+{
+ $extraAttr = ArrayHelper::toString($img->attributes) . ' loading="lazy"';
+}
?>
-image_intro)) : ?>
- float_intro) ? $params->get('float_intro') : $images->float_intro; ?>
- image_intro_alt) && empty($images->image_intro_alt_empty) ? '' : 'alt="'. htmlspecialchars($images->image_intro_alt, ENT_COMPAT, 'UTF-8') .'"'; ?>
-
-
+
+ />
+
+ image_intro_caption) && $images->image_intro_caption !== '') : ?>
+ image_intro_caption, ENT_COMPAT, 'UTF-8'); ?>
+
+
diff --git a/layouts/joomla/editors/buttons/modal.php b/layouts/joomla/editors/buttons/modal.php
index b1edb21f233b0..070a6c9e909e1 100644
--- a/layouts/joomla/editors/buttons/modal.php
+++ b/layouts/joomla/editors/buttons/modal.php
@@ -32,8 +32,8 @@
if (is_array($button->get('options')) && isset($options['confirmText']) && isset($options['confirmCallback']))
{
- $confirm = ''
- . $options['confirmText'] . '';
+ $confirm = '';
}
if (null !== $button->get('id'))
@@ -57,7 +57,7 @@
'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',
- 'footer' => $confirm . '