--- a/yaymail/src/Ajax.php
+++ b/yaymail/src/Ajax.php
@@ -231,6 +231,11 @@
if ( ! wp_verify_nonce( $nonce, 'yaymail_frontend_nonce' ) ) {
return wp_send_json_error( [ 'mess' => __( 'Verify nonce failed', 'yaymail' ) ] );
}
+
+ if ( ! current_user_can( 'install_plugins' ) && ! current_user_can( 'activate_plugins' ) ) {
+ return wp_send_json_error( [ 'mess' => __( 'You do not have permission to install plugins', 'yaymail' ) ] );
+ }
+
try {
$is_installed = $this->process_plugin_installer( 'yaysmtp' );
--- a/yaymail/src/Controllers/AddonController.php
+++ b/yaymail/src/Controllers/AddonController.php
@@ -21,6 +21,10 @@
$this->init_hooks();
}
+ protected function permission_callback_admin_only() {
+ return current_user_can( 'activate_plugins' );
+ }
+
protected function init_hooks() {
register_rest_route(
YAYMAIL_REST_NAMESPACE,
@@ -40,7 +44,7 @@
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'exec_activate_addon' ],
- 'permission_callback' => [ $this, 'permission_callback' ],
+ 'permission_callback' => [ $this, 'permission_callback_admin_only' ],
],
]
);
@@ -51,7 +55,7 @@
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'exec_deactivate_addon' ],
- 'permission_callback' => [ $this, 'permission_callback' ],
+ 'permission_callback' => [ $this, 'permission_callback_admin_only' ],
],
]
);
--- a/yaymail/src/Elements/OrderDetails.php
+++ b/yaymail/src/Elements/OrderDetails.php
@@ -265,6 +265,21 @@
'default_value' => esc_html__( 'Discount:', 'woocommerce' ),
'type' => 'content',
],
+ 'custom_footer_rows_breaker' => [
+ 'component' => 'LineBreaker',
+ ],
+ 'custom_footer_rows_group' => [
+ 'component' => 'GroupDefinition',
+ 'title' => __( 'Custom Footer Rows', 'yaymail' ),
+ 'description' => __( 'Add custom rows to the order totals footer', 'yaymail' ),
+ ],
+ 'custom_footer_rows' => [
+ 'value_path' => 'custom_footer_rows',
+ 'component' => 'CustomFooterRowsEditor',
+ 'title' => __( 'Custom Footer Rows', 'yaymail' ),
+ 'default_value' => isset( $attributes['custom_footer_rows'] ) ? $attributes['custom_footer_rows'] : [],
+ 'type' => 'content',
+ ],
],
];
}
--- a/yaymail/src/Engine/Backend/SettingsPage.php
+++ b/yaymail/src/Engine/Backend/SettingsPage.php
@@ -271,6 +271,7 @@
wp_dequeue_script( 'real-media-library-lite-rml' );
wp_dequeue_style( 'real-media-library-rml' );
wp_dequeue_script( 'real-media-library-rml' );
+ wp_dequeue_style( 'real-category-library-admin' );
}
}
}
--- a/yaymail/src/License/RestAPI.php
+++ b/yaymail/src/License/RestAPI.php
@@ -140,6 +140,6 @@
}
public function permission_callback() {
- return true;
+ return current_user_can( 'manage_options' );
}
}
--- a/yaymail/src/Models/MigrationModel.php
+++ b/yaymail/src/Models/MigrationModel.php
@@ -141,7 +141,10 @@
// Restore backed-up options
foreach ( $backup['options'] as $option ) {
- update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+ // Only restore options that belong to YayMail (same pattern as export)
+ if ( strpos( $option->option_name, 'yaymail' ) !== false ) {
+ update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+ }
}
// Remove the succeeded migration log from db
--- a/yaymail/src/Models/TemplateModel.php
+++ b/yaymail/src/Models/TemplateModel.php
@@ -13,6 +13,7 @@
use YayMailPostTypesTemplatePostType;
use YayMailShortcodesShortcodesExecutor;
use YayMailSupportedPlugins;
+use YayMailUtilsTemplateHelpers;
/**
* Template Model
@@ -290,7 +291,8 @@
public static function update( $template_id, $data, $is_save_revision = false ) {
if ( isset( $data['elements'] ) && is_array( $data['elements'] ) && isset( self::$meta_keys['elements'] ) ) {
- update_post_meta( $template_id, self::$meta_keys['elements'], $data['elements'] );
+ $elements_data = TemplateHelpers::sanitize_elements_recursive( $data['elements'] );
+ update_post_meta( $template_id, self::$meta_keys['elements'], $elements_data );
}
if ( ! empty( $data['background_color'] ) && isset( self::$meta_keys['background_color'] ) ) {
--- a/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
+++ b/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
@@ -251,6 +251,7 @@
$show_sku = isset( $yaymail_settings['show_product_sku'] ) ? boolval( $yaymail_settings['show_product_sku'] ) : false;
$show_des = isset( $yaymail_settings['show_product_description'] ) ? boolval( $yaymail_settings['show_product_description'] ) : false;
$show_regular_price = isset( $yaymail_settings['show_product_regular_price'] ) ? boolval( $yaymail_settings['show_product_regular_price'] ) : false;
+ $show_hyper_links = isset( $yaymail_settings['show_product_hyper_links'] ) ? boolval( $yaymail_settings['show_product_hyper_links'] ) : false;
$image_style = 'left' === $image_position ? $this->get_styles_product_image() . $style_image_position_left : $this->get_styles_product_image();
$image_url = wc_placeholder_img_src();
@@ -404,29 +405,150 @@
}
public function render_item_totals( $structure_footer ) {
+ $custom_rows = isset( $this->element_data['custom_footer_rows'] )
+ ? $this->element_data['custom_footer_rows']
+ : [];
+
+ // Group custom rows by zone
+ $rows_by_zone = [
+ 'before_all' => [],
+ 'after_subtotal' => [],
+ 'before_total' => [],
+ 'after_total' => [],
+ ];
+
+ foreach ( $custom_rows as $row ) {
+ // Check if row is enabled - handle both boolean and string values
+ $is_enabled = isset( $row['enabled'] ) ? boolval( $row['enabled'] ) : false;
+
+ if ( $is_enabled && isset( $row['zone'] ) ) {
+ $zone = $row['zone'];
+ if ( isset( $rows_by_zone[ $zone ] ) ) {
+ $rows_by_zone[ $zone ][] = $row;
+ }
+ }
+ }
+
+ // Sort each zone by order
+ foreach ( $rows_by_zone as &$zone_rows ) {
+ usort(
+ $zone_rows,
+ function( $a, $b ) {
+ $order_a = isset( $a['order'] ) ? intval( $a['order'] ) : 0;
+ $order_b = isset( $b['order'] ) ? intval( $b['order'] ) : 0;
+ return $order_a - $order_b;
+ }
+ );
+ }
+
$index = 0;
+
+ // ZONE 1: Before all
+ foreach ( $rows_by_zone['before_all'] as $row ) {
+ $this->render_custom_row( $row, $index++, $structure_footer );
+ }
+
+ // Process standard rows
foreach ( $this->item_totals as $key => $total ) {
- if ( in_array( $key, $structure_footer['hidden_rows'], true ) ) {
+ if ( in_array( $key, $structure_footer['hidden_rows'] ?? [], true ) ) {
continue;
}
- ++$index;
- $tr_class = "yaymail-order-detail-row-{$key}";
- $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
- $label = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
- $style = $this->get_styles() . TemplateHelpers::get_style(
- [
- 'border-top-width' => 1 === $index ? '4px' : '0',
- ]
- );
- ?>
- <tr class="<?php echo esc_attr( $tr_class ); ?>">
- <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
- <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
- </tr>
- <?php
+
+ // ZONE 2: After subtotal
+ if ( 'cart_subtotal' === $key ) {
+ $this->render_standard_row( $key, $total, $index++, $structure_footer );
+ foreach ( $rows_by_zone['after_subtotal'] as $row ) {
+ $this->render_custom_row( $row, $index++, $structure_footer );
+ }
+ continue;
+ }
+
+ // ZONE 3: Before total
+ if ( 'order_total' === $key ) {
+ foreach ( $rows_by_zone['before_total'] as $row ) {
+ $this->render_custom_row( $row, $index++, $structure_footer );
+ }
+ $this->render_standard_row( $key, $total, $index++, $structure_footer );
+ continue;
+ }
+
+ // Other rows - render normally
+ $this->render_standard_row( $key, $total, $index++, $structure_footer );
+ }//end foreach
+
+ // ZONE 4: After all (after total)
+ foreach ( $rows_by_zone['after_total'] as $row ) {
+ $this->render_custom_row( $row, $index++, $structure_footer );
}
}
+ /**
+ * Render a standard order total row
+ *
+ * @param string $key The order total key.
+ * @param array $total The order total data.
+ * @param int $index The row index.
+ * @param array $structure_footer The footer structure data.
+ */
+ private function render_standard_row( $key, $total, $index, $structure_footer ) {
+ $tr_class = "yaymail-order-detail-row-{$key}";
+ $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
+ $label = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
+ $style = $this->get_styles() . TemplateHelpers::get_style(
+ [
+ 'border-top-width' => 1 === $index ? '4px' : '0',
+ ]
+ );
+ $heading_style = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+ $content_style = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+ ?>
+ <tr class="<?php echo esc_attr( $tr_class ); ?>">
+ <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $heading_style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
+ <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $content_style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
+ </tr>
+ <?php
+ }
+
+ /**
+ * Render a custom footer row
+ *
+ * @param array $custom_row The custom row data.
+ * @param int $index The row index.
+ * @param array $structure_footer The footer structure data.
+ */
+ private function render_custom_row( $custom_row, $index, $structure_footer ) {
+ // Process shortcodes in label and value
+ $label = isset( $custom_row['label'] ) ? do_shortcode( $custom_row['label'] ) : '';
+ $value = isset( $custom_row['value'] ) ? do_shortcode( $custom_row['value'] ) : '';
+
+ if ( empty( $label ) && empty( $value ) ) {
+ return;
+ }
+
+ $style = $this->get_styles() . TemplateHelpers::get_style(
+ [
+ 'border-top-width' => 1 === $index ? '4px' : '0',
+ ]
+ );
+
+ $heading_style = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+ $content_style = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+
+ $row_id = isset( $custom_row['id'] ) ? esc_attr( $custom_row['id'] ) : '';
+ ?>
+ <tr class="yaymail-order-detail-row-custom custom-row-<?php echo esc_attr( $row_id ); ?>">
+ <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>"
+ style="<?php echo esc_attr( $heading_style ); ?>">
+ <?php echo wp_kses_post( $label ); ?>
+ </th>
+ <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>"
+ style="<?php echo esc_attr( $content_style ); ?>">
+ <?php echo wp_kses_post( $value ); ?>
+ </td>
+ </tr>
+ <?php
+ }
+
public function render_customer_note() {
if ( ! empty( $this->order_note ) ) :
$style = $this->get_styles();
--- a/yaymail/src/Utils/TemplateHelpers.php
+++ b/yaymail/src/Utils/TemplateHelpers.php
@@ -339,4 +339,27 @@
$border['color']
);
}
+
+ public static function sanitize_elements_recursive( $elements ) {
+ if ( ! is_array( $elements ) ) {
+ return [];
+ }
+
+ foreach ( $elements as &$element ) {
+ if ( isset( $element['data']['rich_text'] ) ) {
+ $element['data']['rich_text'] = yaymail_kses_post( $element['data']['rich_text'], $allowed );
+ }
+
+ if ( isset( $element['data']['title'] ) ) {
+ $element['data']['title'] = yaymail_kses_post( $element['data']['title'] );
+ }
+
+ // Recursive cho nested elements
+ if ( isset( $element['children'] ) ) {
+ $element['children'] = self::sanitize_elements_recursive( $element['children'] );
+ }
+ }
+
+ return $elements;
+ }
}
--- a/yaymail/templates/elements/order-details.php
+++ b/yaymail/templates/elements/order-details.php
@@ -120,6 +120,5 @@
<?php
$element_content = ob_get_contents();
ob_end_clean();
-$element_content .= do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' );
-
+$element_content .= yaymail_kses_post( do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' ) );
TemplateHelpers::wrap_element_content( $element_content, $element, $wrapper_style );
--- a/yaymail/templates/elements/text.php
+++ b/yaymail/templates/elements/text.php
@@ -35,7 +35,7 @@
ob_start();
?>
- <div style="<?php echo esc_attr( $text_style ); ?>"><?php echo do_shortcode( $data['rich_text'] ); ?></div>
+ <div style="<?php echo esc_attr( $text_style ); ?>"><?php yaymail_kses_post_e( do_shortcode( $data['rich_text'] ) ); ?></div>
<?php
$element_content = ob_get_clean();
--- a/yaymail/templates/emails/before-email-content.php
+++ b/yaymail/templates/emails/before-email-content.php
@@ -14,8 +14,8 @@
h1{ font-family:inherit;text-shadow:unset;text-align:inherit;}
h2,h3{ font-family:inherit;color:inherit;text-align:inherit;}
.yaymail-inline-block {display: inline-block;}
- .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
-
+ .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
+ .yaymail-order-details-table .wc-item-meta {list-style-type: none;}
/**
* Media queries are not supported by all email clients, however they do work on modern mobile
* Gmail clients and can help us achieve better consistency there.
--- a/yaymail/vendor/autoload.php
+++ b/yaymail/vendor/autoload.php
@@ -14,10 +14,7 @@
echo $err;
}
}
- trigger_error(
- $err,
- E_USER_ERROR
- );
+ throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
--- a/yaymail/vendor/composer/InstalledVersions.php
+++ b/yaymail/vendor/composer/InstalledVersions.php
@@ -27,12 +27,23 @@
class InstalledVersions
{
/**
+ * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+ * @internal
+ */
+ private static $selfDir = null;
+
+ /**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
* @var bool|null
*/
private static $canGetVendors;
@@ -309,6 +320,24 @@
{
self::$installed = $data;
self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return string
+ */
+ private static function getSelfDir()
+ {
+ if (self::$selfDir === null) {
+ self::$selfDir = strtr(__DIR__, '\', '/');
+ }
+
+ return self::$selfDir;
}
/**
@@ -322,19 +351,27 @@
}
$installed = array();
+ $copiedLocalDir = false;
if (self::$canGetVendors) {
+ $selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
- $installed[] = self::$installedByVendor[$vendorDir] = $required;
- if (null === self::$installed && strtr($vendorDir.'/composer', '\', '/') === strtr(__DIR__, '\', '/')) {
- self::$installed = $installed[count($installed) - 1];
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
}
}
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
}
}
@@ -350,7 +387,7 @@
}
}
- if (self::$installed !== array()) {
+ if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
--- a/yaymail/vendor/composer/autoload_static.php
+++ b/yaymail/vendor/composer/autoload_static.php
@@ -11,11 +11,11 @@
);
public static $prefixLengthsPsr4 = array (
- 'Y' =>
+ 'Y' =>
array (
'YayMail\' => 8,
),
- 'T' =>
+ 'T' =>
array (
'TenQuality\WP\Database\' => 23,
'TenQuality\Data\' => 16,
@@ -23,15 +23,15 @@
);
public static $prefixDirsPsr4 = array (
- 'YayMail\' =>
+ 'YayMail\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
- 'TenQuality\WP\Database\' =>
+ 'TenQuality\WP\Database\' =>
array (
0 => __DIR__ . '/..' . '/10quality/wp-query-builder/src',
),
- 'TenQuality\Data\' =>
+ 'TenQuality\Data\' =>
array (
0 => __DIR__ . '/..' . '/10quality/php-data-model/src',
),
--- a/yaymail/vendor/composer/installed.php
+++ b/yaymail/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+ 'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -31,7 +31,7 @@
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+ 'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/yaymail/vendor/composer/platform_check.php
+++ b/yaymail/vendor/composer/platform_check.php
@@ -19,8 +19,7 @@
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
- trigger_error(
- 'Composer detected issues in your platform: ' . implode(' ', $issues),
- E_USER_ERROR
+ throw new RuntimeException(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}
--- a/yaymail/yaymail.php
+++ b/yaymail/yaymail.php
@@ -3,12 +3,12 @@
* Plugin Name: YayMail - WooCommerce Email Customizer
* Plugin URI: https://yaycommerce.com/yaymail-woocommerce-email-customizer/
* Description: Create awesome transactional emails with a drag and drop email builder
- * Version: 4.3.2
+ * Version: 4.3.3
* Author: YayCommerce
* Author URI: https://yaycommerce.com
* Text Domain: yaymail
* WC requires at least: 3.0.0
- * WC tested up to: 10.3.0
+ * WC tested up to: 10.5.1
* Domain Path: /i18n/languages/
*
* @package YayMail
@@ -27,7 +27,7 @@
}
if ( ! defined( 'YAYMAIL_VERSION' ) ) {
- define( 'YAYMAIL_VERSION', '4.3.2' );
+ define( 'YAYMAIL_VERSION', '4.3.3' );
}
if ( ! defined( 'YAYMAIL_PLUGIN_URL' ) ) {