Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions assets/js/backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* URL Generator - Backend JavaScript
* Adds click-to-copy functionality for URL call snippets
*/

(function($) {
'use strict';

$(document).ready(function() {
// Add click-to-copy functionality to code elements with data-copy-target attribute
$('.url-code-copy').on('click', function(e) {
e.preventDefault();

var $this = $(this);
var targetId = $this.data('copy-target');
var $textarea = $('#' + targetId);

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code constructs a jQuery selector by directly concatenating the targetId from the data attribute ($('#' + targetId)). If the ID contains special characters (like dots, colons, brackets), the selector could fail or behave unexpectedly.

Consider using document.getElementById(targetId) wrapped in jQuery ($(document.getElementById(targetId))) instead, which is safer and doesn't require escaping special characters in IDs.

Suggested change
var $textarea = $('#' + targetId);
var $textarea = $(document.getElementById(targetId));

Copilot uses AI. Check for mistakes.

if ($textarea.length === 0) {
console.error('Copy target textarea not found:', targetId);
return;
}

var textToCopy = $textarea.val();

// Use the Clipboard API if available, fallback to older method
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(textToCopy).then(function() {
showCopyFeedback($this);
}).catch(function(err) {
fallbackCopyToClipboard($textarea, $this);
});
} else {
fallbackCopyToClipboard($textarea, $this);
}
});

// Add keyboard accessibility (Enter or Space to trigger copy)
$('.url-code-copy').on('keydown', function(e) {
// Enter or Space key
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
$(this).trigger('click');
}
});
});

/**
* Fallback copy method for older browsers or non-HTTPS contexts
*/
function fallbackCopyToClipboard($textarea, $element) {
$textarea[0].select();

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The select() method on a hidden textarea (positioned off-screen with left: -9999px) may not work reliably in all browsers, particularly on mobile devices or with certain browser security settings. The element needs to be temporarily visible or in the viewport for selection to work consistently.

Consider temporarily making the textarea visible (e.g., with position: fixed; top: 0; left: 0; opacity: 0;) before calling select(), then hiding it again after the copy operation.

Copilot uses AI. Check for mistakes.

try {
document.execCommand('copy');
showCopyFeedback($element);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}

/**
* Show visual feedback when text is copied
*/
function showCopyFeedback($element) {
var $icon = $element.find('.rex-icon');

// Change icon to checkmark temporarily
if ($icon.length) {
$icon.removeClass('fa-copy').addClass('fa-check');
}

// Add success class
$element.addClass('url-copied');

// Reset after 2 seconds
setTimeout(function() {
if ($icon.length) {
$icon.removeClass('fa-check').addClass('fa-copy');
}
$element.removeClass('url-copied');
}, 2000);
}

})(jQuery);
27 changes: 27 additions & 0 deletions assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,30 @@
.addon-url-data-table .label-default {
background: #ccc;
}

/* Click-to-copy styles */
.url-code-copy {
cursor: pointer;
transition: all 0.2s ease;
position: relative;
padding: 2px 8px;
border-radius: 3px;
}
Comment thread
AWqxKAWERbXo marked this conversation as resolved.

.url-code-copy:hover {
background-color: rgba(0, 123, 255, 0.1);
}

.url-code-copy:focus {
outline: 2px solid rgba(0, 123, 255, 0.5);
outline-offset: 2px;
}

.url-code-copy.url-copied {
background-color: rgba(40, 167, 69, 0.2);
}

.url-code-copy .rex-icon {
font-size: 0.9em;
margin-right: 4px;
}
1 change: 1 addition & 0 deletions boot.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@

if (rex::isBackend() && rex::getUser() !== null) {
rex_view::addCssFile($addon->getAssetsUrl('styles.css'));
rex_view::addJsFile($addon->getAssetsUrl('js/backend.js'));
}

if (null !== Url::getRewriter() && Url::getRewriter()->getSeoTagsExtensionPoint() !== '') {
Expand Down
6 changes: 4 additions & 2 deletions fragments/url/profiles/card.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ class="btn btn-primary btn-xs pull-right"
?>
<p class="help-block rex-note" style="font-size: 0.8em;">
Aufruf via
<code>rex_getUrl('', '', ['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code>
<code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-1" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code>
<textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea>
oder via Artikel
<code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br>
<code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br>
Comment on lines +99 to +102

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <code> elements are being used as interactive buttons but lack the role="button" attribute. This can cause confusion for screen reader users who may not understand that these elements are clickable.

Add role="button" to the code elements to properly communicate their interactive nature to assistive technologies. Additionally, consider adding aria-label attributes with localized text to provide clear context about what will be copied.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +102

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The icon classes used here (fa-copy and fa-check) don't follow the REDAXO icon naming pattern used elsewhere in the file. Other icons in this file use the pattern rex-icon rex-icon-{name} (e.g., rex-icon-refresh, rex-icon-edit, rex-icon-delete on lines 72, 222, 228).

Consider using REDAXO's standard icon naming convention if icons like rex-icon-copy and rex-icon-check are available, or verify that the FontAwesome icons are intentionally being used here.

Copilot uses AI. Check for mistakes.
<textarea id="url-copy-<?= $profile['id'] ?>-2" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea>
Comment on lines +99 to +103

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $profile['id'] value is used directly in HTML attributes without escaping, which could lead to XSS if the ID contains malicious content. Additionally, using an unescaped ID in the jQuery selector on line 16 ($('#' + targetId)) could lead to selector injection vulnerabilities.

Both the data-copy-target attribute and the textarea id should escape the profile ID value. Consider using rex_escape() or htmlspecialchars() for the ID values.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +103

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The code uses rex_escape() for escaping values in the new click-to-copy functionality, while the rest of the file consistently uses htmlspecialchars() for similar escaping purposes (see lines 66, 82, 85, 88, 91, etc.).

For consistency with the existing codebase and to avoid confusion, consider using htmlspecialchars() instead of rex_escape() unless there's a specific reason to use the REDAXO-specific function.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +103

Copilot AI Nov 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inline styles are used here for positioning the hidden textarea (style="position: absolute; left: -9999px;"). While this works, it would be better to move this to a CSS class in styles.css for better maintainability and consistency.

Consider adding a class like .url-copy-textarea with these styles in the CSS file instead of using inline styles.

Copilot uses AI. Check for mistakes.


</p>
Expand Down
3 changes: 3 additions & 0 deletions lang/de_de.lang
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ url.profile.seo_description = Beschreibung
url.profile.seo_image = Bild
url.profile.sitemap-lastmod = Letzte Änderung
url.profile.not_set = keine Auswahl

url_click_to_copy = Klicken zum Kopieren

// Neue Update-Seite

url_generator_update = Neue Version verfügbar
2 changes: 2 additions & 0 deletions lang/en_gb.lang
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ url_yform_value_profil = Profile
url_yform_value_anchor = Anchor text
url_yform_value_description = Adds a link to the frontend generated by the URL addon

url_click_to_copy = Click to copy




Expand Down
2 changes: 2 additions & 0 deletions lang/es_es.lang
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ url_yform_value_profil = Perfil
url_yform_value_anchor = Anclaje de texto
url_yform_value_description = Agrega un enlace a la interfaz creada por la URL AddOn.

url_click_to_copy = Haga clic para copiar




Expand Down
2 changes: 2 additions & 0 deletions lang/it_it.lang
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ url_generator_table_not_selected = nessuna tabella specificata
url_generator_table_relation_legend = Tabelle di relazione {0}
url_generator_url_notice =

url_click_to_copy = Clicca per copiare




Expand Down
2 changes: 2 additions & 0 deletions lang/pt_br.lang
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ url_generator_table_not_selected = Nenhuma tabela selecioanda
url_generator_table_relation_legend = Tabela da relação {0}
url_generator_url_notice = Coluna para gerar o URL

url_click_to_copy = Clique para copiar




Expand Down
2 changes: 2 additions & 0 deletions lang/sv_se.lang
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ url_yform_value_profil = Profil
url_yform_value_anchor = Ankar-text
url_yform_value_description = Tillfog länk till frontend, som skapades av URL-Addon:et

url_click_to_copy = Klicka för att kopiera




Expand Down
Loading