Passed
Pull Request — master (#377)
by Brian
05:21
created

GetPaid_Invoice_Data_Store::save_items()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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