PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.1
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
easy-invoice / easy-invoice.php

easy-invoice.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.1, at easy-invoice.php

335 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Easy Invoice
4 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice
5 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease.
6 * Version: 2.4.1
7 * Author: MatrixAddons
8 * Author URI: https://matrixaddons.com
9 * License: GPL v2 or later
10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 * Text Domain: easy-invoice
12 * Domain Path: /languages
13 * Requires at least: 6.0
14 * Tested up to: 7.1
15 * Requires PHP: 7.4
16 *
17 * @package EasyInvoice
18 */
19
20 // Exit if accessed directly.
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit;
23 }
24
25
26 // Define plugin constants.
27 define( 'EASY_INVOICE_VERSION', '2.4.1' );
28 define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ );
29 define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
30 define( 'EASY_INVOICE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
31
32 // Load composer autoloader if it exists.
33 if ( file_exists( EASY_INVOICE_PLUGIN_DIR . 'vendor/autoload.php' ) ) {
34 require_once EASY_INVOICE_PLUGIN_DIR . 'vendor/autoload.php';
35 }
36
37 // Include standalone function files (not autoloaded by Composer)
38 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/NotificationHelper.php';
39 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Admin/Helpers/Utility.php';
40 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/GeneralHelper.php';
41 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/ToastHelper.php';
42 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/CapabilityHelper.php';
43 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/AuditHelper.php';
44 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/AutoloadOptimizer.php';
45 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/BrandHelper.php';
46 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/TaxHelper.php';
47 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/FormattingHelper.php';
48 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/EmailHelper.php';
49 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/MoneyHelper.php';
50 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/EnqueueHelper.php';
51 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/PdfHelper.php';
52 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/QuoteInvoiceHelper.php';
53 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Helpers/TemplateLocator.php';
54
55 // Addons system — Free plugin owns the listing UI + license gate;
56 // Pro plugin owns the executable addon code.
57 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Addons/LicensePlanResolver.php';
58 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Addons/AddonRegistry.php';
59 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Addons/AddonManager.php';
60 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Addons/ProAddonMigration.php';
61 require_once EASY_INVOICE_PLUGIN_DIR . 'includes/Controllers/AddonsController.php';
62
63 // Register the Pro→addon migration. Only fires inside admin (admin_init
64 // hook), only when Pro is active, only once per MIGRATION_VERSION bump.
65 \EasyInvoice\Addons\ProAddonMigration::register();
66
67 /**
68 * Initialize the plugin.
69 *
70 * @since 1.0.0
71 * @return void
72 */
73 function easy_invoice_init() {
74 // Initialize the main plugin class.
75 $plugin = \EasyInvoice\EasyInvoice::getInstance();
76 $plugin->init();
77
78 // Initialize service providers.
79 $invoice_service_provider = new \EasyInvoice\Providers\InvoiceServiceProvider();
80 $invoice_service_provider->register();
81
82 $invoice_item_service_provider = new \EasyInvoice\Providers\InvoiceItemServiceProvider();
83 $invoice_item_service_provider->register();
84
85 $client_service_provider = new \EasyInvoice\Providers\ClientServiceProvider();
86 $client_service_provider->register();
87
88 $invoice_number_service_provider = new \EasyInvoice\Providers\InvoiceNumberServiceProvider();
89 $invoice_number_service_provider->register();
90
91 $quote_number_service_provider = new \EasyInvoice\Providers\QuoteNumberServiceProvider();
92 $quote_number_service_provider->register();
93
94 $quote_service_provider = new \EasyInvoice\Providers\QuoteServiceProvider();
95 $quote_service_provider->register();
96
97 // Initialize EmailManager
98 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
99
100 // Initialize controllers.
101 $payment_controller = new \EasyInvoice\Controllers\PaymentController();
102 $payment_controller->init();
103
104 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
105 $settings_controller->init();
106
107 $quote_controller = new \EasyInvoice\Controllers\QuoteController();
108 $quote_controller->init();
109
110 // Initialize shortcode manager
111 $shortcode_manager = new \EasyInvoice\Shortcodes\ShortcodeManager();
112
113 // Initialize review notice service
114 \EasyInvoice\Services\ReviewNoticeService::init();
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
134 // Initialize promotion service
135 \EasyInvoice\Services\PromotionService::init();
136
137 // Initialize Addons controller (admin page + AJAX toggle)
138 (new \EasyInvoice\Controllers\AddonsController())->init();
139
140 do_action('easy_invoice_loaded');
141
142 }
143 add_action( 'init', 'easy_invoice_init' );
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
231 /**
232 * Handle payment gateway logging
233 */
234 function easy_invoice_payment_gateway_log_handler($message, $level, $gateway_name) {
235 $log_message = sprintf('[%s] Easy Invoice %s Gateway: %s',
236 gmdate('Y-m-d H:i:s'),
237 ucfirst($gateway_name),
238 $message
239 );
240
241 if (defined('WP_DEBUG') && WP_DEBUG) {
242 error_log($log_message);
243 }
244 }
245 add_action('easy_invoice_payment_gateway_log', 'easy_invoice_payment_gateway_log_handler', 10, 3);
246
247 // Initialize migration system
248 if ( class_exists( 'EasyInvoice\Migration\MigrationInit' ) ) {
249
250 EasyInvoice\Migration\MigrationInit::init();
251 }
252 /**
253 * Register post types early to ensure they're available
254 */
255 function easy_invoice_register_post_types() {
256 // Initialize the main plugin class and register post types
257 $plugin = \EasyInvoice\EasyInvoice::getInstance();
258 $plugin->registerPostTypes();
259 $plugin->registerCustomStatuses();
260 }
261 add_action( 'init', 'easy_invoice_register_post_types', 0 ); // Priority 0 to run early
262
263 /**
264 * Load textdomain early to avoid translation loading warnings.
265 *
266 * @since 1.0.0
267 * @return void
268 */
269 function easy_invoice_load_textdomain() {
270 load_plugin_textdomain('easy-invoice', false, dirname(plugin_basename(__FILE__)) . '/languages');
271 }
272 add_action( 'init', 'easy_invoice_load_textdomain', 1 );
273
274
275
276
277
278 /**
279 * Plugin activation hook.
280 *
281 * @since 1.0.0
282 * @return void
283 */
284 register_activation_hook( __FILE__, 'easy_invoice_activate' );
285 function easy_invoice_activate() {
286 // Initialization of post types so rewrite rules can be flushed properly.
287 \EasyInvoice\EasyInvoice::getInstance()->registerPostTypes();
288
289 // Initialize default settings
290 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
291 $settings_controller->initializeSettings();
292
293 // Record first install timestamp (do not overwrite if it already exists)
294 if (!get_option('easy_invoice_installed')) {
295 update_option('easy_invoice_installed', current_time('mysql'));
296 }
297
298 // Check if this is a fresh installation or an upgrade
299 $stored_version = get_option('easy_invoice_version', '');
300
301 // If no version is stored, this is a fresh installation
302 if (empty($stored_version)) {
303 update_option('easy_invoice_version', EASY_INVOICE_VERSION);
304 update_option('easy_invoice_migration_completed', true);
305 } else {
306 // This is an upgrade. Do NOT bump version here; migration will set it when completed.
307 }
308
309 // Flush rewrite rules.
310 flush_rewrite_rules();
311 }
312
313 /**
314 * Plugin deactivation hook.
315 *
316 * @since 1.0.0
317 * @return void
318 */
319 register_deactivation_hook( __FILE__, 'easy_invoice_deactivate' );
320 function easy_invoice_deactivate() {
321 // Flush rewrite rules.
322 flush_rewrite_rules();
323
324 // Clear scheduled events
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 }
329 }
330
331 /**
332 * Toast notification helper functions
333 */
334
335