Passed
Pull Request — master (#377)
by Brian
04:53
created

GetPaid_Invoice_Data_Store::create()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
c 1
b 0
f 0
dl 0
loc 45
rs 9.408
cc 4
nc 3
nop 1
1
<?php
2
3
/**
4
 * GetPaid_Invoice_Data_Store class file.
5
 *
6
 */
7
if ( ! defined( 'ABSPATH' ) ) {
8
	exit;
9
}
10
11
/**
12
 * Invoice Data Store: Stored in CPT.
13
 *
14
 * @version  1.0.19
15
 */
16
class GetPaid_Invoice_Data_Store extends GetPaid_Data_Store_WP {
17
18
	/**
19
	 * Data stored in meta keys, but not considered "meta" for a discount.
20
	 *
21
	 * @since 1.0.19
22
	 * @var array
23
	 */
24
	protected $internal_meta_keys = array(
25
		'_wpinv_subscr_profile_id',
26
		'_wpinv_taxes',
27
		'_wpinv_fees',
28
		'_wpinv_discounts',
29
		'_wpinv_submission_id',
30
		'_wpinv_payment_form',
31
		'_wpinv_is_viewed',
32
		'wpinv_email_cc',
33
		'wpinv_template'
34
	);
35
36
	/**
37
	 * A map of meta keys to data props.
38
	 *
39
	 * @since 1.0.19
40
	 *
41
	 * @var array
42
	 */
43
	protected $meta_key_to_props = array(
44
		'_wpinv_subscr_profile_id' => 'subscription_id',
45
		'_wpinv_taxes'             => 'taxes',
46
		'_wpinv_fees'              => 'fees',
47
		'_wpinv_discounts'         => 'discounts',
48
		'_wpinv_submission_id'     => 'submission_id',
49
		'_wpinv_payment_form'      => 'payment_form',
50
		'_wpinv_is_viewed'         => 'is_viewed',
51
		'wpinv_email_cc'           => 'email_cc',
52
		'wpinv_template'           => 'template',
53
	);
54
55
	/**
56
	 * A map of database fields to data props.
57
	 *
58
	 * @since 1.0.19
59
	 *
60
	 * @var array
61
	 */
62
	protected $database_fields_to_props = array(
63
		'post_id'            => 'id',
64
		'number'             => 'number',
65
		'currency'           => 'currency',
66
		'key'                => 'key',
67
		'type'               => 'type',
68
		'mode'               => 'mode',
69
		'user_ip'            => 'user_ip',
70
		'first_name'         => 'first_name',
71
		'last_name'          => 'last_name',
72
		'address'            => 'address',
73
		'city'               => 'city',
74
		'state'              => 'state',
75
		'country'            => 'country',
76
		'zip'                => 'zip',
77
		'zip'                => 'zip',
78
		'adddress_confirmed' => 'address_confirmed',
79
		'gateway'            => 'gateway',
80
		'transaction_id'     => 'transaction_id',
81
		'currency'           => 'currency',
82
		'subtotal'           => 'subtotal',
83
		'tax'                => 'total_tax',
84
		'fees_total'         => 'total_fees',
85
		'discount'           => 'total_discount',
86
		'total'              => 'total',
87
		'discount_code'      => 'discount_code',
88
		'disable_taxes'      => 'disable_taxes',
89
		'due_date'           => 'due_date',
90
		'completed_date'     => 'completed_date',
91
		'company'            => 'company',
92
		'vat_number'         => 'vat_number',
93
		'vat_rate'           => 'vat_rate',
94
	);
95
96
	/*
97
	|--------------------------------------------------------------------------
98
	| CRUD Methods
99
	|--------------------------------------------------------------------------
100
	*/
101
	/**
102
	 * Method to create a new invoice in the database.
103
	 *
104
	 * @param WPInv_Invoice $invoice Invoice object.
105
	 */
106
	public function create( &$invoice ) {
107
		$invoice->set_version( WPINV_VERSION );
108
		$invoice->set_date_created( current_time('mysql') );
109
110
		// Ensure both the key and number are set.
111
		$invoice->get_key();
112
		$invoice->get_number();
113
114
		// Create a new post.
115
		$id = wp_insert_post(
116
			apply_filters(
117
				'getpaid_new_invoice_data',
118
				array(
119
					'post_date'     => $invoice->get_date_created( 'edit' ),
120
					'post_type'     => $invoice->get_post_type( 'edit' ),
121
					'post_status'   => $this->get_post_status( $invoice ),
122
					'ping_status'   => 'closed',
123
					'post_author'   => $invoice->get_user_id( 'edit' ),
124
					'post_title'    => $invoice->get_number( 'edit' ),
125
					'post_excerpt'  => $invoice->get_description( 'edit' ),
126
					'post_parent'   => $invoice->get_parent_id( 'edit' ),
127
					'post_name'     => $invoice->get_path( 'edit' ),
128
				)
129
			),
130
			true
131
		);
132
133
		if ( $id && ! is_wp_error( $id ) ) {
134
			$invoice->set_id( $id );
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type WP_Error; however, parameter $id of GetPaid_Data::set_id() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
			$invoice->set_id( /** @scrutinizer ignore-type */ $id );
Loading history...
135
			getpaid_save_invoice_user_address( $invoice );
136
			$this->save_special_fields( $invoice );
137
			$this->save_items( $invoice );
138
			$this->update_post_meta( $invoice );
139
			$invoice->save_meta_data();
140
			$invoice->apply_changes();
141
			$this->clear_caches( $invoice );
142
			do_action( 'getpaid_new_' . $invoice->get_type(), $invoice->get_id(), $invoice );
143
			return true;
144
		}
145
146
		if ( is_wp_error( $id ) ) {
147
			$invoice->last_error = $id->get_error_message();
148
		}
149
150
		return false;
151
	}
152
153
	/**
154
	 * Method to read an invoice from the database.
155
	 *
156
	 * @param WPInv_Invoice $invoice Invoice object.
157
	 *
158
	 */
159
	public function read( &$invoice ) {
160
161
		$invoice->set_defaults();
162
		$invoice_object = get_post( $invoice->get_id() );
163
164
		if ( ! $invoice->get_id() || ! $invoice_object || ! getpaid_is_invoice_post_type( $invoice_object->post_type ) ) {
165
			$invoice->last_error = __( 'Invalid invoice.', 'invoicing' );
166
			$invoice->set_id( 0 );
167
			return false;
168
		}
169
170
		$invoice->set_props(
171
			array(
172
				'date_created'  => 0 < $invoice_object->post_date ? $invoice_object->post_date : null,
173
				'date_modified' => 0 < $invoice_object->post_modified ? $invoice_object->post_modified : null,
174
				'status'        => $invoice_object->post_status,
175
				'author'        => $invoice_object->post_author,
176
				'description'   => $invoice_object->post_excerpt,
177
				'parent_id'     => $invoice_object->post_parent,
178
				'name'          => $invoice_object->post_title,
179
				'path'          => $invoice_object->post_name,
180
				'post_type'     => $invoice_object->post_type,
181
			)
182
		);
183
184
		$invoice->set_type( $invoice_object->post_type );
185
186
		$this->read_object_data( $invoice, $invoice_object );
187
		$this->add_special_fields( $invoice );
188
		$this->add_items( $invoice );
189
		$invoice->read_meta_data();
190
		$invoice->set_object_read( true );
191
		do_action( 'getpaid_read_' . $invoice->get_type(), $invoice->get_id(), $invoice );
192
193
	}
194
195
	/**
196
	 * Method to update an invoice in the database.
197
	 *
198
	 * @param WPInv_Invoice $invoice Invoice object.
199
	 */
200
	public function update( &$invoice ) {
201
		$invoice->save_meta_data();
202
		$invoice->set_version( WPINV_VERSION );
203
204
		if ( null === $invoice->get_date_created( 'edit' ) ) {
0 ignored issues
show
introduced by
The condition null === $invoice->get_date_created('edit') is always false.
Loading history...
205
			$invoice->set_date_created(  current_time('mysql') );
206
		}
207
208
		// Ensure both the key and number are set.
209
		$invoice->get_key();
210
		$invoice->get_number();
211
212
		// Grab the current status so we can compare.
213
		$previous_status = get_post_status( $invoice->get_id() );
214
215
		$changes = $invoice->get_changes();
216
217
		// Only update the post when the post data changes.
218
		if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'name', 'author', 'description', 'parent_id', 'post_excerpt', 'path' ), array_keys( $changes ) ) ) {
219
			$post_data = array(
220
				'post_date'         => $invoice->get_date_created( 'edit' ),
221
				'post_status'       => $invoice->get_status( 'edit' ),
222
				'post_title'        => $invoice->get_name( 'edit' ),
223
				'post_author'       => $invoice->get_user_id( 'edit' ),
224
				'post_modified'     => $invoice->get_date_modified( 'edit' ),
225
				'post_excerpt'      => $invoice->get_description( 'edit' ),
226
				'post_parent'       => $invoice->get_parent_id( 'edit' ),
227
				'post_name'         => $invoice->get_path( 'edit' ),
228
				'post_type'         => $invoice->get_post_type( 'edit' ),
229
			);
230
231
			/**
232
			 * When updating this object, to prevent infinite loops, use $wpdb
233
			 * to update data, since wp_update_post spawns more calls to the
234
			 * save_post action.
235
			 *
236
			 * This ensures hooks are fired by either WP itself (admin screen save),
237
			 * or an update purely from CRUD.
238
			 */
239
			if ( doing_action( 'save_post' ) ) {
240
				$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $invoice->get_id() ) );
241
				clean_post_cache( $invoice->get_id() );
242
			} else {
243
				wp_update_post( array_merge( array( 'ID' => $invoice->get_id() ), $post_data ) );
244
			}
245
			$invoice->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
246
		}
247
		$this->update_post_meta( $invoice );
248
		$this->save_special_fields( $invoice );
249
		$this->save_items( $invoice );
250
		$invoice->apply_changes();
251
		getpaid_save_invoice_user_address( $invoice );
252
		$this->clear_caches( $invoice );
253
254
		// Fire a hook depending on the status - this should be considered a creation if it was previously draft status.
255
		$new_status = $invoice->get_status( 'edit' );
256
257
		if ( $new_status !== $previous_status && in_array( $previous_status, array( 'new', 'auto-draft', 'draft' ), true ) ) {
258
			do_action( 'getpaid_new_' . $invoice->get_type(), $invoice->get_id(), $invoice );
259
		} else {
260
			do_action( 'getpaid_update_' . $invoice->get_type(), $invoice->get_id(), $invoice );
261
		}
262
263
	}
264
265
	/*
266
	|--------------------------------------------------------------------------
267
	| Additional Methods
268
	|--------------------------------------------------------------------------
269
	*/
270
271
	/**
272
     * Retrieves special fields and adds to the invoice.
273
	 *
274
	 * @param WPInv_Invoice $invoice Invoice object.
275
     */
276
    public function add_special_fields( &$invoice ) {
277
		global $wpdb;
278
279
		// Maybe retrieve from the cache.
280
		$data   = wp_cache_get( $invoice->get_id(), 'getpaid_invoice_special_fields' );
281
282
		// If not found, retrieve from the db.
283
		if ( false === $data ) {
284
			$table =  $wpdb->prefix . 'getpaid_invoices';
285
286
			$data  = $wpdb->get_row(
287
				$wpdb->prepare( "SELECT * FROM $table WHERE `post_id`=%d LIMIT 1", $invoice->get_id() ),
288
				ARRAY_A
289
			);
290
291
			// Update the cache with our data
292
			wp_cache_set( $invoice->get_id(), $data, 'getpaid_invoice_special_fields' );
293
294
		}
295
296
		// Abort if the data does not exist.
297
		if ( empty( $data ) ) {
298
			return;
299
		}
300
301
		$props = array();
302
303
		foreach ( $this->database_fields_to_props as $db_field => $prop ) {
304
			
305
			if ( $db_field == 'post_id' ) {
306
				continue;
307
			}
308
309
			$props[ $prop ] = $data[ $db_field ];
310
		}
311
312
		$invoice->set_props( $props );
313
314
	}
315
316
	/**
317
	 * Gets a list of special fields that need updated based on change state
318
	 * or if they are present in the database or not.
319
	 *
320
	 * @param  WPInv_Invoice $invoice       The Invoice object.
321
	 * @return array                        A mapping of field keys => prop names, filtered by ones that should be updated.
322
	 */
323
	protected function get_special_fields_to_update( $invoice ) {
324
		$fields_to_update = array();
325
		$changed_props   = $invoice->get_changes();
326
327
		// Props should be updated if they are a part of the $changed array or don't exist yet.
328
		foreach ( $this->database_fields_to_props as $database_field => $prop ) {
329
			if ( array_key_exists( $prop, $changed_props ) ) {
330
				$fields_to_update[ $database_field ] = $prop;
331
			}
332
		}
333
334
		return $fields_to_update;
335
	}
336
337
	/**
338
	 * Helper method that updates all the database fields for an invoice based on it's settings in the WPInv_Invoice class.
339
	 *
340
	 * @param WPInv_Invoice $invoice WPInv_Invoice object.
341
	 * @since 1.0.19
342
	 */
343
	protected function update_special_fields( &$invoice ) {
344
		global $wpdb;
345
346
		$updated_props    = array();
347
		$fields_to_update = $this->get_special_fields_to_update( $invoice );
348
349
		foreach ( $fields_to_update as $database_field => $prop ) {
350
			$value = $invoice->{"get_$prop"}( 'edit' );
351
			$value = is_string( $value ) ? wp_slash( $value ) : $value;
352
			$value = is_bool( $value ) ? ( int ) $value : $value;
353
			$updated_props[ $database_field ] = maybe_serialize( $value );
354
		}
355
356
		if ( ! empty( $updated_props ) ) {
357
358
			$table = $wpdb->prefix . 'getpaid_invoices';
359
			$wpdb->update( $table, $updated_props, array( 'post_id' => $invoice->get_id() ) );
360
			wp_cache_delete( $invoice->get_id(), 'getpaid_invoice_special_fields' );
361
			do_action( "getpaid_invoice_update_database_fields", $invoice, $updated_props );
362
363
		}
364
365
	}
366
367
	/**
368
	 * Helper method that inserts special fields to the database.
369
	 *
370
	 * @param WPInv_Invoice $invoice WPInv_Invoice object.
371
	 * @since 1.0.19
372
	 */
373
	protected function insert_special_fields( &$invoice ) {
374
		global $wpdb;
375
376
		$updated_props   = array();
377
378
		foreach ( $this->database_fields_to_props as $database_field => $prop ) {
379
			$value = $invoice->{"get_$prop"}( 'edit' );
380
			$value = is_string( $value ) ? wp_slash( $value ) : $value;
381
			$value = is_bool( $value ) ? ( int ) $value : $value;
382
			$updated_props[ $database_field ] = maybe_serialize( $value );
383
		}
384
385
		$table = $wpdb->prefix . 'getpaid_invoices';
386
		$wpdb->insert( $table, $updated_props );
387
		wp_cache_delete( $invoice->get_id(), 'getpaid_invoice_special_fields' );
388
		do_action( "getpaid_invoice_insert_database_fields", $invoice, $updated_props );
389
390
	}
391
392
	/**
393
     * Saves all special fields.
394
	 *
395
	 * @param WPInv_Invoice $invoice Invoice object.
396
     */
397
    public function save_special_fields( $invoice ) {
398
		global $wpdb;
399
400
		// The invoices table.
401
		$table = $wpdb->prefix . 'getpaid_invoices';
402
		$id    = (int) $invoice->get_id();
403
404
		if ( $wpdb->get_var( "SELECT `post_id` FROM $table WHERE `post_id`= $id" ) ) {
405
406
			$this->update_special_fields( $invoice );
407
408
		} else {
409
410
			$this->insert_special_fields( $invoice );
411
412
		}
413
414
	}
415
416
	/**
417
     * Set's up cart details.
418
	 *
419
	 * @param WPInv_Invoice $invoice Invoice object.
420
     */
421
    public function add_items( &$invoice ) {
422
		global $wpdb;
423
424
		// Maybe retrieve from the cache.
425
		$items = wp_cache_get( $invoice->get_id(), 'getpaid_invoice_cart_details' );
426
427
		// If not found, retrieve from the db.
428
		if ( false === $items ) {
429
			$table =  $wpdb->prefix . 'getpaid_invoice_items';
430
431
			$items = $wpdb->get_results(
432
				$wpdb->prepare( "SELECT * FROM $table WHERE `post_id`=%d", $invoice->get_id() )
433
			);
434
435
			// Update the cache with our data
436
			wp_cache_set( $invoice->get_id(), $items, 'getpaid_invoice_cart_details' );
437
438
		}
439
440
		// Abort if no items found.
441
        if ( empty( $items ) ) {
442
            return;
443
		}
444
445
		foreach ( $items as $item_data ) {
446
			$item = new GetPaid_Form_Item( $item_data->item_id );
447
448
			// Set item data.
449
			$item->item_tax      = wpinv_sanitize_amount( $item_data->tax );
0 ignored issues
show
Documentation Bug introduced by
It seems like wpinv_sanitize_amount($item_data->tax) can also be of type string. However, the property $item_tax is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
450
			$item->item_discount = wpinv_sanitize_amount( $item_data->discount );
0 ignored issues
show
Documentation Bug introduced by
It seems like wpinv_sanitize_amount($item_data->discount) can also be of type string. However, the property $item_discount is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
451
			$item->set_name( $item_data->item_name );
452
			$item->set_description( $item_data->item_description );
453
			$item->set_price( $item_data->item_price );
454
			$item->set_quantity( $item_data->quantity );
455
			$item->set_item_meta( $item_data->meta );
456
457
			$invoice->add_item( $item );
458
		}
459
460
	}
461
462
	/**
463
     * Saves cart details.
464
	 *
465
	 * @param WPInv_Invoice $invoice Invoice object.
466
     */
467
    public function save_items( $invoice ) {
468
469
		// Delete previously existing items.
470
		$this->delete_items( $invoice );
471
472
		$table   =  $GLOBALS['wpdb']->prefix . 'getpaid_invoice_items';
473
474
		foreach ( $invoice->get_cart_details() as $item_data ) {
475
			$item_data = array_map( 'maybe_serialize', $item_data );
476
			$GLOBALS['wpdb']->insert( $table, $item_data );
477
		}
478
479
		wp_cache_delete( $invoice->get_id(), 'getpaid_invoice_cart_details' );
480
		do_action( "getpaid_invoice_save_items", $invoice );
481
482
	}
483
484
	/**
485
     * Deletes an invoice's cart details from the database.
486
	 *
487
	 * @param WPInv_Invoice $invoice Invoice object.
488
     */
489
    public function delete_items( $invoice ) {
490
		$table =  $GLOBALS['wpdb']->prefix . 'getpaid_invoice_items';
491
		return $GLOBALS['wpdb']->delete( $table, array( 'post_id' => $invoice->get_id() ) );
492
	}
493
	
494
	/**
495
     * Deletes an invoice's special fields from the database.
496
	 *
497
	 * @param WPInv_Invoice $invoice Invoice object.
498
     */
499
    public function delete_special_fields( $invoice ) {
500
		$table =  $GLOBALS['wpdb']->prefix . 'getpaid_invoices';
501
		return $GLOBALS['wpdb']->delete( $table, array( 'post_id' => $invoice->get_id() ) );
502
    }
503
504
}
505