Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 2449
0.00% covered (danger)
0.00%
0 / 44
CRAP
n/a
0 / 0
jpcrm_hide_woo_promo
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
jpcrm_hide_track_notice
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
jpcrm_hide_feature_alert
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zbs_create_email_templates
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
zbs_save_email_status
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_AJAX_logClose
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
jpcrm_set_jpcrm_transient
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_AJAX_markFeedback
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_AJAX_getCustInvs
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_removeFile
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
306
zeroBSCRM_AJAX_filterCustomers
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_AJAX_addLog
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
182
zeroBSCRM_AJAX_updateLog
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
272
zeroBSCRM_AJAX_deleteLog
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
jpcrm_ajax_pin_log
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
jpcrm_ajax_unpin_log
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
ZeroBSCRM_get_quote_template
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 1
2162
jpcrm_ajax_quote_send_email
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 1
870
ZeroBSCRM_accept_quote
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
342
zbs_send_quote_accept_email
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
zbs_lead_form_views
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
zbs_lead_form_capture
0.00% covered (danger)
0.00%
0 / 186
0.00% covered (danger)
0.00%
0 / 1
1190
zeroBSCRM_AJAX_addAlias
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_AJAX_removeAlias
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_AJAX_updateListViewColumns
0.00% covered (danger)
0.00%
0 / 114
0.00% covered (danger)
0.00%
0 / 1
380
zeroBSCRM_AJAX_listViewRetrieveData
0.00% covered (danger)
0.00%
0 / 744
0.00% covered (danger)
0.00%
0 / 1
147840
zeroBSCRM_AJAX_enactListViewBulkAction
0.00% covered (danger)
0.00%
0 / 307
0.00% covered (danger)
0.00%
0 / 1
13572
zeroBSCRM_bulkAction_enact_addTags
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_bulkAction_enact_removeTags
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_AJAX_previewSegment
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
210
zeroBSCRM_AJAX_saveSegment
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_admin_top_menu_save
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_AJAX_addTag
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_AJAX_deleteTag
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_AJAX_previewTagged
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_AJAX_saveScreenOptions
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_AJAX_listViewInlineEdit_save
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
zbs_invoice_send_invoice
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
210
zeroBSCRM_AJAX_sendInvoiceEmail_v3
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 1
812
zeroBSCRM_AJAX_sendStatement
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
110
zbs_invoice_mark_paid
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
zbs_invoice_send_test_invoice
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
210
zeroBSCRM_AJAX_getInvoice
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_ajax_mark_task_complete
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 * V1.20
6 *
7 * Copyright 2020 Automattic
8 *
9 * Date: 01/11/16
10 */
11use Automattic\JetpackCRM\Segment_Condition_Exception;
12
13/*
14======================================================
15    Breaking Checks ( stops direct access )
16    ====================================================== */
17if ( ! defined( 'ZEROBSCRM_PATH' ) ) {
18    exit( 0 );
19}
20/*
21======================================================
22    / Breaking Checks
23    ====================================================== */
24
25/*
26======================================================
27    Admin AJAX
28    ====================================================== */
29
30    add_action( 'wp_ajax_jpcrm_hide_woo_promo', 'jpcrm_hide_woo_promo' );
31function jpcrm_hide_woo_promo() {
32    if ( current_user_can( 'activate_plugins' ) ) {
33        $option = update_option( 'jpcrm_hide_woo_promo', 'hide', false );
34        wp_send_json_success( null, 200, JSON_UNESCAPED_SLASHES );
35    }
36}
37
38    add_action( 'wp_ajax_jpcrm_hide_track_notice', 'jpcrm_hide_track_notice' );
39function jpcrm_hide_track_notice() {
40    if ( current_user_can( 'activate_plugins' ) ) {
41        $option = update_option( 'jpcrm_hide_track_notice', 'hide', false );
42        wp_send_json_success( null, 200, JSON_UNESCAPED_SLASHES );
43    }
44}
45
46    add_action( 'wp_ajax_jpcrm_hide_feature_alert', 'jpcrm_hide_feature_alert' );
47function jpcrm_hide_feature_alert() {
48    if ( current_user_can( 'activate_plugins' ) && isset( $_POST['feature_alert'] ) ) {
49        $option = 'jpcrm_hide_' . sanitize_text_field( $_POST['feature_alert'] );
50        update_option( $option, true, false );
51        wp_send_json_success( null, 200, JSON_UNESCAPED_SLASHES );
52    }
53}
54
55    // AJAX email template population (as backup)
56    add_action( 'wp_ajax_zbs_create_email_templates', 'zbs_create_email_templates' );
57function zbs_create_email_templates() {
58    check_ajax_referer( 'zbs_create_email_nonce', 'security' );
59    // } only allow admin to do this?
60    $m = array();
61    if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
62        zeroBSCRM_checkTablesExist();
63        zeroBSCRM_populateEmailTemplateList();
64        $m['message'] = 'emails created';
65    } else {
66        $m['message'] = 'no permissions';
67    }
68    wp_send_json( $m, 200, JSON_UNESCAPED_SLASHES );
69}
70
71    // save email template
72    add_action( 'wp_ajax_zbs_save_email_status', 'zbs_save_email_status' );
73function zbs_save_email_status() {
74
75    $m = array();
76
77    global $wpdb, $ZBSCRM_t;
78
79    // } nonce..
80    check_ajax_referer( 'zbs-save-email_active', 'security' );
81    if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
82        // our variables
83        $the_id = (int) sanitize_text_field( $_POST['id'] );
84        $a_or_i = sanitize_text_field( $_POST['status'] );
85
86        // the emails are $ZBSCRM_t['system_mail_templates']
87
88        if ( $a_or_i == 'a' ) {
89            // turning active
90
91            if ( $wpdb->update(
92                $ZBSCRM_t['system_mail_templates'],
93                array(
94                    'zbsmail_active'      => 1,
95                    'zbsmail_lastupdated' => time(),
96                ),
97                array( // where
98                    'zbsmail_id' => $the_id,
99                ),
100                array(
101                    '%d',    // zbs_site
102                    '%d',    // zbs_team
103                ),
104                array(
105                    '%d',
106                )
107            ) !== false ) {
108
109                $m['message'] = 'success turned active';
110                $m['id']      = $the_id;
111                $m['type']    = $a_or_i;
112
113            } else {
114
115                $m['message'] = 'insert failed';
116                $m['id']      = $the_id;
117                $m['type']    = $a_or_i;
118
119            }
120        } elseif ( $a_or_i == 'i' ) {
121
122            if ( $wpdb->update(
123                $ZBSCRM_t['system_mail_templates'],
124                array(
125                    'zbsmail_active'      => 0,
126                    'zbsmail_lastupdated' => time(),
127                ),
128                array( // where
129                    'zbsmail_id' => $the_id,
130                ),
131                array(
132                    '%d',    // zbs_site
133                    '%d',    // zbs_team
134                ),
135                array(
136                    '%d',
137                )
138            ) !== false ) {
139
140                $m['message'] = 'success turned inactive';
141                $m['id']      = $the_id;
142                $m['type']    = $a_or_i;
143
144            } else {
145
146                $m['message'] = 'insert failed';
147                $m['id']      = $the_id;
148                $m['type']    = $a_or_i;
149
150            }
151        }
152    } else {
153        $m['message'] = 'no perms';
154    }
155
156    wp_send_json( $m, 200, JSON_UNESCAPED_SLASHES );
157    // nonce field is zbs-save-email_active
158}
159
160    // } General App Helpers - log user closing a modal (see also zeroBSCRM_getCloseState)
161    // basically log a dismissed dialog..
162    add_action( 'wp_ajax_logclose', 'zeroBSCRM_AJAX_logClose' );
163function zeroBSCRM_AJAX_logClose() {
164
165    // } Check nonce
166    check_ajax_referer( 'zbscrmjs-glob-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
167
168    if ( zeroBSCRM_permsCustomers() ) {
169        // } This is a list of keys that can be "set"
170        // } e.g. if this is fired for "pdfinvinstall" it's saying user has X'd the "Want to install PDF invoicing? modal from Invoice builder"
171        $potentialClosers = array( 'pdfinvinstall', 'v3prep2997' );
172        $potentialKey     = '';
173        if ( isset( $_POST['closing'] ) && ! empty( $_POST['closing'] ) && in_array( $_POST['closing'], $potentialClosers ) ) {
174            $potentialKey = sanitize_text_field( $_POST['closing'] );
175        }
176
177        // } Only has one val, sets as the time...
178
179        // } Brutally add option
180        update_option( 'zbs_closers_' . $potentialKey, time(), false );
181    }
182
183    wp_send_json( array( 'fini' => 1 ), 200, JSON_UNESCAPED_SLASHES );
184}
185
186    /*
187    * set_jpcrm_transient
188    * Sets a JPCRM transient
189    */
190    add_action( 'wp_ajax_jpcrmsettransient', 'jpcrm_set_jpcrm_transient' );
191function jpcrm_set_jpcrm_transient() {
192
193    // Check Nonce
194    check_ajax_referer( 'jpcrm-set-transient-nonce', 'sec' );
195
196    // Check permissions
197    // > Backend JPCRM user or WP Admin
198    if ( zeroBSCRM_permsIsZBSUserOrAdmin() ) {
199
200        global $zbs;
201
202        // retrieve data
203        $transientKey        = '';
204        $transientValue      = '';
205        $transientExpiration = 0;
206
207        if ( isset( $_POST['transient-key'] ) && ! empty( $_POST['transient-key'] ) ) {
208
209            $transientKey = sanitize_text_field( $_POST['transient-key'] );
210
211        }
212
213        if ( isset( $_POST['transient-value'] ) && ! empty( $_POST['transient-value'] ) ) {
214
215            $transientValue = sanitize_text_field( $_POST['transient-value'] );
216
217        }
218
219        if ( isset( $_POST['transient-expiration'] ) && ! empty( $_POST['transient-expiration'] ) ) {
220
221            $transientExpiration = (int) $_POST['transient-expiration'];
222
223        }
224
225        // Check that this transient is on the "allowed list"
226        if ( ! empty( $transientKey ) && array_key_exists( $transientKey, $zbs->transients ) ) {
227
228            // within our realm, set
229            set_transient( $transientKey, $transientValue, $transientExpiration );
230
231        }
232    }
233
234    wp_send_json( array( 'fini' => 1 ), 200, JSON_UNESCAPED_SLASHES );
235}
236
237    // } Feedback
238    add_action( 'wp_ajax_markFeedback', 'zeroBSCRM_AJAX_markFeedback' );
239function zeroBSCRM_AJAX_markFeedback() {
240
241    if ( zeroBSCRM_permsCustomers() ) {
242        $feedbackVal = 'nope';
243        if ( isset( $_POST['feedbackgiven'] ) ) {
244            $feedbackVal = 'yep';
245        }
246        update_option( 'zbsfeedback', $feedbackVal, false );
247    }
248    wp_send_json( array( 'fini' => 1 ), 200, JSON_UNESCAPED_SLASHES );
249}
250
251    // } Retrieve list of invoice deets for customer ID
252    add_action( 'wp_ajax_getinvs', 'zeroBSCRM_AJAX_getCustInvs' );
253function zeroBSCRM_AJAX_getCustInvs() {
254
255    // } Check nonce
256    check_ajax_referer( 'zbscrmjs-glob-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
257
258    $ret = array();
259
260    // } If perms?
261    if ( zeroBSCRM_permsCustomers() ) {
262
263        // } Retrieve ID
264        $cID = -1;
265        if ( isset( $_POST['cid'] ) ) {
266            $cID = (int) $_POST['cid'];
267        }
268
269        if ( $cID > 0 ) {
270
271            // } Retrieve the customers invoices:
272            $ret = zeroBS_getInvoicesForCustomer( $cID, true, 100 );
273
274        }
275    }
276
277    wp_send_json( $ret, 200, JSON_UNESCAPED_SLASHES );
278}
279
280    // } Remove file
281    add_action( 'wp_ajax_delFile', 'zeroBSCRM_removeFile' );
282function zeroBSCRM_removeFile() {
283
284    // } req
285    $res    = false;
286    $errors = array();
287
288    // } Check nonce
289    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
290
291    // } Check perms
292    if (
293        ( $_POST['zbsfType'] == 'customer' && zeroBSCRM_permsCustomers() ) ||
294        ( $_POST['zbsfType'] == 'company' && zeroBSCRM_permsCustomers() ) ||
295        ( $_POST['zbsfType'] == 'quotes' && zeroBSCRM_permsQuotes() ) ||
296        ( $_POST['zbsfType'] == 'invoices' && zeroBSCRM_permsInvoices() )
297        ) {
298
299        // } Retrieve deets
300        if ( isset( $_POST['zbsDel'] ) && ! empty( $_POST['zbsDel'] ) ) {
301
302            // } Type? ID?
303            if ( isset( $_POST['zbsCID'] ) && ! empty( $_POST['zbsCID'] ) ) {
304
305                $objectID = (int) $_POST['zbsCID'];
306                $fileType = sanitize_text_field( $_POST['zbsfType'] ); // assured as checked by if above (customer, quotes, invoices)
307                $zbsDel   = sanitize_text_field( $_POST['zbsDel'] );
308
309                // } potentially csv of to-delete
310                if ( strpos( '#' . $zbsDel, ',' ) > 0 ) {
311                    $delFiles = explode( ',', $zbsDel );
312                } else {
313                    $delFiles = array( $zbsDel );
314                }
315
316                if ( count( $delFiles ) > 0 ) {
317                    foreach ( $delFiles as $delFile ) {
318
319                        $deleted = zeroBS_removeFile( $objectID, $fileType, $delFile );
320                        if ( $deleted !== true ) {
321                            $errors[] = $deleted;
322                        }
323                    }
324                }
325
326                $res = true;
327
328            }
329        }
330    }
331
332    wp_send_json(
333        array(
334            'res'    => $res,
335            'errors' => $errors,
336        ),
337        200,
338        JSON_UNESCAPED_SLASHES
339    );
340}
341
342    // } Filter customers + retrieve count
343    add_action( 'wp_ajax_filterCustomers', 'zeroBSCRM_AJAX_filterCustomers' );
344function zeroBSCRM_AJAX_filterCustomers() {
345
346    // } req
347    $res = false;
348
349    // } Check nonce
350    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
351
352    if ( ! zeroBSCRM_permsCustomers() ) {
353        wp_send_json( array( 'processed' => -1 ), 200, JSON_UNESCAPED_SLASHES );
354    }
355
356    // } Running this auto-pulls POSTED filters + finds customers
357
358        // } Apply filters - it's funky to have to force this :/
359        global $zbsCustomerFiltersInEffect;
360        $zbsCustomerFiltersInEffect = zbs_customerFiltersGetApplied();
361
362        // } Retrieve
363        $res                      = zeroBS__customerFiltersRetrieveCustomerCountAndTopCustomers();
364        $res['filters_in_effect'] = $zbsCustomerFiltersInEffect;
365
366    wp_send_json( $res, 200, JSON_UNESCAPED_SLASHES );
367}
368
369    // Add log
370    add_action( 'wp_ajax_zbsaddlog', 'zeroBSCRM_AJAX_addLog' );
371function zeroBSCRM_AJAX_addLog() {
372    // req
373    $res = -1;
374
375    // Check nonce
376    check_ajax_referer( 'zbscrmjs-ajax-nonce-logs', 'sec' );
377
378    // brutal
379    if ( ! zeroBSCRM_permsCustomers() ) {
380        wp_send_json( array( 'processed' => -1 ), 403, JSON_UNESCAPED_SLASHES );
381    }
382
383    global $zbs;
384
385    // Retrieve vars - this allows notes against ALL post types (just by id)
386    if ( ! empty( $_POST['zbsnagainstid'] ) ) {
387        $zbsNoteAgainstPostID = (int) $_POST['zbsnagainstid'];
388    }
389    if ( ! empty( $_POST['zbsntype'] ) ) {
390        $zbsNoteType = sanitize_text_field( $_POST['zbsntype'] );
391    }
392    if ( ! empty( $_POST['zbsnshortdesc'] ) ) {
393        $zbsNoteShortDesc = zeroBSCRM_preDBStr( sanitize_text_field( $_POST['zbsnshortdesc'] ) );
394    }
395
396    $zbsNoteLongDesc = '';
397    if ( ! empty( $_POST['zbsnlongdesc'] ) ) {
398
399        $zbsNoteLongDesc = zeroBSCRM_preDBStr(
400            zeroBSCRM_textProcess(
401                wp_kses( nl2br( $_POST['zbsnlongdesc'] ), $zbs->acceptable_restricted_html )
402            )
403        );
404
405    }
406
407    $zbsNoteObjType = '';
408    if ( ! empty( $_POST['zbsnobjtype'] ) ) {
409        $zbsNoteObjType = zeroBSCRM_textProcess( $_POST['zbsnobjtype'] );
410    }
411
412    // optional: logid to overwrite:
413    $zbsNoteIDtoUpdate = -1;
414    if ( ! empty( $_POST['zbsnoverwriteid'] ) ) {
415        $zbsNoteIDtoUpdate = (int) $_POST['zbsnoverwriteid'];
416    }
417
418    $pinned = empty( $_POST['pinned'] ) ? -1 : 1;
419
420    // Validate
421    if (
422        ! empty( $zbsNoteAgainstPostID ) && $zbsNoteAgainstPostID > 0 &&
423        ! empty( $zbsNoteType ) &&
424        ! empty( $zbsNoteShortDesc )
425    ) {
426
427        // Only raw checked... but proceed. (ADD or Update?) (if $zbsNoteIDtoUpdate = -1 it'll add, else it'll overwrite)
428        $res = zeroBS_addUpdateLog(
429            $zbsNoteAgainstPostID,
430            $zbsNoteIDtoUpdate,
431            -1,
432            array(
433                // Anything here will get wrapped into an array and added as the meta vals
434                'type'      => $zbsNoteType,
435                'shortdesc' => $zbsNoteShortDesc,
436                'longdesc'  => $zbsNoteLongDesc,
437                'pinned'    => $pinned,
438            ),
439            $zbsNoteObjType
440        );
441
442    }
443
444    wp_send_json( array( 'processed' => $res ), 200, JSON_UNESCAPED_SLASHES );
445}
446
447    // Update log
448    add_action( 'wp_ajax_zbsupdatelog', 'zeroBSCRM_AJAX_updateLog' );
449function zeroBSCRM_AJAX_updateLog() {
450    // req
451    $res = -1;
452
453    // Check nonce
454    check_ajax_referer( 'zbscrmjs-ajax-nonce-logs', 'sec' );
455
456    // brutal
457    if ( ! zeroBSCRM_permsLogsAddEdit() ) {
458        wp_send_json( array( 'processed' => -1 ), 403, JSON_UNESCAPED_SLASHES );
459    }
460
461    global $zbs;
462
463    // Retrieve vars - this allows notes against ALL post types (just by id)
464    if ( ! empty( $_POST['zbsnprevid'] ) ) {
465        $zbsNoteID = (int) $_POST['zbsnprevid'];
466    }
467    if ( ! empty( $_POST['zbsnagainstid'] ) ) {
468        $zbsNoteAgainstPostID = (int) $_POST['zbsnagainstid'];
469    }
470    if ( ! empty( $_POST['zbsntype'] ) ) {
471        $zbsNoteType = sanitize_text_field( $_POST['zbsntype'] );
472    }
473    if ( ! empty( $_POST['zbsnshortdesc'] ) ) {
474        $zbsNoteShortDesc = zeroBSCRM_preDBStr( sanitize_text_field( $_POST['zbsnshortdesc'] ) );
475    }
476
477    $zbsNoteLongDesc = '';
478    if ( ! empty( $_POST['zbsnlongdesc'] ) ) {
479
480        $zbsNoteLongDesc = zeroBSCRM_preDBStr(
481            zeroBSCRM_textProcess(
482                wp_kses( nl2br( $_POST['zbsnlongdesc'] ), $zbs->acceptable_restricted_html )
483            )
484        );
485
486    }
487
488    $zbsNoteObjType = '';
489    if ( ! empty( $_POST['zbsnobjtype'] ) ) {
490        $zbsNoteObjType = zeroBSCRM_textProcess( $_POST['zbsnobjtype'] );
491    }
492
493    $pinned = empty( $_POST['pinned'] ) ? -1 : 1;
494
495    // Validate
496    if (
497        ! empty( $zbsNoteID ) && $zbsNoteID > 0 &&
498        ! empty( $zbsNoteAgainstPostID ) && $zbsNoteAgainstPostID > 0 &&
499        ! empty( $zbsNoteType ) &&
500        ! empty( $zbsNoteShortDesc )
501    ) {
502
503        // Only raw checked... but proceed. (Update?) (if $zbsNoteIDtoUpdate = -1 it'll add, else it'll overwrite)
504        $newOrUpdatedLogID = zeroBS_addUpdateLog(
505            $zbsNoteAgainstPostID,
506            $zbsNoteID,
507            -1,
508            array(
509                // Anything here will get wrapped into an array and added as the meta vals
510                'type'      => $zbsNoteType,
511                'shortdesc' => $zbsNoteShortDesc,
512                'longdesc'  => $zbsNoteLongDesc,
513                'pinned'    => $pinned,
514            ),
515            $zbsNoteObjType
516        );
517
518        $res = $newOrUpdatedLogID;
519
520        // Internal Automator
521        if ( ! empty( $res ) ) {
522            zeroBSCRM_FireInternalAutomator(
523                'log.update',
524                array(
525                    'id'           => $zbsNoteID,
526                    'logagainst'   => $zbsNoteAgainstPostID,
527                    'logtype'      => $zbsNoteType,
528                    'logshortdesc' => $zbsNoteShortDesc,
529                    'loglongdesc'  => $zbsNoteLongDesc,
530                )
531            );
532        }
533    }
534
535    wp_send_json( array( 'processed' => $res ), 200, JSON_UNESCAPED_SLASHES );
536}
537
538    // } Del log
539    add_action( 'wp_ajax_zbsdellog', 'zeroBSCRM_AJAX_deleteLog' );
540function zeroBSCRM_AJAX_deleteLog() {
541    // } req
542    $res = -1;
543
544    // } Check nonce
545    check_ajax_referer( 'zbscrmjs-ajax-nonce-logs', 'sec' );
546
547    if ( ! zeroBSCRM_permsLogsDelete() ) {
548        wp_send_json( array( 'processed' => -1 ), 403, JSON_UNESCAPED_SLASHES );
549    }
550    // if (!current_user_can('edit_page', $post_id)) return;
551
552    // } Retrieve vars - this allows notes against ALL post types (just by id)
553    if ( isset( $_POST['zbsnid'] ) && ! empty( $_POST['zbsnid'] ) ) {
554        $zbsNoteID = (int) $_POST['zbsnid'];
555    }
556
557    // } Validate
558    if (
559        isset( $zbsNoteID ) &&
560        ! empty( $zbsNoteID )
561    ) {
562
563        global $zbs;
564
565        // } Brutal
566        $res = $zbs->DAL->logs->deleteLog( array( 'id' => $zbsNoteID ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
567    }
568
569    wp_send_json( array( 'processed' => $res ), 200, JSON_UNESCAPED_SLASHES );
570}
571
572    // Pin log
573    add_action( 'wp_ajax_jpcrmpinlog', 'jpcrm_ajax_pin_log' );
574function jpcrm_ajax_pin_log() {
575
576    // req
577    $res = false;
578
579    // Check nonce
580    check_ajax_referer( 'zbscrmjs-ajax-nonce-logs', 'sec' );
581
582    if ( ! zeroBSCRM_permsLogsDelete() ) {
583        wp_send_json( array( processed => false ), 403, JSON_UNESCAPED_SLASHES );
584    }
585
586    // Retrieve vars - this allows notes against ALL post types (just by id)
587    $log_id = ! empty( $_POST['zbsnid'] ) ? (int) $_POST['zbsnid'] : false;
588
589    // Basic validation check
590    if ( $log_id > 0 ) {
591
592        global $zbs;
593
594        // Brutal
595        $res = $zbs->DAL->logs->set_log_pin_status(
596            array(
597                'id'     => $log_id,
598                'pinned' => 1,
599            )
600        );
601
602    }
603
604    wp_send_json( array( 'processed' => $res ), 200, JSON_UNESCAPED_SLASHES );
605}
606
607    // Un-Pin log
608    add_action( 'wp_ajax_jpcrmunpinlog', 'jpcrm_ajax_unpin_log' );
609function jpcrm_ajax_unpin_log() {
610
611    // req
612    $res = false;
613
614    // Check nonce
615    check_ajax_referer( 'zbscrmjs-ajax-nonce-logs', 'sec' );
616
617    if ( ! zeroBSCRM_permsLogsDelete() ) {
618        wp_send_json( array( processed => false ), 403, JSON_UNESCAPED_SLASHES );
619    }
620
621    // Retrieve vars - this allows notes against ALL post types (just by id)
622    $log_id = ! empty( $_POST['zbsnid'] ) ? (int) $_POST['zbsnid'] : false;
623
624    // Basic validation check
625    if ( $log_id > 0 ) {
626
627        global $zbs;
628
629        // Brutal
630        $res = $zbs->DAL->logs->set_log_pin_status(
631            array(
632                'id'     => $log_id,
633                'pinned' => -1,
634            )
635        );
636
637    }
638
639    wp_send_json( array( 'processed' => $res ), 200, JSON_UNESCAPED_SLASHES );
640}
641
642/*
643======================================================
644    / Admin AJAX
645======================================================
646*/
647
648/*
649======================================================
650    Admin AJAX: Quote Builder
651======================================================
652*/
653
654add_action( 'wp_ajax_zbs_get_quote_template', 'ZeroBSCRM_get_quote_template' );
655function ZeroBSCRM_get_quote_template() {
656
657    // } Starting
658    $content = array();
659
660    // } Check nonce
661    check_ajax_referer( 'quo-ajax-nonce', 'security' );  // nonce..
662
663    // } brutal
664    if ( ! zeroBSCRM_permsCustomers() || ! zeroBSCRM_permsQuotes() ) {
665        wp_send_json( array( 'processed' => -1 ), 403, JSON_UNESCAPED_SLASHES );
666    }
667
668    // Retrieve deets
669    $customer_ID = -1;
670    if ( isset( $_POST['cust_id'] ) ) {
671        $customer_ID = (int) $_POST['cust_id']; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
672    }
673    $quote_template_id = -1;
674    if ( isset( $_POST['quote_type'] ) ) {
675        $quote_template_id = (int) $_POST['quote_type'];
676    }
677
678    // <DAL3
679    $quote_title = '';
680    if ( isset( $_POST['quote_title'] ) ) {
681        $quote_title = sanitize_text_field( wp_unslash( $_POST['quote_title'] ) );
682    }
683    $quote_val = '';
684    if ( isset( $_POST['quote_val'] ) ) {
685        $quote_val = sanitize_text_field( wp_unslash( $_POST['quote_val'] ) );
686    }
687    $quote_date = '';
688    if ( isset( $_POST['quote_dt'] ) ) {
689        $quote_date = sanitize_text_field( wp_unslash( $_POST['quote_dt'] ) );
690    }
691
692    $quote_notes = '';
693
694    // } needs at least customer id + template id
695    if ( $customer_ID !== -1 && $quote_template_id !== -1 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
696
697        global $zbs;
698
699        // DEBUG: print_r($_POST['quote_fields']); exit( 0 );
700        // DAL3+ takes all quote inputs into account and fills out based on these (quote_fields), not above
701        if ( isset( $_POST['quote_fields'] ) && is_array( $_POST['quote_fields'] ) ) {
702
703            // retrieve basics over above
704            if ( isset( $_POST['quote_fields']['zbscq_title'] ) && ! empty( $_POST['quote_fields']['zbscq_title'] ) ) {
705                $quote_title = sanitize_text_field( wp_unslash( $_POST['quote_fields']['zbscq_title'] ) );
706            }
707            if ( isset( $_POST['quote_fields']['zbscq_value'] ) && ! empty( $_POST['quote_fields']['zbscq_value'] ) ) {
708                $quote_val = sanitize_text_field( wp_unslash( $_POST['quote_fields']['zbscq_value'] ) );
709            }
710            if ( isset( $_POST['quote_fields']['zbscq_date'] ) && ! empty( $_POST['quote_fields']['zbscq_date'] ) ) {
711                $sanitized_date = jpcrm_date_str_to_uts( sanitize_text_field( wp_unslash( $_POST['quote_fields']['zbscq_date'] ) ) );
712                $quote_date     = jpcrm_uts_to_date_str( $sanitized_date );
713            }
714            if ( isset( $_POST['quote_fields']['zbscq_notes'] ) && ! empty( $_POST['quote_fields']['zbscq_notes'] ) ) {
715                $quote_notes = sanitize_text_field( wp_unslash( $_POST['quote_fields']['zbscq_notes'] ) );
716            }
717        }
718
719        // } Fill out rest
720        $your_biz_name  = zeroBSCRM_getSetting( 'businessname' );
721        $customerName   = zeroBS_getCustomerNameShort( $customer_ID );
722        $contact_object = zeroBS_getCustomer( $customer_ID );
723        // $customerMeta = zeroBS_getCustomerMeta($customer_ID);
724        // $fname = $customerMeta['fname'];
725        // $lname = $customerMeta['lname'];
726        $bizState = '[STATE]'; // NOT EASILY ACCESSIBLE FROM YOUR SETTINGS... suggest we add to inv settings, addr proper.
727
728        // load templater
729        $placeholder_templating = $zbs->get_templating();
730
731        // } Load template
732        $quote_template = zeroBS_getQuoteTemplate( $quote_template_id );
733
734        if ( isset( $quote_template ) && is_array( $quote_template ) && isset( $quote_template['content'] ) ) {
735
736            // if no title/value is passed at this point, but there is one seet in quote template, we should use those values
737            if ( empty( $quote_title ) && ! empty( $quote_template['title'] ) ) {
738                $quote_title = $quote_template['title'];
739            }
740            if ( empty( $quote_val ) && ! empty( $quote_template['value'] ) ) {
741                $quote_val = $quote_template['value'];
742            }
743            if ( empty( $quote_notes ) && ! empty( $quote_template['notes'] ) ) {
744                $quote_notes = $quote_template['notes'];
745            }
746
747            // catch empty pass...
748            if ( empty( $quote_title ) ) {
749                $quote_title = '[QUOTETITLE]';
750            }
751            if ( empty( $quote_val ) ) {
752                $quote_val = '[QUOTEVALUE]';
753            }
754            if ( empty( $quote_date ) ) {
755                $quote_date = jpcrm_uts_to_date_str( time(), get_option( 'date_format' ) );
756            }
757
758            // HTML is escaped just prior to the complete HTML in this function being returned
759            $working_html = wpautop( $quote_template['content'] );
760
761            // replacements
762            $replacements = $placeholder_templating->get_generic_replacements();
763
764            $replacements['quote-title']      = $quote_title;
765            $replacements['quote-value']      = zeroBSCRM_formatCurrency( $quote_val );
766            $replacements['quote-date']       = $quote_date;
767            $replacements['quote-notes']      = $quote_notes;
768            $replacements['biz-state']        = $bizState;
769            $replacements['contact-fullname'] = $customerName;
770
771            $settings = $zbs->settings->getAll();
772            if ( $settings['currency'] && $settings['currency']['strval'] ) {
773                $replacements['quote-currency'] = $settings['currency']['strval'];
774            }
775
776            // if DAL3, also replace any custom fields
777            if ( isset( $_POST['quote_fields'] ) && is_array( $_POST['quote_fields'] ) ) {
778
779                // $cF = $zbs->settings->get('customfields');
780                $cF = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_QUOTE ) );
781
782                if ( isset( $cF ) && is_array( $cF ) ) { // &&isset($cF['quotes'])
783
784                    foreach ( $cF as $k => $f ) { // ['quotes']
785
786                        // annoyingly proper key is stored in [3] ?
787                        $key = '';
788                        if ( is_array( $f ) && isset( $f[3] ) ) {
789                            $key = $f[3];
790                        }
791
792                        if ( ! empty( $key ) ) {
793
794                            $v = '';
795                            if ( isset( $_POST['quote_fields'][ 'zbscq_' . $key ] ) ) {
796                                $v = sanitize_text_field( $_POST['quote_fields'][ 'zbscq_' . $key ] );
797
798                                // Here is where we search and replace placeholders for dates with a date string and date time strings), initially checking the value is similar to that of 'yyyy-mm-dd'.
799                                if ( preg_match( '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/', $v ) ) {
800
801                                    // Additional date validation to confirm the date is valid, before processing (creating placeholder strings for searching and replacing).
802                                    $date_time = DateTime::createFromFormat( 'Y-m-d', $v );
803                                    if ( $date_time && $date_time->format( 'Y-m-d' ) === $v ) {
804
805                                        $working_html = jpcrm_process_date_variables( $v, $key, $working_html, $placeholder_str_start = '##QUOTE-' );
806
807                                    }
808                                }
809                            }
810
811                            // allow upper or lower to catch various uses
812                            $working_html = str_replace( '##QUOTE-' . strtoupper( $key ) . '##', $v, $working_html );
813                            $working_html = str_replace( '##QUOTE-' . strtolower( $key ) . '##', $v, $working_html );
814                            $working_html = str_replace( '##quote-' . strtolower( $key ) . '##', $v, $working_html );
815                        }
816                    }
817                }
818            }
819            $keys_staying_unrendered = array( 'quote-ID', 'quote-url', 'quote-created', 'quote-created_datetime_str', 'quote-created_date_str', 'quote-accepted', 'quote-accepted_datetime_str', 'quote-accepted_date_str', 'quote-lastupdated', 'quote-lastupdated_datetime_str', 'quote-lastupdated_date_str', 'quote-lastviewed', 'quote-lastviewed_datetime_str', 'quote-lastviewed_date_str' );
820            $working_html            = $placeholder_templating->replace_placeholders( array( 'global', 'contact', 'quote' ), $working_html, $replacements, array( ZBS_TYPE_CONTACT => $contact_object ), false, $keys_staying_unrendered ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
821
822            // } replace the rest (#fname, etc)
823            // WH: moved to nice filter :) $working_html = zeroBSCRM_replace_customer_placeholders($customer_ID, $working_html);
824            $working_html = apply_filters( 'zerobscrm_quote_html_generate', $working_html, $customer_ID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
825
826            // } set return
827            $content['html']           = wp_kses( $working_html, $zbs->acceptable_html );
828            $content['template_title'] = $quote_template['title'];
829            $content['template_value'] = $quote_template['value'];
830            $content['template_notes'] = $quote_template['notes'];
831
832            // } return
833            wp_send_json( $content, 200, JSON_UNESCAPED_SLASHES );
834
835        } // / if content
836
837    } // / if vars
838
839    wp_send_json( array( 'error' => 1 ), 200, JSON_UNESCAPED_SLASHES );
840}
841
842// Send a quote via email
843add_action( 'wp_ajax_jpcrm_quotes_send_quote', 'jpcrm_ajax_quote_send_email' );
844function jpcrm_ajax_quote_send_email() {
845
846    // Check nonce
847    check_ajax_referer( 'edit-nonce-quote', 'sec' );
848
849    // Check Permissions
850    if ( ! zeroBSCRM_permsCustomers() || ! zeroBSCRM_permsQuotes() ) {
851        wp_send_json( array( 'processed' => -1 ), 403, JSON_UNESCAPED_SLASHES );
852    }
853
854    // Retrive details
855    $quoteID = -1;
856    if ( isset( $_POST['qid'] ) ) {
857        $quoteID = (int) $_POST['qid'];
858    }
859    $target_email = '';
860    if ( isset( $_POST['em'] ) ) {
861        $target_email = sanitize_email( wp_unslash( $_POST['em'] ) );
862    }
863    $contactID = -1;
864    if ( isset( $_POST['cid'] ) ) {
865        $contactID = (int) $_POST['cid'];
866    }
867    $companyID = -1;
868    if ( isset( $_POST['coid'] ) ) {
869        $companyID = (int) $_POST['coid']; // track if companyID - not wired in via fronend yet, but will work
870    }
871    $attachAssignedDocs = false;
872    $attachAsPDF        = false;
873    if ( isset( $_POST['attachassoc'] ) && $_POST['attachassoc'] == 1 ) {
874        $attachAssignedDocs = true;
875    }
876    if ( isset( $_POST['attachpdf'] ) && $_POST['attachpdf'] == 1 ) {
877        $attachAsPDF = true;
878    }
879
880    // validate the email
881    if ( ! zeroBSCRM_validateEmail( $target_email ) || empty( $target_email ) ) {
882        wp_send_json_error( array( 'message' => __( 'Invalid email', 'zero-bs-crm' ) ), 400, JSON_UNESCAPED_SLASHES );
883    }
884
885    // Check id
886    if ( $quoteID == -1 ) {
887        wp_send_json_error( array( 'message' => __( 'Invalid parameters', 'zero-bs-crm' ) ), 400, JSON_UNESCAPED_SLASHES );
888    }
889
890    global $zbs;
891
892    // as of 4.0.8 no need to check if the email template is switched to active.. (always is)
893    // $active = zeroBSCRM_get_email_status(ZBSEMAIL_NEWQUOTE);
894
895    // retrieve quote
896    $quote = $zbs->DAL->quotes->getQuote(
897        $quoteID,
898        array(
899            'withLineItems'    => true,
900            'withCustomFields' => true,
901            'withAssigned'     => true,
902            'withTags'         => true,
903            'withOwner'        => true,
904            'withFiles'        => true,
905        )
906    );
907
908    // retrieve assoc records
909    // .. this would lead tracking to assign to whomever is assigned the quote, yet we pass this from front-end, arguably this makes more sense, but leaving for us to finalise contact<->company
910    // $contactID = -1;  if (is_array($quote) && isset($quote['contact']) && is_array($quote['contact']) && count($quote['contact']) > 0) $contactID = $quote['contact'][0]['id'];
911    // $companyID = -1;  if (is_array($quote) && isset($quote['company']) && is_array($quote['company']) && count($quote['company']) > 0) $companyID = $quote['company'][0]['id'];
912
913    // ==========================================================================================
914    // =================================== MAIL SENDING =========================================
915
916    // Attachments?
917    $attachments = array();
918    if ( $attachAssignedDocs ) {
919        if ( isset( $quote['files'] ) && is_array( $quote['files'] ) && count( $quote['files'] ) > 0 ) {
920
921            // cycle through files + add as attachments
922            // we pass as 2part array so they don't have their funky md5 prefixes..
923            foreach ( $quote['files'] as $file ) {
924
925                $filename = basename( $file['file'] );
926                // if in privatised system, ignore first hash in name
927                if ( isset( $file['priv'] ) ) {
928
929                    $filename = substr( $filename, strpos( $filename, '-' ) + 1 );
930                }
931
932                $attachments[] = array( $file['file'], 'x' . $filename );
933
934            }
935        }
936    }
937
938    // Attach as PDF?
939    if ( $attachAsPDF ) {
940
941        // make pdf.
942        $pdf_path = jpcrm_quote_generate_pdf( $quoteID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
943
944        // attach it
945        if ( $pdf_path !== false ) {
946
947            $attachments[] = array( $pdf_path );
948
949        }
950
951        // NOTE: for security / hygiene, we delete this PDF after email is sent
952
953    }
954
955    // generate html
956    $emailHTML = zeroBSCRM_quote_generateNotificationHTML( $quoteID, true );
957
958        // build send array
959        $mailArray = array(
960            'toEmail'     => $target_email,
961            'toName'      => '',
962            'subject'     => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_NEWQUOTE ),
963            'headers'     => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_NEWQUOTE ),
964            'body'        => $emailHTML,
965            'textbody'    => '',
966            'attachments' => $attachments,
967            'options'     => array(
968                'html' => 1,
969            ),
970        );
971
972        // track if contactID
973        if ( $contactID > 0 ) {
974
975            // senderWPID = -12 = new quote email to contact
976            $mailArray['tracking'] = array(
977                // tracking :D (auto-inserted pixel + saved in history db)
978                'emailTypeID'     => ZBSEMAIL_NEWQUOTE,
979                'targetObjID'     => $contactID,
980                'senderWPID'      => -12,
981                'associatedObjID' => $quoteID,
982            );
983
984        }
985
986        // track if companyID - not wired in via fronend yet, but will work
987        if ( $companyID > 0 ) {
988
989            // senderWPID = -17 = new quote email to company
990            $mailArray['tracking'] = array(
991                // tracking :D (auto-inserted pixel + saved in history db)
992                'emailTypeID'     => ZBSEMAIL_NEWQUOTE,
993                'targetObjID'     => $companyID,
994                'senderWPID'      => -17,
995                'associatedObjID' => $quoteID,
996            );
997
998        }
999
1000        // Sends email, including tracking, via setting stored route out, (or default if none)
1001        // and logs trcking :)
1002
1003        // discern delivery method
1004        $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_NEWQUOTE );
1005        if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
1006            $mailDeliveryMethod = -1;
1007        }
1008
1009        // send
1010        $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
1011
1012        // delete any gen'd pdf's
1013        if ( $attachAsPDF && $pdf_path !== false ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1014
1015            // delete the PDF file once it's been read (i.e. emailed)
1016            wp_delete_file( $pdf_path );
1017
1018        }
1019
1020        // =================================== / MAIL SENDING =======================================
1021        // ==========================================================================================
1022
1023        if ( $sent ) {
1024
1025            // send result
1026            wp_send_json( array( 'message' => 'sent' ), 200, JSON_UNESCAPED_SLASHES );
1027
1028        }
1029        // send err
1030        wp_send_json_error( array( 'message' => __( 'not sent', 'zero-bs-crm' ) ), 500, JSON_UNESCAPED_SLASHES );
1031}
1032
1033/**
1034* AJAX: Accept a Quote
1035* Quotes can be accepted by logged-in users or via easy-access links
1036*/
1037add_action( 'wp_ajax_nopriv_zbs_quotes_accept_quote', 'ZeroBSCRM_accept_quote' );
1038add_action( 'wp_ajax_zbs_quotes_accept_quote', 'ZeroBSCRM_accept_quote' );
1039
1040function ZeroBSCRM_accept_quote() {
1041    // } Check nonce
1042    check_ajax_referer( 'zbscrmquo-nonce', 'sec' );
1043
1044    $quoteID = isset( $_POST['zbs-quote-id'] ) ? (int) $_POST['zbs-quote-id'] : 0;
1045
1046    // } Got quote ID?
1047    if ( empty( $quoteID ) || $quoteID < 0 ) {
1048        wp_send_json_error( array( 'noparams' => 1 ), 400, JSON_UNESCAPED_SLASHES );
1049    } // / posted data
1050
1051    // If nonced & has quote id, verify user can 'accept'
1052    // .. either has quoteHASH which matches ID, (easy access)
1053    // .. or is logged in client
1054
1055    // easy access links? (hashed)
1056    $quoteHash = zeroBSCRM_getSetting( 'easyaccesslinks' ) && isset( $_POST['zbs-quote-hash'] )
1057        ? sanitize_text_field( $_POST['zbs-quote-hash'] )
1058        : '';
1059
1060    // Either easy access links are disabled or no hash is supplied
1061    if ( empty( $quoteHash ) ) {
1062        $uinfo = wp_get_current_user();
1063
1064        // validate that this has been posted by the contact associated with the quote, allow admin/staff to accept on behalf of the contact
1065        global $zbs;
1066        // Check if user has admin privileges or quote permissions; otherwise, verify contact ownership for access control.
1067        $can = zeroBSCRM_isZBSAdminOrAdmin() || zeroBSCRM_permsQuotes();
1068        if ( ! $can ) {
1069            // Require login
1070            if ( ! is_user_logged_in() ) {
1071                wp_send_json_error( array( 'access' => 1 ), 403, JSON_UNESCAPED_SLASHES );
1072            }
1073            // Resolve IDs safely
1074            $customer_id      = (int) zeroBS_getCustomerIDWithEmail( $uinfo->user_email );
1075            $quote_contact_id = (int) $zbs->DAL->quotes->getQuoteContactID( $quoteID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1076            // Both IDs must exist and match
1077            if ( $customer_id <= 0 || $quote_contact_id <= 0 || $customer_id !== $quote_contact_id ) {
1078                wp_send_json_error( array( 'access' => 1 ), 403, JSON_UNESCAPED_SLASHES );
1079            }
1080        }
1081    } else {
1082        $quote_from_hash = zeroBSCRM_quotes_getFromHash( $quoteHash );
1083        if ( ! $quote_from_hash['success'] || $quoteID !== (int) $quote_from_hash['data']['ID'] ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1084            wp_send_json_error( array( 'hash' => 1 ), 403, JSON_UNESCAPED_SLASHES );
1085        }
1086    }
1087
1088    // We can accept the quote
1089
1090    // mark quote as accepted
1091    zeroBS_markQuoteAccepted( $quoteID );
1092
1093    // Send notification to creator/owner of quote
1094    // ..if the email notification for quote acceptence is active..
1095    if ( zeroBSCRM_get_email_status( ZBSEMAIL_QUOTEACCEPTED ) ) {
1096
1097        // get owner details
1098        $quoteOwnerEmail = jpcrm_get_obj_owner_wordpress_email( $quoteID, ZBS_TYPE_QUOTE );
1099
1100        if ( ! empty( $quoteOwnerEmail ) && zeroBSCRM_validateEmail( $quoteOwnerEmail ) ) {
1101            zbs_send_quote_accept_email( $quoteID, $quoteOwnerEmail );
1102        } // / if has owner with valid email
1103
1104    } // / if email notification active
1105
1106    // success
1107    wp_send_json( array( 'success' => 1 ), 200, JSON_UNESCAPED_SLASHES );
1108}
1109
1110/*
1111======================================================
1112    / Admin AJAX: Quote Builder
1113======================================================
1114*/
1115
1116/**
1117 * Sends the notification emal to the quote owner, informing them that
1118 * the quote has been accepted.
1119 *
1120 * @param int    $quoteID The ID of the accepted quote.
1121 * @param string $quoteOwnerEmail The email address to send the
1122 *  notification to.
1123 * @return array An array of one or two elements. The first is a boolean
1124 *  showing whether the email was successfully sent. The second is any
1125 *  error messoge.
1126 */
1127function zbs_send_quote_accept_email( $quoteID, $quoteOwnerEmail ) {
1128
1129    $quoteOwnerWPID = zeroBS_getOwner( $quoteID, false, ZBS_TYPE_QUOTE );
1130
1131    // generate html
1132    $emailHTML = zeroBSCRM_quote_generateAcceptNotifHTML( $quoteID, '', true );
1133
1134    // build send array
1135    $mailArray = array(
1136        'toEmail'  => $quoteOwnerEmail,
1137        'toName'   => '',
1138        'subject'  => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_QUOTEACCEPTED ),
1139        'headers'  => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_QUOTEACCEPTED ),
1140        'body'     => $emailHTML,
1141        'textbody' => '',
1142        'options'  => array(
1143            'html' => 1,
1144        ),
1145        'tracking' => array(
1146            // tracking :D (auto-inserted pixel + saved in history db)
1147            'emailTypeID'     => ZBSEMAIL_QUOTEACCEPTED,
1148            'targetObjID'     => $quoteOwnerWPID,
1149            'senderWPID'      => -11,
1150            'associatedObjID' => $quoteID, // none
1151        ),
1152    );
1153
1154    // Sends email, including tracking, via setting stored route out, (or default if none)
1155    // and logs trcking :)
1156
1157    // discern del method
1158    $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_QUOTEACCEPTED );
1159    if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
1160        $mailDeliveryMethod = -1;
1161    }
1162
1163    // send
1164    return zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
1165}
1166
1167/*
1168======================================================
1169    Admin AJAX: Front End Forms
1170======================================================
1171*/
1172
1173function zbs_lead_form_views() {
1174
1175    global $zbs;
1176
1177    // fired via AJAX on page view (uniqued by cookie - test will send on each page refresh...)
1178    // will not have a nonce available since from another site.
1179    // only passing a form ID (which is (int) set and then updating a counter
1180    $form_id    = (int) sanitize_text_field( $_POST['id'] );
1181    $form_views = $zbs->DAL->forms->add_form_view( $form_id );
1182
1183    wp_send_json( array( 'view_logged' => 'true' ), 200, JSON_UNESCAPED_SLASHES );
1184}
1185    add_action( 'wp_ajax_nopriv_zbs_lead_form_views', 'zbs_lead_form_views' );
1186    add_action( 'wp_ajax_zbs_lead_form_views', 'zbs_lead_form_views' );
1187
1188    // } Handle form submissions interesting to see how this works cross domain...
1189function zbs_lead_form_capture() {
1190    /**
1191     * At this point, $_GET/$_POST variable are available
1192     *
1193     * We can do our normal processing here
1194     */
1195
1196    global $zbs;
1197
1198    // } Declare this...
1199    $r = array();
1200
1201    // reCaptcha check first (if present):
1202    $reCaptcha       = zeroBSCRM_getSetting( 'usegcaptcha' );
1203    $reCaptchaKey    = zeroBSCRM_getSetting( 'gcaptchasitekey' );
1204    $reCaptchaSecret = zeroBSCRM_getSetting( 'gcaptchasitesecret' );
1205
1206    if ( $reCaptcha && ! empty( $reCaptchaKey ) && ! empty( $reCaptchaSecret ) ) {
1207
1208        // } Assume fail
1209        $reCaptchaOkay = false;
1210
1211        // } Retrieve from post
1212        $possibleCaptchaResponse = '';
1213        if ( isset( $_POST['recaptcha'] ) && ! empty( $_POST['recaptcha'] ) ) {
1214            $possibleCaptchaResponse = sanitize_text_field( $_POST['recaptcha'] );
1215        }
1216
1217        // } Validate it
1218        $gSays = wp_remote_post(
1219            'https://www.google.com/recaptcha/api/siteverify',
1220            array(
1221                'method'      => 'POST',
1222                'timeout'     => 45,
1223                'redirection' => 5,
1224                'httpversion' => '1.0',
1225                'blocking'    => true,
1226                'headers'     => array(),
1227                'body'        => array(
1228                    'secret'   => $reCaptchaSecret,
1229                    'response' => $possibleCaptchaResponse,
1230                                    // not req 'remoteip' => zeroBSCRM_getRealIpAddr()
1231                ),
1232                'cookies'     => array(),
1233            )
1234        );
1235
1236        // } Should be a response json obj
1237        if ( ! empty( $gSays ) ) {
1238            // } get it
1239            $gSaysObj = json_decode( wp_remote_retrieve_body( $gSays ) );
1240
1241            if ( isset( $gSaysObj->success ) && $gSaysObj->success ) {
1242                $reCaptchaOkay = true;
1243            }
1244        }
1245
1246        // } Fail?
1247        if ( ! $reCaptchaOkay ) {
1248
1249            // } AXE IT
1250            $r['message'] = 'Nope.';
1251            $r['code']    = 'recaptcha';
1252            wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
1253
1254        }
1255    }
1256
1257    // } All need this, (if no form id, is dodgy?)
1258    $zbs_form_id = -1;
1259    if ( isset( $_POST['zbs_form_id'] ) && ! empty( $_POST['zbs_form_id'] ) ) {
1260        $zbs_form_id = (int) sanitize_text_field( $_POST['zbs_form_id'] );  // each form has an ID so we can track the conversions
1261    }
1262
1263    // } Fail?
1264    if ( empty( $zbs_form_id ) ) {
1265
1266        // } AXE IT
1267        $r['message'] = 'Nope.';
1268        $r['code']    = 'form';
1269        wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
1270
1271    }
1272
1273    // honeypot
1274    $zbs_honey = sanitize_text_field( $_POST['zbs_hpot_email'] );  // this should be blank
1275    if ( $zbs_honey != '' ) {
1276        // then this is likely a spambot who has filled in the form since its hidden from humans
1277        $r['message'] = 'This is a honeypot.. something has gone wrong can alert the member on response';
1278        $r['code']    = 'honey';
1279        wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
1280    } else {
1281
1282        // } Added here: REQUIRE email...
1283        if ( isset( $_POST['zbs_email'] ) && ! empty( $_POST['zbs_email'] ) && zeroBSCRM_validateEmail( $_POST['zbs_email'] ) ) {
1284
1285            // } Email is OKAY!
1286            // } For now do nothing here
1287
1288        } else {
1289
1290            // } AXE IT
1291            $r['message'] = 'Email Required.';
1292            $r['code']    = 'emailfail';
1293            wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
1294
1295        }
1296
1297        // do our usual processing
1298        $zbs_form_style = (string) sanitize_text_field( $_POST['zbs_form_style'] );
1299
1300        // } WH add - filter any not mentioned here
1301        if ( ! in_array( $zbs_form_style, array( 'zbs_simple', 'zbs_naked', 'zbs_cgrab' ) ) ) {
1302            $zbs_form_style = '';
1303        }
1304
1305        // } NOTE! at this point form id hasn't been validated... could be random number!
1306
1307        // } "Form x filled out from y" (will be added as note / meta)
1308
1309            // } form str
1310            $form_details = zeroBS_getForm( $zbs_form_id );
1311        if ( isset( $form_details['title'] ) ) {
1312            $formTitle = $form_details['title'] . ' (#' . $zbs_form_id . ')';
1313        } else {
1314            $formTitle = '#' . $zbs_form_id;
1315        }
1316
1317            // } pid is now passed, however it will only be passed on embed's
1318            $pageID = '';
1319        if ( isset( $_POST['pid'] ) && ! empty( $_POST['pid'] ) ) {
1320            $pageID = (int) sanitize_text_field( $_POST['pid'] );
1321        }
1322            $fromPageName = '';
1323        if ( ! empty( $pageID ) ) {
1324            $fromPageName = get_the_title( $pageID );
1325        }
1326
1327            // } Form style str
1328            $formStyle = '';
1329        if ( $zbs_form_style == 'zbs_simple' ) {
1330            $formStyle = 'Simple';
1331        }
1332        if ( $zbs_form_style == 'zbs_naked' ) {
1333            $formStyle = 'Naked';
1334        }
1335        if ( $zbs_form_style == 'zbs_cgrab' ) {
1336            $formStyle = 'Content Grab';
1337        }
1338            $formStyleStr = '';
1339        if ( ! empty( $formStyle ) ) {
1340            $formStyleStr = ' (' . $formStyle . ')';
1341        }
1342
1343            // } Could add these:
1344            // videoTNT_retrieveDom(get_bloginfo('wpurl')).' at '.date("F j, Y, g:i a")
1345            // videoTNT_getRealIpAddr()
1346
1347            // } Build str's - refactor at some point... rough first fix
1348        if ( ! empty( $pageID ) ) {
1349
1350            // } Shortcode form
1351
1352                // } Existing user signed a form
1353                $existingUserFormSourceShort = 'User completed form <i class="fa fa-wpforms"></i>';
1354                $existingUserFormSourceLong  = 'Form <span class="zbsEmphasis">' . $formTitle . '</span>' . $formStyleStr . ', which was filled out from the page: <span class="zbsEmphasis">' . $fromPageName . '</span> (#' . $pageID . ')';
1355
1356                // } New User from form
1357                $newUserFormSourceShort = 'Created from Form Capture <i class="fa fa-wpforms"></i>';
1358                $newUserFormSourceLong  = 'User created from the form <span class="zbsEmphasis">' . $formTitle . '</span>' . $formStyleStr . ', which was filled out from the page: <span class="zbsEmphasis">' . $fromPageName . '</span> (#' . $pageID . ')';
1359
1360        } else {
1361
1362            // } embed
1363
1364                // } Existing user signed a form
1365                $existingUserFormSourceShort = 'User completed form <i class="fa fa-wpforms"></i>';
1366                $existingUserFormSourceLong  = 'Form <span class="zbsEmphasis">' . $formTitle . '</span>' . $formStyleStr . ', which was filled out from an externally embedded form.';
1367
1368                // } New User from form
1369                $newUserFormSourceShort = 'Created from Form Capture <i class="fa fa-wpforms"></i>';
1370                $newUserFormSourceLong  = 'User created from the form <span class="zbsEmphasis">' . $formTitle . '</span>' . $formStyleStr . ', which was filled out from an externally embedded form.';
1371
1372        }
1373
1374            // } Actual log var passed
1375            $fallBackLog = array(
1376                'type'      => 'Form Filled', // 'form_filled',
1377                'shortdesc' => $existingUserFormSourceShort,
1378                'longdesc'  => $existingUserFormSourceLong,
1379            );
1380
1381            // } Internal automator overrides - here we pass a "customer.create" note override (so we can pass it a custom str, else we let it fall back to "created by form")
1382            $internalAutomatorOverride = array(
1383
1384                'note_override' => array(
1385
1386                    'type'      => 'Form Filled', // 'form_filled',
1387                    'shortdesc' => $newUserFormSourceShort,
1388                    'longdesc'  => $newUserFormSourceLong,
1389
1390                ),
1391
1392            );
1393
1394            // TO LATER DO:
1395            // Log above notes as meta vals... e.g. user has completed form 1, 2, and 5
1396
1397            // TO LATER DO:
1398            // COMBINE THE FOLLOWING RETRIEVES... no need to have separate input gathering...
1399
1400            switch ( $zbs_form_style ) {
1401
1402                case 'zbs_simple':
1403                    // simple just has email
1404                    $zbs_email = sanitize_email( wp_unslash( $_POST['zbs_email'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1405                    // have added a new 'form' for 'externals'
1406                    $cID = zeroBS_integrations_addOrUpdateCustomer(
1407                        'form',
1408                        $zbs_email,
1409                        array(
1410
1411                            // } Removed this, as it'll default to lead if it's not already customer!
1412                            // } re-added as temp fix... WH 18/10/16
1413                            // } changed to __() to support translation MS 06/09/19
1414                            'zbsc_status' => __( 'Lead', 'zero-bs-crm' ),
1415
1416                            'zbsc_email'  => $zbs_email,
1417                        ),
1418                        '', // ) Customer date (auto)
1419                        // } Fallback log (for customers who already exist)
1420                        $fallBackLog,
1421                        false, // } Extra meta
1422                        // } Internal automator overrides - here we pass a "customer.create" note override (so we can pass it a custom str, else we let it fall back to "created by form")
1423                        $internalAutomatorOverride
1424                    );
1425
1426                    // 2.97.7 - added this:
1427                    // if autolog for contact creation = off, still add the message to form:
1428                    $autoLogCCreation = zeroBSCRM_getSetting( 'autolog_customer_new' );
1429                    if ( $autoLogCCreation <= 0 && $cID > 0 ) {
1430
1431                        // add form log manually
1432                        $zbs->DAL->logs->addUpdateLog( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1433                            array(
1434
1435                                // fields (directly)
1436                                'data' => array(
1437
1438                                    'objtype'   => ZBS_TYPE_CONTACT,
1439                                    'objid'     => $cID,
1440                                    'type'      => zeroBSCRM_permifyLogType( 'Form Filled' ),
1441                                    'shortdesc' => __( 'Contact added via Form Submit', 'zero-bs-crm' ),
1442                                    'longdesc'  => '<blockquote>' . __( 'Contact added via Form Submit', 'zero-bs-crm' ) . '</blockquote>',
1443
1444                                ),
1445                            )
1446                        );
1447                    }
1448
1449                    break;
1450
1451                case 'zbs_naked':
1452                    // } Naked only has name + email?
1453
1454                    // validate these...  (use functions in form save down...)
1455                    $zbs_email = sanitize_email( wp_unslash( $_POST['zbs_email'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1456                    $zbs_fname = sanitize_text_field( $_POST['zbs_fname'] );
1457                    // $zbs_lname = sanitize_text_field($_POST['zbs_lname']);
1458                    // $zbs_notes = "Customer Form Submit Message:\r\n===========\r\n".sanitize_text_field($_POST['zbs_notes'])."\r\n===========\r\n";
1459
1460                    // have added a new 'form' for 'externals'
1461                    zeroBS_integrations_addOrUpdateCustomer(
1462                        'form',
1463                        $zbs_email,
1464                        array(
1465
1466                            // } Removed this, as it'll default to lead if it's not already customer!
1467                            // } re-added as temp fix... WH 18/10/16
1468                            // } changed to __() to support translation MS 06/09/19
1469                            'zbsc_status' => __( 'Lead', 'zero-bs-crm' ),
1470
1471                            'zbsc_email'  => $zbs_email,
1472                            'zbsc_fname'  => $zbs_fname,
1473                        // 'zbsc_lname' => $zbs_lname,
1474                        // 'zbsc_notes' => $zbs_notes,
1475                        ),
1476                        '', // ) Customer date (auto)
1477                        // } Fallback log (for customers who already exist)
1478                        $fallBackLog,
1479                        false, // } Extra meta
1480                        // } Internal automator overrides - here we pass a "customer.create" note override (so we can pass it a custom str, else we let it fall back to "created by form")
1481                        $internalAutomatorOverride
1482                    );
1483
1484                    break;
1485                case 'zbs_cgrab':
1486                    // validate these...  (use functions in form save down...)
1487                    $zbs_email = sanitize_email( wp_unslash( $_POST['zbs_email'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1488                    $zbs_fname = sanitize_text_field( $_POST['zbs_fname'] );
1489                    $zbs_lname = sanitize_text_field( $_POST['zbs_lname'] );
1490                    // Raw: $zbs_notes = "Customer Form Submit Message:\r\n===========\r\n".zeroBSCRM_textProcess($_POST['zbs_notes'])."\r\n===========\r\n";
1491                    // HTML:
1492                        $formMessage = zeroBSCRM_textProcess( $_POST['zbs_notes'] );
1493                        $zbs_notes   = '<blockquote>Customer Form Submit Message:<br />===========<br />' . $formMessage . '<br />===========</blockquote>';
1494
1495                        // } 27/09/16 WH - rather than pass as note field, add to log:
1496
1497                            // } for if user exists:
1498                            $fallBackLog['longdesc'] .= $zbs_notes;
1499
1500                            // } for if user is fresh:
1501                            $internalAutomatorOverride['note_override']['longdesc'] .= $zbs_notes;
1502
1503                    // have added a new 'form' for 'externals'
1504                    $cID = zeroBS_integrations_addOrUpdateCustomer(
1505                        'form',
1506                        $zbs_email,
1507                        array(
1508
1509                            // } Removed this, as it'll default to lead if it's not already customer!
1510                            // } re-added as temp fix... WH 18/10/16
1511                            // } changed to __() to support translation MS 06/09/19
1512                            'zbsc_status' => __( 'Lead', 'zero-bs-crm' ),
1513
1514                            'zbsc_email'  => $zbs_email,
1515                            'zbsc_fname'  => $zbs_fname,
1516                            'zbsc_lname'  => $zbs_lname,
1517                        // } Removed this and added to logs (just above!) 'zbsc_notes' => $zbs_notes,
1518                        ),
1519                        '', // ) Customer date (auto)
1520                        // } Fallback log (for customers who already exist)
1521                        $fallBackLog,
1522                        false, // } Extra meta
1523                        // } Internal automator overrides - here we pass a "customer.create" note override (so we can pass it a custom str, else we let it fall back to "created by form")
1524                        $internalAutomatorOverride
1525                    );
1526
1527                    // 2.97.7 - added this:
1528                    // if autolog for contact creation = off, still add the message to form:
1529                    $autoLogCCreation = zeroBSCRM_getSetting( 'autolog_customer_new' );
1530                    if ( $autoLogCCreation <= 0 ) {
1531
1532                        global $zbs;
1533
1534                        // add form log manually
1535                        $zbs->DAL->logs->addUpdateLog( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1536                            array(
1537
1538                                // fields (directly)
1539                                'data' => array(
1540
1541                                    'objtype'   => ZBS_TYPE_CONTACT,
1542                                    'objid'     => $cID,
1543                                    'type'      => zeroBSCRM_permifyLogType( 'Form Filled' ),
1544                                    'shortdesc' => $fallBackLog['shortdesc'],
1545                                    'longdesc'  => $fallBackLog['longdesc'],
1546
1547                                    'meta'      => array( 'message' => $formMessage ),
1548
1549                                ),
1550                            )
1551                        );
1552                    }
1553
1554                    break;
1555                default:
1556                    exit( 0 );  // if not one of our cases then die.
1557            }
1558
1559            // } TODO we could add some tracking here (e.g. "originated from form x on page y")
1560
1561            // update the counter for "conversions"
1562            $zbs->DAL->forms->add_form_conversion( $zbs_form_id );
1563
1564            // return
1565            $r['message'] = 'Contact received.';
1566            $r['code']    = 'success';
1567            wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
1568
1569    }
1570}
1571    add_action( 'wp_ajax_nopriv_zbs_lead_form_capture', 'zbs_lead_form_capture' );
1572    add_action( 'wp_ajax_zbs_lead_form_capture', 'zbs_lead_form_capture' );
1573
1574    /*
1575    POST ACTIONS
1576    add_action( 'admin_post_nopriv_zbs_lead_form_capture', 'zbs_lead_form_capture' );
1577    add_action( 'admin_post_zbs_lead_form_capture', 'zbs_lead_form_capture' );
1578    */
1579
1580/*
1581======================================================
1582    / Admin AJAX: Front End Forms
1583======================================================
1584*/
1585
1586/*
1587======================================================
1588    Admin AJAX: Customer Record stuff
1589======================================================
1590*/
1591
1592    // } Add/remove aliases
1593    add_action( 'wp_ajax_addAlias', 'zeroBSCRM_AJAX_addAlias' );
1594function zeroBSCRM_AJAX_addAlias() {
1595
1596    // } Check nonce
1597    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
1598
1599    // } Check perms
1600    if ( ! zeroBSCRM_permsCustomers() ) {
1601        wp_send_json( array( 'err' => 1 ), 403, JSON_UNESCAPED_SLASHES );
1602    }
1603
1604    // } Proceed :)
1605    $passback = array();
1606
1607        $custID = -1;
1608    if ( isset( $_POST['cid'] ) ) {
1609        $custID = (int) $_POST['cid'];
1610    }
1611        $alias = '';
1612    if ( isset( $_POST['aka'] ) ) {
1613        $alias = sanitize_text_field( $_POST['aka'] );
1614    }
1615
1616        // } Any good?
1617    if ( ! empty( $custID ) && ! empty( $alias ) ) {
1618
1619        // check if already exists as alias
1620        if ( zeroBS_canUseCustomerAlias( $alias ) == false ) {
1621
1622            $passback['fail'] = 'existing';
1623
1624        } else {
1625
1626            // all good, proceed
1627
1628            $passback['res'] = zeroBS_addCustomerAlias( $custID, $alias ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1629
1630            // } For now, no checks :)
1631
1632        }
1633
1634        // } Return
1635        wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1636
1637    }
1638
1639        // err really :o
1640        wp_send_json( array(), 200, JSON_UNESCAPED_SLASHES );
1641}
1642    add_action( 'wp_ajax_removeAlias', 'zeroBSCRM_AJAX_removeAlias' );
1643function zeroBSCRM_AJAX_removeAlias() {
1644
1645    // } Check nonce
1646    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
1647
1648    // } Check perms
1649    if ( ! zeroBSCRM_permsCustomers() ) {
1650        wp_send_json( array( 'err' => 1 ), 200, JSON_UNESCAPED_SLASHES );
1651    }
1652
1653    // } Proceed :)
1654    $passback = array();
1655
1656        $custID = -1;
1657    if ( isset( $_POST['cid'] ) ) {
1658        $custID = (int) $_POST['cid'];
1659    }
1660        $aliasID = -1;
1661    if ( isset( $_POST['akaid'] ) ) {
1662        $aliasID = (int) $_POST['akaid'];
1663    }
1664
1665        // } Any good?
1666    if ( ! empty( $custID ) && ! empty( $aliasID ) ) {
1667
1668        // NOTE: by passing cust + alias id's, rather than just ALIAS id, we do ANOTHER check to make sure
1669        // that user's deleting smt they mean to (this is also pre-emptive for provider-platform + ownership rights)
1670        $passback['res'] = zeroBS_removeCustomerAliasByID( $custID, $aliasID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1671
1672        // } For now, no checks :)
1673
1674            // } Return
1675            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1676
1677    }
1678
1679        // err really :o
1680        wp_send_json( array(), 200, JSON_UNESCAPED_SLASHES );
1681}
1682
1683/*
1684======================================================
1685    / Admin AJAX: Customer Record stuff
1686======================================================
1687*/
1688
1689/*
1690======================================================
1691    Admin AJAX: List View (API STYLE)
1692======================================================
1693*/
1694
1695    // } Update Columns - list view column update
1696    add_action( 'wp_ajax_updateListViewColumns', 'zeroBSCRM_AJAX_updateListViewColumns' );
1697function zeroBSCRM_AJAX_updateListViewColumns() {
1698
1699    // } Check nonce
1700    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
1701
1702    // } Check perms
1703    if ( ! zeroBSCRM_isZBSAdminOrAdmin() ) {
1704        wp_send_json( array( 'err' => 1 ), 403, JSON_UNESCAPED_SLASHES );
1705    }
1706
1707        global $zbs;
1708
1709        // } Retrieve type + columns arr
1710        $listtype    = sanitize_text_field( $_POST['listtype'] );
1711        $listColumns = $_POST['v']; // NEEDS SANITATION!
1712
1713        /*
1714        #} Centralised into ZeroBSCRM.List.Columns.php 30/7/17
1715        global $zeroBSCRM_columns_customer;
1716        $defaultColumns = $zeroBSCRM_columns_customer['default'];
1717        $allColumns = $zeroBSCRM_columns_customer['all'];
1718        */
1719        $customViews = $zbs->settings->get( 'customviews2' );
1720
1721        // } switch by type
1722    switch ( $listtype ) {
1723
1724        case 'customer':
1725            // } Brutal save over anyway..
1726
1727            // } Use existing (stores all types of custom views - not just this one)
1728            $newCustomViews = $customViews;
1729            $passback       = array();
1730
1731            // } Build
1732            $newCustomerColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1733
1734                $newCustomerColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1735                $passback[]                                = array(
1736                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1737                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1738                );
1739
1740            }
1741
1742            // } Update
1743            $newCustomViews['customer'] = $newCustomerColumns;
1744            $zbs->settings->update( 'customviews2', $newCustomViews );
1745
1746            // } Return
1747            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1748
1749            break;
1750
1751        case 'company':
1752            // } Brutal save over anyway..
1753
1754            // } Use existing (stores all types of custom views - not just this one)
1755            $newCustomViews = $customViews;
1756            $passback       = array();
1757
1758            // } Build
1759            $newCoColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1760
1761                $newCoColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1762                $passback[]                          = array(
1763                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1764                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1765                );
1766
1767            }
1768
1769            // } Update
1770            $newCustomViews['company'] = $newCoColumns;
1771            $zbs->settings->update( 'customviews2', $newCustomViews );
1772
1773            // } Return
1774            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1775
1776            break;
1777
1778        case 'quote':
1779            // } Brutal save over anyway..
1780
1781            // } Use existing (stores all types of custom views - not just this one)
1782            $newCustomViews = $customViews;
1783            $passback       = array();
1784
1785            // } Build
1786            $newQuoColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1787
1788                $newQuoColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1789                $passback[]                           = array(
1790                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1791                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1792                );
1793
1794            }
1795
1796            // } Update
1797            $newCustomViews['quote'] = $newQuoColumns;
1798            $zbs->settings->update( 'customviews2', $newCustomViews );
1799
1800            // } Return
1801            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1802
1803            break;
1804
1805        case 'invoice':
1806            // } Brutal save over anyway..
1807
1808            // } Use existing (stores all types of custom views - not just this one)
1809            $newCustomViews = $customViews;
1810            $passback       = array();
1811
1812            // } Build
1813            $newInvColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1814
1815                $newInvColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1816                $passback[]                           = array(
1817                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1818                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1819                );
1820
1821            }
1822
1823            // } Update
1824            $newCustomViews['invoice'] = $newInvColumns;
1825            $zbs->settings->update( 'customviews2', $newCustomViews );
1826
1827            // } Return
1828            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1829
1830            break;
1831
1832        case 'transaction':
1833            // } Brutal save over anyway..
1834
1835            // } Use existing (stores all types of custom views - not just this one)
1836            $newCustomViews = $customViews;
1837            $passback       = array();
1838
1839            // } Build
1840            $newTransColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1841
1842                $newTransColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1843                $passback[]                             = array(
1844                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1845                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1846                );
1847
1848            }
1849
1850            // } Update
1851            $newCustomViews['transaction'] = $newTransColumns;
1852            $zbs->settings->update( 'customviews2', $newCustomViews );
1853
1854            // } Return
1855            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1856
1857            break;
1858
1859        case 'form':
1860            // } Brutal save over anyway..
1861
1862            // } Use existing (stores all types of custom views - not just this one)
1863            $newCustomViews = $customViews;
1864            $passback       = array();
1865
1866            // } Build
1867            $newFormsColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1868
1869                $newFormsColumns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1870                $passback[]                             = array(
1871                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1872                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1873                );
1874
1875            }
1876
1877            // } Update
1878            $newCustomViews['form'] = $newFormsColumns;
1879            $zbs->settings->update( 'customviews2', $newCustomViews );
1880
1881            // } Return
1882            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1883
1884            break;
1885
1886        case 'segment':
1887            // } Brutal save over anyway..
1888
1889            // } Use existing (stores all types of custom views - not just this one)
1890            $newCustomViews = $customViews;
1891            $passback       = array();
1892
1893            // } Build
1894            $newColumns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1895
1896                $newColumns[ $colVal['fieldstr'] ] = array( $colVal['namestr'] );
1897                $passback[]                        = array(
1898                    'fieldstr' => $colVal['fieldstr'],
1899                    'namestr'  => $colVal['namestr'],
1900                );
1901
1902            }
1903
1904            // } Update
1905            $newCustomViews['segment'] = $newColumns;
1906            $zbs->settings->update( 'customviews2', $newCustomViews );
1907
1908            // } Return
1909            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1910
1911            break;
1912
1913        case 'event':
1914            // } Use existing (stores all types of custom views - not just this one)
1915            $newCustomViews = $customViews;
1916            $passback       = array();
1917
1918            // } Build
1919            $new_task_columns = array(); foreach ( $listColumns as $colKey => $colVal ) {
1920
1921                $new_task_columns[ $colVal['fieldstr'] ] = array( __( $colVal['namestr'], 'zero-bs-crm' ) );
1922                $passback[]                              = array(
1923                    'fieldstr' => __( $colVal['fieldstr'], 'zero-bs-crm' ),
1924                    'namestr'  => __( $colVal['namestr'], 'zero-bs-crm' ),
1925                );
1926
1927            }
1928
1929            // Update
1930            $newCustomViews['event'] = $new_task_columns;
1931            $zbs->settings->update( 'customviews2', $newCustomViews );
1932
1933            // } Return
1934            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
1935
1936            break;
1937
1938        default:
1939            // err really :o
1940            wp_send_json( array(), 200, JSON_UNESCAPED_SLASHES );
1941
1942            break;
1943
1944    }
1945
1946        exit( 0 );
1947}
1948
1949    // } Retrieves data sets for list views, with passed params :)
1950    add_action( 'wp_ajax_retrieveListViewData', 'zeroBSCRM_AJAX_listViewRetrieveData' );
1951function zeroBSCRM_AJAX_listViewRetrieveData() {
1952
1953    // } req
1954    $res = false;
1955
1956    // } Check nonce
1957    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
1958
1959    global $zbs;
1960
1961    // } Retrieve params
1962    $pArray = array();
1963    if ( isset( $_POST['v'] ) && is_array( $_POST['v'] ) ) {
1964        $pArray = $_POST['v'];
1965    }
1966
1967    // to properly sanitize, we hand-pass each var here, rather than trust the array :)
1968    // else defaults :)
1969    $listViewParams = array(
1970        'listtype'   => ( isset( $pArray['listtype'] ) ) ? sanitize_text_field( $pArray['listtype'] ) : '',
1971        'columns'    => array(),
1972        'editinline' => ( isset( $pArray['editinline'] ) ) ? sanitize_text_field( $pArray['editinline'] ) : '',
1973        'retrieved'  => ( isset( $pArray['retrieved'] ) ) ? false : true, // doesn't look like this is used
1974        'count'      => ( isset( $pArray['count'] ) ) ? (int) $pArray['count'] : 20,
1975        'pagination' => ( isset( $pArray['pagination'] ) ) ? sanitize_text_field( $pArray['pagination'] ) : true,
1976        'paged'      => ( isset( $pArray['paged'] ) ) ? (int) $pArray['paged'] : 1,
1977        'filters'    => array(),
1978        'sort'       => ( isset( $pArray['sort'] ) ) ? sanitize_text_field( $pArray['sort'] ) : false,
1979        'sortorder'  => ( isset( $pArray['sortorder'] ) ) ? sanitize_text_field( $pArray['sortorder'] ) : false,
1980        'pagekey'    => ( isset( $pArray['pagekey'] ) ) ? sanitize_text_field( $pArray['pagekey'] ) : '',
1981    );
1982
1983    // Security: validate sort field is a safe identifier to prevent SQL injection via ORDER BY.
1984    // Hyphens are allowed because custom field slugs use them (e.g. "new-field", "forced-numeric").
1985    if ( ! empty( $listViewParams['sort'] ) && ! preg_match( '/^[a-zA-Z_][a-zA-Z0-9_-]*$/', $listViewParams['sort'] ) ) {
1986        $listViewParams['sort'] = false;
1987    }
1988
1989    // deal with arrayed items
1990
1991        // cols
1992    if ( isset( $_POST['v'] ) && is_array( $_POST['v'] ) && isset( $_POST['v']['columns'] ) && is_array( $_POST['v']['columns'] ) ) {
1993
1994        foreach ( $_POST['v']['columns'] as $colIndx => $col ) {
1995
1996            // check
1997            if ( isset( $col['namestr'] ) && isset( $col['fieldstr'] ) ) { // removed v3.0.5 - think legacy, if no issue by 3.1, kill this comment. : && isset($col['inline'])
1998
1999                // sanitize + add
2000                $listViewParams['columns'][] = array(
2001
2002                    'namestr'  => ( isset( $col['namestr'] ) ) ? sanitize_text_field( $col['namestr'] ) : '',
2003                    'fieldstr' => ( isset( $col['fieldstr'] ) ) ? sanitize_text_field( $col['fieldstr'] ) : '',
2004                    'inline'   => ( isset( $col['inline'] ) ) ? (int) $col['inline'] : -1,
2005
2006                );
2007
2008            }
2009        }
2010    } // /cols
2011
2012        // filters
2013        // could do with refactoring to account for multi-dimensionality more elegantly
2014    if ( isset( $_POST['v'] ) && is_array( $_POST['v'] ) && isset( $_POST['v']['filters'] ) && is_array( $_POST['v']['filters'] ) ) {
2015
2016        foreach ( $_POST['v']['filters'] as $filterIndx => $filter ) {
2017
2018            // check (if tags, will be 0 indexed index)
2019            $filterIndexStr = sanitize_text_field( $filterIndx );
2020            if ( is_array( $filter ) ) {
2021
2022                foreach ( $filter as $filterSubIndx => $filterSub ) {
2023
2024                    if ( ! is_int( $filterSubIndx ) ) {
2025                        $filterSubIndx = sanitize_text_field( $filterSubIndx );
2026                    }
2027
2028                        // can be an array or a string, so allow multidimension:
2029                    if ( is_array( $filterSub ) ) {
2030
2031                        foreach ( $filterSub as $filterSubSubIndx => $filterSubSub ) {
2032
2033                            if ( ! is_int( $filterSubSubIndx ) ) {
2034                                $filterSubSubIndx = sanitize_text_field( $filterSubSubIndx );
2035                            }
2036
2037                            if ( ! isset( $listViewParams['filters'][ $filterIndexStr ][ $filterSubIndx ] ) || ! is_array( $listViewParams['filters'][ $filterIndexStr ][ $filterSubIndx ] ) ) {
2038                                $listViewParams['filters'][ $filterIndexStr ][ $filterSubIndx ] = array();
2039                            }
2040                            $listViewParams['filters'][ $filterIndexStr ][ $filterSubIndx ][ $filterSubSubIndx ] = sanitize_text_field( $filterSubSub );
2041
2042                        }
2043                    } elseif ( is_string( $filterSub ) ) {
2044
2045                        if ( ! isset( $listViewParams['filters'][ $filterIndexStr ] ) || ! is_array( $listViewParams['filters'][ $filterIndexStr ] ) ) {
2046                            $listViewParams['filters'][ $filterIndexStr ] = array();
2047                        }
2048                            $listViewParams['filters'][ $filterIndexStr ][ $filterSubIndx ] = sanitize_text_field( $filterSub );
2049
2050                    }
2051                }
2052            } elseif ( is_string( $filter ) ) {
2053
2054                    // e.g. s = test
2055                    $listViewParams['filters'][ $filterIndexStr ] = sanitize_text_field( $filter );
2056
2057            }
2058        }
2059    }
2060
2061        // / sanitising
2062
2063    if ( isset( $listViewParams ) && gettype( $listViewParams ) == 'array' && isset( $listViewParams['listtype'] ) ) {
2064
2065        // if it's not got columns, do this, for now.
2066        if ( ! isset( $listViewParams['columns'] ) || ! is_array( $listViewParams['columns'] ) ) {
2067            $listViewParams['columns'] = array();
2068        }
2069
2070        global $zbs;
2071
2072        // } check perms first
2073        if ( $listViewParams['listtype'] == 'customer' && ! zeroBSCRM_permsViewCustomers() ) {
2074            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2075        }
2076        if ( $listViewParams['listtype'] == 'company' && ! zeroBSCRM_permsViewCustomers() ) {
2077            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2078        }
2079        if ( $listViewParams['listtype'] == 'segment' && ! zeroBSCRM_permsViewCustomers() ) {
2080            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2081        }
2082        if ( $listViewParams['listtype'] == 'quote' && ! zeroBSCRM_permsViewQuotes() ) {
2083            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2084        }
2085        if ( $listViewParams['listtype'] == 'quotetemplate' && ! zeroBSCRM_permsViewQuotes() ) {
2086            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2087        }
2088        if ( $listViewParams['listtype'] == 'invoice' && ! zeroBSCRM_permsViewInvoices() ) {
2089            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2090        }
2091        if ( $listViewParams['listtype'] == 'transaction' && ! zeroBSCRM_permsViewTransactions() ) {
2092            wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
2093        }
2094
2095        // } Check for screen options (perpage)
2096        $pageKey  = '';
2097        $per_page = 20;
2098        if ( isset( $listViewParams['pagekey'] ) && ! empty( $listViewParams['pagekey'] ) ) {
2099
2100            // has a key, get screen opts
2101            $screenOpts = $zbs->global_screen_options( $listViewParams['pagekey'] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2102            if ( is_array( $screenOpts ) ) {
2103
2104                if ( isset( $screenOpts['perpage'] ) ) {
2105                    $per_page = (int) $screenOpts['perpage'];
2106                }
2107                // catch
2108                if ( $per_page < 1 ) {
2109                    $per_page = 20;
2110                }
2111            }
2112        }
2113
2114        // } generate a 'col list' quickly (for all type list views)
2115        $columnsRequired = array();
2116        foreach ( $listViewParams['columns'] as $col ) {
2117            $columnsRequired[] = $col['fieldstr'];
2118        }
2119
2120        // default return, regardless of type (allows us to keep main generic)
2121        $res = array(
2122            'objects'     => array(),
2123            'objectcount' => -1,
2124            'paged'       => 1,
2125        );
2126
2127        switch ( $listViewParams['listtype'] ) {
2128
2129            /*
2130            ==============================================================================
2131            ===================== CUSTOMER ============================================== */
2132
2133            // } Customer list view :)
2134            case 'customer':
2135                // } Build query
2136                // now got by screenopt above $per_page = 20;
2137                $page_number            = 0;
2138                $possibleSearchTerm     = '';
2139                $withQuotes             = false;
2140                $withTransactions       = false;
2141                $possibleCoID           = '';
2142                $possibleTagIDs         = '';
2143                $possibleQuickFilters   = '';
2144                $inArray                = '';
2145                $withTags               = false;
2146                $withAssigned           = false;
2147                $withCompany            = false;
2148                $latestLog              = false;
2149                $withValues             = false;
2150                $with_total_group_value = false;
2151
2152                // } Sorting
2153                $sortField = 'id';
2154                $sortOrder = 'desc';
2155
2156                // } Catch filters :)
2157
2158                    // } Search
2159                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
2160                    $possibleSearchTerm = $listViewParams['filters']['s'];
2161                }
2162
2163                    // } Tags
2164                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
2165
2166                    $possibleTagIDs = array();
2167                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
2168
2169                        // DAL1:
2170                        if ( isset( $tagObj['term_id'] ) ) {
2171                            $possibleTagIDs[] = $tagObj['term_id'];
2172                        }
2173                        // DAL2:
2174                        if ( isset( $tagObj['id'] ) ) {
2175                            $possibleTagIDs[] = $tagObj['id'];
2176                        }
2177                    }
2178                }
2179
2180                    // } QuickFilters
2181                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
2182
2183                    $possibleQuickFilters = array();
2184                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
2185                        $possibleQuickFilters[] = $quickFilter;
2186                    }
2187                }
2188
2189                    // if with total group value
2190                if ( $zbs->settings->get( 'show_totals_table' ) == 1 ) {
2191
2192                    $with_total_group_value = true;
2193
2194                }
2195
2196                    // } Total val present?
2197                if ( in_array( 'totalvalue', $columnsRequired ) ) {
2198
2199                    $withValues = true;
2200
2201                }
2202                    // } Quote val present? // ONLY WORKS DAL3
2203                if ( in_array( 'quotesvalue', $columnsRequired ) ) {
2204
2205                    $withValues = true;
2206
2207                }
2208                    // } Invoices val present? // ONLY WORKS DAL3
2209                if ( in_array( 'invoicesvalue', $columnsRequired ) ) {
2210
2211                    $withValues = true;
2212
2213                }
2214                    // } trans total present? // ONLY WORKS DAL3
2215                // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2216                if ( in_array( 'transactiontotal', $columnsRequired, true ) ) {
2217
2218                    $withValues = true;
2219
2220                }
2221
2222                    // } Tags
2223                if ( in_array( 'tagged', $columnsRequired ) ) {
2224
2225                    $withTags = true;
2226
2227                }
2228
2229                    // } Quotes
2230                if ( in_array( 'hasquote', $columnsRequired ) || in_array( 'quotecount', $columnsRequired ) || in_array( 'quotetotal', $columnsRequired ) ) {
2231
2232                    $withQuotes = true;
2233
2234                }
2235
2236                    // } Trans
2237                // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2238                if ( in_array( 'hastransaction', $columnsRequired ) || in_array( 'transactioncount', $columnsRequired ) || in_array( 'transactiontotal', $columnsRequired, true ) ) {
2239
2240                    $withTransactions = true;
2241
2242                }
2243
2244                    // } Assigned to
2245                if ( in_array( 'assigned', $columnsRequired ) ) {
2246
2247                    $withAssigned = true;
2248
2249                }
2250
2251                    // } Company
2252                if ( in_array( 'company', $columnsRequired ) ) {
2253
2254                    $withCompany = true;
2255
2256                }
2257
2258                    // } latest log
2259
2260                    // see if in notcontactedin (quickfilter)
2261                    $hasQuickFilterForLogs = false;
2262                if ( is_array( $possibleQuickFilters ) && count( $possibleQuickFilters ) > 0 ) {
2263                    foreach ( $possibleQuickFilters as $pqf ) {
2264                        if ( str_starts_with( $pqf, 'notcontactedin' ) ) {
2265                                        $hasQuickFilterForLogs = true;
2266                        }
2267                    }
2268                }
2269
2270                if ( in_array( 'latestlog', $columnsRequired ) || in_array( 'lastcontacted', $columnsRequired ) || $hasQuickFilterForLogs ) {
2271
2272                    $latestLog = true;
2273
2274                }
2275
2276                    // } Catch paging :)
2277
2278                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
2279
2280                    $possiblePage = (int) $listViewParams['paged'];
2281                    if ( $possiblePage > 0 ) {
2282
2283                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
2284                        $page_number = $possiblePage;
2285                    }
2286                }
2287                    // $res['paged'] = $page_number;
2288
2289                    // } Catch sorting
2290
2291                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2292
2293                    $possSortField = $listViewParams['sort'];
2294
2295                    // DAL2 - allow all fields for now :) (little interpretation needed)
2296                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2297                        $sortField = $possSortField;
2298
2299                        // ... though if id...
2300                        if ( $sortField == 'zbsc_id' ) {
2301                            $sortField = 'ID';
2302                        }
2303
2304                        // ... and this
2305                        if ( $sortField == 'added' ) {
2306                            $sortField = 'created';
2307                        }
2308                        if ( $sortField == 'nameavatar' ) {
2309                            $sortField = 'fullname';
2310                        }
2311                        if ( $sortField == 'name' ) {
2312                            $sortField = 'fullname';
2313                        }
2314                        if ( $sortField == 'assigned' ) {
2315                            $sortField = 'zbs_owner';
2316                        }
2317                        if ( $sortField == 'post_id' ) {
2318                            $sortField = 'ID';
2319                        }
2320                        if ( $sortField == 'post_title' ) {
2321                            $sortField = 'zbsc_lname';
2322                        }
2323                        if ( $sortField == 'post_excerpt' ) {
2324                            $sortField = 'zbsc_lname';
2325                        }
2326                    }
2327
2328                    if ( ! empty( $listViewParams['sortorder'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2329                        $sortOrder = $listViewParams['sortorder']; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2330                    }
2331                }
2332
2333                    // Retrieve data
2334
2335                    /* we need to prepend zbsc_ when not using cf */
2336                    $custFields = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_CONTACT ) );
2337
2338                    // needs to check if field name is custom field:
2339                    $sortIsCustomField = false;
2340                if ( is_array( $custFields ) && array_key_exists( $sortField, $custFields ) ) {
2341                    $sortIsCustomField = true;
2342                }
2343                if ( ! $sortIsCustomField && $sortField != 'ID' ) {
2344                    $sortField = 'zbsc_' . $sortField;
2345                }
2346
2347                    // catch empties
2348                if ( empty( $sortField ) ) {
2349                    $sortField = 'ID';
2350                }
2351
2352                if ( $page_number < 0 ) {
2353                    $page_number = 0;
2354                }
2355
2356                // If using pagination, get total count
2357                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
2358
2359                    $count_args = array(
2360
2361                        'searchPhrase' => $possibleSearchTerm,
2362                        'inCompany'    => $possibleCoID,
2363                        'inArr'        => $inArray,
2364                        'quickFilters' => $possibleQuickFilters,
2365                        'isTagged'     => $possibleTagIDs,
2366
2367                        // just count
2368                        'count'        => true,
2369
2370                        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2371
2372                    );
2373
2374                    $res['objectcount'] = (int) $zbs->DAL->contacts->getContacts( $count_args );
2375
2376                    // If the page requested is out of range (e.g. after a bulk action emptied the
2377                    // last page), use the last valid page instead.
2378                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
2379                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
2380                        if ( $page_number > $last_valid_page ) {
2381                            $page_number = $last_valid_page;
2382                        }
2383                    }
2384                }
2385
2386                $res['paged'] = $page_number;
2387
2388                // make ARGS
2389                $args = array(
2390
2391                    'searchPhrase'     => $possibleSearchTerm,
2392                    'inCompany'        => $possibleCoID,
2393                    'inArr'            => $inArray,
2394                    'quickFilters'     => $possibleQuickFilters,
2395                    'isTagged'         => $possibleTagIDs,
2396                    'ownedBy'          => false,
2397
2398                    'withCustomFields' => true,
2399                    'withQuotes'       => $withQuotes,
2400                    'withInvoices'     => false,
2401                    'withTransactions' => $withTransactions,
2402                    'withLogs'         => false,
2403                    'withLastLog'      => $latestLog,
2404                    'withTags'         => $withTags,
2405                    'withOwner'        => $withAssigned,
2406                    'withValues'       => $withValues,
2407
2408                    'sortByField'      => $sortField,
2409                    'sortOrder'        => $sortOrder,
2410                    'page'             => $page_number,
2411                    'perPage'          => $per_page,
2412
2413                    'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2414
2415                );
2416
2417                $customers = $zbs->DAL->contacts->getContacts( $args );
2418
2419                $customers = jpcrm_inject_contacts( $customers, $args );
2420
2421                // with total
2422                if ( $with_total_group_value ) {
2423
2424                    // redo call for total valuesS
2425                    $args = array(
2426
2427                        'searchPhrase'  => $possibleSearchTerm,
2428                        'inCompany'     => $possibleCoID,
2429                        'inArr'         => $inArray,
2430                        'quickFilters'  => $possibleQuickFilters,
2431                        'isTagged'      => $possibleTagIDs,
2432                        'ownedBy'       => false,
2433                        'ignoreowner'   => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2434
2435                        'onlyObjTotals' => true,
2436
2437                    );
2438
2439                    $res['totals'] = $zbs->DAL->contacts->getContacts( $args );
2440
2441                }
2442
2443                // } Tidy
2444
2445                // glob as used below. not pretty
2446                global $companyNameCache;
2447                $companyNameCache = array();
2448
2449                if ( count( $customers ) > 0 ) {
2450                    foreach ( $customers as $customer ) {
2451                        // DAL3 now processes these in the OBJ class (starting to centralise properly.)
2452                        $res['objects'][] = $zbs->DAL->contacts->listViewObj( $customer, $columnsRequired );
2453                    }
2454                }
2455                break;
2456
2457            /*
2458            =================== / CUSTOMER ===============================================
2459            ============================================================================= */
2460
2461            /*
2462            ==============================================================================
2463            ===================== COMPANY =============================================== */
2464
2465            // } Company list view :) - ADDED BY MIKE
2466            case 'company':
2467                // } Build query
2468                // now got by screenopt above $per_page = 20;
2469                $page_number          = 0;
2470                $possibleSearchTerm   = '';
2471                $possibleTagIDs       = '';
2472                $possibleQuickFilters = '';
2473                $withTags             = false;
2474                $withAssigned         = false;
2475                $latestLog            = false;
2476                $withTransactions     = false;
2477                $withValues           = false;
2478
2479                // } Sorting
2480                $sortField = 'id';
2481                $sortOrder = 'desc';
2482
2483                // } Catch filters :)
2484
2485                    // } Search
2486                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
2487                    $possibleSearchTerm = $listViewParams['filters']['s'];
2488                }
2489
2490                    // } Tags
2491                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
2492
2493                    $possibleTagIDs = array();
2494                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
2495
2496                        // DAL2:
2497                        if ( isset( $tagObj['term_id'] ) ) {
2498                            $possibleTagIDs[] = $tagObj['term_id'];
2499                        }
2500                        // V3+:
2501                        if ( isset( $tagObj['id'] ) ) {
2502                            $possibleTagIDs[] = $tagObj['id'];
2503                        }
2504                    }
2505                }
2506
2507                    // } QuickFilters
2508                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
2509
2510                    $possibleQuickFilters = array();
2511                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
2512                        $possibleQuickFilters[] = $quickFilter;
2513                    }
2514                }
2515
2516                    // } Tags
2517                if ( in_array( 'tagged', $columnsRequired ) ) {
2518
2519                    $withTags = true;
2520
2521                }
2522
2523                    // } Assigned to
2524                if ( in_array( 'assigned', $columnsRequired ) ) {
2525
2526                    $withAssigned = true;
2527
2528                }
2529
2530                if ( in_array( 'transactioncount', $columnsRequired ) ) {
2531
2532                    $withTransactions = true;
2533
2534                }
2535
2536                    // } Total val present?
2537                if ( in_array( 'totalvalue', $columnsRequired ) ) {
2538
2539                    $withValues = true;
2540
2541                }
2542                    // } Quote val present?
2543                if ( in_array( 'quotesvalue', $columnsRequired ) ) {
2544
2545                    $withValues = true;
2546
2547                }
2548                    // } Invoices val present?
2549                if ( in_array( 'invoicesvalue', $columnsRequired ) ) {
2550
2551                    $withValues = true;
2552
2553                }
2554                    // } trans val present?
2555                // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2556                if ( in_array( 'transactiontotal', $columnsRequired, true ) ) {
2557
2558                    $withValues = true;
2559
2560                }
2561
2562                    // } latest log
2563
2564                    // see if in notcontactedin (quickfilter)
2565                    $hasQuickFilterForLogs = false;
2566                if ( is_array( $possibleQuickFilters ) && count( $possibleQuickFilters ) > 0 ) {
2567                    foreach ( $possibleQuickFilters as $pqf ) {
2568                        if ( str_starts_with( $pqf, 'notcontactedin' ) ) {
2569                                        $hasQuickFilterForLogs = true;
2570                        }
2571                    }
2572                }
2573
2574                if ( in_array( 'latestlog', $columnsRequired ) || in_array( 'lastcontacted', $columnsRequired ) || $hasQuickFilterForLogs ) {
2575
2576                    $latestLog = true;
2577
2578                }
2579
2580                    // } Catch paging :)
2581
2582                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
2583
2584                    $possiblePage = (int) $listViewParams['paged'];
2585                    if ( $possiblePage > 0 ) {
2586
2587                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
2588                        $page_number = $possiblePage;
2589                    }
2590                }
2591                    // $res['paged'] = $page_number;
2592
2593                    // } Catch sorting
2594
2595                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2596
2597                    $possSortField = $listViewParams['sort'];
2598
2599                    // DAL3: allow all fields for now :) (little interpretation needed)
2600                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2601                        $sortField = $possSortField;
2602
2603                        // ... and this
2604                        if ( $sortField == 'added' ) {
2605                            $sortField = 'created';
2606                        }
2607                        if ( $sortField == 'nameavatar' ) {
2608                            $sortField = 'fullname'; // TEMP
2609                        }
2610                        if ( $sortField == 'name' ) {
2611                            $sortField = 'fullname'; // TEMP
2612                        }
2613                        if ( $sortField == 'assigned' ) {
2614                            $sortField = 'zbs_owner'; // TEMP
2615                        }
2616                    }
2617
2618                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2619                        $sortOrder = $listViewParams['sortorder']; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2620                    }
2621                }
2622                // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2623
2624                // If using pagination, get total count
2625                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
2626
2627                    $count_args = array(
2628                        'searchPhrase' => $possibleSearchTerm,
2629                        'quickFilters' => $possibleQuickFilters,
2630                        'isTagged'     => $possibleTagIDs,
2631                        // just count
2632                        'count'        => true,
2633                        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
2634                    );
2635
2636                    $res['objectcount'] = (int) $zbs->DAL->companies->getCompanies( $count_args ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
2637
2638                    // If the page requested is out of range (e.g. after a bulk action emptied the
2639                    // last page), use the last valid page instead.
2640                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
2641                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
2642                        if ( $page_number > $last_valid_page ) {
2643                            $page_number = $last_valid_page;
2644                        }
2645                    }
2646                }
2647
2648                $res['paged'] = $page_number;
2649
2650                // make ARGS
2651                $args = array(
2652                    'searchPhrase'     => $possibleSearchTerm,
2653                    'isTagged'         => $possibleTagIDs,
2654                    'quickFilters'     => $possibleQuickFilters,
2655                    'withCustomFields' => true,
2656                    'withInvoices'     => false,
2657                    'withTransactions' => $withTransactions,
2658                    'withLogs'         => false,
2659                    'withLastLog'      => $latestLog,
2660                    'withTags'         => $withTags,
2661                    'withOwner'        => $withAssigned,
2662                    'withValues'       => $withValues,
2663                    'sortByField'      => $sortField,
2664                    'sortOrder'        => $sortOrder,
2665                    'page'             => $page_number,
2666                    'perPage'          => $per_page,
2667                    'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
2668                );
2669
2670                $companies = $zbs->DAL->companies->getCompanies( $args ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
2671                // phpcs:enable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2672
2673                    // } Tidy
2674                if ( count( $companies ) > 0 ) {
2675                    foreach ( $companies as $company ) {
2676
2677                                            // DAL3 now processes these in the OBJ class (starting to centralise properly.)
2678                                            $res['objects'][] = $zbs->DAL->companies->listViewObj( $company, $columnsRequired );
2679
2680                    }
2681                }
2682                break;
2683
2684            /*
2685            =================== / COMPANY ===============================================
2686            ============================================================================= */
2687
2688            /*
2689            ==============================================================================
2690            ===================== QUOTE ================================================= */
2691
2692            // } Quote List View
2693            case 'quote':
2694                // } Build query
2695                // now got by screenopt above $per_page = 20;
2696                $page_number          = 0;
2697                $possibleSearchTerm   = '';
2698                $possibleQuickFilters = '';
2699                $possibleTagIDs       = '';
2700                $inArray              = '';
2701                $withCustomer         = false;
2702
2703                // } Sorting
2704                $sortField = 'id';
2705                $sortOrder = 'desc';
2706
2707                // } Catch filters :)
2708
2709                    // } Search
2710                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
2711                    $possibleSearchTerm = $listViewParams['filters']['s'];
2712                }
2713
2714                    // } Tags
2715                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
2716
2717                    $possibleTagIDs = array();
2718                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
2719
2720                        // DAL2:
2721                        if ( isset( $tagObj['term_id'] ) ) {
2722                            $possibleTagIDs[] = $tagObj['term_id'];
2723                        }
2724                        // V3+:
2725                        if ( isset( $tagObj['id'] ) ) {
2726                            $possibleTagIDs[] = $tagObj['id'];
2727                        }
2728                    }
2729                }
2730
2731                    // } QuickFilters
2732                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
2733
2734                    $possibleQuickFilters = array();
2735                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
2736                        $possibleQuickFilters[] = $quickFilter;
2737                    }
2738                }
2739
2740                    // } Assigned to
2741                if ( in_array( 'customer', $columnsRequired ) ) {
2742
2743                    $withCustomer = true;
2744
2745                }
2746
2747                    // } Catch paging :)
2748
2749                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
2750
2751                    $possiblePage = (int) $listViewParams['paged'];
2752                    if ( $possiblePage > 0 ) {
2753
2754                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
2755                        $page_number = $possiblePage;
2756                    }
2757                }
2758                    // $res['paged'] = $page_number;
2759
2760                    // } Catch sorting
2761
2762                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2763
2764                    $possSortField = $listViewParams['sort'];
2765
2766                    // DAL3: allow all fields for now :) (little interpretation needed)
2767                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2768
2769                        $sortField = $possSortField;
2770
2771                        // ... and this
2772                        if ( $sortField == 'added' ) {
2773                            $sortField = 'created';
2774                        }
2775                        if ( $sortField == 'nameavatar' ) {
2776                            $sortField = 'fullname'; // TEMP
2777                        }
2778                        if ( $sortField == 'name' ) {
2779                            $sortField = 'fullname'; // TEMP
2780                        }
2781                        if ( $sortField == 'assigned' ) {
2782                            $sortField = 'zbs_owner'; // TEMP
2783                        }
2784                    }
2785
2786                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
2787                        $sortOrder = $listViewParams['sortorder'];
2788                    }
2789                }
2790
2791                    // If using pagination, get total count
2792                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
2793
2794                    $res['objectcount'] = (int) zeroBS_getQuotesCountIncParams( true, $per_page, $page_number, true, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
2795
2796                    // If the page requested is out of range (e.g. after a bulk action emptied the
2797                    // last page), use the last valid page instead.
2798                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
2799                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
2800                        if ( $page_number > $last_valid_page ) {
2801                            $page_number = $last_valid_page;
2802                        }
2803                    }
2804                }
2805
2806                $res['paged'] = $page_number;
2807
2808                // Retrieve data
2809                $quotes = zeroBS_getQuotes( true, $per_page, $page_number, true, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
2810
2811                // Tidy
2812                if ( count( $quotes ) > 0 ) {
2813                    foreach ( $quotes as $quote ) {
2814
2815                                            // DAL3 now processes these in the OBJ class (starting to centralise properly.)
2816                                            $res['objects'][] = $zbs->DAL->quotes->listViewObj( $quote, $columnsRequired );
2817
2818                    } // / foreach
2819                }
2820                break;
2821
2822            /*
2823            =================== / QUOTE ==================================================
2824            ============================================================================= */
2825
2826            /*
2827            ==============================================================================
2828            ===================== INVOICE =============================================== */
2829
2830            case 'invoice':
2831                // } Build query
2832                // now got by screenopt above $per_page = 20;
2833                $page_number          = 0;
2834                $possibleCoID         = '';
2835                $possibleQuickFilters = '';
2836                $possibleSearchTerm   = '';
2837                $possibleTagIDs       = '';
2838                $inArray              = '';
2839                $withCustomer         = false;
2840
2841                // } Sorting
2842                $sortField = 'id';
2843                $sortOrder = 'desc';
2844
2845                // } Filters
2846
2847                    // } Search
2848                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
2849                    $possibleSearchTerm = $listViewParams['filters']['s'];
2850                }
2851
2852                    // } Tags
2853                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
2854
2855                    $possibleTagIDs = array();
2856                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
2857
2858                        // DAL2:
2859                        if ( isset( $tagObj['term_id'] ) ) {
2860                            $possibleTagIDs[] = $tagObj['term_id'];
2861                        }
2862                        // V3+:
2863                        if ( isset( $tagObj['id'] ) ) {
2864                            $possibleTagIDs[] = $tagObj['id'];
2865                        }
2866                    }
2867                }
2868
2869                    // } QuickFilters
2870                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
2871
2872                    $possibleQuickFilters = array();
2873                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
2874                        $possibleQuickFilters[] = $quickFilter;
2875                    }
2876                }
2877
2878                    // } Assigned to
2879                if ( in_array( 'customer', $columnsRequired ) ) {
2880
2881                    $withCustomer = true;
2882
2883                }
2884
2885                    // } Catch paging :)
2886
2887                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
2888
2889                    $possiblePage = (int) $listViewParams['paged'];
2890                    if ( $possiblePage > 0 ) {
2891
2892                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
2893                        $page_number = $possiblePage;
2894                    }
2895                }
2896                    // $res['paged'] = $page_number;
2897
2898                    // } Catch sorting
2899
2900                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2901
2902                    $possSortField = $listViewParams['sort'];
2903
2904                    // DAL3: allow all fields for now :) (little interpretation needed)
2905                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2906
2907                        $sortField = $possSortField;
2908
2909                        // ... and this
2910                        if ( $sortField == 'added' ) {
2911                            $sortField = 'created';
2912                        }
2913                        if ( $sortField == 'nameavatar' ) {
2914                            $sortField = 'fullname'; // TEMP
2915                        }
2916                        if ( $sortField == 'name' ) {
2917                            $sortField = 'fullname'; // TEMP
2918                        }
2919                        if ( $sortField == 'assigned' ) {
2920                            $sortField = 'zbs_owner'; // TEMP
2921                        }
2922                    }
2923
2924                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
2925                        $sortOrder = $listViewParams['sortorder'];
2926                    }
2927                }
2928
2929                    // If using pagination, get total count
2930                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
2931
2932                    $res['objectcount'] = (int) zeroBS_getInvoicesCountIncParams( true, $per_page, $page_number, $withCustomer, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
2933
2934                    // If the page requested is out of range (e.g. after a bulk action emptied the
2935                    // last page), use the last valid page instead.
2936                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
2937                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
2938                        if ( $page_number > $last_valid_page ) {
2939                            $page_number = $last_valid_page;
2940                        }
2941                    }
2942                }
2943
2944                $res['paged'] = $page_number;
2945
2946                // Retrieve data
2947                $invoices = zeroBS_getInvoices( true, $per_page, $page_number, $withCustomer, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
2948
2949                // Tidy
2950                if ( count( $invoices ) > 0 ) {
2951                    foreach ( $invoices as $invoice ) {
2952
2953                                            // DAL3 now processes these in the OBJ class (starting to centralise properly.)
2954                                            $res['objects'][] = $zbs->DAL->invoices->listViewObj( $invoice, $columnsRequired );
2955
2956                    } // / foreach
2957                }
2958                break;
2959
2960            /*
2961            =================== / INVOICE ================================================
2962            ============================================================================= */
2963
2964            /*
2965            ==============================================================================
2966            ===================== TRANSACTION =========================================== */
2967
2968            // } Transaction list view :)
2969            case 'transaction':
2970                // } Build query
2971                // now got by screenopt above $per_page = 20;
2972                $page_number          = 0;
2973                $possibleSearchTerm   = '';
2974                $possibleCoID         = '';
2975                $possibleTagIDs       = '';
2976                $possibleQuickFilters = '';
2977                $inArray              = '';
2978                $withTags             = false;
2979                $withCustomer         = true;
2980                $latestLog            = false;
2981                $external_source_uid  = true;
2982
2983                // } Sorting
2984                $sortField = 'id';
2985                $sortOrder = 'desc';
2986
2987                // } Catch filters :)
2988
2989                    // } Search
2990                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
2991                    $possibleSearchTerm = $listViewParams['filters']['s'];
2992                }
2993
2994                    // } Tags
2995                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
2996
2997                    $possibleTagIDs = array();
2998                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
2999
3000                        // DAL2:
3001                        if ( isset( $tagObj['term_id'] ) ) {
3002                            $possibleTagIDs[] = $tagObj['term_id'];
3003                        }
3004                        // V3+:
3005                        if ( isset( $tagObj['id'] ) ) {
3006                            $possibleTagIDs[] = $tagObj['id'];
3007                        }
3008                    }
3009                }
3010
3011                    // } QuickFilters
3012                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
3013
3014                    $possibleQuickFilters = array();
3015                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
3016                        $possibleQuickFilters[] = $quickFilter;
3017                    }
3018                }
3019
3020                    // } Tags
3021                if ( in_array( 'tagged', $columnsRequired ) ) {
3022
3023                    $withTags = true;
3024
3025                }
3026
3027                    // } Assigned to
3028                if ( in_array( 'customer', $columnsRequired ) ) {
3029
3030                    $withCustomer = true;
3031
3032                }
3033
3034                    // } Catch paging :)
3035
3036                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
3037
3038                    $possiblePage = (int) $listViewParams['paged'];
3039                    if ( $possiblePage > 0 ) {
3040
3041                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
3042                        $page_number = $possiblePage;
3043                    }
3044                }
3045                    // $res['paged'] = $page_number;
3046
3047                    // } Catch sorting
3048
3049                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3050
3051                    $possSortField = $listViewParams['sort'];
3052
3053                    // DAL3: allow all fields for now :) (little interpretation needed)
3054                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3055
3056                        $sortField = $possSortField;
3057
3058                        // ... and this
3059                        if ( $sortField == 'added' ) {
3060                            $sortField = 'created';
3061                        }
3062                        if ( $sortField == 'nameavatar' ) {
3063                            $sortField = 'fullname'; // TEMP
3064                        }
3065                        if ( $sortField == 'name' ) {
3066                            $sortField = 'fullname'; // TEMP
3067                        }
3068                        if ( $sortField == 'assigned' ) {
3069                            $sortField = 'zbs_owner'; // TEMP
3070                        }
3071                    }
3072
3073                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
3074                        $sortOrder = $listViewParams['sortorder'];
3075                    }
3076                }
3077
3078                // If using pagination, get total count
3079                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
3080
3081                    $res['objectcount'] = (int) zeroBS_getTransactionsCountIncParams( true, $per_page, $page_number, $withCustomer, $possibleSearchTerm, $possibleTagIDs, $inArray, $sortField, $sortOrder, $withTags, $possibleQuickFilters );
3082
3083                    // If the page requested is out of range (e.g. after a bulk action emptied the
3084                    // last page), use the last valid page instead.
3085                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
3086                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
3087                        if ( $page_number > $last_valid_page ) {
3088                            $page_number = $last_valid_page;
3089                        }
3090                    }
3091                }
3092
3093                $res['paged'] = $page_number;
3094
3095                // Retrieve data
3096                $transactions = zeroBS_getTransactions( true, $per_page, $page_number, $withCustomer, $possibleSearchTerm, $possibleTagIDs, $inArray, $sortField, $sortOrder, $withTags, $possibleQuickFilters, $external_source_uid );
3097
3098                // Tidy
3099                if ( count( $transactions ) > 0 ) {
3100                    foreach ( $transactions as $transaction ) {
3101
3102                                            // DAL3 now processes these in the OBJ class (starting to centralise properly.)
3103                                            $res['objects'][] = $zbs->DAL->transactions->listViewObj( $transaction, $columnsRequired );
3104
3105                    } // / foreach
3106                }
3107                break;
3108
3109            /*
3110            =================== / TRANSACTION ============================================
3111            ============================================================================= */
3112
3113            /*
3114            ==============================================================================
3115            ===================== FORM ================================================== */
3116
3117            // } Form list view :) ADDED BY MS - WARY ABOUT WHAT TO COMMENT OUT HERE
3118            case 'form':
3119                // } Build query
3120                // now got by screenopt above $per_page = 20;
3121                $page_number          = 0;
3122                $possibleSearchTerm   = '';
3123                $withQuotes           = false;
3124                $withTransactions     = false;
3125                $possibleCoID         = '';
3126                $possibleTagIDs       = '';
3127                $possibleQuickFilters = '';
3128                $inArray              = '';
3129                $withTags             = false;
3130                $withAssigned         = false;
3131                $latestLog            = false;
3132
3133                // } Sorting
3134                $sortField = 'id';
3135                $sortOrder = 'desc';
3136
3137                // } Search
3138                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
3139                    $possibleSearchTerm = $listViewParams['filters']['s'];
3140                }
3141
3142                // } Tags
3143                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
3144
3145                    $possibleTagIDs = array();
3146                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
3147
3148                        // DAL2:
3149                        if ( isset( $tagObj['term_id'] ) ) {
3150                            $possibleTagIDs[] = $tagObj['term_id'];
3151                        }
3152                        // V3+:
3153                        if ( isset( $tagObj['id'] ) ) {
3154                            $possibleTagIDs[] = $tagObj['id'];
3155                        }
3156                    }
3157                }
3158
3159                // } Catch paging :)
3160
3161                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
3162
3163                    $possiblePage = (int) $listViewParams['paged'];
3164                    if ( $possiblePage > 0 ) {
3165
3166                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
3167                        $page_number = $possiblePage;
3168                    }
3169                }
3170                    // $res['paged'] = $page_number;
3171
3172                // } Catch sorting
3173
3174                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3175
3176                    $possSortField = $listViewParams['sort'];
3177
3178                    // DAL3: allow all fields for now :) (little interpretation needed)
3179                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3180
3181                        $sortField = $possSortField;
3182
3183                        // ... and this
3184                        if ( $sortField == 'added' ) {
3185                            $sortField = 'created';
3186                        }
3187                        if ( $sortField == 'nameavatar' ) {
3188                            $sortField = 'fullname'; // TEMP
3189                        }
3190                        if ( $sortField == 'name' ) {
3191                            $sortField = 'fullname'; // TEMP
3192                        }
3193                        if ( $sortField == 'assigned' ) {
3194                            $sortField = 'zbs_owner'; // TEMP
3195                        }
3196                    }
3197
3198                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
3199                        $sortOrder = $listViewParams['sortorder'];
3200                    }
3201                }
3202
3203                // If using pagination, get total count
3204                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
3205
3206                    $res['objectcount'] = (int) zeroBS_getFormsCountIncParams( false, $per_page, $page_number, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
3207
3208                    // If the page requested is out of range (e.g. after a bulk action emptied the
3209                    // last page), use the last valid page instead.
3210                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
3211                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
3212                        if ( $page_number > $last_valid_page ) {
3213                            $page_number = $last_valid_page;
3214                        }
3215                    }
3216                }
3217
3218                $res['paged'] = $page_number;
3219
3220                $forms = zeroBS_getForms( false, $per_page, $page_number, $possibleSearchTerm, $inArray, $sortField, $sortOrder, $possibleQuickFilters, $possibleTagIDs );
3221
3222                // } Tidy
3223                if ( count( $forms ) > 0 ) {
3224                    foreach ( $forms as $form ) {
3225
3226                        // DAL3 now processes these in the OBJ class (starting to centralise properly.)
3227                        $res['objects'][] = $zbs->DAL->forms->listViewObj( $form, $columnsRequired );
3228
3229                    } // / foreach
3230                }
3231                break;
3232
3233            /*
3234            =================== / FORM ===================================================
3235            ============================================================================= */
3236
3237            /*
3238            ==============================================================================
3239            ===================== SEGMENT =============================================== */
3240
3241            case 'segment':
3242                // } Build query
3243                // now got by screenopt above $per_page = 20;
3244                $page_number        = 0;
3245                $ownerID            = -99;
3246                $possibleSearchTerm = '';
3247                $withAudienceCount  = false;
3248                $inArray            = '';
3249
3250                // } Sorting
3251                $sortField = 'ID';
3252                $sortOrder = 'DESC';
3253
3254                // } Catch filters :)
3255
3256                    // } Search
3257                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
3258                    $possibleSearchTerm = $listViewParams['filters']['s'];
3259                }
3260
3261                    // } latest log
3262                if ( in_array( 'audiencecount', $columnsRequired ) ) {
3263
3264                    $withAudienceCount = true;
3265
3266                }
3267
3268                    // } Catch paging :)
3269
3270                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
3271
3272                    $possiblePage = (int) $listViewParams['paged'];
3273                    if ( $possiblePage > 0 ) {
3274
3275                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
3276                        $page_number = $possiblePage;
3277                    }
3278                }
3279                    // $res['paged'] = $page_number;
3280
3281                    // } Catch sorting
3282
3283                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3284
3285                    $possSortField = $listViewParams['sort'];
3286
3287                        // } Actually these need translating for now..
3288                    switch ( $possSortField ) {
3289
3290                        case 'id':
3291                            $sortField = 'ID';
3292
3293                            break;
3294
3295                        case 'name':
3296                            $sortField = 'zbsseg_name';
3297
3298                            break;
3299
3300                        case 'added':
3301                            $sortField = 'zbsseg_created';
3302
3303                            break;
3304
3305                        // todo
3306                        /*
3307                            case 'audiencecount':
3308
3309
3310                            $sortField = 'post_title';
3311
3312                            break;*/
3313
3314                        default:
3315                            $sortField = '';
3316
3317                            break;
3318
3319                    }
3320
3321                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
3322                        $sortOrder = strtoupper( $listViewParams['sortorder'] );
3323                    }
3324                }
3325
3326                // If using pagination, get total count
3327                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
3328                    // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase, WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3329                    $res['objectcount'] = (int) $zbs->DAL->segments->getSegmentsCountIncParams( $ownerID, $per_page, $page_number, false, $possibleSearchTerm, $inArray, $sortField, $sortOrder );
3330
3331                    // If the page requested is out of range (e.g. after a bulk action emptied the
3332                    // last page), use the last valid page instead.
3333                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
3334                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
3335                        if ( $page_number > $last_valid_page ) {
3336                            $page_number = $last_valid_page;
3337                        }
3338                    }
3339                }
3340
3341                $res['paged'] = $page_number;
3342
3343                // } Retrieve data
3344                $segments = $zbs->DAL->segments->getSegments( $ownerID, $per_page, $page_number, false, $possibleSearchTerm, $inArray, $sortField, $sortOrder );
3345
3346                $res['objects'] = $segments;
3347
3348                break;
3349
3350            /*
3351            =================== / SEGMENT ================================================
3352            ============================================================================= */
3353
3354            /*
3355            ==============================================================================
3356            ===================== QUOTE TEMPLATE ======================================== */
3357
3358            case 'quotetemplate':
3359                // } Build query
3360                // now got by screenopt above $per_page = 20;
3361                $page_number          = 0;
3362                $possibleSearchTerm   = '';
3363                $withQuotes           = false;
3364                $withTransactions     = false;
3365                $possibleCoID         = '';
3366                $possibleTagIDs       = '';
3367                $possibleQuickFilters = '';
3368                $inArray              = '';
3369                $withTags             = false;
3370                $withAssigned         = false;
3371                $latestLog            = false;
3372
3373                // } Sorting
3374                $sortField = 'id';
3375                $sortOrder = 'desc';
3376
3377                // } Search
3378                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['s'] ) && ! empty( $listViewParams['filters']['s'] ) ) {
3379                    $possibleSearchTerm = $listViewParams['filters']['s'];
3380                }
3381
3382                // } Catch paging :)
3383
3384                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
3385
3386                    $possiblePage = (int) $listViewParams['paged'];
3387                    if ( $possiblePage > 0 ) {
3388
3389                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
3390                        $page_number = $possiblePage;
3391                    }
3392                }
3393                    // $res['paged'] = $page_number;
3394
3395                // } Catch sorting
3396
3397                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3398
3399                    $possSortField = $listViewParams['sort'];
3400
3401                    // DAL3: allow all fields for now :) (little interpretation needed)
3402                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3403
3404                        $sortField = $possSortField;
3405
3406                        // ... and this
3407                        if ( $sortField == 'added' ) {
3408                            $sortField = 'created';
3409                        }
3410                        if ( $sortField == 'assigned' ) {
3411                            $sortField = 'zbs_owner';
3412                        }
3413                    }
3414
3415                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
3416                        $sortOrder = $listViewParams['sortorder'];
3417                    }
3418                }
3419
3420                // If using pagination, get total count
3421                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
3422
3423                    $res['objectcount'] = (int) zeroBS_getQuoteTemplatesCountIncParams( false, $per_page, $page_number, $possibleSearchTerm );
3424
3425                    // If the page requested is out of range (e.g. after a bulk action emptied the
3426                    // last page), use the last valid page instead.
3427                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
3428                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
3429                        if ( $page_number > $last_valid_page ) {
3430                            $page_number = $last_valid_page;
3431                        }
3432                    }
3433                }
3434
3435                $res['paged'] = $page_number;
3436
3437                // Retrieve data
3438                $quote_templates = zeroBS_getQuoteTemplates( false, $per_page, $page_number, $possibleSearchTerm );// phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3439
3440                // } Tidy
3441                if ( count( $quote_templates ) > 0 ) {
3442                    foreach ( $quote_templates as $quote_template ) {
3443
3444                        // DAL3 now processes these in the OBJ class (starting to centralise properly.)
3445                        $res['objects'][] = $zbs->DAL->quotetemplates->listViewObj( $quote_template, $columnsRequired ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase, WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3446
3447                    } // / foreach
3448                }
3449
3450                break;
3451
3452            /*
3453            =================== / QUOTE TEMPLATE =========================================
3454            ============================================================================= */
3455
3456            /*
3457            ==============================================================================
3458            ===================== TASK ================================================= */
3459
3460            case 'event':
3461                // build query
3462                $page_number          = 0;
3463                $possibleSearchTerm   = '';
3464                $possibleTagIDs       = '';
3465                $possibleQuickFilters = array();
3466                $inArray              = '';
3467                $withTags             = false;
3468                $withAssigned         = false;
3469
3470                // } Sorting
3471                $sortField = 'id';
3472                $sortOrder = 'desc';
3473
3474                // Search
3475                if ( ! empty( $listViewParams['filters']['s'] ) ) {
3476                    $possibleSearchTerm = $listViewParams['filters']['s'];
3477                }
3478
3479                // Tags
3480                if ( ! empty( $listViewParams['filters']['tags'] ) && is_array( $listViewParams['filters']['tags'] ) ) {
3481
3482                    $possibleTagIDs = array();
3483                    foreach ( $listViewParams['filters']['tags'] as $tagObj ) {
3484
3485                        // DAL2:
3486                        if ( isset( $tagObj['term_id'] ) ) {
3487                            $possibleTagIDs[] = $tagObj['term_id'];
3488                        }
3489                        // V3+:
3490                        if ( isset( $tagObj['id'] ) ) {
3491                            $possibleTagIDs[] = $tagObj['id'];
3492                        }
3493                    }
3494                }
3495
3496                // QuickFilters
3497                if ( isset( $listViewParams['filters'] ) && isset( $listViewParams['filters']['quickfilters'] ) && is_array( $listViewParams['filters']['quickfilters'] ) ) {
3498
3499                    foreach ( $listViewParams['filters']['quickfilters'] as $quickFilter ) {
3500                        $possibleQuickFilters[] = $quickFilter;
3501                    }
3502                }
3503
3504                // Catch paging :)
3505                if ( isset( $listViewParams['paged'] ) && ! empty( $listViewParams['paged'] ) ) {
3506
3507                    $possiblePage = (int) $listViewParams['paged'];
3508                    if ( $possiblePage > 0 ) {
3509
3510                        // NVM! // it'll come in +1 (because this is zero-indexed, where as js is +1)
3511                        $page_number = $possiblePage;
3512                    }
3513                }
3514
3515                // Catch sorting
3516                if ( ! empty( $listViewParams['sort'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3517
3518                    $possSortField = $listViewParams['sort'];
3519
3520                    // DAL3: allow all fields for now :) (little interpretation needed)
3521                    if ( $possSortField !== 'false' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3522
3523                        $sortField = $possSortField;
3524
3525                        switch ( $sortField ) {
3526
3527                            case 'added':
3528                                $sortField = 'created';
3529                                break;
3530                            case 'assigned':
3531                                $sortField = 'zbs_owner';
3532                                break;
3533                            case 'status':
3534                                $sortField = 'zbse_complete';
3535                                break;
3536                            case 'start':
3537                            case 'end':
3538                            case 'title':
3539                            case 'desc':
3540                                $sortField = 'zbse_' . $sortField;
3541                                break;
3542
3543                        }
3544                    }
3545
3546                    if ( isset( $listViewParams['sortorder'] ) && ! empty( $listViewParams['sortorder'] ) ) {
3547                        $sortOrder = $listViewParams['sortorder'];
3548                    }
3549                }
3550
3551                // if ($page_number < 0) $page_number = 0;
3552
3553                // make ARGS
3554                $args = array(
3555
3556                    'withAssigned' => true,
3557                    'withOwner'    => true,
3558
3559                    'isTagged'     => $possibleTagIDs,
3560
3561                    'sortByField'  => $sortField,
3562                    'sortOrder'    => $sortOrder,
3563
3564                    'page'         => $page_number,
3565                    'perPage'      => $per_page,
3566
3567                    'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
3568
3569                );
3570
3571                // owner
3572                // if ($ownedByID > 0) $args['ownedBy'] = $ownedByID;
3573
3574                // search term
3575                if ( ! empty( $possibleSearchTerm ) ) {
3576                    $args['searchPhrase'] = $possibleSearchTerm;
3577                }
3578
3579                // filters
3580                foreach ( $possibleQuickFilters as $quick_filter ) {
3581
3582                    switch ( $quick_filter ) {
3583
3584                        case 'status_incomplete':
3585                            $args['isIncomplete'] = true;
3586
3587                            break;
3588
3589                        case 'status_completed':
3590                            $args['isComplete'] = true;
3591
3592                            break;
3593
3594                        case 'next30':
3595                            $args['datedAfter']  = time() - ( 60 * 60 ); // add an hour's leeway
3596                            $args['datedBefore'] = strtotime( '1 month' );
3597
3598                            break;
3599
3600                        case 'last30':
3601                            $args['datedAfter']  = strtotime( '-1 months' );
3602                            $args['datedBefore'] = strtotime( '+1 days' );
3603
3604                            break;
3605
3606                        case 'next7':
3607                            $args['datedAfter']  = time() - ( 60 * 60 ); // add an hour's leeway
3608                            $args['datedBefore'] = strtotime( '+7 days' );
3609
3610                            break;
3611
3612                        case 'last7':
3613                            $args['datedAfter']  = strtotime( '-7 days' );
3614                            $args['datedBefore'] = strtotime( '+1 days' );
3615
3616                            break;
3617
3618                    }
3619                }
3620
3621                // If using pagination, get total count
3622                if ( isset( $listViewParams['pagination'] ) && $listViewParams['pagination'] ) {
3623
3624                    $count_args            = $args;
3625                    $count_args['count']   = true;
3626                    $count_args['page']    = -1;
3627                    $count_args['perPage'] = -1;
3628
3629                    $res['objectcount'] = (int) $zbs->DAL->events->getEvents( $count_args );
3630
3631                    // If the page requested is out of range (e.g. after a bulk action emptied the
3632                    // last page), use the last valid page instead.
3633                    if ( $res['objectcount'] > 0 && $page_number > 1 ) {
3634                        $last_valid_page = max( 1, (int) ceil( $res['objectcount'] / $per_page ) );
3635                        if ( $page_number > $last_valid_page ) {
3636                            $page_number  = $last_valid_page;
3637                            $args['page'] = $page_number;
3638                        }
3639                    }
3640                }
3641
3642                $res['paged'] = $page_number;
3643
3644                $tasks = $zbs->DAL->events->getEvents( $args );
3645
3646                // } Tidy
3647                if ( count( $tasks ) > 0 ) {
3648                    foreach ( $tasks as $task ) {
3649
3650                        // DAL3 now processes these in the OBJ class (starting to centralise properly.)
3651                        $res['objects'][] = $zbs->DAL->events->listViewObj( $task, $columnsRequired );
3652
3653                    } // / foreach
3654                }
3655
3656                break;
3657
3658            /*
3659            =================== / TASK ==================================================
3660            ============================================================================= */
3661
3662            // } Default = non hard typed listtype !
3663            default:
3664                // allow bolt-ins from extensions (mailcamps uses this)
3665                // funcs which fire here have to return internally, they can't rely on $res return
3666                do_action( 'zerobs_ajax_list_view_' . $listViewParams['listtype'], $listViewParams );
3667
3668                // err really
3669
3670                break;
3671
3672        }
3673    }
3674    wp_send_json( $res, 200, JSON_UNESCAPED_SLASHES );
3675}
3676
3677    // } Enact some bulk action :)
3678    add_action( 'wp_ajax_enactListViewBulkAction', 'zeroBSCRM_AJAX_enactListViewBulkAction' );
3679function zeroBSCRM_AJAX_enactListViewBulkAction() {
3680
3681    // } Check nonce
3682    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
3683
3684    global $zbs;
3685
3686    // Get object type (string, not ID)
3687    $objtype = empty( $_POST['objtype'] ) ? '' : sanitize_text_field( $_POST['objtype'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
3688
3689    // Check perms for given object
3690    $has_perms = zeroBSCRM_permsObjType( $zbs->DAL->objTypeID( $objtype ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3691    if ( ! $has_perms ) {
3692        $reply = array(
3693            'status'  => __( 'Forbidden', 'zero-bs-crm' ),
3694            'message' => __( 'You do not have permission to access this resource.', 'zero-bs-crm' ),
3695        );
3696        wp_send_json_error( $reply, 403, JSON_UNESCAPED_SLASHES );
3697    }
3698
3699    // ret
3700    $passback = array();
3701
3702        $actionstr = '';
3703    if ( isset( $_POST['actionstr'] ) ) {
3704        $actionstr = sanitize_text_field( $_POST['actionstr'] );
3705    }
3706        $idsToChange = zeroBSCRM_dataIO_postedArrayOfInts( $_POST['ids'] );
3707
3708        // Check ID's legit
3709        $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
3710        foreach ( $idsToChange as $id ) {
3711
3712            $intID = (int) $id;
3713            if ( $intID > 0 ) {
3714                $legitIDs[] = $intID;
3715            }
3716        }
3717        }
3718
3719        // Any ID's to process?
3720        if ( count( $legitIDs ) > 0 ) {
3721
3722            // Switch by type
3723            switch ( $objtype ) {
3724
3725                case 'customer':
3726                        // Actions:
3727                    switch ( $actionstr ) {
3728
3729                        // delete customers
3730                        case 'delete':
3731                            // delete sub stuff?
3732                            $leaveOrphans = true;
3733
3734                            if ( isset( $_POST['leaveorphans'] ) ) {
3735                                if ( $_POST['leaveorphans'] == '0' ) {
3736                                    $leaveOrphans = false;
3737                                }
3738                            }
3739
3740                            // cycle through + delete (should have sanity checked via SWAL)
3741                            $deleted = 0;
3742                            foreach ( $legitIDs as $id ) {
3743
3744                                // delete all orphans
3745                                zeroBS_deleteCustomer( $id, $leaveOrphans );
3746                                ++$deleted;
3747
3748                            }
3749
3750                            $passback['deleted'] = $deleted;
3751
3752                            // } Return
3753                            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3754
3755                            break;
3756
3757                        // change status
3758                        case 'changestatus':
3759                            $new_status = isset( $_POST['newstatus'] ) ? sanitize_text_field( $_POST['newstatus'] ) : '';
3760                            $accepted   = 0;
3761
3762                            $valid_statuses = zeroBSCRM_getCustomerStatuses( true );
3763
3764                            // legit status?
3765                            if ( in_array( $new_status, $valid_statuses ) ) {
3766
3767                                // cycle through + mark
3768                                foreach ( $legitIDs as $id ) {
3769
3770                                    // Update contact status
3771                                    $zbs->DAL->contacts->setContactStatus( $id, $new_status );
3772
3773                                    ++$accepted;
3774                                }
3775                            } else {
3776                                zeroBSCRM_API_error( 'Invalid status!' );
3777                            }
3778
3779                            $passback['accepted'] = $accepted;
3780
3781                            // } Return
3782                            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3783
3784                            break;
3785
3786                        // add tag(s) to customers
3787                        case 'addtag':
3788                            zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_CONTACT ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3789
3790                            break;
3791
3792                        // remove tag(S) from customers
3793                        case 'removetag':
3794                            zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_CONTACT ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3795
3796                            break;
3797
3798                        // merge customers
3799                        case 'merge':
3800                            // merge which into which
3801                            $dominant = false;
3802                            if ( isset( $_POST['dominant'] ) && ! empty( $_POST['dominant'] ) ) {
3803                                $dominant = (int) $_POST['dominant'];
3804                            }
3805                            $slave = false; if ( ! empty( $dominant ) ) {
3806
3807                                // discern slave (should only ever be 2 id's)
3808                                foreach ( $legitIDs as $id ) {
3809                                    if ( $id != $dominant ) {
3810                                        $slave = $id;
3811                                    }
3812                                }
3813                            }
3814
3815                            if ( ! empty( $dominant ) && ! empty( $slave ) ) {
3816
3817                                $passback['merged'] = zeroBSCRM_mergeCustomers( $dominant, $slave );
3818
3819                            } else {
3820
3821                                $passback = false;
3822
3823                            }
3824
3825                            // } Return
3826                            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3827
3828                            break;
3829
3830                    }
3831
3832                        // } Return - will be an error if here
3833                        wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3834
3835                    break;
3836
3837                case 'company':
3838                    // check id's legit
3839                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
3840                        foreach ( $idsToChange as $id ) {
3841
3842                                                $intID = (int) $id;
3843                            if ( $intID > 0 ) {
3844                                $legitIDs[] = $intID;
3845                            }
3846                        }
3847                    }
3848
3849                    if ( count( $legitIDs ) > 0 ) {
3850
3851                        // actions:
3852                        switch ( $actionstr ) {
3853
3854                            // delete company
3855                            case 'delete':
3856                                // delete sub stuff?
3857                                $leaveOrphans = true;
3858
3859                                if ( isset( $_POST['leaveorphans'] ) ) {
3860                                    if ( $_POST['leaveorphans'] == '0' ) {
3861                                        $leaveOrphans = false;
3862                                    }
3863                                }
3864
3865                                // cycle through + delete (should have sanity checked via SWAL)
3866                                $deleted = 0;
3867                                foreach ( $legitIDs as $id ) {
3868
3869                                    // delete all orphans
3870                                    zeroBS_deleteCompany( $id, $leaveOrphans );
3871                                    ++$deleted;
3872
3873                                }
3874
3875                                $passback['deleted'] = $deleted;
3876
3877                                // } Return
3878                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3879
3880                                break;
3881
3882                            // add tag(s) to company(s)
3883                            case 'addtag':
3884                                zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_COMPANY ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3885
3886                                break;
3887
3888                            // remove tag(S) from company(s)
3889                            case 'removetag':
3890                                zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_COMPANY ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3891
3892                                break;
3893
3894                        }
3895                    } else {
3896
3897                        // NO IDS!
3898
3899                    }
3900
3901                    // } Return - will be an error if here
3902                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3903
3904                    break;
3905
3906                case 'quote':
3907                    // check id's legit
3908                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
3909                        foreach ( $idsToChange as $id ) {
3910
3911                                                $intID = (int) $id;
3912                            if ( $intID > 0 ) {
3913                                $legitIDs[] = $intID;
3914                            }
3915                        }
3916                    }
3917
3918                    if ( count( $legitIDs ) > 0 ) {
3919
3920                        // actions:
3921                        switch ( $actionstr ) {
3922
3923                            // delete quote
3924                            case 'delete':
3925                                // cycle through + delete (should have sanity checked via SWAL)
3926                                $deleted = 0;
3927                                foreach ( $legitIDs as $id ) {
3928
3929                                    // delete all orphans
3930                                    $zbs->DAL->quotes->deleteQuote(
3931                                        array(
3932                                            'id'          => $id,
3933                                            'saveOrphans' => true,
3934                                        )
3935                                    );
3936
3937                                    ++$deleted;
3938
3939                                }
3940
3941                                $passback['deleted'] = $deleted;
3942
3943                                // } Return
3944                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3945
3946                                break;
3947
3948                            // mark accepted
3949                            case 'markaccepted':
3950                                // cycle through + mark
3951                                $accepted = 0;
3952                                foreach ( $legitIDs as $id ) {
3953
3954                                    // } Update quote as accepted (should verify this worked...)
3955                                    zeroBS_markQuoteAccepted( $id, zeroBS_getCurrentUserUsername() );
3956
3957                                    ++$accepted;
3958
3959                                }
3960
3961                                $passback['accepted'] = $accepted;
3962
3963                                // } Return
3964                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3965
3966                                break;
3967
3968                            // mark unaccepted
3969                            case 'markunaccepted':
3970                                // cycle through + mark
3971                                $unaccepted = 0;
3972                                foreach ( $legitIDs as $id ) {
3973
3974                                    // } Update quote as unaccepted (should verify this worked...)
3975                                    zeroBS_markQuoteUnAccepted( $id );
3976
3977                                    ++$unaccepted;
3978
3979                                }
3980
3981                                $passback['unaccepted'] = $unaccepted;
3982
3983                                // } Return
3984                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
3985
3986                                break;
3987
3988                            // add tag(s) to quote(s)
3989                            case 'addtag':
3990                                zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_QUOTE ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3991
3992                                break;
3993
3994                            // remove tag(S) from quote(s)
3995                            case 'removetag':
3996                                zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_QUOTE ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3997
3998                                break;
3999
4000                        }
4001                    } else {
4002
4003                        // NO IDS!
4004
4005                    }
4006
4007                    // } Return - will be an error if here
4008                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4009
4010                    break;
4011
4012                case 'invoice':
4013                    // check id's legit
4014                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4015                        foreach ( $idsToChange as $id ) {
4016
4017                                                $intID = (int) $id;
4018                            if ( $intID > 0 ) {
4019                                $legitIDs[] = $intID;
4020                            }
4021                        }
4022                    }
4023
4024                    if ( count( $legitIDs ) > 0 ) {
4025
4026                        // actions:
4027                        switch ( $actionstr ) {
4028
4029                            // delete quote
4030                            case 'delete':
4031                                // cycle through + delete (should have sanity checked via SWAL)
4032                                $deleted = 0;
4033                                foreach ( $legitIDs as $id ) {
4034
4035                                    // delete all orphans
4036                                    $zbs->DAL->invoices->deleteInvoice(
4037                                        array(
4038                                            'id'          => $id,
4039                                            'saveOrphans' => false,
4040                                        )
4041                                    );
4042
4043                                    ++$deleted;
4044                                }
4045
4046                                $passback['deleted'] = $deleted;
4047
4048                                // } Return
4049                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4050
4051                                break;
4052
4053                            // change status
4054                            case 'changestatus':
4055                                $accepted = 0;
4056
4057                                // legit status?
4058                                $statusStr = sanitize_text_field( $_POST['newstatus'] );
4059                                if ( in_array( $statusStr, zeroBSCRM_getInvoicesStatuses() ) ) {
4060
4061                                    // cycle through + mark
4062                                    foreach ( $legitIDs as $id ) {
4063
4064                                        // } Update invoice status (should verify this worked...)
4065                                        zeroBS_updateInvoiceStatus( $id, $statusStr );
4066
4067                                        ++$accepted;
4068
4069                                    }
4070                                }
4071
4072                                $passback['accepted'] = $accepted;
4073
4074                                // } Return
4075                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4076
4077                                break;
4078
4079                            // add tag(s) to invoice(s)
4080                            case 'addtag':
4081                                zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_INVOICE ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4082
4083                                break;
4084
4085                            // remove tag(S) from invoice(s)
4086                            case 'removetag':
4087                                zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_INVOICE ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4088
4089                                break;
4090
4091                        }
4092                    } else {
4093
4094                        // NO IDS!
4095
4096                    }
4097
4098                    // } Return - will be an error if here
4099                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4100
4101                    break;
4102
4103                case 'transaction':
4104                    // check id's legit
4105                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4106                        foreach ( $idsToChange as $id ) {
4107
4108                                                $intID = (int) $id;
4109                            if ( $intID > 0 ) {
4110                                $legitIDs[] = $intID;
4111                            }
4112                        }
4113                    }
4114
4115                    if ( count( $legitIDs ) > 0 ) {
4116
4117                        // actions:
4118                        switch ( $actionstr ) {
4119
4120                            // delete transaction(s)
4121                            case 'delete':
4122                                // cycle through + delete (should have sanity checked via SWAL)
4123                                $deleted = 0;
4124                                foreach ( $legitIDs as $id ) {
4125
4126                                    // delete all orphans
4127                                    $zbs->DAL->transactions->deleteTransaction(
4128                                        array(
4129                                            'id'          => $id,
4130                                            'saveOrphans' => true,
4131                                        )
4132                                    );
4133
4134                                    ++$deleted;
4135
4136                                }
4137
4138                                $passback['deleted'] = $deleted;
4139
4140                                // } Return
4141                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4142
4143                                break;
4144
4145                            // add tag(s) to transaction(s)
4146                            case 'addtag':
4147                                zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_TRANSACTION ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4148
4149                                break;
4150
4151                            // remove tag(S) from transaction(s)
4152                            case 'removetag':
4153                                zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_TRANSACTION ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4154
4155                                break;
4156
4157                        }
4158                    } else {
4159
4160                        // NO IDS!
4161
4162                    }
4163
4164                    // } Return - will be an error if here
4165                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4166
4167                    break;
4168
4169                case 'form':
4170                    // check id's legit
4171                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4172                        foreach ( $idsToChange as $id ) {
4173
4174                                                $intID = (int) $id;
4175                            if ( $intID > 0 ) {
4176                                $legitIDs[] = $intID;
4177                            }
4178                        }
4179                    }
4180
4181                    if ( count( $legitIDs ) > 0 ) {
4182
4183                        // actions:
4184                        switch ( $actionstr ) {
4185
4186                            // delete quote
4187                            case 'delete':
4188                                // cycle through + delete (should have sanity checked via SWAL)
4189                                $deleted = 0;
4190                                foreach ( $legitIDs as $id ) {
4191
4192                                    // delete all orphans
4193                                    $zbs->DAL->forms->deleteForm(
4194                                        array(
4195                                            'id'          => $id,
4196                                            'saveOrphans' => true,
4197                                        )
4198                                    );
4199
4200                                    ++$deleted;
4201
4202                                }
4203
4204                                $passback['deleted'] = $deleted;
4205
4206                                // } Return
4207                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4208
4209                                break;
4210
4211                        }
4212                    } else {
4213
4214                        // NO IDS!
4215
4216                    }
4217
4218                    // } Return - will be an error if here
4219                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4220
4221                    break;
4222
4223                case 'segment':
4224                    // check id's legit
4225                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4226                        foreach ( $idsToChange as $id ) {
4227
4228                                                $intID = (int) $id;
4229                            if ( $intID > 0 ) {
4230                                $legitIDs[] = $intID;
4231                            }
4232                        }
4233                    }
4234
4235                    if ( count( $legitIDs ) > 0 ) {
4236
4237                        // actions:
4238                        switch ( $actionstr ) {
4239
4240                            // delete segments
4241                            case 'delete':
4242                                // cycle through + delete (should have sanity checked via SWAL)
4243                                $deleted = 0;
4244                                foreach ( $legitIDs as $id ) {
4245
4246                                    // delete
4247                                    $zbs->DAL->segments->deleteSegment( array( 'id' => $id ) );
4248                                    ++$deleted;
4249
4250                                }
4251
4252                                $passback['deleted'] = $deleted;
4253
4254                                // } Return
4255                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4256
4257                                break;
4258
4259                        }
4260                    } else {
4261
4262                        // NO IDS!
4263
4264                    }
4265
4266                    // } Return - will be an error if here, really!?!?
4267                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4268
4269                    break;
4270
4271                case 'quotetemplate':
4272                    // check id's legit
4273                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4274                        foreach ( $idsToChange as $id ) {
4275
4276                                                $intID = (int) $id;
4277                            if ( $intID > 0 ) {
4278                                $legitIDs[] = $intID;
4279                            }
4280                        }
4281                    }
4282
4283                    if ( count( $legitIDs ) > 0 ) {
4284
4285                        // actions:
4286                        switch ( $actionstr ) {
4287
4288                            // delete segments
4289                            case 'delete':
4290                                // cycle through + delete (should have sanity checked via SWAL)
4291                                $deleted = 0;
4292                                foreach ( $legitIDs as $id ) {
4293
4294                                    // delete
4295                                    $zbs->DAL->quotetemplates->deleteQuotetemplate( array( 'id' => $id ) );
4296                                    ++$deleted;
4297
4298                                }
4299
4300                                $passback['deleted'] = $deleted;
4301
4302                                // } Return
4303                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4304
4305                                break;
4306
4307                        }
4308                    } else {
4309
4310                        // NO IDS!
4311
4312                    }
4313
4314                    // } Return - will be an error if here, really!?!? should be passsing headers as such.
4315                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4316
4317                    break;
4318
4319                case 'event':
4320                    // check id's legit
4321                    $legitIDs = array(); if ( is_array( $idsToChange ) && count( $idsToChange ) > 0 ) {
4322                        foreach ( $idsToChange as $id ) {
4323
4324                                                $intID = (int) $id;
4325                            if ( $intID > 0 ) {
4326                                $legitIDs[] = $intID;
4327                            }
4328                        }
4329                    }
4330
4331                    if ( count( $legitIDs ) > 0 ) {
4332
4333                        // actions:
4334                        switch ( $actionstr ) {
4335
4336                            // delete quote
4337                            case 'delete':
4338                                // cycle through + delete (should have sanity checked via SWAL)
4339                                $deleted = 0;
4340                                foreach ( $legitIDs as $id ) {
4341
4342                                    // delete all orphans
4343                                    $zbs->DAL->events->deleteEvent(
4344                                        array(
4345                                            'id'          => $id,
4346                                            'saveOrphans' => true,
4347                                        )
4348                                    );
4349
4350                                    ++$deleted;
4351
4352                                }
4353
4354                                $passback['deleted'] = $deleted;
4355
4356                                // } Return
4357                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4358
4359                                break;
4360
4361                                // add tag(s) to transaction(s)
4362                            case 'addtag':
4363                                zeroBSCRM_bulkAction_enact_addTags( $legitIDs, ZBS_TYPE_TASK ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4364
4365                                break;
4366
4367                                // remove tag(S) from transaction(s)
4368                            case 'removetag':
4369                                zeroBSCRM_bulkAction_enact_removeTags( $legitIDs, ZBS_TYPE_TASK ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4370
4371                                break;
4372
4373                                // mark completed
4374                            case 'markcomplete':
4375                                // cycle through + mark
4376                                $completed = 0;
4377                                foreach ( $legitIDs as $id ) {
4378
4379                                    // update task as completed
4380                                    $zbs->DAL->events->setEventCompleteness( $id, 1 );
4381
4382                                    ++$completed;
4383
4384                                }
4385
4386                                $passback['completed'] = $completed;
4387
4388                                // } Return
4389                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4390
4391                                break;
4392
4393                                // mark completed
4394                            case 'markincomplete':
4395                                // cycle through + mark
4396                                $incompleted = 0;
4397                                foreach ( $legitIDs as $id ) {
4398
4399                                    // update task as completed
4400                                    $zbs->DAL->events->setEventCompleteness( $id, -1 );
4401
4402                                    ++$incompleted;
4403
4404                                }
4405
4406                                $passback['incompleted'] = $incompleted;
4407
4408                                // } Return
4409                                wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4410
4411                                break;
4412
4413                        }
4414                    } else {
4415
4416                        // NO IDS!
4417
4418                    }
4419
4420                    // } Return - will be an error if here, really!?!? should be passsing headers as such.
4421                    wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES );
4422
4423                    break;
4424
4425                default:
4426                    // err really :o
4427                    wp_send_json( array(), 200, JSON_UNESCAPED_SLASHES );
4428
4429                    break;
4430
4431            }
4432        } else {
4433
4434            // NO IDS!
4435
4436        }
4437
4438        exit( 0 );
4439}
4440
4441    /**
4442     * Adds tags to any object (for bulk action AJAX requests called in zeroBSCRM_AJAX_enactListViewBulkAction())
4443     *
4444     * @param int[] $obj_ids     Array of object ids.
4445     * @param int   $obj_type_id Object type ID.
4446     *
4447     * @return void Outputs JSON and exits.
4448     */
4449function zeroBSCRM_bulkAction_enact_addTags( $obj_ids = array(), $obj_type_id = -1 ) {
4450
4451        global $zbs;
4452
4453        // return
4454        $passback = array();
4455
4456        // retrieve tag (array of id's)
4457        $tagArr = zeroBSCRM_dataIO_postedArrayOfInts( $_POST['tags'] );
4458        $tagIDs = array();
4459    if ( is_array( $tagArr ) && count( $tagArr ) > 0 ) {
4460        foreach ( $tagArr as $t ) {
4461
4462            $tInt = (int) $t;
4463            if ( $tInt > 0 ) {
4464                $tagIDs[] = $tInt;
4465            }
4466        }
4467    }
4468
4469    if ( count( $tagIDs ) > 0 ) {
4470
4471        // tags to add
4472
4473            // cycle through + add tag
4474            $tagged = 0;
4475        foreach ( $obj_ids as $id ) {
4476
4477            // pass as array of term ID's :)
4478
4479            $zbs->DAL->addUpdateObjectTags(
4480                array(
4481                    'objid'   => $id,
4482                    'objtype' => $obj_type_id,
4483                    'tagIDs'  => $tagIDs,
4484                    'mode'    => 'append',
4485                )
4486            );
4487
4488            // no checks.?
4489            ++$tagged;
4490
4491        }
4492
4493            $passback['tagged'] = $tagged;
4494
4495            // This function outputs JSON and exits.
4496            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4497
4498    } else {
4499
4500        // no tags
4501
4502    }
4503
4504        // err
4505        wp_send_json_error( -1, 500, JSON_UNESCAPED_SLASHES );
4506}
4507
4508    /**
4509     * Remove tags from any object (for bulk action AJAX requests called in zeroBSCRM_AJAX_enactListViewBulkAction())
4510     *
4511     * @param int[] $obj_ids     Array of object ids.
4512     * @param int   $obj_type_id Object type ID.
4513     *
4514     * @return void Outputs JSON and exits.
4515     */
4516function zeroBSCRM_bulkAction_enact_removeTags( $obj_ids = array(), $obj_type_id = -1 ) {
4517
4518        global $zbs;
4519
4520        // return
4521        $passback = array();
4522
4523        // retrieve tag (array of id's)
4524        $tagArr = zeroBSCRM_dataIO_postedArrayOfInts( $_POST['tags'] );
4525        $tagIDs = array();
4526    if ( is_array( $tagArr ) && count( $tagArr ) > 0 ) {
4527        foreach ( $tagArr as $t ) {
4528
4529            $tInt = (int) $t;
4530            if ( $tInt > 0 ) {
4531                $tagIDs[] = $tInt;
4532            }
4533        }
4534    }
4535
4536    if ( count( $tagIDs ) > 0 ) {
4537
4538        // tags to add
4539
4540            // cycle through + remove tags
4541            $untagged = 0;
4542        foreach ( $obj_ids as $id ) {
4543
4544            // pass as array of term ID's :)
4545            // https://codex.wordpress.org/Function_Reference/wp_remove_object_terms
4546            $zbs->DAL->addUpdateObjectTags(
4547                array(
4548                    'objid'   => $id,
4549                    'objtype' => $obj_type_id,
4550                    'tagIDs'  => $tagIDs,
4551                    'mode'    => 'remove',
4552                )
4553            );
4554
4555            // no checks.?
4556            ++$untagged;
4557
4558        }
4559
4560            $passback['untagged'] = $untagged;
4561
4562            // This function outputs JSON and exits.
4563            wp_send_json( $passback, 200, JSON_UNESCAPED_SLASHES ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4564
4565    } else {
4566
4567        // no tags
4568
4569    }
4570
4571        // err
4572        wp_send_json_error( -1, 500, JSON_UNESCAPED_SLASHES );
4573}
4574
4575/*
4576======================================================
4577    / Admin AJAX: List View (API STYLE)
4578======================================================
4579*/
4580
4581/*
4582======================================================
4583    Admin AJAX: Segments
4584======================================================
4585*/
4586
4587// } Preview a segment
4588add_action( 'wp_ajax_zbs_segment_previewsegment', 'zeroBSCRM_AJAX_previewSegment' );
4589function zeroBSCRM_AJAX_previewSegment() {
4590
4591    // } Check nonce
4592    check_ajax_referer( 'zbs-ajax-nonce', 'sec' );
4593
4594    if ( current_user_can( 'admin_zerobs_customers' ) ) {
4595
4596        global $zbs;
4597
4598        // sanitize?
4599        $segmentID = -1;
4600        if ( isset( $_POST['sID'] ) ) {
4601            $segmentID = (int) $_POST['sID'];
4602        }
4603        $segmentTitle = __( 'Untitled Segment', 'zero-bs-crm' );
4604        if ( isset( $_POST['sTitle'] ) ) {
4605            $segmentTitle = sanitize_text_field( $_POST['sTitle'] );
4606        }
4607        $segmentMatchType = 'all';
4608        if ( isset( $_POST['sMatchType'] ) ) {
4609            $segmentMatchType = sanitize_text_field( $_POST['sMatchType'] );
4610        }
4611        $segmentConditions = array();
4612        if ( isset( $_POST['sConditions'] ) ) {
4613            $segmentConditions = zeroBSCRM_segments_filterConditions( $_POST['sConditions'], false );
4614        }
4615
4616        // optional 2.90+ can just pass id and this'll fill the conditions from saved
4617        if ( $segmentID > 0 && count( $segmentConditions ) == 0 ) {
4618
4619            $potentialSegment = $zbs->DAL->segments->getSegment( $segmentID, true );
4620            if ( is_array( $potentialSegment ) && isset( $potentialSegment['id'] ) ) {
4621                $segment           = $potentialSegment;
4622                $segmentConditions = $segment['conditions'];
4623                $segmentMatchType  = $segment['matchtype'];
4624                $segmentTitle      = $segment['name'];
4625            }
4626        }
4627
4628        try {
4629
4630            // attempt to build a top 5 customer list + total count for segment
4631            $ret = $zbs->DAL->segments->previewSegment( $segmentConditions, $segmentMatchType );
4632
4633        } catch ( Segment_Condition_Exception $exception ) {
4634
4635            // We're missing the condition class for one or more of this segment's conditions.
4636            $zbs->DAL->segments->segment_error_condition_missing( $segmentID, $exception );
4637
4638            // return error str
4639            $error_string = $exception->get_error_code();
4640            $status       = 500;
4641            if ( $error_string === 'segment_condition_produces_no_args' ) {
4642                $status = 400;
4643            }
4644
4645            // return fail
4646            wp_send_json_error(
4647                array(
4648                    'count' => 0,
4649                    'error' => $error_string,
4650                ),
4651                $status,
4652                JSON_UNESCAPED_SLASHES
4653            );
4654
4655        }
4656
4657        if ( is_array( $ret ) && isset( $ret['count'] ) ) {
4658
4659            // return id / fail
4660            wp_send_json( $ret, 200, JSON_UNESCAPED_SLASHES );
4661
4662        }
4663    }
4664
4665    // empty handed
4666    wp_send_json( array( 'count' => 0 ), 200, JSON_UNESCAPED_SLASHES );
4667}
4668// } Save a segment down (update or add)
4669add_action( 'wp_ajax_zbs_segment_savesegment', 'zeroBSCRM_AJAX_saveSegment' );
4670function zeroBSCRM_AJAX_saveSegment() {
4671
4672    // } Check nonce
4673    check_ajax_referer( 'zbs-ajax-nonce', 'sec' );
4674
4675    // either way
4676    header( 'Content-Type: application/json' );
4677
4678    if ( current_user_can( 'admin_zerobs_customers' ) ) {
4679
4680        global $zbs;
4681
4682        // sanitize?
4683        $segmentID = -1;
4684        if ( isset( $_POST['sID'] ) ) {
4685            $segmentID = (int) $_POST['sID'];
4686        }
4687        $segmentTitle = __( 'Untitled Segment', 'zero-bs-crm' );
4688        if ( isset( $_POST['sTitle'] ) ) {
4689            $segmentTitle = sanitize_text_field( zeroBSCRM_textProcess( $_POST['sTitle'] ) );
4690        }
4691        $segmentMatchType = 'all';
4692        if ( isset( $_POST['sMatchType'] ) ) {
4693            $segmentMatchType = sanitize_text_field( $_POST['sMatchType'] );
4694        }
4695        $segmentConditions = array();
4696        if ( isset( $_POST['sConditions'] ) ) {
4697            $segmentConditions = zeroBSCRM_segments_filterConditions( $_POST['sConditions'] );
4698        }
4699
4700        // nice and simple, push to DAL (empty template ID will get created, else updated)
4701        $segmentID = $zbs->DAL->segments->addUpdateSegment( $segmentID, -1, $segmentTitle, $segmentConditions, $segmentMatchType, true );
4702
4703        if ( ! empty( $segmentID ) ) {
4704
4705            // return id / fail
4706            wp_send_json( array( 'id' => $segmentID ), 200, JSON_UNESCAPED_SLASHES ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4707
4708        }
4709    }
4710
4711    // empty handed
4712    exit( 0 );
4713}
4714
4715/*
4716======================================================
4717    / Admin AJAX: Segments
4718======================================================
4719*/
4720
4721/*
4722======================================================
4723    Admin AJAX: Top Menu
4724======================================================
4725*/
4726// } This is our toggle full screen mode for users to be able to control whether the CRM is fullscreen or not.
4727add_action( 'wp_ajax_zbs_admin_top_menu_save', 'zeroBSCRM_admin_top_menu_save' );
4728function zeroBSCRM_admin_top_menu_save() {
4729    // } Check nonce
4730    check_ajax_referer( 'zbscrmjs-ajax-nonce-topmenu', 'sec' );
4731    if ( zeroBSCRM_permsIsZBSUserOrAdmin() ) {
4732        // } current user
4733        $cid  = get_current_user_id();
4734        $hide = (int) sanitize_text_field( $_POST['hide'] );
4735        update_user_meta( $cid, 'zbs-hide-wp-menus', $hide );
4736    }
4737    wp_die();
4738}
4739
4740/*
4741======================================================
4742    / Admin AJAX: Top Menu
4743======================================================
4744*/
4745
4746/*
4747======================================================
4748    Admin AJAX: Tag Management
4749======================================================
4750*/
4751
4752add_action( 'wp_ajax_zbs_add_tag', 'zeroBSCRM_AJAX_addTag' );
4753function zeroBSCRM_AJAX_addTag() {
4754
4755    // } Check nonce
4756    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
4757
4758    // } Permission
4759    if ( zeroBSCRM_permsIsZBSUserOrAdmin() ) {
4760
4761        // } Get
4762        $objType = -1;
4763        if ( isset( $_POST['objtype'] ) && ! empty( $_POST['objtype'] ) ) {
4764            $objType = sanitize_text_field( $_POST['objtype'] );
4765        }
4766        $objTag = '';
4767        if ( isset( $_POST['tag'] ) && ! empty( $_POST['tag'] ) ) {
4768            $objTag = sanitize_text_field( $_POST['tag'] );
4769        }
4770
4771        if ( empty( $objType ) ) {
4772            wp_send_json_error( array( 'notag' => 1 ), 500, JSON_UNESCAPED_SLASHES );
4773            exit( 0 );
4774        }
4775
4776        global $zbs;
4777
4778        // this converts 'contact' => 1 and weeds out any wrongly-typed obj types
4779        $objTypeID = $zbs->DAL->objTypeID( $objType );
4780
4781        if ( $objTypeID !== -1 && $objTypeID > 0 ) {
4782
4783            // addtag to (OBJ) (WILL BE DAL2)
4784            $tagID = $zbs->DAL->addUpdateTag(
4785                array(
4786
4787                    'id'   => -1,
4788
4789                    // fields (directly)
4790                    'data' => array(
4791
4792                        'objtype' => $objTypeID,
4793                        'name'    => $objTag,
4794                        // 'slug'            => '',
4795                        // 'owner'           => -1
4796
4797                    ),
4798                )
4799            );
4800
4801            if ( ! empty( $tagID ) ) {
4802
4803                // retrieve just-made slug
4804                $slug = $zbs->DAL->getTag(
4805                    $tagID,
4806                    array(
4807                        'objtype'  => $objTypeID,
4808                        'onlySlug' => true,
4809                    )
4810                );
4811
4812                wp_send_json(
4813                    array(
4814                        'id'   => $tagID,
4815                        'slug' => $slug,
4816                    ),
4817                    200,
4818                    JSON_UNESCAPED_SLASHES
4819                );
4820            }
4821        } // if objtype match
4822
4823    }
4824
4825    wp_send_json_error( array( 'dataerr' => 1 ), 500, JSON_UNESCAPED_SLASHES );
4826}
4827
4828add_action( 'wp_ajax_zbs_delete_tag', 'zeroBSCRM_AJAX_deleteTag' );
4829function zeroBSCRM_AJAX_deleteTag() {
4830
4831    // } Check nonce
4832    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
4833
4834    // } Permission
4835    if ( zeroBSCRM_permsIsZBSUserOrAdmin() ) {
4836
4837        // } Get
4838        // $objType = -1; if (isset($_POST['objtype']) && !empty($_POST['objtype'])) $objType = (int)sanitize_text_field( $_POST['objtype'] );
4839        $objTagID = -1;
4840        if ( isset( $_POST['tagid'] ) && ! empty( $_POST['tagid'] ) ) {
4841            $objTagID = (int) $_POST['tagid'];
4842        }
4843
4844        if ( empty( $objTagID ) ) {
4845            wp_send_json_error( array( 'notag' => 1 ), 500, JSON_UNESCAPED_SLASHES );
4846        }
4847
4848        global $zbs;
4849
4850        if ( $objTagID !== -1 && $objTagID > 0 ) {
4851
4852            // addtag to (OBJ) (WILL BE DAL2)
4853            $res = $zbs->DAL->deleteTag(
4854                array(
4855
4856                    'id'          => $objTagID,
4857                    'deleteLinks' => true,
4858
4859                )
4860            );
4861
4862            wp_send_json( array( 'res' => $res ), 200, JSON_UNESCAPED_SLASHES );
4863
4864        } // if objtype match
4865
4866    }
4867
4868    wp_send_json_error( array( 'dataerr' => 1 ), 500, JSON_UNESCAPED_SLASHES );
4869}
4870
4871// } Preview a tagged group
4872add_action( 'wp_ajax_zbs_tags_previewtagged', 'zeroBSCRM_AJAX_previewTagged' );
4873function zeroBSCRM_AJAX_previewTagged() {
4874
4875    // } Check nonce
4876    check_ajax_referer( 'zbs-ajax-nonce', 'sec' );
4877
4878    if ( current_user_can( 'admin_zerobs_customers' ) ) {
4879
4880        global $zbs;
4881
4882        // sanitize?
4883        $tagID = -1;
4884        if ( isset( $_POST['tagID'] ) ) {
4885            $tagID = (int) $_POST['tagID'];
4886        }
4887        $tagMatchType = 'hastag';
4888        if ( isset( $_POST['tagMatchType'] ) ) {
4889            $tagMatchType = sanitize_text_field( $_POST['tagMatchType'] );
4890        }
4891
4892        // build quick search
4893        $contactArgs = array(
4894            'withCustomFields' => false, // not req
4895            'page'             => 0,
4896            'perPage'          => 5,
4897            'ignoreowner'      => true,
4898        );
4899
4900        if ( $tagMatchType == 'hastag' ) {
4901            $contactArgs['isTagged'] = $tagID;
4902        }
4903        if ( $tagMatchType == 'nohastag' ) {
4904            $contactArgs['isNotTagged'] = $tagID;
4905        }
4906
4907        // this is to get just the total count
4908        $countContactGetArgs            = $contactArgs;
4909        $countContactGetArgs['perPage'] = 100000;
4910        $countContactGetArgs['count']   = true;
4911
4912        // attempt to build a top 5 customer list + total count for this
4913        $ret = array(
4914            // DEBUG
4915            // 'args' => $contactArgs, // TEMP - remove this
4916            'count' => $zbs->DAL->contacts->getContacts( $countContactGetArgs ),
4917            'list'  => $zbs->DAL->contacts->getContacts( $contactArgs ),
4918        );
4919
4920        if ( is_array( $ret ) && isset( $ret['count'] ) ) {
4921
4922            // return id / fail
4923            wp_send_json( $ret, 200, JSON_UNESCAPED_SLASHES );
4924
4925        }
4926    }
4927
4928    // empty handed
4929    wp_send_json( array( 'count' => 0 ), 200, JSON_UNESCAPED_SLASHES );
4930}
4931
4932/*
4933======================================================
4934    / Admin AJAX: Tag Management
4935======================================================
4936*/
4937
4938/*
4939======================================================
4940    Admin AJAX: Screen options DAL2
4941======================================================
4942*/
4943
4944    // } Feedback
4945    add_action( 'wp_ajax_save_zbs_screen_options', 'zeroBSCRM_AJAX_saveScreenOptions' );
4946function zeroBSCRM_AJAX_saveScreenOptions() {
4947
4948    // } Check nonce
4949    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
4950
4951    // } Check is logged in legit user
4952    if ( ! zeroBS_canUpdateScreenOptions() ) {
4953        wp_send_json_error( array( 'err' => 'rights' ), 500, JSON_UNESCAPED_SLASHES );
4954    }
4955
4956    global $zbs;
4957
4958    // } This is the filtering model for all screenoptions :)
4959    $screenOptionsFilters = array(
4960
4961        // order of metaboxes for 'normal' area of page
4962        'mb_normal'    => array(
4963            'filter' => FILTER_UNSAFE_RAW,
4964            'flags'  => FILTER_FORCE_ARRAY,
4965        ),
4966        // order of metaboxes for 'side' area of page
4967        // e.g. 'key','key2'
4968        'mb_side'      => array(
4969            'filter' => FILTER_UNSAFE_RAW,
4970            'flags'  => FILTER_FORCE_ARRAY,
4971        ),
4972        // list of hidden metaboxes
4973        // e.g. 'key','key2'
4974        'mb_hidden'    => array(
4975            'filter' => FILTER_UNSAFE_RAW,
4976            'flags'  => FILTER_FORCE_ARRAY,
4977        ),
4978        // list of minimised metaboxes
4979        // e.g. 'key','key2'
4980        'mb_mini'      => array(
4981            'filter' => FILTER_UNSAFE_RAW,
4982            'flags'  => FILTER_FORCE_ARRAY,
4983        ),
4984
4985        // for now, this is a catchall :)
4986        'pageoptions'  => array(
4987            'filter' => FILTER_UNSAFE_RAW,
4988            'flags'  => FILTER_FORCE_ARRAY,
4989        ),
4990
4991        // selected table columns (currently just co view)
4992        'tablecolumns' => array(
4993            'filter' => FILTER_UNSAFE_RAW,
4994            'flags'  => FILTER_FORCE_ARRAY,
4995        ),
4996
4997        // perpage (only used for list pages, just an int)
4998        'perpage'      => FILTER_VALIDATE_INT,
4999
5000    );
5001
5002    $screenOpts = array();
5003    $pageKey    = '';
5004    if ( isset( $_POST['screenopts'] ) ) {
5005
5006        // get
5007        $screenOpts = $_POST['screenopts'];
5008
5009        // sanitize - http://php.net/manual/en/function.filter-var-array.php
5010        $screenOpts = filter_var_array( $screenOpts, $screenOptionsFilters );
5011
5012        // Formerly this used FILTER_SANITIZE_STRING, which is now deprecated as it was fairly broken. This is basically equivalent.
5013        // @todo Replace this with something more correct.
5014        foreach ( $screenOpts as $k => $v ) {
5015            if ( isset( $screenOptionsFilters[ $k ]['filter'] ) && $screenOptionsFilters[ $k ]['filter'] === FILTER_UNSAFE_RAW && $v !== null ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5016                foreach ( $v as $k2 => $v2 ) {
5017                    $screenOpts[ $k ][ $k2 ] = strtr(
5018                        strip_tags( $v2 ),
5019                        array(
5020                            "\0" => '',
5021                            '"'  => '&#34;',
5022                            "'"  => '&#39;',
5023                            '<'  => '',
5024                        )
5025                    );
5026                }
5027            }
5028        }
5029    }
5030    if ( isset( $_POST['pagekey'] ) ) {
5031        $pageKey = sanitize_text_field( $_POST['pagekey'] );
5032    }
5033
5034    if ( ! empty( $pageKey ) ) {
5035
5036        // } Brutally update
5037        $zbs->DAL->updateSetting( 'screenopts_' . $pageKey, $screenOpts ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5038
5039        wp_send_json( array( 'fini' => 1 ), 200, JSON_UNESCAPED_SLASHES );
5040
5041    }
5042
5043    wp_send_json_error( array( 'err' => 'pagekey' ), 500, JSON_UNESCAPED_SLASHES );
5044}
5045
5046/*
5047======================================================
5048    / Admin AJAX: Screen options DAL2
5049======================================================
5050*/
5051
5052/*
5053======================================================
5054    Admin AJAX: Inline Editor
5055======================================================
5056*/
5057
5058    // } Save any inline-edits
5059    add_action( 'wp_ajax_zbs_list_save_inline_edit', 'zeroBSCRM_AJAX_listViewInlineEdit_save' );
5060function zeroBSCRM_AJAX_listViewInlineEdit_save() {
5061
5062    // } Nonce
5063    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
5064
5065    global $zbs;
5066
5067    // } Retrieve deets
5068    $listtype = sanitize_text_field( $_POST['listtype'] );
5069    $id       = (int) sanitize_text_field( $_POST['id'] );
5070    $field    = sanitize_text_field( $_POST['field'] );
5071    $v        = sanitize_text_field( $_POST['v'] );
5072
5073    switch ( $listtype ) {
5074
5075        case 'customer':
5076            // } Perms
5077            if ( ! zeroBSCRM_permsCustomers() ) {
5078                wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
5079            }
5080
5081            // } check deets
5082            if ( $id > 0 && ! empty( $field ) ) {
5083
5084                $success = false;
5085                switch ( $field ) {
5086
5087                    case 'status':
5088                        $success = $zbs->DAL->contacts->setContactStatus( $id, $v );
5089                        break;
5090                    case 'assigned':
5091                        $success = $zbs->DAL->contacts->setContactOwner( $id, $v );
5092                        break;
5093
5094                }
5095
5096                if ( $success ) {
5097                    wp_send_json( array( 'success' => 1 ), 200, JSON_UNESCAPED_SLASHES );
5098                }
5099            }
5100
5101            break;
5102
5103    }
5104
5105    wp_send_json_error( array( 'no-action-or-rights' => 1 ), 500, JSON_UNESCAPED_SLASHES );
5106}
5107
5108/*
5109======================================================
5110    / Admin AJAX: Inline Editor
5111======================================================
5112*/
5113
5114/*
5115======================================================
5116    ZBS Invoicing
5117    ====================================================== */
5118
5119// } AJAX Send Inv
5120add_action( 'wp_ajax_zbs_invoice_send_invoice', 'zbs_invoice_send_invoice' );
5121function zbs_invoice_send_invoice() {
5122
5123    check_ajax_referer( 'inv-ajax-nonce', 'security' );
5124
5125    $zbs_invID = -1;
5126    $em        = '';
5127    $r         = array();
5128    if ( isset( $_POST['id'] ) && ! empty( $_POST['id'] ) ) {
5129        $zbs_invID = (int) $_POST['id']; // accepts the post ID
5130    }
5131    if ( ! empty( $_POST['em'] ) ) {
5132        $em = sanitize_email( wp_unslash( $_POST['em'] ) );
5133    }
5134
5135    // v3.0 changed var and added a few more:
5136    $attachAssignedDocs = false;
5137    $attachAsPDF        = false;
5138    if ( ! empty( $_POST['email'] ) ) {
5139        $em = sanitize_email( wp_unslash( $_POST['email'] ) );
5140    }
5141    if ( isset( $_POST['attachassoc'] ) && $_POST['attachassoc'] == 1 ) {
5142        $attachAssignedDocs = true;
5143    }
5144    if ( isset( $_POST['attachpdf'] ) && $_POST['attachpdf'] == 1 ) {
5145        $attachAsPDF = true;
5146    }
5147
5148    // validate the email
5149    if ( ! zeroBSCRM_validateEmail( $em ) ) {
5150        wp_send_json_error( array( 'message' => __( 'Not valid', 'zero-bs-crm' ) ), 500, JSON_UNESCAPED_SLASHES );
5151    }
5152
5153    // } Check id + perms + em
5154    if ( $zbs_invID <= 0 || empty( $em ) || ! zeroBSCRM_permsInvoices() ) {
5155        wp_send_json_error( array( 'message' => __( 'Not valid', 'zero-bs-crm' ) ), 500, JSON_UNESCAPED_SLASHES );
5156    }
5157
5158    $sent = zeroBSCRM_AJAX_sendInvoiceEmail_v3( $em, $zbs_invID, $attachAssignedDocs, $attachAsPDF );
5159
5160    if ( $sent ) {
5161
5162        // send result
5163        wp_send_json( array( 'message' => 'sent' ), 200, JSON_UNESCAPED_SLASHES );
5164
5165    }
5166
5167    // send err
5168    wp_send_json_error( array( 'message' => __( 'not sent', 'zero-bs-crm' ) ), 500, JSON_UNESCAPED_SLASHES );
5169}
5170
5171// v3.0+ send email for an invoice
5172function zeroBSCRM_AJAX_sendInvoiceEmail_v3( $email = '', $invoiceID = -1, $attachAssignedDocs = false, $attachAsPDF = false ) {
5173
5174    global $zbs;
5175
5176    $biz_name  = zeroBSCRM_getSetting( 'businessname' );
5177    $biz_extra = zeroBSCRM_getSetting( 'businessextra' );
5178
5179    // retrieve inv
5180    $invoice = $zbs->DAL->invoices->getInvoice(
5181        $invoiceID,
5182        array(
5183            // with what?
5184            'withLineItems'    => true,
5185            'withCustomFields' => true,
5186            'withTransactions' => true,
5187            'withAssigned'     => true,
5188            'withTags'         => true,
5189            'withOwner'        => true,
5190            'withFiles'        => true,
5191
5192        )
5193    );
5194
5195    // retrieve assoc records
5196    $contactID = -1;
5197    if ( is_array( $invoice ) && isset( $invoice['contact'] ) && is_array( $invoice['contact'] ) && count( $invoice['contact'] ) > 0 ) {
5198        $contactID = $invoice['contact'][0]['id'];
5199    }
5200    $companyID = -1;
5201    if ( is_array( $invoice ) && isset( $invoice['company'] ) && is_array( $invoice['company'] ) && count( $invoice['company'] ) > 0 ) {
5202        $companyID = $invoice['company'][0]['id'];
5203    }
5204    // now $contactID $cID =  get_post_meta($zbs_invID, 'zbs_customer_invoice_customer',true);
5205
5206    // } check if the email is active..
5207    $active = zeroBSCRM_get_email_status( ZBSEMAIL_EMAILINVOICE );
5208    if ( zeroBSCRM_validateEmail( $email ) && $invoiceID > 0 && $active ) {
5209
5210        // send welcome email (tracking will now be dealt with by zeroBSCRM_mailDelivery_sendMessage)
5211
5212        // ==========================================================================================
5213        // =================================== MAIL SENDING =========================================
5214
5215        // Attachments?
5216        $attachments = array();
5217        if ( $attachAssignedDocs ) {
5218            if ( isset( $invoice['files'] ) && is_array( $invoice['files'] ) && count( $invoice['files'] ) > 0 ) {
5219
5220                // cycle through files + add as attachments
5221                // we pass as 2part array so they don't have their funky md5 prefixes..
5222                foreach ( $invoice['files'] as $invFile ) {
5223
5224                    $filename = basename( $invFile['file'] );
5225                    // if in privatised system, ignore first hash in name
5226                    if ( isset( $invFile['priv'] ) ) {
5227
5228                        $filename = substr( $filename, strpos( $filename, '-' ) + 1 );
5229                    }
5230
5231                    $attachments[] = array( $invFile['file'], 'x' . $filename );
5232
5233                }
5234            }
5235        }
5236
5237        // Attach as PDF?
5238        if ( $attachAsPDF ) {
5239
5240            // make pdf.
5241
5242            // generate the PDF
5243            $pdf_path = jpcrm_invoice_generate_pdf( $invoiceID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5244
5245            if ( $pdf_path !== false ) {
5246
5247                // attach inv
5248                $attachments[] = array( $pdf_path );
5249
5250            }
5251
5252            // NOTE: for security / hygiene, we delete this PDF after email is sent
5253
5254        }
5255
5256        // generate html
5257        $emailHTML = zeroBSCRM_invoice_generateNotificationHTML( $invoiceID, true );
5258
5259            // build send array
5260            $mailArray = array(
5261                'toEmail'     => $email,
5262                'toName'      => '',
5263                'subject'     => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_EMAILINVOICE ),
5264                'headers'     => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_EMAILINVOICE ),
5265                'body'        => $emailHTML,
5266                'textbody'    => '',
5267                'attachments' => $attachments,
5268                'options'     => array(
5269                    'html' => 1,
5270                ),
5271            );
5272            // track if contactID
5273            if ( $contactID > 0 ) {
5274
5275                // senderWPID = -14 = new inv email to contact
5276                $mailArray['tracking'] = array(
5277                    // tracking :D (auto-inserted pixel + saved in history db)
5278                    'emailTypeID'     => ZBSEMAIL_EMAILINVOICE,
5279                    'targetObjID'     => $contactID,
5280                    'senderWPID'      => -14,
5281                    'associatedObjID' => $invoiceID,
5282                );
5283
5284            }
5285            // track if companyID
5286            if ( $companyID > 0 ) {
5287
5288                // senderWPID = -16 = new inv email to contact
5289                $mailArray['tracking'] = array(
5290                    // tracking :D (auto-inserted pixel + saved in history db)
5291                    'emailTypeID'     => ZBSEMAIL_EMAILINVOICE,
5292                    'targetObjID'     => $companyID,
5293                    'senderWPID'      => -16,
5294                    'associatedObjID' => $invoiceID,
5295                );
5296
5297            }
5298
5299            // DEBUG echo 'Sending:<pre>'; print_r($mailArray); echo '</pre>Result:';
5300
5301            // Sends email, including tracking, via setting stored route out, (or default if none)
5302            // and logs trcking :)
5303
5304            // discern del method
5305            $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_EMAILINVOICE );
5306            if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
5307                $mailDeliveryMethod = -1;
5308            }
5309
5310            // send
5311            $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
5312
5313            // delete any gen'd pdf's
5314            if ( $attachAsPDF && $pdf_path !== false ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5315
5316                // delete the PDF file once it's been read (i.e. emailed)
5317                wp_delete_file( $pdf_path );
5318
5319            }
5320
5321            // =================================== / MAIL SENDING =======================================
5322            // ==========================================================================================
5323
5324            // once the invoice is sent it will mark it as unpaid (automatically)
5325            // (if is draft)
5326            if ( isset( $invoice['status'] ) && $invoice['status'] === 'Draft' ) {
5327
5328                $zbs->DAL->invoices->setInvoiceStatus( $invoiceID, 'Unpaid' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5329
5330            }
5331
5332            return true;
5333
5334    } else {
5335
5336        // err
5337        return false;
5338
5339    }
5340}
5341
5342// } AJAX Send Inv
5343add_action( 'wp_ajax_zbs_invoice_send_statement', 'zeroBSCRM_AJAX_sendStatement' );
5344function zeroBSCRM_AJAX_sendStatement() {
5345
5346    // } Check nonce
5347    check_ajax_referer( 'zbscrmjs-glob-ajax-nonce', 'sec' );  // nonce to bounce out if not from right page
5348
5349    $cID = -1;
5350    $em  = '';
5351    $r   = array();
5352    if ( ! empty( $_POST['cid'] ) ) {
5353        $cID = (int) $_POST['cid']; // accepts the post ID
5354    }
5355    if ( ! empty( $_POST['em'] ) ) {
5356        $em = sanitize_email( wp_unslash( $_POST['em'] ) );
5357    }
5358
5359    // validate the email
5360    if ( ! zeroBSCRM_validateEmail( $em ) ) {
5361
5362        $r['error'] = __( 'Not a valid email', 'zero-bs-crm' );
5363        wp_send_json_error( $r, 500, JSON_UNESCAPED_SLASHES );
5364
5365    } else {
5366        $email = $em;
5367    }
5368
5369    // } Check id + perms + em
5370    if ( $cID <= 0 || empty( $email ) || ! zeroBSCRM_permsInvoices() ) {
5371
5372        $r['error'] = '';
5373        wp_send_json_error( $r, 500, JSON_UNESCAPED_SLASHES );
5374
5375    }
5376
5377    // ==== BUILD STATEMENT PDF
5378
5379        // generates pdf file
5380        $statementPDFfilepath = zeroBSCRM_invoicing_generateStatementPDF( $cID, false );
5381
5382        // check worked
5383    if ( ! file_exists( $statementPDFfilepath ) ) {
5384
5385        $r['error'] = '';
5386        wp_send_json_error( $r, 500, JSON_UNESCAPED_SLASHES );
5387
5388    }
5389
5390    // ==== SEND VIA EMAIL ATTACHMENT
5391    // ==========================================================================================
5392    // =================================== MAIL SENDING =========================================
5393
5394    // Attachment
5395    $attachments = array(
5396        array( $statementPDFfilepath, __( 'statement', 'zero-bs-crm' ) . '.pdf' ),
5397    );
5398
5399    // generate html
5400    $emailHTML = zeroBSCRM_statement_generateNotificationHTML( $cID, true );
5401
5402        // build send array
5403        $mailArray = array(
5404            'toEmail'     => $email,
5405            'toName'      => '',
5406            'subject'     => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_STATEMENT ),
5407            'headers'     => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_STATEMENT ),
5408            'body'        => $emailHTML,
5409            'textbody'    => '',
5410            'attachments' => $attachments,
5411            'options'     => array(
5412                'html' => 1,
5413            ),
5414            'tracking'    => array(
5415                // tracking :D (auto-inserted pixel + saved in history db)
5416                'emailTypeID'     => ZBSEMAIL_STATEMENT,
5417                'targetObjID'     => $cID,
5418                'senderWPID'      => -15, // wh added -15 you have a statement sent to customer,
5419                'associatedObjID' => -1,
5420            ),
5421        );
5422
5423        // DEBUG echo 'Sending:<pre>'; print_r($mailArray); echo '</pre>Result:';
5424
5425        // Sends email, including tracking, via setting stored route out, (or default if none)
5426        // and logs trcking :)
5427
5428        // discern del method
5429        $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_STATEMENT );
5430        if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
5431            $mailDeliveryMethod = -1;
5432        }
5433
5434        // send
5435        $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
5436
5437        // =================================== / MAIL SENDING =======================================
5438        // ==========================================================================================
5439
5440        // DELETE statement
5441        // delete the PDF file once it's been read (i.e. sent)
5442        unlink( $statementPDFfilepath );
5443
5444        $r['success'] = __( 'Sent', 'zero-bs-crm' );
5445        wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
5446}
5447
5448add_action( 'wp_ajax_zbs_invoice_mark_paid', 'zbs_invoice_mark_paid' );
5449function zbs_invoice_mark_paid() {
5450
5451    // } get if poss
5452    $zbs_invID = -1;
5453    if ( isset( $_POST['id'] ) && ! empty( $_POST['id'] ) ) {
5454        $zbs_invID = (int) sanitize_text_field( $_POST['id'] );  // accepts the post ID
5455    }
5456
5457    // } Check id + perms + em
5458    if ( $zbs_invID < 1 || ! zeroBSCRM_permsInvoices() ) {
5459
5460        die( 0 );
5461
5462    }
5463
5464    // } Continue
5465
5466    // once the invoice is sent it will mark it as unpaid (automatically)
5467    $zbs_inv_meta           = get_post_meta( $zbs_invID, 'zbs_customer_invoice_meta', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5468    $zbs_inv_meta['status'] = 'Paid';
5469    update_post_meta( $zbs_invID, 'zbs_customer_invoice_meta', $zbs_inv_meta ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5470
5471    // all OK ....
5472    $r = array( 'message' => 'All done OK' );
5473    wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
5474}
5475
5476// } and send test so they can test before actually sending the invoice
5477add_action( 'wp_ajax_zbs_invoice_send_test_invoice', 'zbs_invoice_send_test_invoice' );
5478function zbs_invoice_send_test_invoice() {
5479
5480    check_ajax_referer( 'inv-ajax-nonce', 'security' );
5481    $zbs_invID = -1;
5482    $em        = '';
5483    $r         = array();
5484
5485    if ( ! empty( $_POST['id'] ) ) {
5486        $zbs_invID = (int) $_POST['id']; // accepts the post ID
5487    }
5488    if ( ! empty( $_POST['em'] ) ) {
5489        $em = sanitize_email( wp_unslash( $_POST['em'] ) );
5490    }
5491
5492    // debug
5493    $r['em'] = $em;
5494    // debug $r['id'] = $zbs_invID;
5495
5496    // validate the email
5497    if ( ! zeroBSCRM_validateEmail( $em ) ) {
5498        $r['message'] = 'Not a valid email';
5499        wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
5500    } else {
5501        $email = $em;
5502    }
5503
5504    // } Check id + perms + em
5505    if ( $zbs_invID <= 0 || empty( $em ) || ! zeroBSCRM_permsInvoices() ) {
5506        die( 0 );
5507    }
5508
5509    $body = zeroBSCRM_invoice_generateNotificationHTML( $zbs_invID, true );
5510
5511    $biz_name  = zeroBSCRM_getSetting( 'businessname' );
5512    $biz_extra = zeroBSCRM_getSetting( 'businessextra' );
5513
5514    $subject     = '[Test Email] You have received an invoice';
5515    $headers     = array( 'Content-Type: text/html; charset=UTF-8' );
5516    $attachments = array();
5517
5518    /*
5519    WH did unbeknownst, separately //invoice attachments (actually called invoices but these now can be things like toggl timesheet reports(?) or T&Cs....
5520    $zbsCustomerInvoices = get_post_meta($zbs_invID, 'zbs_customer_invoices', true);
5521    foreach($zbsCustomerInvoices as $invoice){
5522        $attachments[] = $invoice['file'];
5523    }
5524    */
5525        // Attachments?
5526        $attachments        = array();
5527        $zbsSendAttachments = get_post_meta( $zbs_invID, 'zbs_inv_sendattachments', true );
5528    if ( $zbsSendAttachments == '1' ) {
5529        $invFiles = get_post_meta( $zbs_invID, 'zbs_customer_invoices', true );
5530        if ( is_array( $invFiles ) && count( $invFiles ) > 0 ) {
5531
5532            // cycle through files + add as attachments
5533            // we pass as 2part array so they don't have their funky md5 prefixes..
5534            foreach ( $invFiles as $invFile ) {
5535
5536                $filename = basename( $invFile['file'] );
5537                // if in privatised system, ignore first hash in name
5538                if ( isset( $invFile['priv'] ) ) {
5539
5540                    $filename = substr( $filename, strpos( $filename, '-' ) + 1 );
5541                }
5542
5543                $attachments[] = array( $invFile['file'], $filename );
5544
5545            }
5546        }
5547    }
5548
5549    // ah.. still uses WP mail - but this should still be sending.
5550    // wp_mail( $email, $subject, $body, $headers, $attachments );
5551
5552    /* new HTML send - to code up with actual invoice html (i.e. replace the body, properly) */
5553
5554    // $html = zeroBSCRM_mailTemplate_emailPreview($emailtab);
5555
5556    /*
5557    old way
5558
5559
5560    wp_mail( $test_email, $subject, $html, $headers );
5561
5562    */
5563
5564    // discern del method
5565    $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_EMAILINVOICE );
5566    if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
5567        $mailDeliveryMethod = -1;
5568    }
5569
5570    // build send array
5571    $mailArray = array(
5572        'toEmail'     => $email,
5573        'toName'      => '',
5574        'subject'     => $subject,
5575        'headers'     => $headers,
5576        'body'        => $body,
5577        'textbody'    => '',
5578        'attachments' => $attachments,
5579        'options'     => array(
5580            'html' => 1,
5581        ),
5582    );
5583
5584    // Sends email
5585    $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
5586
5587    // sends the invoice via wp_mail (for now)...
5588    $r['message'] = 'All done OK';
5589    wp_send_json( $r, 200, JSON_UNESCAPED_SLASHES );
5590}
5591
5592/*
5593Not req.
5594function my_custom_email_content_type() {
5595    return 'text/html';
5596}
5597*/
5598
5599// } We need to set the from email (mail campaigns may do this too?)
5600// } REMOVED - THESE FILTERS CHANGE IT FOR EVERYTHING. BEST DONE VIA THE HEADERS passed to wp_mail..
5601/*
5602add_filter( 'wp_mail_from', 'zbs_wp_mail_from' );
5603function zbs_wp_mail_from( $original_email_address ) {
5604    $f = zeroBSCRM_getSetting('invfromemail');
5605    if($f == ''){
5606    return $original_email_address;
5607    }else{
5608    return $f;
5609    }
5610}
5611
5612add_filter( 'wp_mail_from_name', 'zbs_wp_mail_from_name' );
5613function zbs_wp_mail_from_name( $original_email_from ) {
5614        $n = zeroBSCRM_getSetting('invfromname');
5615        if($n == ''){
5616            return $original_email_from;
5617        }else{
5618            return $n;
5619    }
5620}
5621*/
5622    add_action( 'wp_ajax_zbs_get_invoice_data', 'zeroBSCRM_AJAX_getInvoice' );
5623function zeroBSCRM_AJAX_getInvoice() {
5624
5625    // check nonce
5626    check_ajax_referer( 'zbscrmjs-ajax-nonce', 'sec' );
5627
5628    // check perms
5629    if ( ! zeroBSCRM_permsIsZBSUser() ) {
5630        wp_send_json_error( null, 500, JSON_UNESCAPED_SLASHES );
5631    }
5632
5633        // build + return
5634        $invID = -1;
5635    if ( isset( $_POST['invid'] ) ) {
5636        $invID = (int) $_POST['invid'];
5637    }
5638
5639    if ( $invID > 0 ) {
5640
5641        // retrieve ID
5642        $invID = (int) $_POST['invid'];
5643
5644        // retrieve obj to return
5645        $data = zeroBSCRM_invoicing_getInvoiceData( $invID );
5646
5647        // pass back in json
5648        wp_send_json( $data, 200, JSON_UNESCAPED_SLASHES );
5649
5650    } else {
5651
5652        // pass -1 if it is a new invoice (vs edit invoice)
5653        // defaults (invoice_id) will be the next available ID?
5654        // WH how do we handle the "New" creation want it to return defaults
5655        // but if a new invoice, the $objID will be -1?
5656        // WP makes and 'auto-draft' and gets that postID
5657        // so if 2 people make an invoice at once, it won't use the same ID.
5658        // probably need to consider this and race conditions on save or smt?
5659        // WH Notes: Agreed, for now just rolling this in, to discuss, (perhaps v3.1?)
5660
5661        // build default
5662        $data = array();
5663
5664        $data['invoiceObj']   = zeroBSCRM_get_invoice_defaults( -1 );
5665        $data['tax_linesObj'] = zeroBSCRM_taxRates_getTaxTableArr();
5666
5667        // pass back in json
5668        wp_send_json( $data, 200, JSON_UNESCAPED_SLASHES );
5669
5670    }
5671
5672        // exit json
5673        wp_send_json_error( array( 'here' ), 500, JSON_UNESCAPED_SLASHES );
5674}
5675
5676/*
5677======================================================
5678    / ZBS Invoicing
5679    ====================================================== */
5680
5681/*
5682======================================================
5683    Admin AJAX: Tasks
5684======================================================
5685*/
5686
5687add_action( 'wp_ajax_mark_task_complete', 'zeroBSCRM_ajax_mark_task_complete' );
5688function zeroBSCRM_ajax_mark_task_complete() {
5689
5690    check_ajax_referer( 'zbscrmjs-glob-ajax-nonce', 'sec' );
5691
5692    if ( ! zeroBSCRM_perms_tasks() ) {
5693        wp_send_json_error( array( 'permission_error' => 1 ), 403, JSON_UNESCAPED_SLASHES );
5694    }
5695
5696    global $zbs;
5697
5698    if ( isset( $_POST['status'] ) && isset( $_POST['taskID'] ) ) {
5699
5700        $status  = (int) $_POST['status'];
5701        $task_id = (int) $_POST['taskID'];
5702
5703        if ( $zbs->DAL->events->setEventCompleteness( $task_id, $status ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5704
5705            wp_send_json_success(
5706                array(
5707                    'task_id' => $task_id,
5708                    'status'  => $status,
5709                ),
5710                200,
5711                JSON_UNESCAPED_SLASHES
5712            );
5713        }
5714    }
5715
5716    wp_send_json_error( array( 'params_error' => 1 ), 400, JSON_UNESCAPED_SLASHES );
5717}
5718
5719/*
5720======================================================
5721    / Admin AJAX: Tasks
5722======================================================
5723*/