| @@ -10,48 +10,97 @@ | ||
| 10 | 10 | if (!defined('ABSPATH')) { |
| 11 | 11 | exit; |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | -// Function to dequeue WordPress admin styles and load our custom styles | |
| 15 | -function easy_invoice_dequeue_admin_styles() { | |
| 16 | - // Get current screen | |
| 17 | - $screen = get_current_screen(); | |
| 14 | +/** | |
| 15 | + * Single source of truth for per-section icons. | |
| 16 | + * | |
| 17 | + * Used by BOTH the left-side section nav (small icons, h-5 w-5) AND the | |
| 18 | + * right-side content-panel header (larger icon, h-6 w-6 inside an | |
| 19 | + * indigo-100 background tile). The nav previously had its own inline | |
| 20 | + * SVGs and the header used Font Awesome — different glyphs for the | |
| 21 | + * same section. This helper unifies them so changing the icon for a | |
| 22 | + * section is a one-line edit here. | |
| 23 | + * | |
| 24 | + * @param string $section_id Section slug (company / invoice / quote / etc.) | |
| 25 | + * @param string $color_class Tailwind classes to apply to the SVG (color + hover). | |
| 26 | + * @param string $size_class Tailwind size classes (h-5 w-5 for nav, h-6 w-6 for header). | |
| 27 | + * @param string $extra_class Any extra wrapper classes (e.g. mr-3 for nav alignment). | |
| 28 | + */ | |
| 29 | +if (!function_exists('easy_invoice_render_settings_section_icon')) { | |
| 30 | + function easy_invoice_render_settings_section_icon(string $section_id, string $color_class, string $size_class, string $extra_class = ''): void { | |
| 31 | + $klass = trim($color_class . ' ' . $size_class . ' ' . $extra_class); | |
| 32 | + $open = '<svg class="' . esc_attr($klass) . '" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'; | |
| 33 | + $close = '</svg>'; | |
| 18 | 34 | |
| 19 | - // Check if we're on an Easy Invoice page | |
| 20 | - if ($screen && property_exists($screen, 'id') && strpos($screen->id, 'easy-invoice') !== false) { | |
| 21 | - // Remove all WordPress admin styles | |
| 22 | - global $wp_styles; | |
| 23 | - | |
| 24 | - $styles_to_remove = [ | |
| 25 | - 'admin-bar', 'colors', 'ie', 'common', 'forms', 'admin-menu', | |
| 26 | - 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', | |
| 27 | - 'nav-menus', 'widgets', 'site-icon', 'l10n', 'code-editor', 'wp-auth-check', | |
| 28 | - 'dashicons', 'wp-pointer', 'buttons', 'wp-jquery-ui-dialog', 'wp-color-picker', | |
| 29 | - 'wp-components', 'wp-edit-post', 'wp-format-library', 'wp-block-library', | |
| 30 | - 'wp-block-library-theme', 'wp-nux', 'wp-reusable-blocks', 'wp-editor', | |
| 31 | - 'wp-edit-blocks', 'wp-block-editor', | |
| 32 | - ]; | |
| 33 | - $styles_to_remove = array_unique($styles_to_remove); // Ensure no duplicates | |
| 34 | - | |
| 35 | - foreach ($styles_to_remove as $style) { | |
| 36 | - if (isset($wp_styles->registered[$style])) { | |
| 37 | - wp_dequeue_style($style); | |
| 38 | - wp_deregister_style($style); | |
| 39 | - } | |
| 35 | + $paths = ''; | |
| 36 | + switch ($section_id) { | |
| 37 | + case 'company': | |
| 38 | + // House icon — represents the business / company. | |
| 39 | + $paths = '<path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 40 | + . '<polyline points="9,22 9,12 15,12 15,22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 41 | + break; | |
| 42 | + case 'invoice': | |
| 43 | + // Document with $ inside — distinct from the quote icon. | |
| 44 | + $paths = '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 45 | + . '<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 46 | + . '<path d="M12 11V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 47 | + . '<path d="M14.5 13H10.75a1.75 1.75 0 0 0 0 3.5h2.5a1.75 1.75 0 0 1 0 3.5H9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 48 | + break; | |
| 49 | + case 'quote': | |
| 50 | + // Document with checkmark — proposal / awaiting approval. | |
| 51 | + $paths = '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 52 | + . '<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 53 | + . '<path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 54 | + break; | |
| 55 | + case 'currency': | |
| 56 | + // Dollar-sign-in-circle. | |
| 57 | + $paths = '<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 58 | + . '<path d="M12 1V23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 59 | + . '<path d="M17 5H9.5C8.57174 5 7.6815 5.36875 7.02513 6.02513C6.36875 6.6815 6 7.57174 6 8.5C6 9.42826 6.36875 10.3185 7.02513 10.9749C7.6815 11.6313 8.57174 12 9.5 12H14.5C15.4283 12 16.3185 12.3687 16.9749 13.0251C17.6313 13.6815 18 14.5717 18 15.5C18 16.4283 17.6313 17.3185 16.9749 17.9749C16.3185 18.6313 15.4283 19 14.5 19H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 60 | + break; | |
| 61 | + case 'tax': | |
| 62 | + // Percent symbol — clean tax glyph. | |
| 63 | + $paths = '<line x1="19" y1="5" x2="5" y2="19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 64 | + . '<circle cx="6.5" cy="6.5" r="2.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 65 | + . '<circle cx="17.5" cy="17.5" r="2.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 66 | + break; | |
| 67 | + case 'payment': | |
| 68 | + // Card with horizontal stripe. | |
| 69 | + $paths = '<rect x="1" y="4" width="22" height="16" rx="2" ry="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 70 | + . '<line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 71 | + break; | |
| 72 | + case 'email': | |
| 73 | + // Envelope with flap. | |
| 74 | + $paths = '<path d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 75 | + . '<polyline points="22,6 12,13 2,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 76 | + break; | |
| 77 | + case 'text_settings': | |
| 78 | + // Document with text lines + edit-arrow indicator. | |
| 79 | + $paths = '<path d="M4 7V4C4 3.44772 4.44772 3 5 3H19C19.5523 3 20 3.44772 20 4V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 80 | + . '<path d="M4 7V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 81 | + . '<path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 82 | + . '<path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 83 | + . '<path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 84 | + . '<path d="M12 11L14 13L12 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 85 | + break; | |
| 86 | + case 'account': | |
| 87 | + // User in circle — distinct from the gear used elsewhere. | |
| 88 | + $paths = '<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 89 | + . '<circle cx="12" cy="10" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 90 | + . '<path d="M6.34 19a6 6 0 0 1 11.32 0" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 91 | + break; | |
| 92 | + case 'advanced': | |
| 93 | + default: | |
| 94 | + // Gear icon — used by Advanced + as the fallback for any | |
| 95 | + // section that didn't ship a case here. | |
| 96 | + $paths = '<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' | |
| 97 | + . '<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; | |
| 98 | + break; | |
| 40 | 99 | } |
| 41 | - add_filter('show_admin_bar', '__return_false'); | |
| 42 | - | |
| 43 | - // Add custom CSS to hide screen-meta-links | |
| 44 | - echo '<style> | |
| 45 | - .screen-meta-links, | |
| 46 | - #screen-meta, | |
| 47 | - #screen-meta-links { | |
| 48 | - display: none !important; | |
| 49 | - } | |
| 50 | - </style>'; | |
| 100 | + echo $open . $paths . $close; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- inline SVG icon markup assembled from literal path data above. | |
| 51 | 101 | } |
| 52 | 102 | } |
| 53 | -add_action('admin_enqueue_scripts', 'easy_invoice_dequeue_admin_styles', 9999); | |
| 54 | 103 | |
| 55 | 104 | /** |
| 56 | 105 | * Helper function to render a single settings field. |
| 57 | 106 | * |
| @@ -71,18 +120,18 @@ | ||
| 71 | 120 | $placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : ''; |
| 72 | 121 | $required = !empty($field_config['required']) ? 'required' : ''; |
| 73 | 122 | $aria_describedby = !empty($description_text) ? 'aria-describedby="' . $field_id . '-description"' : ''; |
| 74 | 123 | |
| 75 | - echo '<div class=" ' . $col_span_class . '">'; // Added 'form-group' class for JS show/hide functionality | |
| 124 | + echo '<div class=" ' . esc_html($col_span_class) . '">'; // Added 'form-group' class for JS show/hide functionality | |
| 76 | 125 | |
| 77 | 126 | // Special layout for checkboxes with descriptions (e.g., Invoice Numbering type) |
| 78 | 127 | if ($type === 'checkbox' && !empty($description_text) && ($option_key === 'easy_invoice_invoice_numbering')) { |
| 79 | 128 | echo '<div class="flex items-start space-x-3 p-4 bg-gray-50 rounded-lg border border-gray-200">'; |
| 80 | 129 | echo ' <div class="flex items-center h-5 mt-0.5">'; |
| 81 | - echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" ' . $required . '>'; | |
| 130 | + echo ' <input type="checkbox" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" ' . esc_html($required) . '>'; | |
| 82 | 131 | echo ' </div>'; |
| 83 | 132 | echo ' <div class="flex-1">'; |
| 84 | - echo ' <label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-1">' . $label . '</label>'; | |
| 133 | + echo ' <label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-1">' . esc_html($label) . '</label>'; | |
| 85 | 134 | echo ' <p id="' . $field_id . '-description" class="text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array( |
| 86 | 135 | 'a' => array( |
| 87 | 136 | 'href' => array(), |
| 88 | 137 | 'target' => array(), |
| @@ -93,33 +142,32 @@ | ||
| 93 | 142 | echo '</div>'; |
| 94 | 143 | } else { |
| 95 | 144 | // Standard label for other types, or checkboxes without the special description structure |
| 96 | 145 | if ($type !== 'checkbox') { |
| 97 | - echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-2">' . $label . '</label>'; | |
| 146 | + echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-2">' . esc_html($label) . '</label>'; | |
| 98 | 147 | } |
| 99 | 148 | |
| 100 | 149 | $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'; |
| 101 | 150 | $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md'; |
| 102 | 151 | |
| 103 | - | |
| 104 | 152 | switch ($type) { |
| 105 | 153 | case 'text': |
| 106 | 154 | case 'email': |
| 107 | 155 | case 'url': |
| 108 | 156 | case 'tel': |
| 109 | - echo '<input type="' . esc_attr($type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 157 | + echo '<input type="' . esc_attr($type) . '" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 110 | 158 | break; |
| 111 | 159 | case 'number': |
| 112 | 160 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 113 | 161 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 114 | 162 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 115 | - echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 163 | + echo '<input type="number" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="' . esc_html($input_class) . '" placeholder="' . esc_html($placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 116 | 164 | break; |
| 117 | 165 | case 'textarea': |
| 118 | - echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($value) . '</textarea>'; | |
| 166 | + echo '<textarea id="' . $field_id . '" name="' . esc_html($field_name) . '" rows="3" class="' . esc_html($input_class) . '" placeholder="' . esc_html($placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>' . wp_kses_post($value) . '</textarea>'; | |
| 119 | 167 | break; |
| 120 | 168 | case 'select': |
| 121 | - echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 169 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '" class="' . esc_html($select_class) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 122 | 170 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 123 | 171 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 124 | 172 | echo '<option value="' . esc_attr($opt_val) . '" ' . selected($value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>'; |
| 125 | 173 | } |
| @@ -126,25 +174,25 @@ | ||
| 126 | 174 | } |
| 127 | 175 | echo '</select>'; |
| 128 | 176 | break; |
| 129 | 177 | case 'multiselect': |
| 130 | - echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($label) . '">'; | |
| 178 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '[]" class="' . esc_html($select_class) . '" multiple="multiple" aria-label="' . esc_attr($label) . '">'; | |
| 131 | 179 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 132 | 180 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 133 | 181 | $selected = is_array($value) && in_array($opt_val, $value) ? 'selected="selected"' : ''; |
| 134 | - echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>'; | |
| 182 | + echo '<option value="' . esc_attr($opt_val) . '" ' . esc_html($selected) . '>' . esc_html($opt_label) . '</option>'; | |
| 135 | 183 | } |
| 136 | 184 | } |
| 137 | 185 | echo '</select>'; |
| 138 | 186 | break; |
| 139 | 187 | case 'readonly': |
| 140 | - echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . ' bg-gray-50" readonly ' . $aria_describedby . '>'; | |
| 141 | - echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($value) . '">'; | |
| 188 | + echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . esc_html($input_class) . ' bg-gray-50" readonly ' . $aria_describedby . '>'; | |
| 189 | + echo '<input type="hidden" name="' . esc_html($field_name) . '" value="' . esc_attr($value) . '">'; | |
| 142 | 190 | break; |
| 143 | 191 | case 'checkbox': // Checkboxes that don't use the special description layout |
| 144 | 192 | echo '<div class="flex items-center">'; // Original 'tax_enabled' was just this simple structure |
| 145 | - echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" ' . $required . ' ' . $aria_describedby . '>'; | |
| 146 | - echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $label . '</label>'; | |
| 193 | + echo ' <input type="checkbox" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 194 | + echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . esc_html($label) . '</label>'; | |
| 147 | 195 | echo '</div>'; |
| 148 | 196 | if (!empty($description_text)) { // Show description below the field |
| 149 | 197 | echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array( |
| 150 | 198 | 'a' => array( |
| @@ -157,14 +205,14 @@ | ||
| 157 | 205 | break; |
| 158 | 206 | case 'image': |
| 159 | 207 | echo '<div class="mt-1 flex items-center">'; |
| 160 | 208 | echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">'; |
| 161 | - echo ' <img id="' . $field_id . '-preview" src="' . esc_url($value) . '" alt="' . esc_attr($label) . '" class="h-full w-full object-cover' . (empty($value) ? ' hidden' : '') . '">'; | |
| 209 | + echo ' <img id="' . $field_id . '-preview"' . (empty($value) ? '' : ' src="' . esc_url($value) . '"') . ' alt="' . esc_attr($label) . '" class="h-full w-full object-cover' . (empty($value) ? ' hidden' : '') . '">'; | |
| 162 | 210 | echo ' </span>'; |
| 163 | 211 | echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">'; |
| 164 | 212 | echo esc_html__('Change', 'easy-invoice'); |
| 165 | 213 | echo '</button>'; |
| 166 | - echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 214 | + echo ' <input type="hidden" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 167 | 215 | echo '</div>'; |
| 168 | 216 | break; |
| 169 | 217 | case 'wp_editor': |
| 170 | 218 | // For wp_editor, the label is usually handled before calling it. |
| @@ -193,15 +241,15 @@ | ||
| 193 | 241 | echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>'; |
| 194 | 242 | echo '</div>'; |
| 195 | 243 | break; |
| 196 | 244 | case 'color': |
| 197 | - echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="h-10 w-20 border border-gray-300 rounded">'; | |
| 245 | + echo '<input type="color" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="h-10 w-20 border border-gray-300 rounded">'; | |
| 198 | 246 | break; |
| 199 | 247 | case 'range': |
| 200 | 248 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 201 | 249 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 202 | 250 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 203 | - echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="w-full">'; | |
| 251 | + echo '<input type="range" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="w-full">'; | |
| 204 | 252 | echo '<span class="text-sm text-gray-400">' . esc_html($value) . '%</span>'; |
| 205 | 253 | break; |
| 206 | 254 | case 'button': |
| 207 | 255 | $button_text = $field_config['button_text'] ?? $label; |
| @@ -209,9 +257,9 @@ | ||
| 209 | 257 | $button_class_name = 'regenerate-invoice-numbers-button'; |
| 210 | 258 | if ($option_key === 'easy_invoice_regenerate_quote_numbers') { |
| 211 | 259 | $button_class_name = 'regenerate-quote-numbers-button'; |
| 212 | 260 | } |
| 213 | - echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">'; | |
| 261 | + echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . wp_kses_post($button_class_name) . '">'; | |
| 214 | 262 | echo esc_html($button_text); |
| 215 | 263 | echo '</button>'; |
| 216 | 264 | break; |
| 217 | 265 | case 'image_upload': |
| @@ -216,9 +264,9 @@ | ||
| 216 | 264 | break; |
| 217 | 265 | case 'image_upload': |
| 218 | 266 | echo '<div class="image-upload-wrap">'; |
| 219 | 267 | echo '<div class="flex items-center space-x-3">'; |
| 220 | - echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '">'; | |
| 268 | + echo ' <input type="hidden" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '">'; | |
| 221 | 269 | echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">'; |
| 222 | 270 | echo esc_html__('Upload Image', 'easy-invoice'); |
| 223 | 271 | echo '</button>'; |
| 224 | 272 | echo '</div>'; |
| @@ -243,9 +291,9 @@ | ||
| 243 | 291 | echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' regenerate-quote-numbers-button">'; |
| 244 | 292 | echo esc_html($button_text); |
| 245 | 293 | echo '</button>'; |
| 246 | 294 | } else { |
| 247 | - echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 295 | + echo '<input type="text" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 248 | 296 | } |
| 249 | 297 | break; |
| 250 | 298 | } |
| 251 | 299 | // General description for non-checkbox and non-wp_editor fields (if it exists and not handled by special layouts) |
| @@ -267,9 +315,9 @@ | ||
| 267 | 315 | * Note: This filter is already applied in the controller, so this is redundant |
| 268 | 316 | */ |
| 269 | 317 | // $settings_config = apply_filters('easy_invoice_settings_fields_config', $settings_config); |
| 270 | 318 | |
| 271 | -$active_section = $_GET['section'] ?? key($settings_config); | |
| 319 | +$active_section = isset($_GET['section']) ? sanitize_key(wp_unslash($_GET['section'])) : key($settings_config); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 272 | 320 | if (!isset($settings_config[$active_section])) { |
| 273 | 321 | $active_section = key($settings_config); |
| 274 | 322 | } |
| 275 | 323 | ?> |
| @@ -274,10 +322,10 @@ | ||
| 274 | 322 | } |
| 275 | 323 | ?> |
| 276 | 324 | |
| 277 | 325 | <div class="p-8"> |
| 278 | - <!-- Header with Actions --> | |
| 279 | - <div class="bg-white border-b border-gray-200 px-6 py-5" style="margin-left: -2rem; margin-top: -2rem; padding-left:2rem; padding-right:2rem; margin-right: -2rem;"> | |
| 326 | + | |
| 327 | + <div class="ei-app-page-header bg-white border-b border-gray-200 px-6 easy-invoice-admin-page-header"> | |
| 280 | 328 | <div class="flex items-center justify-between"> |
| 281 | 329 | <div> |
| 282 | 330 | <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Settings', 'easy-invoice'); ?></h1> |
| 283 | 331 | <p class="mt-1 text-sm text-gray-400"><?php esc_html_e('Configure your invoice management system', 'easy-invoice'); ?></p> |
| @@ -290,12 +338,12 @@ | ||
| 290 | 338 | </div> |
| 291 | 339 | </div> |
| 292 | 340 | </div> |
| 293 | 341 | |
| 294 | - <!-- Main Content --> | |
| 342 | + | |
| 295 | 343 | <div class="max-w-7xl mx-auto"> |
| 296 | 344 | <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8"> |
| 297 | - <!-- Left Sidebar - Navigation --> | |
| 345 | + | |
| 298 | 346 | <div class="lg:col-span-1"> |
| 299 | 347 | <div class="bg-white shadow-lg rounded-lg sticky top-24 border border-gray-200"> |
| 300 | 348 | <nav class="space-y-2 p-4" aria-label="<?php esc_attr_e('Settings Navigation', 'easy-invoice'); ?>"> |
| 301 | 349 | <?php foreach ($settings_config as $section_id => $section_data) : ?> |
| @@ -300,80 +348,23 @@ | ||
| 300 | 348 | <nav class="space-y-2 p-4" aria-label="<?php esc_attr_e('Settings Navigation', 'easy-invoice'); ?>"> |
| 301 | 349 | <?php foreach ($settings_config as $section_id => $section_data) : ?> |
| 302 | 350 | <div class="space-y-1"> |
| 303 | 351 | <a href="?page=easy-invoice-settings§ion=<?php echo esc_attr($section_id); ?>" |
| 304 | - class="nav-link group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 <?php echo ($section_id === $active_section) ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>" | |
| 352 | + class="nav-link group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 <?php echo ($section_id === $active_section) ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'; ?>" | |
| 305 | 353 | aria-selected="<?php echo $section_id === $active_section ? 'true' : 'false'; ?>" |
| 306 | 354 | role="tab"> |
| 307 | 355 | |
| 308 | - <?php if ($section_id === 'company'): ?> | |
| 309 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 310 | - <path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 311 | - <polyline points="9,22 9,12 15,12 15,22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 312 | - </svg> | |
| 313 | - <?php elseif ($section_id === 'invoice'): ?> | |
| 314 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 315 | - <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 316 | - <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 317 | - <path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 318 | - <path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 319 | - <path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 320 | - </svg> | |
| 321 | - <?php elseif ($section_id === 'quote'): ?> | |
| 322 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 323 | - <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 324 | - <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 325 | - <path d="M9 9H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 326 | - <path d="M9 13H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 327 | - <path d="M9 17H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 328 | - </svg> | |
| 329 | - <?php elseif ($section_id === 'currency'): ?> | |
| 330 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 331 | - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 332 | - <path d="M12 1V23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 333 | - <path d="M17 5H9.5C8.57174 5 7.6815 5.36875 7.02513 6.02513C6.36875 6.6815 6 7.57174 6 8.5C6 9.42826 6.36875 10.3185 7.02513 10.9749C7.6815 11.6313 8.57174 12 9.5 12H14.5C15.4283 12 16.3185 12.3687 16.9749 13.0251C17.6313 13.6815 18 14.5717 18 15.5C18 16.4283 17.6313 17.3185 16.9749 17.9749C16.3185 18.6313 15.4283 19 14.5 19H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 334 | - </svg> | |
| 335 | - <?php elseif ($section_id === 'tax'): ?> | |
| 336 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 337 | - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 338 | - <path d="M8 14L16 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 339 | - <path d="M8 8L16 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 340 | - <path d="M12 6V18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 341 | - </svg> | |
| 342 | - <?php elseif ($section_id === 'payment'): ?> | |
| 343 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 344 | - <rect x="1" y="4" width="22" height="16" rx="2" ry="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 345 | - <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 346 | - </svg> | |
| 347 | - <?php elseif ($section_id === 'email'): ?> | |
| 348 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 349 | - <path d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 350 | - <polyline points="22,6 12,13 2,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 351 | - </svg> | |
| 352 | - <?php elseif ($section_id === 'advanced'): ?> | |
| 353 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 354 | - <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 355 | - <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 356 | - </svg> | |
| 357 | - <?php elseif ($section_id === 'text_settings'): ?> | |
| 358 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 359 | - <path d="M4 7V4C4 3.44772 4.44772 3 5 3H19C19.5523 3 20 3.44772 20 4V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 360 | - <path d="M4 7V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 361 | - <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 362 | - <path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 363 | - <path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 364 | - <path d="M12 11L14 13L12 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 365 | - </svg> | |
| 366 | - <?php else: ?> | |
| 367 | - <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 368 | - <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 369 | - <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 370 | - </svg> | |
| 371 | - <?php endif; ?> | |
| 356 | + <?php | |
| 357 | + // Section nav icon — same source as the content | |
| 358 | + // header below. See easy_invoice_render_settings_section_icon(). | |
| 359 | + $is_active = ($section_id === $active_section); | |
| 360 | + $icon_color = $is_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; | |
| 361 | + easy_invoice_render_settings_section_icon($section_id, $icon_color, 'h-5 w-5', 'mr-3 flex-shrink-0'); | |
| 362 | + ?> | |
| 372 | 363 | |
| 373 | 364 | <span class="flex-1"><?php echo esc_html($section_data['title']); ?></span> |
| 374 | 365 | <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?> |
| 375 | - <svg class="flex-shrink-0 h-4 w-4 text-gray-400 group-hover:text-gray-500 transition-transform duration-200" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 366 | + <svg class="flex-shrink-0 h-4 w-4 text-gray-400 group-hover:text-indigo-500 transition-transform duration-200" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 376 | 367 | <polyline points="6,9 12,15 18,9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> |
| 377 | 368 | </svg> |
| 378 | 369 | <?php endif; ?> |
| 379 | 370 | </a> |
| @@ -384,11 +375,11 @@ | ||
| 384 | 375 | // Only show active subsection if we're actually on an email settings page |
| 385 | 376 | $active_email_subsection = null; |
| 386 | 377 | if ($active_section === 'email') { |
| 387 | 378 | if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) { |
| 388 | - $active_email_subsection = $_GET['subsection']; | |
| 379 | + $active_email_subsection = sanitize_key(wp_unslash($_GET['subsection'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 389 | 380 | } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) { |
| 390 | - $active_email_subsection = $_GET['subsection']; | |
| 381 | + $active_email_subsection = sanitize_key(wp_unslash($_GET['subsection'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 391 | 382 | } else { |
| 392 | 383 | // Default to 'general' only if we're on email section but no specific subsection |
| 393 | 384 | $active_email_subsection = 'general'; |
| 394 | 385 | } |
| @@ -399,9 +390,9 @@ | ||
| 399 | 390 | <a href="?page=easy-invoice-settings§ion=email&subsection=<?php echo esc_attr($subsection_id); ?>" |
| 400 | 391 | class="email-submenu-link group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 <?php echo $is_active_subsection ? 'text-indigo-700 bg-indigo-50 border-l-2 border-indigo-500' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'; ?>" |
| 401 | 392 | data-subsection="<?php echo esc_attr($subsection_id); ?>" |
| 402 | 393 | data-section="<?php echo esc_attr($section_id); ?>"> |
| 403 | - <svg class="mr-2 flex-shrink-0 h-4 w-4 <?php echo $is_active_subsection ? 'text-indigo-500' : 'text-gray-400 group-hover:text-gray-500'; ?>" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 394 | + <svg class="mr-2 flex-shrink-0 h-4 w-4 <?php echo $is_active_subsection ? 'text-indigo-500' : 'text-gray-400 group-hover:text-indigo-500'; ?>" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 404 | 395 | <polyline points="9,18 15,12 9,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> |
| 405 | 396 | </svg> |
| 406 | 397 | <?php |
| 407 | 398 | // Display shorter names for better UX |
| @@ -434,9 +425,9 @@ | ||
| 434 | 425 | </nav> |
| 435 | 426 | </div> |
| 436 | 427 | </div> |
| 437 | 428 | |
| 438 | - <!-- Right Content - Settings --> | |
| 429 | + | |
| 439 | 430 | <div class="lg:col-span-2"> |
| 440 | 431 | <form id="settings-form" method="post" action=""> |
| 441 | 432 | <?php wp_nonce_field('easy_invoice_settings', 'easy_invoice_settings_nonce'); ?> |
| 442 | 433 | <input type="hidden" name="action" value="easy_invoice_save_settings"> |
| @@ -448,20 +439,15 @@ | ||
| 448 | 439 | aria-labelledby="tab-<?php echo esc_attr($section_id); ?>"> |
| 449 | 440 | <div class="px-6 py-5 border-b border-gray-200"> |
| 450 | 441 | <div class="flex items-center"> |
| 451 | 442 | <div class="flex-shrink-0 bg-indigo-100 rounded-lg p-3"> |
| 452 | - <?php if (isset($section_data['icon']) && !empty($section_data['icon'])): ?> | |
| 453 | - <?php if (strpos($section_data['icon'], '<svg') === 0): ?> | |
| 454 | - <?php echo $section_data['icon']; ?> | |
| 455 | - <?php else: ?> | |
| 456 | - <i class="<?php echo esc_attr($section_data['icon']); ?> text-indigo-600 text-xl"></i> | |
| 457 | - <?php endif; ?> | |
| 458 | - <?php else: ?> | |
| 459 | - <svg class="text-indigo-600 text-xl h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 460 | - <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 461 | - <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
| 462 | - </svg> | |
| 463 | - <?php endif; ?> | |
| 443 | + <?php | |
| 444 | + // Content-header icon — render via the same | |
| 445 | + // helper the section nav uses. Same SVG glyph, | |
| 446 | + // bigger size (h-6 w-6) and the brand colour | |
| 447 | + // sits on top of the indigo-100 tile. | |
| 448 | + easy_invoice_render_settings_section_icon($section_id, 'text-indigo-600', 'h-6 w-6'); | |
| 449 | + ?> | |
| 464 | 450 | </div> |
| 465 | 451 | <div class="ml-4"> |
| 466 | 452 | <h2 class="text-lg font-medium text-gray-900"><?php echo esc_html($section_data['title']); ?></h2> |
| 467 | 453 | <p class="mt-1 text-sm text-gray-400"><?php echo esc_html($section_data['description']); ?></p> |
| @@ -474,11 +460,11 @@ | ||
| 474 | 460 | <?php |
| 475 | 461 | // Determine the active subsection from the hash or default to 'general' |
| 476 | 462 | $active_email_subsection = 'general'; |
| 477 | 463 | if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) { |
| 478 | - $active_email_subsection = $_GET['subsection']; | |
| 464 | + $active_email_subsection = sanitize_key(wp_unslash($_GET['subsection'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 479 | 465 | } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) { |
| 480 | - $active_email_subsection = $_GET['subsection']; | |
| 466 | + $active_email_subsection = sanitize_key(wp_unslash($_GET['subsection'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 481 | 467 | } |
| 482 | 468 | if (!isset($section_data['subsections'][$active_email_subsection])) { |
| 483 | 469 | $active_email_subsection = array_key_first($section_data['subsections']); |
| 484 | 470 | } |
| @@ -488,9 +474,9 @@ | ||
| 488 | 474 | // Render all email subsections so JS can show/hide them |
| 489 | 475 | foreach ($section_data['subsections'] as $subsection_key => $subsection_data) { |
| 490 | 476 | $is_active = ($subsection_key === $active_email_subsection) ? '' : 'hidden'; |
| 491 | 477 | ?> |
| 492 | - <div id="email--<?php echo esc_attr($subsection_key); ?>-content" class="email-subsection-content <?php echo $is_active; ?>"> | |
| 478 | + <div id="email--<?php echo esc_attr($subsection_key); ?>-content" class="email-subsection-content <?php echo esc_attr($is_active); ?>"> | |
| 493 | 479 | <?php |
| 494 | 480 | // Display subsection title |
| 495 | 481 | $subsection_title = ''; |
| 496 | 482 | switch ($subsection_key) { |
| @@ -516,8 +502,55 @@ | ||
| 516 | 502 | <?php if (!empty($subsection_data['description'])): ?> |
| 517 | 503 | <p class="text-sm text-gray-400"><?php echo esc_html($subsection_data['description']); ?></p> |
| 518 | 504 | <?php endif; ?> |
| 519 | 505 | </div> |
| 506 | + <?php | |
| 507 | + // Optional per-subsection notice. Subsection configs may set | |
| 508 | + // 'notice' => [ | |
| 509 | + // 'type' => 'info' | 'warning' | 'error' | 'success', | |
| 510 | + // 'title' => string, | |
| 511 | + // 'message' => string (plain text, may contain links rendered via 'cta_url' + 'cta_label'), | |
| 512 | + // 'cta_url' => optional URL, | |
| 513 | + // 'cta_label' => optional CTA button label, | |
| 514 | + // ] | |
| 515 | + // Used by PaymentReminderEmail to flag when Smart Reminders | |
| 516 | + // has taken over so the user understands why the fields below | |
| 517 | + // are inactive. Renders inline above the fields, not as a | |
| 518 | + // global admin_notice, so it's anchored to the right section. | |
| 519 | + if (!empty($subsection_data['notice']) && is_array($subsection_data['notice'])): | |
| 520 | + $notice = $subsection_data['notice']; | |
| 521 | + $notice_type = $notice['type'] ?? 'info'; | |
| 522 | + $palette = [ | |
| 523 | + 'info' => ['bg' => 'bg-blue-50', 'border' => 'border-blue-200', 'title' => 'text-blue-900', 'body' => 'text-blue-800', 'icon' => '#1d4ed8'], | |
| 524 | + 'warning' => ['bg' => 'bg-amber-50', 'border' => 'border-amber-200', 'title' => 'text-amber-900', 'body' => 'text-amber-800', 'icon' => '#b45309'], | |
| 525 | + 'error' => ['bg' => 'bg-red-50', 'border' => 'border-red-200', 'title' => 'text-red-900', 'body' => 'text-red-800', 'icon' => '#b91c1c'], | |
| 526 | + 'success' => ['bg' => 'bg-green-50', 'border' => 'border-green-200', 'title' => 'text-green-900', 'body' => 'text-green-800', 'icon' => '#15803d'], | |
| 527 | + ]; | |
| 528 | + $c = $palette[$notice_type] ?? $palette['info']; | |
| 529 | + ?> | |
| 530 | + <div class="rounded-md <?php echo esc_attr($c['bg'] . ' border ' . $c['border']); ?> p-4 mb-6"> | |
| 531 | + <div class="flex"> | |
| 532 | + <div class="flex-shrink-0"> | |
| 533 | + <span class="dashicons dashicons-info-outline" style="color: <?php echo esc_attr($c['icon']); ?>;"></span> | |
| 534 | + </div> | |
| 535 | + <div class="ml-3 flex-1"> | |
| 536 | + <?php if (!empty($notice['title'])): ?> | |
| 537 | + <h4 class="text-sm font-semibold <?php echo esc_attr($c['title']); ?>"><?php echo esc_html($notice['title']); ?></h4> | |
| 538 | + <?php endif; ?> | |
| 539 | + <?php if (!empty($notice['message'])): ?> | |
| 540 | + <p class="mt-1 text-sm <?php echo esc_attr($c['body']); ?>"><?php echo wp_kses_post($notice['message']); ?></p> | |
| 541 | + <?php endif; ?> | |
| 542 | + <?php if (!empty($notice['cta_url']) && !empty($notice['cta_label'])): ?> | |
| 543 | + <p class="mt-3"> | |
| 544 | + <a href="<?php echo esc_url($notice['cta_url']); ?>" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700"> | |
| 545 | + <?php echo esc_html($notice['cta_label']); ?> | |
| 546 | + </a> | |
| 547 | + </p> | |
| 548 | + <?php endif; ?> | |
| 549 | + </div> | |
| 550 | + </div> | |
| 551 | + </div> | |
| 552 | + <?php endif; ?> | |
| 520 | 553 | <div class="space-y-6"> |
| 521 | 554 | <?php if (!empty($subsection_data['fields'])) { |
| 522 | 555 | foreach ($subsection_data['fields'] as $option_key => $field_config) { |
| 523 | 556 | $current_value = $settings[$option_key] ?? ($field_config['default'] ?? ''); |
| @@ -531,9 +564,9 @@ | ||
| 531 | 564 | $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md'; |
| 532 | 565 | $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6'); |
| 533 | 566 | echo '<div class="field-wrapper form-group">'; |
| 534 | 567 | if ($field_type !== 'checkbox') { |
| 535 | - echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>'; | |
| 568 | + echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . esc_html($field_label) . '</label>'; | |
| 536 | 569 | } |
| 537 | 570 | |
| 538 | 571 | // Define required and aria_describedby variables |
| 539 | 572 | $required = !empty($field_config['required']) ? 'required' : ''; |
| @@ -543,21 +576,21 @@ | ||
| 543 | 576 | case 'text': |
| 544 | 577 | case 'email': |
| 545 | 578 | case 'url': |
| 546 | 579 | case 'tel': |
| 547 | - echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 580 | + echo '<input type="' . esc_attr($field_type) . '" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 548 | 581 | break; |
| 549 | 582 | case 'number': |
| 550 | 583 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 551 | 584 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 552 | 585 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 553 | - echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 586 | + echo '<input type="number" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 554 | 587 | break; |
| 555 | 588 | case 'textarea': |
| 556 | - echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>'; | |
| 589 | + echo '<textarea id="' . $field_id . '" name="' . esc_html($field_name) . '" rows="3" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>'; | |
| 557 | 590 | break; |
| 558 | 591 | case 'select': |
| 559 | - echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 592 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '" class="' . esc_html($select_class) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 560 | 593 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 561 | 594 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 562 | 595 | echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>'; |
| 563 | 596 | } |
| @@ -564,25 +597,25 @@ | ||
| 564 | 597 | } |
| 565 | 598 | echo '</select>'; |
| 566 | 599 | break; |
| 567 | 600 | case 'multiselect': |
| 568 | - echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">'; | |
| 601 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '[]" class="' . esc_html($select_class) . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">'; | |
| 569 | 602 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 570 | 603 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 571 | 604 | $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : ''; |
| 572 | - echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>'; | |
| 605 | + echo '<option value="' . esc_attr($opt_val) . '" ' . esc_html($selected) . '>' . esc_html($opt_label) . '</option>'; | |
| 573 | 606 | } |
| 574 | 607 | } |
| 575 | 608 | echo '</select>'; |
| 576 | 609 | break; |
| 577 | 610 | case 'readonly': |
| 578 | - echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>'; | |
| 579 | - echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">'; | |
| 611 | + echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . ' bg-gray-50" readonly>'; | |
| 612 | + echo '<input type="hidden" name="' . esc_html($field_name) . '" value="' . esc_attr($current_value) . '">'; | |
| 580 | 613 | break; |
| 581 | 614 | case 'checkbox': |
| 582 | 615 | echo '<div class="flex items-center">'; |
| 583 | - echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">'; | |
| 584 | - echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>'; | |
| 616 | + echo ' <input type="checkbox" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">'; | |
| 617 | + echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . esc_html($field_label) . '</label>'; | |
| 585 | 618 | echo '</div>'; |
| 586 | 619 | if (!empty($field_description)) { |
| 587 | 620 | echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>'; |
| 588 | 621 | } |
| @@ -589,14 +622,14 @@ | ||
| 589 | 622 | break; |
| 590 | 623 | case 'image': |
| 591 | 624 | echo '<div class="mt-1 flex items-center">'; |
| 592 | 625 | echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">'; |
| 593 | - echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">'; | |
| 626 | + echo ' <img id="' . $field_id . '-preview"' . (empty($current_value) ? '' : ' src="' . esc_url($current_value) . '"') . ' alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">'; | |
| 594 | 627 | echo ' </span>'; |
| 595 | 628 | echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">'; |
| 596 | 629 | echo esc_html__('Change', 'easy-invoice'); |
| 597 | 630 | echo '</button>'; |
| 598 | - echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 631 | + echo ' <input type="hidden" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 599 | 632 | echo '</div>'; |
| 600 | 633 | break; |
| 601 | 634 | case 'wp_editor': |
| 602 | 635 | wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] ); |
| @@ -601,9 +634,9 @@ | ||
| 601 | 634 | case 'wp_editor': |
| 602 | 635 | wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] ); |
| 603 | 636 | // Handle description for wp_editor specifically |
| 604 | 637 | if (!empty($field_description)) { |
| 605 | - echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>'; | |
| 638 | + echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($field_description, ['a' => ['href' => [], 'target' => [], 'class' => []], 'code' => [], 'strong' => [], 'em' => []]) . '</p>'; | |
| 606 | 639 | } |
| 607 | 640 | break; |
| 608 | 641 | case 'test_email': |
| 609 | 642 | echo '<div class="mt-1">'; |
| @@ -622,10 +655,10 @@ | ||
| 622 | 655 | $is_payment_test = ($field_id === 'easy_invoice_payment_test_template'); |
| 623 | 656 | $width_class = $is_payment_test ? 'w-[200px]' : 'w-64'; |
| 624 | 657 | echo '<div class="mt-1">'; |
| 625 | 658 | echo ' <div class="flex items-center space-x-3">'; |
| 626 | - echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block ' . $width_class . ' border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">'; | |
| 627 | - echo ' <button type="button" id="' . $field_id . '_button" class="test-template-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 h-[38px]" data-template-type="' . esc_attr($template_type) . '">'; | |
| 659 | + echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block ' . esc_html($width_class) . ' border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">'; | |
| 660 | + echo ' <button type="button" id="' . $field_id . '_button" data-template-type="' . esc_attr($field_config['template_type'] ?? '') . '" class="test-template-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 h-[38px]" data-template-type="' . esc_attr($template_type) . '">'; | |
| 628 | 661 | echo ' <i class="fas fa-envelope mr-2" aria-hidden="true"></i>'; |
| 629 | 662 | echo ' ' . esc_html__('Test Template', 'easy-invoice'); |
| 630 | 663 | echo ' </button>'; |
| 631 | 664 | echo ' </div>'; |
| @@ -644,13 +677,13 @@ | ||
| 644 | 677 | echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>'; |
| 645 | 678 | echo '</div>'; |
| 646 | 679 | break; |
| 647 | 680 | default: |
| 648 | - echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">'; | |
| 681 | + echo '<input type="text" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '">'; | |
| 649 | 682 | break; |
| 650 | 683 | } |
| 651 | 684 | if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) { |
| 652 | - echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>'; | |
| 685 | + echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($field_description, ['a' => ['href' => [], 'target' => [], 'class' => []], 'code' => [], 'strong' => [], 'em' => []]) . '</p>'; | |
| 653 | 686 | } |
| 654 | 687 | echo '</div>'; // Close the field wrapper div |
| 655 | 688 | } |
| 656 | 689 | } |
| @@ -663,9 +696,9 @@ | ||
| 663 | 696 | <div class="bg-blue-50 border border-blue-200 rounded-lg p-4"> |
| 664 | 697 | <p class="text-sm font-medium text-blue-900 mb-3"><?php esc_html_e('Available Placeholders:', 'easy-invoice'); ?></p> |
| 665 | 698 | <div class="grid grid-cols-2 md:grid-cols-3 gap-2"> |
| 666 | 699 | <?php if (in_array($subsection_key, ['invoice_available', 'payment_received', 'payment_reminder'])): ?> |
| 667 | - <!-- Invoice-specific placeholders --> | |
| 700 | + | |
| 668 | 701 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_number}}</div> |
| 669 | 702 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{due_date}}</div> |
| 670 | 703 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_url}}</div> |
| 671 | 704 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_url}}</div> |
| @@ -671,15 +704,15 @@ | ||
| 671 | 704 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_url}}</div> |
| 672 | 705 | <?php endif; ?> |
| 673 | 706 | |
| 674 | 707 | <?php if (in_array($subsection_key, ['quote_available'])): ?> |
| 675 | - <!-- Quote-specific placeholders --> | |
| 708 | + | |
| 676 | 709 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_number}}</div> |
| 677 | 710 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{expiry_date}}</div> |
| 678 | 711 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_url}}</div> |
| 679 | 712 | <?php endif; ?> |
| 680 | 713 | |
| 681 | - <!-- Common placeholders for all templates --> | |
| 714 | + | |
| 682 | 715 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_name}}</div> |
| 683 | 716 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_first_name}}</div> |
| 684 | 717 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_last_name}}</div> |
| 685 | 718 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_email}}</div> |
| @@ -685,8 +718,9 @@ | ||
| 685 | 718 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_email}}</div> |
| 686 | 719 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_address}}</div> |
| 687 | 720 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_name}}</div> |
| 688 | 721 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{total_amount}}</div> |
| 722 | + <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{amount_due}}</div> | |
| 689 | 723 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{issue_date}}</div> |
| 690 | 724 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_email}}</div> |
| 691 | 725 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_phone}}</div> |
| 692 | 726 | <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_terms}}</div> |
| @@ -704,19 +738,139 @@ | ||
| 704 | 738 | <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6"> |
| 705 | 739 | <div class="flex items-center"> |
| 706 | 740 | <i class="fas fa-info-circle text-blue-500 mr-2"></i> |
| 707 | 741 | <p class="text-sm text-blue-700"> |
| 708 | - <?php _e('Drag and drop payment methods to reorder them. The order will be reflected on payment forms.', 'easy-invoice'); ?> | |
| 742 | + <?php esc_html_e('Drag and drop payment methods to reorder them. The order will be reflected on payment forms.', 'easy-invoice'); ?> | |
| 709 | 743 | </p> |
| 710 | 744 | </div> |
| 711 | 745 | </div> |
| 712 | 746 | <?php endif; ?> |
| 713 | 747 | |
| 748 | + <?php | |
| 749 | + /** | |
| 750 | + * Pro-gateway teaser block (renders only when Pro is NOT active). | |
| 751 | + * | |
| 752 | + * Showing the names of Pro gateways here drives conversion — users | |
| 753 | + * see exactly what they unlock without leaving the settings page. | |
| 754 | + * Each row links to the pricing page in a new tab. Free gateways | |
| 755 | + * (PayPal, Manual) remain enabled below this block as usual. | |
| 756 | + */ | |
| 757 | + if (!easy_invoice_has_pro()) : | |
| 758 | + $pro_gateway_teasers = [ | |
| 759 | + 'stripe' => [ | |
| 760 | + 'title' => __('Stripe', 'easy-invoice'), | |
| 761 | + 'desc' => __('Cards, Apple Pay, Google Pay, Link. SCA / 3-D Secure built in.', 'easy-invoice'), | |
| 762 | + ], | |
| 763 | + 'square' => [ | |
| 764 | + 'title' => __('Square', 'easy-invoice'), | |
| 765 | + 'desc' => __('US / CA / UK / AU / JP card processing with webhook reconciliation.', 'easy-invoice'), | |
| 766 | + ], | |
| 767 | + 'authorizenet' => [ | |
| 768 | + 'title' => __('Authorize.Net', 'easy-invoice'), | |
| 769 | + 'desc' => __('Long-standing US gateway — credit cards + eChecks via AIM.', 'easy-invoice'), | |
| 770 | + ], | |
| 771 | + 'mollie' => [ | |
| 772 | + 'title' => __('Mollie', 'easy-invoice'), | |
| 773 | + 'desc' => __('Europe-friendly: SEPA, iDEAL, Bancontact, Sofort, Klarna, card.', 'easy-invoice'), | |
| 774 | + ], | |
| 775 | + 'paystack' => [ | |
| 776 | + 'title' => __('Paystack', 'easy-invoice'), | |
| 777 | + 'desc' => __('Africa-first (NGN / GHS / ZAR / KES / USD): card, bank, USSD, mobile money, QR.', 'easy-invoice'), | |
| 778 | + ], | |
| 779 | + 'moneris' => [ | |
| 780 | + 'title' => __('Moneris', 'easy-invoice'), | |
| 781 | + 'desc' => __('Canada\'s largest card processor — Visa, Mastercard, AMEX, Interac.', 'easy-invoice'), | |
| 782 | + ], | |
| 783 | + 'bank_transfer'=> [ | |
| 784 | + 'title' => __('Bank Transfer', 'easy-invoice'), | |
| 785 | + 'desc' => __('Show your IBAN / SWIFT on invoices; mark paid on wire arrival.', 'easy-invoice'), | |
| 786 | + ], | |
| 787 | + 'cheque' => [ | |
| 788 | + 'title' => __('Cheque', 'easy-invoice'), | |
| 789 | + 'desc' => __('Display your mailing address; mark paid when the cheque clears.', 'easy-invoice'), | |
| 790 | + ], | |
| 791 | + 'cash' => [ | |
| 792 | + 'title' => __('Cash', 'easy-invoice'), | |
| 793 | + 'desc' => __('In-person cash with custom collection instructions.', 'easy-invoice'), | |
| 794 | + ], | |
| 795 | + ]; | |
| 796 | + | |
| 797 | + // Hide the ones we already register in Free / are already shown above. | |
| 798 | + $already_registered = array_keys($all_gateways_sorted); | |
| 799 | + foreach ($already_registered as $id) { | |
| 800 | + unset($pro_gateway_teasers[$id]); | |
| 801 | + } | |
| 802 | + ?> | |
| 803 | + <?php if (!empty($pro_gateway_teasers)): ?> | |
| 804 | + <div class="ei-pro-gateways mt-2 mb-6 rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden"> | |
| 805 | + | |
| 806 | + <div class="flex items-center justify-between px-5 py-4 border-b border-gray-100 bg-gradient-to-r from-indigo-50/60 to-transparent"> | |
| 807 | + <div class="flex items-center min-w-0"> | |
| 808 | + <span class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-sm mr-3 flex-shrink-0"> | |
| 809 | + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 810 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/> | |
| 811 | + </svg> | |
| 812 | + </span> | |
| 813 | + <div class="min-w-0"> | |
| 814 | + <h3 class="text-sm font-semibold text-gray-900 truncate"> | |
| 815 | + <?php | |
| 816 | + printf( | |
| 817 | + /* translators: %d is the number of additional gateways */ | |
| 818 | + esc_html__('Unlock %d more payment gateways with Easy Invoice Pro', 'easy-invoice'), | |
| 819 | + count($pro_gateway_teasers) | |
| 820 | + ); | |
| 821 | + ?> | |
| 822 | + </h3> | |
| 823 | + <p class="mt-0.5 text-xs text-gray-500"><?php esc_html_e('Stripe, Square, Mollie, Paystack and more — installed alongside PayPal and Manual.', 'easy-invoice'); ?></p> | |
| 824 | + </div> | |
| 825 | + </div> | |
| 826 | + <a href="https://matrixaddons.com/plugins/easy-invoice/#pricing" target="_blank" rel="noopener" | |
| 827 | + class="ml-4 shrink-0 inline-flex items-center px-3 py-1.5 rounded-md text-xs font-semibold text-white bg-indigo-600 hover:bg-indigo-700 transition-colors"> | |
| 828 | + <?php esc_html_e('View pricing', 'easy-invoice'); ?> | |
| 829 | + <svg class="w-3 h-3 ml-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 830 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 5l7 7-7 7"/> | |
| 831 | + </svg> | |
| 832 | + </a> | |
| 833 | + </div> | |
| 834 | + | |
| 835 | + | |
| 836 | + <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 divide-y sm:divide-y-0 sm:divide-x divide-gray-100 lg:divide-x"> | |
| 837 | + <?php | |
| 838 | + $teaser_idx = 0; | |
| 839 | + $teaser_count = count($pro_gateway_teasers); | |
| 840 | + foreach ($pro_gateway_teasers as $teaser_id => $teaser): | |
| 841 | + $teaser_idx++; | |
| 842 | + // Mark items that start a new row so the row-divider rules look correct on every grid breakpoint. | |
| 843 | + $row_break_sm = ($teaser_idx > 2); // 2 cols | |
| 844 | + $row_break_lg = ($teaser_idx > 3); // 3 cols | |
| 845 | + $initial = strtoupper(mb_substr($teaser['title'], 0, 1)); | |
| 846 | + ?> | |
| 847 | + <a href="https://matrixaddons.com/plugins/easy-invoice/#pricing" target="_blank" rel="noopener" | |
| 848 | + class="ei-pro-gateway-card group relative flex items-center gap-3 px-4 py-3.5 hover:bg-indigo-50/40 transition-colors <?php echo $row_break_sm ? 'sm:border-t sm:border-gray-100' : ''; ?> <?php echo $row_break_lg ? 'lg:border-t lg:border-gray-100' : ''; ?>" | |
| 849 | + data-gateway-id="<?php echo esc_attr($teaser_id); ?>"> | |
| 850 | + | |
| 851 | + <span class="flex-shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-md bg-gradient-to-br from-gray-50 to-gray-100 border border-gray-200 text-sm font-bold text-gray-700 group-hover:from-indigo-50 group-hover:to-indigo-100 group-hover:border-indigo-200 group-hover:text-indigo-700 transition-all"> | |
| 852 | + <?php echo esc_html($initial); ?> | |
| 853 | + </span> | |
| 854 | + <div class="min-w-0 flex-1"> | |
| 855 | + <div class="text-sm font-semibold text-gray-900 truncate"><?php echo esc_html($teaser['title']); ?></div> | |
| 856 | + <p class="mt-0.5 text-xs text-gray-500 leading-snug line-clamp-2"><?php echo esc_html($teaser['desc']); ?></p> | |
| 857 | + </div> | |
| 858 | + <svg class="w-4 h-4 text-gray-300 group-hover:text-indigo-500 group-hover:translate-x-0.5 flex-shrink-0 transition-all" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 859 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> | |
| 860 | + </svg> | |
| 861 | + </a> | |
| 862 | + <?php endforeach; ?> | |
| 863 | + </div> | |
| 864 | + </div> | |
| 865 | + <?php endif; ?> | |
| 866 | + <?php endif; ?> | |
| 867 | + | |
| 714 | 868 | <div id="sortable-gateways" class="space-y-5"> |
| 715 | 869 | <?php foreach ($all_gateways_sorted as $gateway_id => $gateway) : ?> |
| 716 | 870 | <div class="gateway-item bg-gray-50 p-5 rounded-lg border border-gray-200 cursor-move" data-gateway-id="<?php echo esc_attr($gateway_id); ?>"> |
| 717 | 871 | <div class="flex items-center mb-3 space-x-3"> |
| 718 | - <!-- Drag Handle --> | |
| 872 | + | |
| 719 | 873 | <div class="gateway-handle text-gray-400 hover:text-gray-600 cursor-move mr-2"> |
| 720 | 874 | <i class="fas fa-grip-vertical"></i> |
| 721 | 875 | </div> |
| 722 | 876 | |
| @@ -731,18 +885,18 @@ | ||
| 731 | 885 | <label for="gateway-enable-<?php echo esc_attr($gateway_id); ?>" class="font-medium text-gray-900"> |
| 732 | 886 | <?php echo esc_html(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : $gateway_id); ?> |
| 733 | 887 | </label> |
| 734 | 888 | <?php if (method_exists($gateway, 'getDescription') && $gateway->getDescription()): ?> |
| 735 | - <span class="block text-sm text-gray-400"><?php echo esc_html($gateway->getDescription()); ?></span> | |
| 889 | + <span class="block text-sm text-gray-400"><?php echo wp_kses_post($gateway->getDescription()); ?></span> | |
| 736 | 890 | <?php endif; ?> |
| 737 | 891 | </div> |
| 738 | 892 | </div> |
| 739 | 893 | |
| 740 | - <!-- Custom Display Name Field --> | |
| 894 | + | |
| 741 | 895 | <div class="gateway-settings <?php echo empty($payment_methods_enabled) || !in_array($gateway_id, $payment_methods_enabled) ? 'hidden' : ''; ?>"> |
| 742 | 896 | <div class="mb-4"> |
| 743 | 897 | <label for="gateway-display-name-<?php echo esc_attr($gateway_id); ?>" class="block text-sm font-medium text-gray-700"> |
| 744 | - <?php _e('Display Name', 'easy-invoice'); ?> | |
| 898 | + <?php esc_html_e('Display Name', 'easy-invoice'); ?> | |
| 745 | 899 | </label> |
| 746 | 900 | <input type="text" |
| 747 | 901 | id="gateway-display-name-<?php echo esc_attr($gateway_id); ?>" |
| 748 | 902 | name="settings[easy_invoice_gateway_display_name_<?php echo esc_attr($gateway_id); ?>]" |
| @@ -749,9 +903,9 @@ | ||
| 749 | 903 | value="<?php echo esc_attr($settings['easy_invoice_gateway_display_name_' . $gateway_id] ?? (method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id)))); ?>" |
| 750 | 904 | placeholder="<?php echo esc_attr(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id))); ?>" |
| 751 | 905 | class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> |
| 752 | 906 | <p class="mt-1 text-xs text-gray-500"> |
| 753 | - <?php _e('Leave blank to use the default name. This name will appear on invoices and payment forms.', 'easy-invoice'); ?> | |
| 907 | + <?php esc_html_e('Leave blank to use the default name. This name will appear on invoices and payment forms.', 'easy-invoice'); ?> | |
| 754 | 908 | </p> |
| 755 | 909 | </div> |
| 756 | 910 | |
| 757 | 911 | <?php |
| @@ -833,18 +987,18 @@ | ||
| 833 | 987 | case 'text': |
| 834 | 988 | case 'email': |
| 835 | 989 | case 'url': |
| 836 | 990 | case 'tel': |
| 837 | - echo '<input type="' . esc_attr($field_config['type']) . '" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">'; | |
| 991 | + echo '<input type="' . esc_attr($field_config['type']) . '" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">'; | |
| 838 | 992 | break; |
| 839 | 993 | case 'number': |
| 840 | 994 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 841 | 995 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 842 | 996 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 843 | - echo '<input type="number" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">'; | |
| 997 | + echo '<input type="number" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . esc_html($input_class) . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">'; | |
| 844 | 998 | break; |
| 845 | 999 | case 'select': |
| 846 | - echo '<select id="' . esc_attr($tax_key) . '" name="settings[' . esc_attr($tax_key) . ']" class="' . $select_class . '">'; | |
| 1000 | + echo '<select id="' . esc_attr($tax_key) . '" name="settings[' . esc_attr($tax_key) . ']" class="' . esc_html($select_class) . '">'; | |
| 847 | 1001 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 848 | 1002 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 849 | 1003 | echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>'; |
| 850 | 1004 | } |
| @@ -862,12 +1016,13 @@ | ||
| 862 | 1016 | // Allow additional fields to be added to the tax section |
| 863 | 1017 | $additional_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], $settings); |
| 864 | 1018 | if (!empty($additional_tax_fields)) { |
| 865 | 1019 | echo '<div class="mt-6 pt-6 border-t border-gray-200">'; |
| 866 | - echo '<h4 class="text-sm font-medium text-gray-900 mb-4">' . __('Additional Tax Settings', 'easy-invoice-pro') . '</h4>'; | |
| 1020 | + echo '<h4 class="text-sm font-medium text-gray-900 mb-4">' . esc_html__('Additional Tax Settings', 'easy-invoice') . '</h4>'; | |
| 867 | 1021 | echo '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">'; |
| 868 | 1022 | foreach ($additional_tax_fields as $field_key => $field_config) { |
| 869 | - $current_value = $settings[$field_key] ?? ($field_config['default'] ?? ''); | |
| 1023 | + // Filter-only fields are not in the loaded settings array; read the option itself. | |
| 1024 | + $current_value = array_key_exists($field_key, (array) $settings) ? $settings[$field_key] : get_option($field_key, $field_config['default'] ?? ''); | |
| 870 | 1025 | easy_invoice_render_field($field_key, $field_config, $current_value); |
| 871 | 1026 | } |
| 872 | 1027 | echo '</div>'; |
| 873 | 1028 | echo '</div>'; |
| @@ -916,11 +1071,11 @@ | ||
| 916 | 1071 | $conditional_class .= ' depends-on-' . esc_attr($depends_key) . '-' . esc_attr($depends_value); |
| 917 | 1072 | } |
| 918 | 1073 | } |
| 919 | 1074 | |
| 920 | - echo '<div class="form-group ' . $form_group_class . $conditional_class . '">'; | |
| 1075 | + echo '<div class="form-group ' . esc_html($form_group_class) . esc_html($conditional_class) . '">'; | |
| 921 | 1076 | if ($field_type !== 'checkbox') { |
| 922 | - echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>'; | |
| 1077 | + echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . esc_html($field_label) . '</label>'; | |
| 923 | 1078 | } |
| 924 | 1079 | |
| 925 | 1080 | // Define required and aria_describedby variables |
| 926 | 1081 | $required = !empty($field_config['required']) ? 'required' : ''; |
| @@ -930,21 +1085,21 @@ | ||
| 930 | 1085 | case 'text': |
| 931 | 1086 | case 'email': |
| 932 | 1087 | case 'url': |
| 933 | 1088 | case 'tel': |
| 934 | - echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 1089 | + echo '<input type="' . esc_attr($field_type) . '" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 935 | 1090 | break; |
| 936 | 1091 | case 'number': |
| 937 | 1092 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 938 | 1093 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 939 | 1094 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 940 | - echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 1095 | + echo '<input type="number" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 941 | 1096 | break; |
| 942 | 1097 | case 'textarea': |
| 943 | - echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>'; | |
| 1098 | + echo '<textarea id="' . $field_id . '" name="' . esc_html($field_name) . '" rows="3" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>'; | |
| 944 | 1099 | break; |
| 945 | 1100 | case 'select': |
| 946 | - echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>'; | |
| 1101 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '" class="' . esc_html($select_class) . '" ' . esc_html($required) . ' ' . $aria_describedby . '>'; | |
| 947 | 1102 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 948 | 1103 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 949 | 1104 | echo '<option value="' . esc_attr($opt_val) . '" ' . selected(strtolower($current_value), strtolower($opt_val), false) . '>' . esc_html($opt_label) . '</option>'; |
| 950 | 1105 | } |
| @@ -951,25 +1106,25 @@ | ||
| 951 | 1106 | } |
| 952 | 1107 | echo '</select>'; |
| 953 | 1108 | break; |
| 954 | 1109 | case 'multiselect': |
| 955 | - echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">'; | |
| 1110 | + echo '<select id="' . $field_id . '" name="' . esc_html($field_name) . '[]" class="' . esc_html($select_class) . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">'; | |
| 956 | 1111 | if (!empty($field_config['options']) && is_array($field_config['options'])) { |
| 957 | 1112 | foreach ($field_config['options'] as $opt_val => $opt_label) { |
| 958 | 1113 | $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : ''; |
| 959 | - echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>'; | |
| 1114 | + echo '<option value="' . esc_attr($opt_val) . '" ' . esc_html($selected) . '>' . esc_html($opt_label) . '</option>'; | |
| 960 | 1115 | } |
| 961 | 1116 | } |
| 962 | 1117 | echo '</select>'; |
| 963 | 1118 | break; |
| 964 | 1119 | case 'readonly': |
| 965 | - echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>'; | |
| 966 | - echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">'; | |
| 1120 | + echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . ' bg-gray-50" readonly>'; | |
| 1121 | + echo '<input type="hidden" name="' . esc_html($field_name) . '" value="' . esc_attr($current_value) . '">'; | |
| 967 | 1122 | break; |
| 968 | 1123 | case 'checkbox': |
| 969 | 1124 | echo '<div class="flex items-center">'; |
| 970 | - echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">'; | |
| 971 | - echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>'; | |
| 1125 | + echo ' <input type="checkbox" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">'; | |
| 1126 | + echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . esc_html($field_label) . '</label>'; | |
| 972 | 1127 | echo '</div>'; |
| 973 | 1128 | if (!empty($field_description)) { |
| 974 | 1129 | echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>'; |
| 975 | 1130 | } |
| @@ -976,14 +1131,14 @@ | ||
| 976 | 1131 | break; |
| 977 | 1132 | case 'image': |
| 978 | 1133 | echo '<div class="mt-1 flex items-center">'; |
| 979 | 1134 | echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">'; |
| 980 | - echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">'; | |
| 1135 | + echo ' <img id="' . $field_id . '-preview"' . (empty($current_value) ? '' : ' src="' . esc_url($current_value) . '"') . ' alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">'; | |
| 981 | 1136 | echo ' </span>'; |
| 982 | 1137 | echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">'; |
| 983 | 1138 | echo esc_html__('Change', 'easy-invoice'); |
| 984 | 1139 | echo '</button>'; |
| 985 | - echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 1140 | + echo ' <input type="hidden" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 986 | 1141 | echo '</div>'; |
| 987 | 1142 | break; |
| 988 | 1143 | case 'wp_editor': |
| 989 | 1144 | wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] ); |
| @@ -988,25 +1143,25 @@ | ||
| 988 | 1143 | case 'wp_editor': |
| 989 | 1144 | wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] ); |
| 990 | 1145 | // Handle description for wp_editor specifically |
| 991 | 1146 | if (!empty($field_description)) { |
| 992 | - echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>'; | |
| 1147 | + echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($field_description, ['a' => ['href' => [], 'target' => [], 'class' => []], 'code' => [], 'strong' => [], 'em' => []]) . '</p>'; | |
| 993 | 1148 | } |
| 994 | 1149 | break; |
| 995 | 1150 | case 'color': |
| 996 | - echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="h-10 w-20 border border-gray-300 rounded">'; | |
| 1151 | + echo '<input type="color" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="h-10 w-20 border border-gray-300 rounded">'; | |
| 997 | 1152 | break; |
| 998 | 1153 | case 'range': |
| 999 | 1154 | $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : ''; |
| 1000 | 1155 | $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : ''; |
| 1001 | 1156 | $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : ''; |
| 1002 | - echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="w-full">'; | |
| 1157 | + echo '<input type="range" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="w-full">'; | |
| 1003 | 1158 | echo '<span class="text-sm text-gray-400">' . esc_html($current_value) . '%</span>'; |
| 1004 | 1159 | break; |
| 1005 | 1160 | case 'image_upload': |
| 1006 | 1161 | echo '<div class="image-upload-wrap">'; |
| 1007 | 1162 | echo '<div class="flex items-center space-x-3">'; |
| 1008 | - echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 1163 | + echo ' <input type="hidden" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">'; | |
| 1009 | 1164 | echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">'; |
| 1010 | 1165 | echo esc_html__('Upload Image', 'easy-invoice'); |
| 1011 | 1166 | echo '</button>'; |
| 1012 | 1167 | echo '</div>'; |
| @@ -1023,18 +1178,18 @@ | ||
| 1023 | 1178 | $button_class_name = 'regenerate-invoice-numbers-button'; |
| 1024 | 1179 | if ($option_key === 'easy_invoice_regenerate_quote_numbers') { |
| 1025 | 1180 | $button_class_name = 'regenerate-quote-numbers-button'; |
| 1026 | 1181 | } |
| 1027 | - echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">'; | |
| 1182 | + echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . wp_kses_post($button_class_name) . '">'; | |
| 1028 | 1183 | echo esc_html($button_text); |
| 1029 | 1184 | echo '</button>'; |
| 1030 | 1185 | break; |
| 1031 | 1186 | default: |
| 1032 | - echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">'; | |
| 1187 | + echo '<input type="text" name="' . esc_html($field_name) . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . esc_html($input_class) . '" placeholder="' . esc_html($field_placeholder) . '">'; | |
| 1033 | 1188 | break; |
| 1034 | 1189 | } |
| 1035 | 1190 | if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) { |
| 1036 | - echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>'; | |
| 1191 | + echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($field_description, ['a' => ['href' => [], 'target' => [], 'class' => []], 'code' => [], 'strong' => [], 'em' => []]) . '</p>'; | |
| 1037 | 1192 | } |
| 1038 | 1193 | echo '</div>'; |
| 1039 | 1194 | } |
| 1040 | 1195 | if ($is_grid) echo '</div>'; |
| @@ -1050,9 +1205,9 @@ | ||
| 1050 | 1205 | </div> |
| 1051 | 1206 | </div> |
| 1052 | 1207 | <?php endforeach; ?> |
| 1053 | 1208 | |
| 1054 | - <!-- Bottom Save Button --> | |
| 1209 | + | |
| 1055 | 1210 | <div class="mt-8 pt-6 border-t border-gray-200"> |
| 1056 | 1211 | <div class="flex items-center justify-start space-x-3"> |
| 1057 | 1212 | <button type="button" id="save-settings-bottom" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200" aria-label="<?php esc_attr_e('Save Settings', 'easy-invoice'); ?>"> |
| 1058 | 1213 | <i class="fas fa-save mr-2" aria-hidden="true"></i> |