Passed
Pull Request — master (#377)
by Brian
06:57
created

GetPaid_Invoice_Data_Store::read()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 29
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 29
rs 8.9617
cc 6
nc 2
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
	);
34
35
	/**
36
	 * A map of meta keys to data props.
37
	 *
38
	 * @since 1.0.19
39
	 *
40
	 * @var array
41
	 */
42
	protected $meta_key_to_props = array(
43
		'_wpinv_subscr_profile_id' => 'subscription_id',
44
		'_wpinv_taxes'             => 'taxes',
45
		'_wpinv_fees'              => 'fees',
46
		'_wpinv_discounts'         => 'discounts',
47
		'_wpinv_submission_id'     => 'submission_id',
48
		'_wpinv_payment_form'      => 'payment_form',
49
		'_wpinv_is_viewed'         => 'is_viewed',
50
		'wpinv_email_cc'           => 'email_cc',
51
	);
52
53
	/*
54
	|--------------------------------------------------------------------------
55
	| CRUD Methods
56
	|--------------------------------------------------------------------------
57
	*/
58
	/**
59
	 * Method to create a new invoice in the database.
60
	 *
61
	 * @param WPInv_Invoice $invoice Invoice object.
62
	 */
63
	public function create( &$invoice ) {
64
		$invoice->set_version( WPINV_VERSION );
65
		$invoice->set_date_created( current_time('mysql') );
66
67
		// Create a new post.
68
		$id = wp_insert_post(
69
			apply_filters(
70
				'getpaid_new_invoice_data',
71
				array(
72
					'post_date'     => $invoice->get_date_created( 'edit' ),
73
					'post_type'     => $invoice->get_post_type( 'edit' ),
74
					'post_status'   => $this->get_post_status( $invoice ),
75
					'ping_status'   => 'closed',
76
					'post_author'   => $invoice->get_user_id( 'edit' ),
77
					'post_title'    => $invoice->get_number( 'edit' ),
78
					'post_excerpt'  => $invoice->get_description( 'edit' ),
79
					'post_parent'   => $invoice->get_parent_id( 'edit' ),
80
					'post_name'     => $invoice->get_path( 'edit' ),
81
				)
82
			),
83
			true
84
		);
85
86
		if ( $id && ! is_wp_error( $id ) ) {
87
			$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

87
			$invoice->set_id( /** @scrutinizer ignore-type */ $id );
Loading history...
88
			$this->save_special_fields( $invoice );
89
			$this->update_post_meta( $invoice );
90
			$invoice->save_meta_data();
91
			$invoice->apply_changes();
92
			$this->clear_caches( $invoice );
93
			do_action( 'getpaid_new_' . $invoice->get_type(), $invoice->get_id(), $invoice );
94
			return true;
95
		}
96
97
		if ( is_wp_error( $id ) ) {
98
			$invoice->last_error = $id->get_error_message();
99
		}
100
101
		return false;
102
	}
103
104
	/**
105
	 * Method to read an invoice from the database.
106
	 *
107
	 * @param WPInv_Invoice $invoice Invoice object.
108
	 *
109
	 */
110
	public function read( &$invoice ) {
111
112
		$invoice->set_defaults();
113
		$invoice_object = get_post( $invoice->get_id() );
114
115
		if ( ! $invoice->get_id() || ! $invoice_object || ! getpaid_is_invoice_post_type( $invoice_object->post_type ) ) {
116
			$invoice->last_error = __( 'Invalid invoice.', 'invoicing' );
117
			return false;
118
		}
119
120
		$invoice->set_props(
121
			array(
122
				'date_created'  => 0 < $invoice_object->post_date ? $invoice_object->post_date : null,
123
				'date_modified' => 0 < $invoice_object->post_modified ? $invoice_object->post_modified : null,
124
				'status'        => $invoice_object->post_status,
125
				'author'        => $invoice_object->post_author,
126
				'description'   => $invoice_object->post_excerpt,
127
				'parent_id'     => $invoice_object->post_parent,
128
				'name'          => $invoice_object->post_title,
129
				'path'          => $invoice_object->post_name,
130
				'post_type'     => $invoice_object->post_type,
131
			)
132
		);
133
134
		$this->read_object_data( $invoice, $invoice_object );
135
		$this->add_special_fields( $invoice );
136
		$invoice->read_meta_data();
137
		$invoice->set_object_read( true );
138
		do_action( 'getpaid_read_' . $invoice->get_type(), $invoice->get_id(), $invoice );
139
140
	}
141
142
	/**
143
	 * Method to update an invoice in the database.
144
	 *
145
	 * @param WPInv_Invoice $invoice Invoice object.
146
	 */
147
	public function update( &$invoice ) {
148
		$invoice->save_meta_data();
149
		$invoice->set_version( WPINV_VERSION );
150
151
		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...
152
			$invoice->set_date_created(  current_time('mysql') );
153
		}
154
155
		// Grab the current status so we can compare.
156
		$previous_status = get_post_status( $invoice->get_id() );
157
158
		$changes = $invoice->get_changes();
159
160
		// Only update the post when the post data changes.
161
		if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'name', 'author', 'description', 'parent_id', 'post_excerpt', 'path' ), array_keys( $changes ) ) ) {
162
			$post_data = array(
163
				'post_date'         => $invoice->get_date_created( 'edit' ),
164
				'post_status'       => $invoice->get_status( 'edit' ),
165
				'post_title'        => $invoice->get_name( 'edit' ),
166
				'post_author'       => $invoice->get_user_id( 'edit' ),
167
				'post_modified'     => $invoice->get_date_modified( 'edit' ),
168
				'post_excerpt'      => $invoice->get_description( 'edit' ),
169
				'post_parent'       => $invoice->get_parent_id( 'edit' ),
170
				'post_name'         => $invoice->get_path( 'edit' ),
171
				'post_type'         => $invoice->get_post_type( 'edit' ),
172
			);
173
174
			/**
175
			 * When updating this object, to prevent infinite loops, use $wpdb
176
			 * to update data, since wp_update_post spawns more calls to the
177
			 * save_post action.
178
			 *
179
			 * This ensures hooks are fired by either WP itself (admin screen save),
180
			 * or an update purely from CRUD.
181
			 */
182
			if ( doing_action( 'save_post' ) ) {
183
				$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $invoice->get_id() ) );
184
				clean_post_cache( $invoice->get_id() );
185
			} else {
186
				wp_update_post( array_merge( array( 'ID' => $invoice->get_id() ), $post_data ) );
187
			}
188
			$invoice->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
189
		}
190
		$this->update_post_meta( $invoice );
191
		$this->save_special_fields( $invoice );
192
		$invoice->apply_changes();
193
		$this->clear_caches( $invoice );
194
195
		// Fire a hook depending on the status - this should be considered a creation if it was previously draft status.
196
		$new_status = $invoice->get_status( 'edit' );
197
198
		if ( $new_status !== $previous_status && in_array( $previous_status, array( 'new', 'auto-draft', 'draft' ), true ) ) {
199
			do_action( 'getpaid_new_' . $invoice->get_type(), $invoice->get_id(), $invoice );
200
		} else {
201
			do_action( 'getpaid_update_' . $invoice->get_type(), $invoice->get_id(), $invoice );
202
		}
203
204
	}
205
206
	/*
207
	|--------------------------------------------------------------------------
208
	| Additional Methods
209
	|--------------------------------------------------------------------------
210
	*/
211
212
	/**
213
     * Retrieves special fields and adds to the invoice.
214
	 *
215
	 * @param WPInv_Invoice $invoice Invoice object.
216
     */
217
    public function add_special_fields( &$invoice ) {
218
		global $wpdb;
219
220
		// Maybe retrieve from the cache.
221
		$data   = wp_cache_get( $invoice->get_id(), 'getpaid_invoice_special_fields' );
222
		$cached = true;
223
224
		// If not found, retrieve from the db.
225
		if ( empty( $data ) ) {
226
			$table =  $wpdb->prefix . 'getpaid_invoices';
227
228
			$data  = $wpdb->get_row(
229
				$wpdb->prepare( "SELECT * FROM $table WHERE `post_id`=%d LIMIT 1", $invoice->get_id() ),
230
				ARRAY_A
231
			);
232
233
			$cached = false;
234
		}
235
236
		// Abort if the data does not exist.
237
		if ( empty( $data ) ) {
238
			return;
239
		}
240
241
		// Update the cache with our data
242
		if ( ! $cached ) {
243
			wp_cache_set( $invoice->get_id(), $data, 'getpaid_invoice_special_fields' );
244
		}
245
246
		$invoice->set_props( $data );
247
248
	}
249
250
	/**
251
	 * Gets a list of special fields that need updated based on change state
252
	 * or if they are present in the database or not.
253
	 *
254
	 * @param  WPInv_Invoice $invoice       The Invoice object.
255
	 * @param  array   $meta_key_to_props   A mapping of prop => value.
256
	 * @return array                        A mapping of field keys => prop names, filtered by ones that should be updated.
257
	 */
258
	protected function get_special_fields_to_update( $invoice, $special_fields ) {
259
		$props_to_update = array();
260
		$changed_props   = $invoice->get_changes();
261
262
		// Props should be updated if they are a part of the $changed array or don't exist yet.
263
		foreach ( $special_fields as $prop => $value ) {
264
			if ( array_key_exists( $prop, $changed_props ) ) {
265
				$props_to_update[ $prop ] = $value;
266
			}
267
		}
268
269
		return $props_to_update;
270
	}
271
272
	/**
273
     * Saves all special fields.
274
	 *
275
	 * @param WPInv_Invoice $invoice Invoice object.
276
     */
277
    public function save_special_fields( $invoice ) {
278
		global $wpdb;
279
280
		// Fields to update.
281
		$fields = array (
282
            'post_id'        => $invoice->get_id(),
283
            'number'         => $invoice->get_number( 'edit' ),
284
            'key'            => $invoice->get_key( 'edit' ),
285
            'type'           => $invoice->get_type( 'edit' ),
286
            'mode'           => $invoice->get_mode( 'edit' ),
287
            'user_ip'        => $invoice->get_user_ip( 'edit' ),
288
            'first_name'     => $invoice->get_first_name( 'edit' ),
289
            'last_name'      => $invoice->get_last_name( 'edit' ),
290
            'address'        => $invoice->get_address( 'edit' ),
291
            'city'           => $invoice->get_city( 'edit' ),
292
            'state'          => $invoice->get_state( 'edit' ),
293
            'country'        => $invoice->get_country( 'edit' ),
294
            'zip'            => $invoice->get_zip( 'edit' ),
295
            'address_confirmed' => (int) $invoice->get_address_confirmed( 'edit' ),
296
            'gateway'        => $invoice->get_gateway( 'edit' ),
297
            'transaction_id' => $invoice->get_transaction_id( 'edit' ),
298
            'currency'       => $invoice->get_currency( 'edit' ),
299
            'subtotal'       => $invoice->get_subtotal( 'edit' ),
300
            'total_tax'      => $invoice->get_total_tax( 'edit' ),
301
            'total_fees'     => $invoice->get_total_fees( 'edit' ),
302
            'total_discount' => $invoice->get_total_discount( 'edit' ),
303
            'discount_code'  => $invoice->get_discount_code( 'edit' ),
304
            'disable_taxes'  => (int) $invoice->get_disable_taxes( 'edit' ),
305
            'due_date'       => $invoice->get_due_date( 'edit' ),
306
            'completed_date' => $invoice->get_completed_date( 'edit' ),
307
            'company'        => $invoice->get_company( 'edit' ),
308
            'vat_number'     => $invoice->get_vat_number( 'edit' ),
309
            'vat_rate'       => $invoice->get_vat_rate( 'edit' ),
310
		);
311
312
		// Update the cache with our data
313
		wp_cache_set( $invoice->get_id(), $fields, 'getpaid_invoice_special_fields' );
314
315
		// The invoices table.
316
		$table = $wpdb->prefix . 'getpaid_invoices';
317
		$id    = (int) $invoice->get_id();
318
		if ( $wpdb->get_var( "SELECT `post_id` FROM $table WHERE `post_id`= $id" ) ) {
319
320
			$to_update = $this->get_special_fields_to_update( $invoice, $fields );
321
322
			if ( empty( $to_update ) ) {
323
				return;
324
			}
325
326
			$changes = array(
327
				'tax'                => 'total_tax',
328
				'fees_total'         => 'total_fees',
329
				'discount'           => 'total_discount',
330
				'adddress_confirmed' => 'address_confirmed',
331
			);
332
333
			foreach ( $changes as $to => $from ) {
334
				if ( isset( $changes[ $from ] ) ) {
335
					$changes[ $to ] = $changes[ $from ];
336
					unset( $changes[ $from ] );
337
				}
338
			}
339
340
			$changes['total'] = $invoice->get_total( 'edit' );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_total() has too many arguments starting with 'edit'. ( Ignorable by Annotation )

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

340
			/** @scrutinizer ignore-call */ 
341
   $changes['total'] = $invoice->get_total( 'edit' );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
341
            $wpdb->update( $table, $fields, array( 'post_id' => $id ) );
342
343
        } else {
344
345
			$fields['tax'] = $fields['total_tax'];
346
			unset( $fields['total_tax'] );
347
348
			$fields['fees_total'] = $fields['total_fees'];
349
			unset( $fields['total_fees'] );
350
351
			$fields['discount'] = $fields['total_discount'];
352
			unset( $fields['total_discount'] );
353
354
			$fields['adddress_confirmed'] = $fields['address_confirmed'];
355
			unset( $fields['address_confirmed'] );
356
			
357
			$fields['total']   = $invoice->get_total( 'edit' );
358
			$fields['post_id'] = $id;
359
            $wpdb->insert( $table, $fields );
360
361
		}
362
363
	}
364
365
	/**
366
     * Set's up cart details.
367
	 *
368
	 * @param WPInv_Invoice $invoice Invoice object.
369
     */
370
    public function add_items( &$invoice ) {
371
		global $wpdb;
372
373
		$table =  $wpdb->prefix . 'getpaid_invoice_items';
374
        $items = $wpdb->get_results(
375
            $wpdb->prepare( "SELECT * FROM $table WHERE `post_id`=%d", $invoice->get_id() )
376
        );
377
378
        if ( empty( $items ) ) {
379
            return;
380
		}
381
382
		foreach ( $items as $item_data ) {
383
			$item = new GetPaid_Form_Item( $item_data->item_id );
384
385
			// Set item data.
386
			$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...
387
			$item->item_discount = 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_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...
388
			$item->set_name( $item_data->item_name );
389
			$item->set_description( $item_data->item_description );
390
			$item->set_price( $item_data->item_price );
391
			$item->set_quantity( $item_data->quantity );
392
393
			$invoice->add_item( $item );
394
		}
395
396
	}
397
398
	/**
399
     * Saves cart details.
400
	 *
401
	 * @param WPInv_Invoice $invoice Invoice object.
402
     */
403
    public function save_items( $invoice ) {
404
405
		// Delete previously existing items.
406
		$this->delete_items( $invoice );
407
408
		$table   =  $GLOBALS['wpdb']->prefix . 'getpaid_invoice_items';
409
        $to_save = $invoice->get_cart_details();
410
411
		foreach ( $to_save as $item_data ) {
412
			$GLOBALS['wpdb']->insert( $table, $item_data );
413
		}
414
415
	}
416
417
	/**
418
     * Deletes an invoice's cart details from the database.
419
	 *
420
	 * @param WPInv_Invoice $invoice Invoice object.
421
     */
422
    public function delete_items( $invoice ) {
423
		$table =  $GLOBALS['wpdb']->prefix . 'getpaid_invoice_items';
424
		return $GLOBALS['wpdb']->delete( $table, array( 'post_id' => $invoice->get_id() ) );
425
    }
426
427
}
428