PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | easy-invoice.php +114 -6 2.3.12.4.0 View file →
@@ -2,9 +2,9 @@
2 2 /**
3 3 * Plugin Name: Easy Invoice
4 4 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice
5 5 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease.
6 - * Version: 2.3.1
6 + * Version: 2.4.0
7 7 * Author: MatrixAddons
8 8 * Author URI: https://matrixaddons.com
9 9 * License: GPL v2 or later
10 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -9,12 +9,11 @@
9 9 * License: GPL v2 or later
10 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 11 * Text Domain: easy-invoice
12 12 * Domain Path: /languages
13 - * Requires at least: 5.6
14 - * Tested up to: 7.0
13 + * Requires at least: 6.0
14 + * Tested up to: 7.1
15 15 * Requires PHP: 7.4
16 - * Network: false
17 16 *
18 17 * @package EasyInvoice
19 18 */
20 19
@@ -24,9 +23,9 @@
24 23 }
25 24
26 25
27 26 // Define plugin constants.
28 -define( 'EASY_INVOICE_VERSION', '2.3.1' );
27 +define( 'EASY_INVOICE_VERSION', '2.4.0' );
29 28 define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ );
30 29 define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
31 30 define( 'EASY_INVOICE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
32 31
@@ -46,11 +45,13 @@
46 45 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/BrandHelper.php';
47 46 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/TaxHelper.php';
48 47 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/FormattingHelper.php';
49 48 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/EmailHelper.php';
49 +require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/MoneyHelper.php';
50 50 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/EnqueueHelper.php';
51 51 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/PdfHelper.php';
52 52 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/QuoteInvoiceHelper.php';
53 +require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/TemplateLocator.php';
53 54
54 55 // Addons system — Free plugin owns the listing UI + license gate;
55 56 // Pro plugin owns the executable addon code.
56 57 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Addons/LicensePlanResolver.php';
@@ -111,8 +112,26 @@
111 112
112 113 // Initialize review notice service
113 114 \EasyInvoice\Services\ReviewNoticeService::init();
114 115
116 + // One-time notice explaining that document links now require an access key.
117 + // Registered here rather than on activation because WordPress updates a plugin
118 + // by replacing files, without firing the activation hook — so an upgrading site
119 + // would otherwise get no warning that previously-emailed links stop working.
120 + \EasyInvoice\Services\DocumentAccessNoticeService::init();
121 + \EasyInvoice\Services\ProCompatibilityNotice::init();
122 + \EasyInvoice\Services\TaxTreatment::init();
123 + \EasyInvoice\Rest\RestController::init();
124 + \EasyInvoice\Services\PdfDownload::init();
125 + \EasyInvoice\Services\DocumentPage::init();
126 + \EasyInvoice\Services\DocumentViews::init();
127 + \EasyInvoice\Services\DocumentAttachments::init();
128 + \EasyInvoice\Services\InvoiceRetention::init();
129 + \EasyInvoice\Controllers\CreditNoteController::init();
130 + \EasyInvoice\Controllers\VatCheckController::init();
131 + \EasyInvoice\Controllers\StatementController::init();
132 + \EasyInvoice\Controllers\ImportController::init();
133 +
115 134 // Initialize promotion service
116 135 \EasyInvoice\Services\PromotionService::init();
117 136
118 137 // Initialize Addons controller (admin page + AJAX toggle)
@@ -122,14 +141,100 @@
122 141
123 142 }
124 143 add_action( 'init', 'easy_invoice_init' );
125 144
145 +// Any plugin option change invalidates the per-request settings cache the
146 +// money formatter reads (see SettingsController::getSettings()).
147 +foreach ( [ 'updated_option', 'added_option', 'deleted_option' ] as $ei_opt_hook ) {
148 + add_action( $ei_opt_hook, static function ( $option ) {
149 + if ( is_string( $option ) && 0 === strpos( $option, 'easy_invoice_' ) ) {
150 + \EasyInvoice\Controllers\SettingsController::flushSharedCache();
151 + // The builder field set depends on settings (tax fields are only registered
152 + // while tax is on), so the models' cached definitions go with it.
153 + \EasyInvoice\Models\Invoice::flushFieldDefinitionsCache();
154 + \EasyInvoice\Models\Quote::flushFieldDefinitionsCache();
155 + }
156 + } );
157 +}
158 +unset( $ei_opt_hook );
159 +
160 +// Payment and credit-note writes invalidate the balance totals primed for
161 +// dashboard and report screens (see InvoiceBalance::prime()).
162 +add_action( 'save_post', static function ( $post_id, $post ) {
163 + if ( $post instanceof WP_Post && in_array( $post->post_type, [ 'easy_invoice_payment', 'easy_invoice_credit', 'easy_invoice', 'easy_invoice_quote' ], true ) ) {
164 + \EasyInvoice\Services\InvoiceBalance::forget();
165 + \EasyInvoice\Services\InvoiceTotalsCache::bumpVersion();
166 + }
167 +}, 10, 2 );
168 +add_action( 'updated_post_meta', static function ( $meta_id, $post_id, $meta_key ) {
169 + if ( in_array( $meta_key, [ '_amount', '_status', '_easy_invoice_total', '_invoice_id', '_easy_invoice_status', '_easy_invoice_due_date', '_easy_invoice_issue_date', '_easy_invoice_client_id', '_easy_invoice_currency_code', '_easy_invoice_quote_status', '_easy_invoice_quote_currency_code' ], true ) ) {
170 + \EasyInvoice\Services\InvoiceBalance::forget();
171 + \EasyInvoice\Services\InvoiceTotalsCache::bumpVersion();
172 + }
173 +}, 10, 3 );
174 +add_action( 'added_post_meta', static function ( $meta_id, $post_id, $meta_key ) {
175 + if ( in_array( $meta_key, [ '_amount', '_status', '_easy_invoice_total', '_invoice_id', '_easy_invoice_status', '_easy_invoice_due_date', '_easy_invoice_issue_date', '_easy_invoice_client_id', '_easy_invoice_currency_code', '_easy_invoice_quote_status', '_easy_invoice_quote_currency_code' ], true ) ) {
176 + \EasyInvoice\Services\InvoiceBalance::forget();
177 + \EasyInvoice\Services\InvoiceTotalsCache::bumpVersion();
178 + }
179 +}, 10, 3 );
180 +
181 +// Persisted invoice totals (InvoiceTotalsCache): written on save, backfilled by cron,
182 +// dropped when a total's inputs change.
183 +add_action( 'easy_invoice_totals_backfill', [ \EasyInvoice\Services\InvoiceTotalsCache::class, 'cronBackfill' ] );
184 +add_action( \EasyInvoice\Services\QuoteTotals::CRON_HOOK, [ \EasyInvoice\Services\QuoteTotals::class, 'cronBackfill' ] );
185 +foreach ( [ 'updated_post_meta', 'added_post_meta', 'deleted_post_meta' ] as $ei_meta_hook ) {
186 + add_action( $ei_meta_hook, static function ( $meta_id, $post_id, $meta_key ) {
187 + if ( in_array( $meta_key, \EasyInvoice\Services\InvoiceTotalsCache::DEPENDENT_META, true ) && 'easy_invoice' === get_post_type( $post_id ) ) {
188 + \EasyInvoice\Services\InvoiceTotalsCache::invalidate( (int) $post_id );
189 + }
190 + if ( in_array( $meta_key, \EasyInvoice\Services\QuoteTotals::DEPENDENT_META, true ) && 'easy_invoice_quote' === get_post_type( $post_id ) ) {
191 + \EasyInvoice\Services\QuoteTotals::invalidate( (int) $post_id );
192 + }
193 + }, 10, 3 );
194 +}
195 +unset( $ei_meta_hook );
196 +foreach ( [ 'trashed_post', 'untrashed_post', 'deleted_post', 'before_delete_post' ] as $ei_post_hook ) {
197 + add_action( $ei_post_hook, static function ( $post_id ) {
198 + if ( in_array( get_post_type( $post_id ), [ 'easy_invoice', 'easy_invoice_payment', 'easy_invoice_credit', 'easy_invoice_quote' ], true ) ) {
199 + \EasyInvoice\Services\InvoiceBalance::forget();
200 + \EasyInvoice\Services\InvoiceTotalsCache::bumpVersion();
201 + }
202 + } );
203 +}
204 +unset( $ei_post_hook );
205 +foreach ( [ 'updated_option', 'added_option', 'deleted_option' ] as $ei_opt_hook ) {
206 + add_action( $ei_opt_hook, static function ( $option ) {
207 + if ( in_array( $option, \EasyInvoice\Services\InvoiceTotalsCache::DEPENDENT_OPTIONS, true ) ) {
208 + \EasyInvoice\Services\InvoiceTotalsCache::invalidateAll();
209 + }
210 + } );
211 +}
212 +unset( $ei_opt_hook );
213 +add_action( 'easy_invoice_loaded', static function () {
214 + $want = \EasyInvoice\Services\InvoiceTotalsCache::CACHE_VERSION;
215 + if ( $want !== get_option( 'easy_invoice_totals_cache_version' ) ) {
216 + update_option( 'easy_invoice_totals_cache_version', $want, false );
217 + \EasyInvoice\Services\InvoiceTotalsCache::invalidateAll(); // drops stale totals and queues the backfill
218 + }
219 + if ( '1' !== get_option( 'easy_invoice_quote_totals_version' ) ) {
220 + update_option( 'easy_invoice_quote_totals_version', '1', false );
221 + \EasyInvoice\Services\QuoteTotals::scheduleBackfill();
222 + }
223 + // Versioned memo keys from the first 2.4.0 builds left one transient per write behind.
224 + if ( '1' !== get_option( 'easy_invoice_stats_memo_keys' ) ) {
225 + global $wpdb;
226 + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_ei_stats_%' OR option_name LIKE '_transient_timeout_ei_stats_%'" );
227 + update_option( 'easy_invoice_stats_memo_keys', '1', false );
228 + }
229 +} );
230 +
126 231 /**
127 232 * Handle payment gateway logging
128 233 */
129 234 function easy_invoice_payment_gateway_log_handler($message, $level, $gateway_name) {
130 235 $log_message = sprintf('[%s] Easy Invoice %s Gateway: %s',
131 - date('Y-m-d H:i:s'),
236 + gmdate('Y-m-d H:i:s'),
132 237 ucfirst($gateway_name),
133 238 $message
134 239 );
135 240
@@ -217,8 +322,11 @@
217 322 flush_rewrite_rules();
218 323
219 324 // Clear scheduled events
220 325 \EasyInvoice\Services\QuoteExpirationService::clear_schedule();
326 + foreach ( [ 'easy_invoice_payment_reminder', 'easy_invoice_send_payment_reminders', 'easy_invoice_quote_expiration_check' ] as $hook ) {
327 + wp_clear_scheduled_hook( $hook );
328 + }
221 329 }
222 330
223 331 /**
224 332 * Toast notification helper functions