Plugin Directory

Changeset 3793045


Ignore:
Timestamp:
11/09/2026 06:29:19 PM (less than one hour ago)
Author:
gresnichconsulting
Message:

GresQuote 1.0.24

  • Fix: a locked wizard step showed an empty page; it now goes to the first open step with a message.
  • US: the sample quote in the settings says Subtotal, Total before tax, Sales tax and Total, like the real PDF.
  • The country choice in the settings is no longer cut off.
  • The step-by-step guide links to the English site for English-speaking admins.
  • The quote PDF is named after its quote number, with "quote" or "offerte" in the language of your quotes.
  • The demo form on an English site uses English field names.
Location:
gresquote
Files:
71 added
10 edited

Legend:

Unmodified
Added
Removed
  • gresquote/trunk/admin/instellingen-offerte.php

    r3792172 r3793045  
    135135                    <p><?php esc_html_e('For new quotes, the emails to your customer and the customer portal. A quote that has already been sent keeps its language and currency.', 'gresquote'); ?></p>
    136136                </div>
    137                 <div class="wpqp-inst-raster">
     137                <div class="wpqp-inst-raster is-twee">
    138138                    <div class="wpqp-inst-veld">
    139139                        <label for="wpquotepro_quote_language"><?php esc_html_e('Language', 'gresquote'); ?></label>
    … …  
    151151                        </select>
    152152                    </div>
    153                     <div class="wpqp-inst-veld">
     153                    <div class="wpqp-inst-veld is-breed">
    154154                        <label for="wpquotepro_country"><?php esc_html_e('Country', 'gresquote'); ?></label>
    155155                        <select id="wpquotepro_country" name="country">
    … …  
    224224                </div>
    225225                <p class="wpqp-pdf-intro" data-role="pdf-intro"<?php echo '' === trim($intro_text) ? ' hidden' : ''; ?>><?php echo esc_html($intro_text); ?></p>
     226                <?php $vb_vs = 'US' === $regio['land']; ?>
    226227                <table class="wpqp-pdf-regels">
    227228                    <thead>
    228                         <tr><th><?php esc_html_e('Description', 'gresquote'); ?></th><th><?php esc_html_e('Quantity', 'gresquote'); ?></th><th><?php esc_html_e('Price', 'gresquote'); ?></th><th><?php esc_html_e('VAT', 'gresquote'); ?></th><th><?php esc_html_e('Amount', 'gresquote'); ?></th></tr>
     229                        <tr><th><?php esc_html_e('Description', 'gresquote'); ?></th><th><?php esc_html_e('Quantity', 'gresquote'); ?></th><th><?php esc_html_e('Price', 'gresquote'); ?></th><th><?php $vb_vs ? esc_html_e('Sales tax', 'gresquote') : esc_html_e('VAT', 'gresquote'); ?></th><th><?php esc_html_e('Amount', 'gresquote'); ?></th></tr>
    229230                    </thead>
    230231                    <?php
    … …  
    245246                <table class="wpqp-pdf-totalen">
    246247                    <tbody>
    247                         <tr><td><?php esc_html_e('Subtotal excl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2300)); ?></td></tr>
     248                        <tr><td><?php $vb_vs ? esc_html_e('Subtotal', 'gresquote') : esc_html_e('Subtotal excl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2300)); ?></td></tr>
    248249                        <tr class="is-korting"><td><?php esc_html_e('Discount', 'gresquote'); ?></td><td>− <?php echo esc_html($vb_bedrag(100)); ?></td></tr>
    249                         <tr class="is-sub"><td><?php esc_html_e('Total excl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2200)); ?></td></tr>
    250                         <tr class="is-btw"><td><?php /* translators: %s: VAT rate, e.g. 21% */ echo esc_html(sprintf(__('VAT rate %s', 'gresquote'), $vb_procent)); ?></td><td><?php echo esc_html($vb_bedrag(2200 * $vb_tarief / 100)); ?></td></tr>
    251                         <tr class="is-totaal"><td><?php esc_html_e('Total incl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2200 * (1 + $vb_tarief / 100))); ?></td></tr>
     250                        <tr class="is-sub"><td><?php $vb_vs ? esc_html_e('Total before tax', 'gresquote') : esc_html_e('Total excl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2200)); ?></td></tr>
     251                        <tr class="is-btw"><td><?php
     252                            /* translators: %s: sales tax rate, e.g. 6% */
     253                            $vb_vs_regel = __('Sales tax %s', 'gresquote');
     254                            /* translators: %s: VAT rate, e.g. 21% */
     255                            $vb_btw_regel = __('VAT rate %s', 'gresquote');
     256                            echo esc_html(sprintf($vb_vs ? $vb_vs_regel : $vb_btw_regel, $vb_procent));
     257                            ?></td><td><?php echo esc_html($vb_bedrag(2200 * $vb_tarief / 100)); ?></td></tr>
     258                        <tr class="is-totaal"><td><?php $vb_vs ? esc_html_e('Total', 'gresquote') : esc_html_e('Total incl. VAT', 'gresquote'); ?></td><td><?php echo esc_html($vb_bedrag(2200 * (1 + $vb_tarief / 100))); ?></td></tr>
    252259                    </tbody>
    253260                </table>
  • gresquote/trunk/includes/class-wpquotepro-admin.php

    r3792929 r3793045  
    6767        add_action('admin_menu', [$this, 'register_menu']);
    6868        add_action('admin_init', [$this, 'stuur_regels_door']);
     69        add_action('admin_init', [$this, 'stuur_vergrendelde_wizardstap_door']);
    6970        add_filter('submenu_file', [$this, 'markeer_menu']);
    7071        add_action('admin_head', [$this, 'verberg_menu_items']);
    … …  
    240241        wp_safe_redirect($this->regels_doel_url($pagina, $_GET));
    241242        exit;
     243    }
     244
     245    /**
     246     * 1.0.24: een vergrendelde wizardstap stuurt door naar de eerste stap die wel
     247     * open is. Dat gebeurde in render_wizard_page(), toen de pagina al uitvoer had
     248     * ("headers already sent"): de beheerder zag een lege pagina in plaats van de
     249     * melding. Nu vóór de uitvoer, net als stuur_regels_door().
     250     */
     251    public function stuur_vergrendelde_wizardstap_door() {
     252        $pagina = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- alleen lezen welke pagina wordt getoond
     253        if ('wpquotepro-wizard' !== $pagina || ! current_user_can('manage_options')) {
     254            return;
     255        }
     256        $doel = $this->vergrendelde_wizardstap_doel();
     257        if ('' !== $doel) {
     258            wp_safe_redirect($doel);
     259            exit;
     260        }
     261    }
     262
     263    /** De wizardstap uit de URL, zoals render_wizard_page() hem leest. */
     264    private function gevraagde_wizardstap() {
     265        $requested_step = isset($_GET['step']) ? absint($_GET['step']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- alleen lezen
     266        // 1.9.13: van vijf naar drie stappen. Oude links naar stap 4 (regels) en
     267        // 5 (testen) komen uit in Prijs en Testen & live.
     268        $oude_stappen = [4 => 2, 5 => 3];
     269        if (isset($oude_stappen[$requested_step])) {
     270            $requested_step = $oude_stappen[$requested_step];
     271        }
     272        return max(0, min(3, $requested_step));
     273    }
     274
     275    /** Waarheen als de gevraagde stap nog op slot zit; leeg als hij open is. */
     276    private function vergrendelde_wizardstap_doel() {
     277        $requested_step = $this->gevraagde_wizardstap();
     278        $selected_form_id = isset($_GET['form_id']) ? absint($_GET['form_id']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- alleen lezen
     279        if (! $selected_form_id) {
     280            return '';
     281        }
     282        $state = $this->get_wizard_state($selected_form_id, true);
     283        $current_step = $this->get_first_accessible_wizard_step(max(1, $requested_step), $state['can_access']);
     284        if ($current_step === $requested_step) {
     285            return '';
     286        }
     287        return admin_url('admin.php?page=wpquotepro-wizard&step=' . (int) $current_step . '&form_id=' . (int) $state['selected_form_id'] . '&message=wizard_step_locked');
    242288    }
    243289
    … …  
    10841130        }
    10851131
    1086         $hulp = 'https://www.wpquotepro.nl/aan-de-slag/';
     1132        $hulp = WPQUOTEPRO_Regio::site_url('aan-de-slag');
    10871133        $items = [
    10881134            [
    … …  
    11621208                <h2><?php esc_html_e('How to go live', 'gresquote'); ?></h2>
    11631209                <span class="wpqp-live-teller"><?php /* translators: 1: steps done, 2: total number of steps */ echo esc_html(sprintf(__('%1$d of %2$d done', 'gresquote'), (int) $status['completed'], (int) $status['total'])); ?></span>
    1164                 <a class="wpqp-live-film" href="https://www.wpquotepro.nl/aan-de-slag/" target="_blank" rel="noopener"><?php esc_html_e('Step-by-step guide with videos', 'gresquote'); ?> &rarr;</a>
     1210                <a class="wpqp-live-film" href="<?php echo esc_url(WPQUOTEPRO_Regio::site_url('aan-de-slag')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Step-by-step guide with videos', 'gresquote'); ?> &rarr;</a>
    11651211            </div>
    11661212            <div class="wpqp-live-balk"><i style="width: <?php echo (int) $pct; ?>%"></i></div>
    … …  
    21182164        global $wpdb;
    21192165
    2120         $requested_step = isset($_GET['step']) ? absint($_GET['step']) : 0;
    2121         // 1.9.13: van vijf naar drie stappen. Oude links naar stap 4 (regels) en
    2122         // 5 (testen) komen uit in Prijs en Testen & live.
    2123         $oude_stappen = [4 => 2, 5 => 3];
    2124         if (isset($oude_stappen[$requested_step])) {
    2125             $requested_step = $oude_stappen[$requested_step];
    2126         }
    2127         $requested_step = max(0, min(3, $requested_step));
     2166        $requested_step = $this->gevraagde_wizardstap();
    21282167        $selected_form_id = isset($_GET['form_id']) ? absint($_GET['form_id']) : 0;
    21292168        $template_key = isset($_GET['template']) ? $this->sanitize_wizard_template_key(sanitize_key(wp_unslash($_GET['template']))) : ($selected_form_id ? $this->get_form_template_key($selected_form_id) : 'blank');
    … …  
    21392178        $current_step = $this->get_first_accessible_wizard_step(max(1, $requested_step), $state['can_access']);
    21402179
     2180        // Een vergrendelde stap is al in admin_init doorgestuurd
     2181        // (stuur_vergrendelde_wizardstap_door). Komt hij hier toch, dan is er al
     2182        // uitvoer: toon de open stap met de melding in plaats van een lege pagina.
    21412183        if ($current_step !== $requested_step && $selected_form_id) {
    2142             wp_safe_redirect(admin_url('admin.php?page=wpquotepro-wizard&step=' . (int) $current_step . '&form_id=' . (int) $state['selected_form_id'] . '&message=wizard_step_locked'));
    2143             exit;
     2184            echo '<div class="wpquotepro-notice error">' . esc_html($this->get_notice_text('wizard_step_locked')) . '</div>';
    21442185        }
    21452186
  • gresquote/trunk/includes/class-wpquotepro-db.php

    r3792172 r3793045  
    392392        }
    393393        $demo_naam = __('Solar Panels Quote', 'gresquote');
     394        // 1.0.24: ook de interne veldnamen en keuzes in de taal van de site. Het
     395        // zijn sleutels (geen vertaalbare tekst): vast per taal, niet via __().
     396        $engels = 0 === strpos((string) get_locale(), 'en');
     397        $veld = $engels
     398            ? ['dak' => 'roof_type', 'plat' => 'flat', 'aantal' => 'number_of_panels', 'spoed' => 'rush', 'ja' => 'yes']
     399            : ['dak' => 'daktype', 'plat' => 'plat', 'aantal' => 'aantal_panelen', 'spoed' => 'spoed', 'ja' => 'ja'];
    394400        $standaard_btw = class_exists('WPQUOTEPRO_Regio') ? WPQUOTEPRO_Regio::standaard_tarief() : 21;
    395401
    … …  
    445451            [
    446452                'form_id'          => $form_id,
    447                 'field_name'       => 'daktype',
     453                'field_name'       => $veld['dak'],
    448454                'rule_operator'    => '=',
    449                 'compare_value'    => 'plat',
     455                'compare_value'    => $veld['plat'],
    450456                'action_type'      => 'add_product',
    451457                'product_id'       => $plat_dak_product_id,
    … …  
    463469            [
    464470                'form_id'          => $form_id,
    465                 'field_name'       => 'aantal_panelen',
     471                'field_name'       => $veld['aantal'],
    466472                'rule_operator'    => '>',
    467473                'compare_value'    => '10',
    … …  
    482488            [
    483489                'form_id'          => $form_id,
    484                 'field_name'       => 'spoed',
     490                'field_name'       => $veld['spoed'],
    485491                'rule_operator'    => '=',
    486                 'compare_value'    => 'ja',
     492                'compare_value'    => $veld['ja'],
    487493                'action_type'      => 'add_percentage_surcharge',
    488494                'product_id'       => 0,
  • gresquote/trunk/includes/class-wpquotepro-regio.php

    r3792172 r3793045  
    7575        $basis = $engels ? 'https://www.gresquote.com' : rtrim(WPQUOTEPRO_SHOP_URL, '/');
    7676        $paden = $engels
    77             ? ['voorwaarden' => '/terms/', 'privacy' => '/privacy/', 'prijzen' => '/pricing/', 'account' => '/my-account/']
    78             : ['voorwaarden' => '/algemene-voorwaarden/', 'privacy' => '/privacy/', 'prijzen' => '/prijzen/', 'account' => '/mijn-account/'];
     77            ? ['voorwaarden' => '/terms/', 'privacy' => '/privacy/', 'prijzen' => '/pricing/', 'account' => '/my-account/', 'aan-de-slag' => '/get-started/']
     78            : ['voorwaarden' => '/algemene-voorwaarden/', 'privacy' => '/privacy/', 'prijzen' => '/prijzen/', 'account' => '/mijn-account/', 'aan-de-slag' => '/aan-de-slag/'];
    7979        return $basis . ($paden[$pad] ?? '/');
    8080    }
  • gresquote/trunk/includes/class-wpquotepro-submission-bridge.php

    r3792172 r3793045  
    17741774            'description' => $description,
    17751775            'is_calculation_line' => $is_calculation_line,
    1776             'currency' => 'EUR',
     1776            'currency' => self::valuta(),
    17771777            'unit_price' => $unit_price,
    17781778            'vat_rate' => $vat_rate,
    … …  
    23052305                'description_text' => isset($row['description_text']) ? (string) $row['description_text'] : '',
    23062306                'line_type' => self::map_rule_line_type(isset($row['action_type']) ? (string) $row['action_type'] : '', ! empty($row['product_id'])),
    2307                 'currency' => 'EUR',
     2307                'currency' => self::valuta(),
    23082308                'unit_price' => $product['unit_price'],
    23092309                // Het eigen btw-tarief van de regel (1.9.16), of null: volgt de
    … …  
    24982498    }
    24992499
     2500    /**
     2501     * 1.0.24: het woord in de PDF-bestandsnaam volgt de taal van de offertes
     2502     * ("quote-…" of "offerte-…"); de eindklant ziet die naam bij het downloaden.
     2503     */
     2504    /** De valuta van de offertes; EUR als de regio (nog) niet te lezen is. */
     2505    private static function valuta() {
     2506        return (class_exists('WPQUOTEPRO_Regio') && function_exists('get_option')) ? WPQUOTEPRO_Regio::huidig()['valuta'] : 'EUR';
     2507    }
     2508
     2509    private static function pdf_woord() {
     2510        return (class_exists('WPQUOTEPRO_Regio') && 'en' === WPQUOTEPRO_Regio::huidig()['taal']) ? 'quote' : 'offerte';
     2511    }
     2512
    25002513    private function build_pdf_filename(array $payload) {
     2514        $woord = self::pdf_woord();
    25012515        $preferred = '';
    25022516        foreach (['filename', 'file_name', 'pdf_filename'] as $key) {
    … …  
    25072521        }
    25082522
    2509         if ('' === $preferred && isset($payload['quote_number']) && is_scalar($payload['quote_number'])) {
    2510             $preferred = 'offerte-' . (string) $payload['quote_number'] . '.pdf';
     2523        // De backend (webhooks/pdf-complete) stuurt quotation_number; quote_number
     2524        // bleef daardoor altijd leeg en de naam werd een datum met willekeurige tekens.
     2525        foreach (['quote_number', 'quotation_number'] as $key) {
     2526            if ('' === $preferred && isset($payload[$key]) && is_scalar($payload[$key]) && '' !== (string) $payload[$key]) {
     2527                $preferred = $woord . '-' . (string) $payload[$key] . '.pdf';
     2528            }
    25112529        }
    25122530
    25132531        if ('' === $preferred && isset($payload['entry_reference']) && is_scalar($payload['entry_reference'])) {
    2514             $preferred = 'offerte-entry-' . (string) $payload['entry_reference'] . '.pdf';
     2532            $preferred = $woord . '-entry-' . (string) $payload['entry_reference'] . '.pdf';
    25152533        }
    25162534
    25172535        if ('' === $preferred) {
    2518             $preferred = 'offerte-' . gmdate('Ymd-His') . '-' . wp_generate_password(8, false, false) . '.pdf';
     2536            $preferred = $woord . '-' . gmdate('Ymd-His') . '-' . wp_generate_password(8, false, false) . '.pdf';
    25192537        }
    25202538
    25212539        $preferred = sanitize_file_name($preferred);
    25222540        if ('' === $preferred) {
    2523             $preferred = 'offerte-' . gmdate('Ymd-His') . '.pdf';
     2541            $preferred = $woord . '-' . gmdate('Ymd-His') . '.pdf';
    25242542        }
    25252543
    … …  
    25372555
    25382556        if ('' === $base_name) {
    2539             $base_name = 'offerte';
     2557            $base_name = self::pdf_woord();
    25402558        }
    25412559
    … …  
    26162634
    26172635        if ('' === $download_filename) {
    2618             $download_filename = 'offerte.pdf';
     2636            $download_filename = self::pdf_woord() . '.pdf';
    26192637        }
    26202638
  • gresquote/trunk/languages/gresquote-nl_NL.po

    r3792314 r3793045  
    27262726msgstr "Loopt tot %s"
    27272727
     2728#: admin/instellingen-offerte.php
    27282729#: includes/class-wpquotepro-acceptance-shortcode.php
    27292730msgid "Sales tax"
    27302731msgstr "Sales tax"
     2732
     2733#: admin/instellingen-offerte.php
     2734msgid "Sales tax %s"
     2735msgstr "Sales tax %s"
    27312736
    27322737#: includes/class-wpquotepro-admin.php
    … …  
    30693074msgstr "Abonnement nog niet gekoppeld. Gebruik de knop Abonneren om GresQuote te activeren."
    30703075
     3076#: admin/instellingen-offerte.php
    30713077#: includes/class-wpquotepro-acceptance-shortcode.php
    30723078msgid "Subtotal"
    … …  
    36663672msgstr "Te veel verzoeken. Wacht alstublieft even."
    36673673
     3674#: admin/instellingen-offerte.php
    36683675#: includes/class-wpquotepro-acceptance-shortcode.php
    36693676#: includes/class-wpquotepro-admin.php
    36703677msgid "Total"
    36713678msgstr "Totaal"
     3679
     3680#: admin/instellingen-offerte.php
     3681msgid "Total before tax"
     3682msgstr "Totaal vóór belasting"
    36723683
    36733684#: admin/instellingen-offerte.php
  • gresquote/trunk/languages/nl_NL.json

    r3792314 r3793045  
    666666  "Runs until %s": "Loopt tot %s",
    667667  "Sales tax": "Sales tax",
     668  "Sales tax %s": "Sales tax %s",
    668669  "Same as the quote": "Zelfde als de offerte",
    669670  "Same as the quote: follows the VAT of the products on the quote, for example for call-out charges. Choose a rate for a separate service with its own rate.": "Zelfde als de offerte: volgt de btw van de producten op de offerte, bijvoorbeeld voor voorrijkosten. Kies een tarief voor een losse dienst met een eigen tarief.",
    … …  
    897898  "Too many requests. Please wait a moment.": "Te veel verzoeken. Wacht alstublieft even.",
    898899  "Total": "Totaal",
     900  "Total before tax": "Totaal vóór belasting",
    899901  "Total excl. VAT": "Totaal excl. BTW",
    900902  "Total incl. VAT": "Totaal incl. btw",
  • gresquote/trunk/readme.txt

    r3792929 r3793045  
    55Tested up to: 7.1
    66Requires PHP: 8.0
    7 Stable tag: 1.0.23
     7Stable tag: 1.0.24
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    9898== Changelog ==
    9999
     100= 1.0.24 =
     101* Fix: a locked wizard step showed an empty page instead of the message; it now goes to the first open step.
     102* In the United States the sample quote in the settings says Subtotal, Sales tax and Total, like the real PDF.
     103* The country choice in the settings is no longer cut off.
     104* The step-by-step guide links to the English site for English-speaking admins.
     105* The PDF of a quote is named after its quote number, with "quote" or "offerte" in the language of your quotes.
     106* The demo form on an English site uses English field names.
     107
    100108= 1.0.23 =
    101109* A new product in the wizard suggests the standard tax rate of your country (0% in the US) instead of always 21%.
    … …  
    175183== Upgrade Notice ==
    176184
     185= 1.0.24 =
     186Fixes an empty page on a locked wizard step and shows US tax wording in the sample quote.
     187
    177188= 1.0.20 =
    178189The plugin is now in English with a Dutch translation, and follows the language of your site.
  • gresquote/trunk/wpquotepro.php

    r3792929 r3793045  
    44 * Plugin URI: https://www.gresquote.nl/wordpress-offerte-plugin/
    55 * Description: Quote automation for WordPress. Automates your quoting process: from a request on your website to a calculated quote, follow-up and acceptance. A customer fills in your quote form and receives a PDF quote in your own branding within a minute, with automatic reminders, online acceptance and an accounting connection. Works with WPForms, Contact Form 7, Gravity Forms and Ninja Forms.
    6  * Version: 1.0.23
     6 * Version: 1.0.24
    77 * Author: Gresnich Consulting
    88 * Author URI: https://www.gresnichconsulting.nl/
Note: See TracChangeset for help on using the changeset viewer.