Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1528
0.00% covered (danger)
0.00%
0 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
zbsDAL_companies
0.00% covered (danger)
0.00%
0 / 1528
0.00% covered (danger)
0.00%
0 / 29
201152
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 add_listview_filters
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
 getSingle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIDList
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getAll
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getFullCount
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 get_company_id_by_name
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getCompany
0.00% covered (danger)
0.00%
0 / 209
0.00% covered (danger)
0.00%
0 / 1
2970
 getCompanies
0.00% covered (danger)
0.00%
0 / 435
0.00% covered (danger)
0.00%
0 / 1
18906
 getCompanyCount
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 addUpdateCompany
0.00% covered (danger)
0.00%
0 / 448
0.00% covered (danger)
0.00%
0 / 1
16770
 addUpdateCompanyTags
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
90
 deleteCompany
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
110
 tidy_company
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
240
 getCompanyMeta
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyEmail
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyOwner
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyStatus
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyNameEtc
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 getCompanyAddress
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 getCompany2ndAddress
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 getCompanyTags
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyLastContactUTS
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 setCompanyLastContactUTS
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanySocials
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
6
 getCompanyPrevNext
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 listViewObj
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
210
 db_ready_company
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 format_name_etc
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
156
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 * V3.0+
6 *
7 * Copyright 2020 Automattic
8 *
9 * Date: 14/01/19
10 */
11
12defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
13
14/**
15 * ZBS DAL >> Companies
16 *
17 * @author   Woody Hayday <hello@jetpackcrm.com>
18 * @version  2.0
19 * @access   public
20 * @see      https://jetpackcrm.com/kb
21 */
22class zbsDAL_companies extends zbsDAL_ObjectLayer {
23
24    protected $objectType              = ZBS_TYPE_COMPANY;
25    protected $objectDBPrefix          = 'zbsco_';
26    protected $objectIncludesAddresses = true;
27    protected $include_in_templating   = true;
28    protected $objectModel             = array(
29
30        // ID
31        'ID'            => array(
32            'fieldname' => 'ID',
33            'format'    => 'int',
34        ),
35
36        // site + team generics
37        'zbs_site'      => array(
38            'fieldname' => 'zbs_site',
39            'format'    => 'int',
40        ),
41        'zbs_team'      => array(
42            'fieldname' => 'zbs_team',
43            'format'    => 'int',
44        ),
45        'zbs_owner'     => array(
46            'fieldname' => 'zbs_owner',
47            'format'    => 'int',
48        ),
49
50        // other fields
51        'status'        => array(
52            // db model:
53            'fieldname'             => 'zbsco_status',
54            'format'                => 'str',
55            // output model
56            'input_type'            => 'select',
57            'label'                 => 'Status',
58            'placeholder'           => '',
59            'options'               => array( 'Lead', 'Customer', 'Refused' ),
60            'essential'             => true,
61            'max_len'               => 50,
62            'do_not_show_on_portal' => true,
63        ),
64        'name'          => array(
65            // db model:
66            'fieldname'    => 'zbsco_name',
67            'format'       => 'str',
68            // output model
69            'input_type'   => 'text',
70            'label'        => 'Name',
71            'placeholder'  => 'e.g. NewCo',
72            'dal1key'      => 'coname',
73            'essential'    => true,
74            'force_unique' => true, // must be unique. This is required and breaking if true,
75            'not_empty'    => true,
76            'max_len'      => 100,
77        ),
78
79        'addr1'         => array(
80            // db model:
81            'fieldname'   => 'zbsco_addr1',
82            'format'      => 'str',
83            // output model
84            'input_type'  => 'text',
85            'label'       => 'Address Line 1',
86            'placeholder' => '',
87            'area'        => 'Main Address',
88            'migrate'     => 'addresses',
89            'max_len'     => 200,
90        ),
91        'addr2'         => array(
92            // db model:
93            'fieldname'   => 'zbsco_addr2',
94            'format'      => 'str',
95            // output model
96            'input_type'  => 'text',
97            'label'       => 'Address Line 2',
98            'placeholder' => '',
99            'area'        => 'Main Address',
100            'migrate'     => 'addresses',
101            'max_len'     => 200,
102        ),
103        'city'          => array(
104            // db model:
105            'fieldname'   => 'zbsc_city',
106            'format'      => 'str',
107            // output model
108            'input_type'  => 'text',
109            'label'       => 'City',
110            'placeholder' => 'e.g. New York',
111            'area'        => 'Main Address',
112            'migrate'     => 'addresses',
113            'max_len'     => 200,
114        ),
115        'county'        => array(
116            // db model:
117            'fieldname'   => 'zbsco_county',
118            'format'      => 'str',
119            // output model
120            'input_type'  => 'text',
121            'label'       => 'County',
122            'placeholder' => 'e.g. Kings County',
123            'area'        => 'Main Address',
124            'migrate'     => 'addresses',
125            'max_len'     => 200,
126        ),
127        'postcode'      => array(
128            // db model:
129            'fieldname'   => 'zbsco_postcode',
130            'format'      => 'str',
131            // output model
132            'input_type'  => 'text',
133            'label'       => 'Post Code',
134            'placeholder' => 'e.g. 10019',
135            'area'        => 'Main Address',
136            'migrate'     => 'addresses',
137            'max_len'     => 50,
138        ),
139        'country'       => array(
140            // db model:
141            'fieldname'   => 'zbsco_country',
142            'format'      => 'str',
143            // output model
144            'input_type'  => 'selectcountry',
145            'label'       => 'Country',
146            'placeholder' => '',
147            'area'        => 'Main Address',
148            'migrate'     => 'addresses',
149            'max_len'     => 200,
150        ),
151
152        'secaddr1'      => array(
153            // db model:
154            'fieldname'   => 'zbsco_secaddr1',
155            'format'      => 'str',
156            // output model
157            'input_type'  => 'text',
158            'label'       => 'Address Line 1',
159            'placeholder' => '',
160            'area'        => 'Second Address',
161            'migrate'     => 'addresses',
162            'opt'         => 'secondaddress',
163            'max_len'     => 200,
164            'dal1key'     => 'secaddr_addr1', // previous field name
165        ),
166        'secaddr2'      => array(
167            // db model:
168            'fieldname'   => 'zbsco_secaddr2',
169            'format'      => 'str',
170            // output model
171            'input_type'  => 'text',
172            'label'       => 'Address Line 2',
173            'placeholder' => '',
174            'area'        => 'Second Address',
175            'migrate'     => 'addresses',
176            'opt'         => 'secondaddress',
177            'max_len'     => 200,
178            'dal1key'     => 'secaddr_addr2', // previous field name
179        ),
180        'seccity'       => array(
181            // db model:
182            'fieldname'   => 'zbsco_seccity',
183            'format'      => 'str',
184            // output model
185            'input_type'  => 'text',
186            'label'       => 'City',
187            'placeholder' => 'e.g. Los Angeles',
188            'area'        => 'Second Address',
189            'migrate'     => 'addresses',
190            'opt'         => 'secondaddress',
191            'max_len'     => 200,
192            'dal1key'     => 'secaddr_city', // previous field name
193        ),
194        'seccounty'     => array(
195            // db model:
196            'fieldname'   => 'zbsco_seccounty',
197            'format'      => 'str',
198            // output model
199            'input_type'  => 'text',
200            'label'       => 'County',
201            'placeholder' => 'e.g. Los Angeles',
202            'area'        => 'Second Address',
203            'migrate'     => 'addresses',
204            'opt'         => 'secondaddress',
205            'max_len'     => 200,
206            'dal1key'     => 'secaddr_county', // previous field name
207        ),
208        'secpostcode'   => array(
209            // db model:
210            'fieldname'   => 'zbsco_secpostcode',
211            'format'      => 'str',
212            // output model
213            'input_type'  => 'text',
214            'label'       => 'Post Code',
215            'placeholder' => 'e.g. 90001',
216            'area'        => 'Second Address',
217            'migrate'     => 'addresses',
218            'opt'         => 'secondaddress',
219            'max_len'     => 50,
220            'dal1key'     => 'secaddr_postcode', // previous field name
221        ),
222        'seccountry'    => array(
223            // db model:
224            'fieldname'   => 'zbsco_seccountry',
225            'format'      => 'str',
226            // output model
227            'input_type'  => 'selectcountry',
228            'label'       => 'Country',
229            'placeholder' => '',
230            'area'        => 'Second Address',
231            'migrate'     => 'addresses',
232            'opt'         => 'secondaddress',
233            'max_len'     => 200,
234            'dal1key'     => 'secaddr_country', // previous field name
235        ),
236
237        'maintel'       => array(
238            // db model:
239            'fieldname'   => 'zbsco_maintel',
240            'format'      => 'str',
241            // output model
242            'input_type'  => 'tel',
243            'label'       => 'Main Telephone',
244            'placeholder' => 'e.g. 877 2733049',
245            'max_len'     => 40,
246        ),
247        'sectel'        => array(
248            // db model:
249            'fieldname'   => 'zbsco_sectel',
250            'format'      => 'str',
251            // output model
252            'input_type'  => 'tel',
253            'label'       => 'Secondary Telephone',
254            'placeholder' => 'e.g. 877 2733049',
255            'max_len'     => 40,
256        ),
257        'email'         => array(
258            // db model:
259            'fieldname'             => 'zbsco_email',
260            'format'                => 'str',
261            // output model
262            'input_type'            => 'email',
263            'label'                 => 'Main Email Address',
264            'placeholder'           => 'e.g. hello@company.com',
265            'max_len'               => 200,
266            'force_unique'          => true, // must be unique. This is required and breaking if true
267            'can_be_blank'          => true,
268            'do_not_show_on_portal' => true,
269        ),
270
271        // ... just removed for DAL3 :) should be custom field anyway by this point
272
273        'wpid'          => array(
274            'fieldname' => 'zbsco_wpid',
275            'format'    => 'int',
276        ),
277        'avatar'        => array(
278            'fieldname' => 'zbsco_avatar',
279            'format'    => 'str',
280        ),
281        'tw'            => array(
282            'fieldname' => 'zbsco_tw',
283            'format'    => 'str',
284            'max_len'   => 100,
285        ),
286        'li'            => array(
287            'fieldname' => 'zbsco_li',
288            'format'    => 'str',
289            'max_len'   => 300,
290        ),
291        'fb'            => array(
292            'fieldname' => 'zbsco_fb',
293            'format'    => 'str',
294            'max_len'   => 200,
295        ),
296        'created'       => array(
297            'fieldname' => 'zbsco_created',
298            'format'    => 'uts',
299        ),
300        'lastupdated'   => array(
301            'fieldname' => 'zbsco_lastupdated',
302            'format'    => 'uts',
303        ),
304        'lastcontacted' => array(
305            'fieldname' => 'zbsco_lastcontacted',
306            'format'    => 'uts',
307        ),
308
309    );
310
311    function __construct( $args = array() ) {
312
313        #} =========== LOAD ARGS ==============
314        $defaultArgs = array(
315
316            // 'tag' => false,
317
318        );
319        foreach ( $defaultArgs as $argK => $argV ) {
320            $this->$argK = $argV;
321            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
322                if ( is_array( $args[ $argK ] ) ) {
323                    $newData = $this->$argK;
324                    if ( ! is_array( $newData ) ) {
325                        $newData = array();
326                    } foreach ( $args[ $argK ] as $subK => $subV ) {
327                        $newData[ $subK ] = $subV;
328                    }$this->$argK = $newData;
329                } else {
330                    $this->$argK = $args[ $argK ]; }
331            }
332        }
333        #} =========== / LOAD ARGS =============
334
335            add_filter( 'jpcrm_listview_filters', array( $this, 'add_listview_filters' ) );
336    }
337
338    /**
339     * Adds items to listview filter using `jpcrm_listview_filters` hook.
340     *
341     * @param array $listview_filters Listview filters.
342     */
343    public function add_listview_filters( $listview_filters ) {
344        global $zbs;
345
346        $quick_filter_settings = $zbs->settings->get( 'quickfiltersettings' );
347
348        // Add 'not-contacted-in-x-days'.
349        if ( ! empty( $quick_filter_settings['notcontactedinx'] ) && $quick_filter_settings['notcontactedinx'] > 0 ) {
350            $days = (int) $quick_filter_settings['notcontactedinx'];
351            $listview_filters[ ZBS_TYPE_COMPANY ]['general'][ 'notcontactedin' . $days ] = sprintf(
352                // translators: %s is the number of days
353                __( 'Not Contacted in %s days', 'zero-bs-crm' ),
354                $days
355            );
356        }
357
358        // Add 'olderthan-x-days'.
359        if ( ! empty( $quick_filter_settings['olderthanx'] ) && $quick_filter_settings['olderthanx'] > 0 ) {
360            $days = (int) $quick_filter_settings['olderthanx'];
361            $listview_filters[ ZBS_TYPE_COMPANY ]['general'][ 'olderthan' . $days ] = sprintf(
362                // translators: %s is the number of days
363                __( 'Older than %s days', 'zero-bs-crm' ),
364                $days
365            );
366        }
367
368        // Add statuses if enabled.
369        if ( $zbs->settings->get( 'filtersfromstatus' ) === 1 ) {
370            $statuses = zeroBSCRM_getCompanyStatuses();
371            foreach ( $statuses as $status ) {
372                $listview_filters[ ZBS_TYPE_COMPANY ]['status'][ 'status_' . $status ] = $status;
373            }
374        }
375
376        return $listview_filters;
377    }
378
379    // generic get Company (by ID)
380    // Super simplistic wrapper used by edit page etc. (generically called via dal->contacts->getSingle etc.)
381    public function getSingle( $ID = -1 ) {
382
383        return $this->getCompany( $ID );
384    }
385
386    // generic get contact (by ID list)
387    // Super simplistic wrapper used by MVP Export v3.0
388    public function getIDList( $IDs = false ) {
389
390        return $this->getCompanies(
391            array(
392                'inArr'            => $IDs,
393                'withCustomFields' => true,
394                'page'             => -1,
395                'perPage'          => -1,
396            )
397        );
398    }
399
400    // generic get (EVERYTHING)
401    // expect heavy load!
402    public function getAll( $IDs = false ) {
403
404        return $this->getCompanies(
405            array(
406                'withCustomFields' => true,
407                'sortByField'      => 'ID',
408                'sortOrder'        => 'ASC',
409                'page'             => -1,
410                'perPage'          => -1,
411            )
412        );
413    }
414
415    // generic get count of (EVERYTHING)
416    public function getFullCount() {
417
418        return $this->getCompanies(
419            array(
420                'count'   => true,
421                'page'    => -1,
422                'perPage' => -1,
423            )
424        );
425    }
426
427    /**
428     * Retrieves the company ID by its name.
429     *
430     * @param  string $name  The name of the company whose ID is to be retrieved.
431     * @return int|bool      Returns the ID of the company if found, false otherwise.
432     *
433     * @throws Exception     Catches and handles exceptions, logging SQL errors.
434     *
435     * @since  6.2.0
436     */
437    public function get_company_id_by_name( $name ) {
438        global $ZBSCRM_t; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
439        global $wpdb;
440
441        try {
442            $query  = $this->prepare( 'SELECT ID FROM ' . $ZBSCRM_t['companies'] . ' WHERE zbsco_name LIKE %s', $name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
443            $result = $wpdb->get_row( $query, OBJECT ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.NotPrepared
444
445            if ( isset( $result ) && ( $result->ID ) ) {
446                return $result->ID;
447            }
448        } catch ( Exception $e ) {
449            $this->catchSQLError( $e );
450        }
451        return false;
452    }
453
454    /**
455     * returns full company line +- details
456     *
457     * @param int id        company id
458     * @param array                    $args   Associative array of arguments
459     *
460     * @return array company object
461     */
462    public function getCompany( $id = -1, $args = array() ) {
463
464        global $zbs;
465
466        #} =========== LOAD ARGS ==============
467        $defaultArgs = array(
468
469            'email'                      => false, // if id -1 and email given, will return based on email
470            'name'                       => false, // if id -1 and name given, will return based on name
471
472            // if theset wo passed, will search based on these
473            'externalSource'             => false,
474            'externalSourceUID'          => false,
475
476            // with what?
477            'withCustomFields'           => true,
478            'withQuotes'                 => false,
479            'withInvoices'               => false,
480            'withTransactions'           => false,
481            'withTasks'                  => false,
482            // 'withLogs'          => false,
483            'withLastLog'                => false,
484            'withTags'                   => false,
485            'withOwner'                  => false,
486            'withValues'                 => false, // if passed, returns with 'total' 'invoices_total' 'transactions_total' etc. (requires getting all obj, use sparingly)
487            'withContacts'               => true, // return ['contact'] objs
488            'withExternalSources'        => false,
489            'withExternalSourcesGrouped' => false,
490
491            // permissions
492            'ignoreowner'                => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ), // this'll let you not-check the owner of obj
493
494            // returns scalar ID of line
495            'onlyID'                     => false,
496
497            'fields'                     => false, // false = *, array = fieldnames
498
499        );
500        foreach ( $defaultArgs as $argK => $argV ) {
501            $$argK = $argV;
502            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
503                if ( is_array( $args[ $argK ] ) ) {
504                    $newData = $$argK;
505                    if ( ! is_array( $newData ) ) {
506                        $newData = array();
507                    } foreach ( $args[ $argK ] as $subK => $subV ) {
508                        $newData[ $subK ] = $subV;
509                    }$$argK = $newData;
510                } else {
511                    $$argK = $args[ $argK ]; }
512            }
513        }
514        #} =========== / LOAD ARGS =============
515
516        #} Check ID
517        $id = (int) $id;
518        if (
519            ( ! empty( $id ) && $id > 0 )
520            ||
521            ( ! empty( $email ) )
522                    ||
523                    ( ! empty( $name ) )
524            ||
525            ( ! empty( $externalSource ) && ! empty( $externalSourceUID ) )
526            ) {
527
528            global $ZBSCRM_t, $wpdb;
529            $wheres          = array( 'direct' => array() );
530            $whereStr        = '';
531            $additionalWhere = '';
532            $params          = array();
533            $res             = array();
534            $extraSelect     = '';
535
536            #} ============= PRE-QUERY ============
537
538                #} Custom Fields
539            if ( $withCustomFields && ! $onlyID ) {
540
541                #} Retrieve any cf
542                $custFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_COMPANY ) );
543
544                #} Cycle through + build into query
545                if ( is_array( $custFields ) ) {
546                    foreach ( $custFields as $cK => $cF ) {
547
548                        // add as subquery
549                        $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . " WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d LIMIT 1) '" . $cK . "'";
550
551                        // add params
552                        $params[] = $cK;
553                        $params[] = ZBS_TYPE_COMPANY;
554
555                    }
556                }
557            }
558
559                // Add any addr custom fields for addr1+addr2
560                $addrCustomFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_ADDRESS ) );
561            if ( $withCustomFields && ! $onlyID && is_array( $addrCustomFields ) && count( $addrCustomFields ) > 0 ) {
562
563                foreach ( $addrCustomFields as $cK => $cF ) {
564
565                    // custom field key
566                    $cfKey  = 'addr_' . $cK;
567                    $cfKey2 = 'secaddr_' . $cK;
568
569                    // address custom field (e.g. 'house name') it'll be passed here as 'house-name'
570                    // ... problem is mysql does not like that :) so we have to chage here:
571                    // in this case we prepend address cf's with addr_ and we switch - for _
572                    $cKey  = 'addrcf_' . str_replace( '-', '_', $cK );
573                    $cKey2 = 'secaddrcf_' . str_replace( '-', '_', $cK );
574
575                    // addr1
576                        // add as subquery
577                        $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d) ' . $cKey;
578                        // add params
579                        $params[] = $cfKey;
580                    $params[]     = ZBS_TYPE_COMPANY;
581                    // addr2
582                        // add as subquery
583                        $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d) ' . $cKey2;
584                        // add params
585                        $params[] = $cfKey2;
586                    $params[]     = ZBS_TYPE_COMPANY;
587
588                }
589            }
590
591                // ==== TOTAL VALUES
592
593                // Calculate total vals etc. with SQL
594            if ( ! $onlyID && $withValues ) {
595
596                // arguably, if getting $withInvoices etc. may be more performant to calc this in php in AFTER loop,
597                // ... for now as a fair guess, this'll be most performant:
598                // ... we calc total by adding invs + trans below :)
599
600                // only include transactions with statuses which should be included in total value:
601                $transStatusQueryAdd = $this->DAL()->transactions->getTransactionStatusesToIncludeQuery();
602                // include invoices without deleted status in the total value for invoices_total_inc_deleted:
603                $inv_status_query_add = $this->DAL()->invoices->get_invoice_status_except_deleted_for_query();
604
605                // quotes:
606                $extraSelect .= ',(SELECT SUM(quotestotal.zbsq_value) FROM ' . $ZBSCRM_t['quotes'] . ' as quotestotal WHERE quotestotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_QUOTE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)) as quotes_total';
607                // invs:
608                $extraSelect .= ',(SELECT IFNULL(SUM(invstotal.zbsi_total),0) FROM ' . $ZBSCRM_t['invoices'] . ' as invstotal WHERE invstotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $inv_status_query_add . ') as invoices_total'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
609                // invs including deleted:
610                $extraSelect .= ',(SELECT SUM(invstotalincdeleted.zbsi_total) FROM ' . $ZBSCRM_t['invoices'] . ' as invstotalincdeleted WHERE invstotalincdeleted.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)) as invoices_total_inc_deleted'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
611                // invs count:
612                $extraSelect .= ',(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $inv_status_query_add . ') as invoices_count'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
613                // invs count including deleted:
614                $extraSelect .= ',(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)) as invoices_count_inc_deleted'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
615                // trans (with status):
616                $extraSelect .= ',(SELECT SUM(transtotal.zbst_total) FROM ' . $ZBSCRM_t['transactions'] . ' as transtotal WHERE transtotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $transStatusQueryAdd . ') as transactions_total';
617                // paid balance against invs  (also in getCompany)
618                // (this allows us to subtract from totals to get a true figure where transactions are part/whole payments for invs)
619                /*
620                    This selects transactions
621                        where there is a link to an invoice
622                            where that invoice has a link to this contact:
623
624                    ==========
625
626                    SELECT * FROM wp_zbs_transactions trans
627                    WHERE trans.ID IN
628
629                        (
630                            SELECT DISTINCT zbsol_objid_from FROM `wp_zbs_object_links`
631                            WHERE zbsol_objtype_from = 5
632                            AND zbsol_objtype_to = 4
633                            AND zbsol_objid_to IN
634
635                                (
636
637                                    SELECT DISTINCT zbsol_objid_from FROM `wp_zbs_object_links`
638                                    WHERE zbsol_objtype_from = 4 AND zbsol_objtype_to = 1 AND zbsol_objid_to = 1
639
640                                )
641
642                        )
643
644
645                */
646                $extraSelect .= ',(SELECT SUM(assignedtranstotal.zbst_total) FROM ' . $ZBSCRM_t['transactions'] . ' assignedtranstotal WHERE assignedtranstotal.ID IN ';
647                $extraSelect .= '(SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objid_to IN ';
648                $extraSelect .= '(SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)';
649                $extraSelect .= ')' . $transStatusQueryAdd . ') as transactions_paid_total';
650            }
651
652                // ==== / TOTAL VALUES
653
654                $selector = 'company.*';
655            if ( is_array( $fields ) ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
656                    $selector = '';
657
658                    // always needs id, so add if not present
659                if ( ! in_array( 'ID', $fields ) ) {
660                    $selector = 'company.ID';
661                }
662
663                foreach ( $fields as $f ) {
664                    if ( ! empty( $selector ) ) {
665                        $selector .= ',';
666                    }
667                    $selector .= 'company.' . $f;
668                }
669            } elseif ( $onlyID ) {
670                $selector = 'company.ID';
671            }
672
673            #} ============ / PRE-QUERY ===========
674
675            #} Build query
676            $query = 'SELECT ' . $selector . $extraSelect . ' FROM ' . $ZBSCRM_t['companies'] . ' as company';
677            #} ============= WHERE ================
678
679            if ( ! empty( $id ) && $id > 0 ) {
680
681                #} Add ID
682                $wheres['ID'] = array( 'ID', '=', '%d', $id );
683
684            }
685
686            if ( ! empty( $email ) ) {
687
688                $emailWheres = array();
689
690                // simple, inc AKA (even tho not in UI yet :))
691                // nope$wheres['emailcheck'] = array('zbsco_email','=','%s',$email);
692
693                #} Add ID
694                $emailWheres['emailcheck'] = array( 'zbsco_email', '=', '%s', $email );
695
696                #} Check AKA
697                $emailWheres['email_alias'] = array( 'ID', 'IN', '(SELECT aka_id FROM ' . $ZBSCRM_t['aka'] . ' WHERE aka_type = ' . ZBS_TYPE_COMPANY . ' AND aka_alias = %s)', $email );
698
699                // This generates a query like 'zbsc_email = %s OR zbsc_email2 = %s',
700                // which we then need to include as direct subquery (below) in main query :)
701                $emailSearchQueryArr = $this->buildWheres( $emailWheres, '', array(), 'OR', false );
702
703                if ( is_array( $emailSearchQueryArr ) && isset( $emailSearchQueryArr['where'] ) && ! empty( $emailSearchQueryArr['where'] ) ) {
704
705                    // add it
706                    $wheres['direct'][] = array( '(' . $emailSearchQueryArr['where'] . ')', $emailSearchQueryArr['params'] );
707
708                }
709            }
710
711            if ( ! empty( $name ) ) {
712
713                // simple
714                $wheres['name'] = array( 'zbsco_name', '=', '%s', $name );
715
716            }
717
718            if ( ! empty( $externalSource ) && ! empty( $externalSourceUID ) ) {
719
720                $wheres['extsourcecheck'] = array( 'ID', 'IN', '(SELECT DISTINCT zbss_objid FROM ' . $ZBSCRM_t['externalsources'] . ' WHERE zbss_objtype = ' . ZBS_TYPE_COMPANY . ' AND zbss_source = %s AND zbss_uid = %s)', array( $externalSource, $externalSourceUID ) );
721
722            }
723
724            #} ============ / WHERE ==============
725
726            #} Build out any WHERE clauses
727            $wheresArr = $this->buildWheres( $wheres, $whereStr, $params );
728            $whereStr  = $wheresArr['where'];
729            $params    = $params + $wheresArr['params'];
730            #} / Build WHERE
731
732            #} Ownership v1.0 - the following adds SITE + TEAM checks, and (optionally), owner
733            $params = array_merge( $params, $this->ownershipQueryVars( $ignoreowner ) ); // merges in any req.
734            $ownQ   = $this->ownershipSQL( $ignoreowner );
735            if ( ! empty( $ownQ ) ) {
736                $additionalWhere = $this->spaceAnd( $additionalWhere ) . $ownQ; // adds str to query
737            }
738            #} / Ownership
739
740            #} Append to sql (this also automatically deals with sortby and paging)
741            $query .= $this->buildWhereStr( $whereStr, $additionalWhere ) . $this->buildSort( 'ID', 'DESC' ) . $this->buildPaging( 0, 1 );
742
743            try {
744
745                #} Prep & run query
746                $queryObj     = $this->prepare( $query, $params );
747                $potentialRes = $wpdb->get_row( $queryObj, OBJECT );
748
749            } catch ( Exception $e ) {
750
751                #} General SQL Err
752                $this->catchSQLError( $e );
753
754            }
755
756            #} Interpret Results (ROW)
757            if ( isset( $potentialRes ) && isset( $potentialRes->ID ) ) {
758
759                #} Has results, tidy + return
760
761                    #} Only ID? return it directly
762                if ( $onlyID ) {
763                    return $potentialRes->ID;
764                }
765
766                    // tidy
767                if ( is_array( $fields ) ) {
768                    // guesses fields based on table col names
769                    $res = $this->lazyTidyGeneric( $potentialRes );
770                } else {
771                    // proper tidy
772                    $res = $this->tidy_company( $potentialRes, $withCustomFields );
773                }
774
775                    // TWO methods here, this feels more "common sense" (other commented out below.)
776                if ( $withInvoices ) {
777
778                    #} only gets first 100?
779                    // DAL3 ver, more perf, gets all
780                    $res['invoices'] = $zbs->DAL->invoices->getInvoices(
781                        array(
782
783                            'assignedCompany' => $potentialRes->ID, // assigned to company id (int)
784                            'page'            => -1,
785                            'perPage'         => -1,
786                            'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_INVOICE ),
787
788                        )
789                    );
790
791                }
792
793                if ( $withQuotes ) {
794
795                    // DAL3 ver, more perf, gets all
796                    $res['quotes'] = $zbs->DAL->quotes->getQuotes(
797                        array(
798
799                            'assignedCompany' => $potentialRes->ID, // assigned to company id (int)
800                            'page'            => -1,
801                            'perPage'         => -1,
802                            'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
803
804                        )
805                    );
806
807                }
808
809                    #} ... brutal for mvp #DB1LEGACY (TOMOVE)
810                if ( $withTransactions ) {
811
812                    // DAL3 ver, more perf, gets all
813                    $res['transactions'] = $zbs->DAL->transactions->getTransactions(
814                        array(
815
816                            'assignedCompany' => $potentialRes->ID, // assigned to company id (int)
817                            'page'            => -1,
818                            'perPage'         => -1,
819                            'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
820
821                        )
822                    );
823
824                }
825                    /*
826                    #} With quotes?
827                    if ($withQuotes){
828
829                        // add all quotes lines
830                        $res['quotes'] = $this->DAL()->getObjsLinkedToObj(array(
831                                'objtypefrom'   =>  ZBS_TYPE_QUOTE, // quote
832                                'objtypeto'     =>  ZBS_TYPE_COMPANY, // company
833                                'objfromid'     =>  $potentialRes->ID));
834
835                    }
836
837                    #} With invoices?
838                    if ($withQuotes){
839
840                        // add all invoices lines
841                        $res['invoices'] = $this->DAL()->getObjsLinkedToObj(array(
842                                'objtypefrom'   =>  ZBS_TYPE_INVOICE, // invoice
843                                'objtypeto'     =>  ZBS_TYPE_COMPANY, // company
844                                'objfromid'     =>  $potentialRes->ID));
845
846                    }
847
848                    #} With transactions?
849                    if ($withTransactions){
850
851                        // add all transactions lines
852                        $res['transactions'] = $this->DAL()->getObjsLinkedToObj(array(
853                                'objtypefrom'   =>  ZBS_TYPE_TRANSACTION, // transaction
854                                'objtypeto'     =>  ZBS_TYPE_COMPANY, // company
855                                'objfromid'     =>  $potentialRes->ID));
856
857                    } */
858
859                    #} With most recent log? #DB1LEGACY (TOMOVE)
860                if ( $withLastLog ) {
861
862                    $res['lastlog'] = $this->DAL()->logs->getLogsForObj(
863                        array(
864
865                            'objtype'     => ZBS_TYPE_COMPANY,
866                            'objid'       => $potentialRes->ID,
867
868                            'incMeta'     => true,
869
870                            'sortByField' => 'zbsl_created',
871                            'sortOrder'   => 'DESC',
872                            'page'        => 0,
873                            'perPage'     => 1,
874
875                        )
876                    );
877
878                }
879
880                if ( $withContacts ) {
881
882                        // add all assigned contacts/companies
883                        $res['contacts'] = $this->DAL()->contacts->getContacts(
884                            array(
885                                'isLinkedToObjType' => ZBS_TYPE_COMPANY,
886                                'isLinkedToObjID'   => $potentialRes->ID,
887                                'page'              => -1,
888                                'perPage'           => 100, // commonsense cap
889                                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
890                            )
891                        );
892
893                }
894
895                if ( $withTags ) {
896
897                    // add all tags lines
898                    $res['tags'] = $this->DAL()->getTagsForObjID(
899                        array(
900                            'objtypeid' => ZBS_TYPE_COMPANY,
901                            'objid'     => $potentialRes->ID,
902                        )
903                    );
904
905                }
906
907                    #} With Assigned?
908                if ( $withOwner ) {
909
910                    $res['owner'] = zeroBS_getOwner( $potentialRes->ID, true, 'zerobs_company', $potentialRes->zbs_owner );
911
912                }
913
914                    #} With Tasks?
915                if ( $withTasks ) {
916
917                    // DAL3 ver, more perf, gets all
918                    $res['tasks'] = $zbs->DAL->events->getEvents(
919                        array(
920
921                            'assignedCompany' => $potentialRes->ID, // assigned to company id (int)
922                            'page'            => -1,
923                            'perPage'         => -1,
924                            'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
925                            'sortByField'     => 'zbse_start',
926                            'sortOrder'       => 'DESC',
927                            'withAssigned'    => false, // no need, it's assigned to this obj already
928
929                        )
930                    );
931
932                }
933
934                    // simplistic, could be optimised (though low use means later.)
935                if ( $withExternalSources ) {
936
937                    $res['external_sources'] = $zbs->DAL->getExternalSources(
938                        array(
939
940                            'objectID'    => $potentialRes->ID,
941                            'objectType'  => ZBS_TYPE_COMPANY,
942                            'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
943
944                        )
945                    );
946
947                }
948                if ( $withExternalSourcesGrouped ) {
949
950                    $res['external_sources'] = $zbs->DAL->getExternalSources(
951                        -1,
952                        array(
953
954                            'objectID'          => $potentialRes->ID,
955                            'objectType'        => ZBS_TYPE_COMPANY,
956                            'grouped_by_source' => true,
957                            'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
958
959                        )
960                    );
961
962                }
963
964                    return $res;
965
966            }
967        } // / if ID
968
969        return false;
970    }
971
972    /**
973     * returns company detail lines
974     *
975     * @param array $args Associative array of arguments
976     *
977     * @return array of company lines
978     */
979    public function getCompanies( $args = array() ) {
980
981        global $zbs;
982
983        #} ============ LOAD ARGS =============
984        $defaultArgs = array(
985
986            // Search/Filtering (leave as false to ignore)
987            'searchPhrase'               => '', // searches which fields?
988            'inArr'                      => false,
989            'isTagged'                   => false, // 1x INT OR array(1,2,3)
990            'isNotTagged'                => false, // 1x INT OR array(1,2,3)
991            'ownedBy'                    => false,
992            'externalSource'             => false, // e.g. paypal
993            'olderThan'                  => false, // uts
994            'newerThan'                  => false, // uts
995            'hasStatus'                  => false, // Lead (this takes over from the quick filter post 19/6/18)
996            'otherStatus'                => false, // status other than 'Lead'
997            'hasContact'                 => false, // has a contact of this ID associated with it
998            'quickFilters'               => false, // booo
999
1000            // addr
1001            'inCounty'                   => false, // Hertfordshire
1002            'inPostCode'                 => false, // AL1 1AA
1003            'inCountry'                  => false, // United Kingdom
1004            'notInCounty'                => false, // Hertfordshire
1005            'notInPostCode'              => false, // AL1 1AA
1006            'notInCountry'               => false, // United Kingdom
1007
1008            // generic assignments - requires both
1009            // Where the link relationship is OBJECT -> CONTACT
1010            'hasObjIDLinkedTo'           => false, // e.g. quoteid 123
1011            'hasObjTypeLinkedTo'         => false, // e.g. ZBS_TYPE_QUOTE
1012
1013            // generic assignments - requires both
1014            // Where the link relationship is CONTACT -> OBJECT
1015            'isLinkedToObjID'            => false, // e.g. quoteid 123
1016            'isLinkedToObjType'          => false, // e.g. ZBS_TYPE_QUOTE
1017
1018            // returns
1019            'count'                      => false,
1020            'withCustomFields'           => true,
1021            'withQuotes'                 => false, // Will only be operable when Company<->Quotes established
1022            'withInvoices'               => false,
1023            'withTransactions'           => false,
1024            'withTasks'                  => false,
1025            'withTags'                   => false,
1026            'withOwner'                  => false,
1027            'withLogs'                   => false,
1028            'withLastLog'                => false,
1029            'withValues'                 => false, // if passed, returns with 'total' 'invoices_total' 'transactions_total' etc. (requires getting all obj, use sparingly)
1030            'withContacts'               => true, // return ['contact'] objs
1031            'withExternalSources'        => false,
1032            'withExternalSourcesGrouped' => false,
1033            'simplified'                 => false, // returns just id,name,created (for typeaheads)
1034            'onlyColumns'                => false, // if passed (array('fname','lname')) will return only those columns (overwrites some other 'return' options). NOTE: only works for base fields (not custom fields)
1035
1036            'sortByField'                => 'ID',
1037            'sortOrder'                  => 'ASC',
1038            'page'                       => 0, // this is what page it is (gets * by for limit)
1039            'perPage'                    => 100,
1040
1041            // permissions
1042            'ignoreowner'                => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ), // this'll let you not-check the owner of obj
1043
1044        );
1045        foreach ( $defaultArgs as $argK => $argV ) {
1046            $$argK = $argV;
1047            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
1048                if ( is_array( $args[ $argK ] ) ) {
1049                    $newData = $$argK;
1050                    if ( ! is_array( $newData ) ) {
1051                        $newData = array();
1052                    } foreach ( $args[ $argK ] as $subK => $subV ) {
1053                        $newData[ $subK ] = $subV;
1054                    }$$argK = $newData;
1055                } else {
1056                    $$argK = $args[ $argK ]; }
1057            }
1058        }
1059        #} =========== / LOAD ARGS =============
1060
1061        global $ZBSCRM_t, $wpdb, $zbs;
1062        $wheres          = array( 'direct' => array() );
1063        $whereStr        = '';
1064        $additionalWhere = '';
1065        $params          = array();
1066        $res             = array();
1067        $joinQ           = '';
1068        $extraSelect     = '';
1069        $whereCase       = 'AND';
1070
1071        #} ============= PRE-QUERY ============
1072
1073            #} Capitalise this
1074            $sortOrder = strtoupper( $sortOrder );
1075
1076            #} If just count, or simplified, turn off any extra gumpf
1077        if ( $count || $simplified ) {
1078            $withCustomFields           = false;
1079            $withTags                   = false;
1080            $withOwner                  = false;
1081            $withLogs                   = false;
1082            $withLastLog                = false;
1083            $withContacts               = false;
1084            $withTasks                  = false;
1085            $withExternalSources        = false;
1086            $withExternalSourcesGrouped = false;
1087        }
1088
1089            #} If onlyColumns, validate
1090        if ( $onlyColumns ) {
1091
1092            #} onlyColumns build out a field arr
1093            if ( is_array( $onlyColumns ) && count( $onlyColumns ) > 0 ) {
1094
1095                $onlyColumnsFieldArr = array();
1096                foreach ( $onlyColumns as $col ) {
1097
1098                    // find db col key from field key (e.g. fname => zbsc_fname)
1099                    $dbCol = '';
1100                    if ( isset( $this->objectModel[ $col ] ) && isset( $this->objectModel[ $col ]['fieldname'] ) ) {
1101                        $dbCol = $this->objectModel[ $col ]['fieldname'];
1102                    }
1103
1104                    if ( ! empty( $dbCol ) ) {
1105
1106                        $onlyColumnsFieldArr[ $dbCol ] = $col;
1107
1108                    }
1109                }
1110            }
1111
1112            // if legit cols:
1113            if ( isset( $onlyColumnsFieldArr ) && is_array( $onlyColumnsFieldArr ) && count( $onlyColumnsFieldArr ) > 0 ) {
1114
1115                $onlyColumns = true;
1116
1117                // If onlyColumns, turn off extras
1118                $withCustomFields           = false;
1119                $withTags                   = false;
1120                $withOwner                  = false;
1121                $withLogs                   = false;
1122                $withLastLog                = false;
1123                $withContacts               = false;
1124                $withTasks                  = false;
1125                $withExternalSources        = false;
1126                $withExternalSourcesGrouped = false;
1127
1128            } else {
1129
1130                // deny
1131                $onlyColumns = false;
1132
1133            }
1134        }
1135
1136            #} Custom Fields
1137                        // @phan-suppress-next-line PhanImpossibleCondition -- Phan is confused; this var is initialized at the beginning of the function.
1138        if ( $withCustomFields ) {
1139
1140            #} Retrieve any cf
1141            $custFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_COMPANY ) );
1142
1143            #} Cycle through + build into query
1144            if ( is_array( $custFields ) ) {
1145                foreach ( $custFields as $cK => $cF ) {
1146
1147                    // custom field (e.g. 'third name') it'll be passed here as 'third-name'
1148                    // ... problem is mysql does not like that :) so we have to chage here:
1149                    // in this case we prepend cf's with cf_ and we switch - for _
1150                    $cKey = 'cf_' . str_replace( '-', '_', $cK );
1151
1152                    // we also check the $sortByField in case that's the same cf
1153                    if ( $cK == $sortByField ) {
1154
1155                        // sort by
1156                        $sortByField = $cKey;
1157
1158                        // check if sort needs any CAST (e.g. numeric):
1159                        $sortByField = $this->DAL()->build_custom_field_order_by_str( $sortByField, $cF );
1160
1161                    }
1162
1163                    // add as subquery
1164                    $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d LIMIT 1) ' . $cKey;
1165
1166                    // add params
1167                    $params[] = $cK;
1168                    $params[] = ZBS_TYPE_COMPANY;
1169
1170                }
1171            }
1172        }
1173
1174            // Add any addr custom fields for addr1+addr2
1175            // no need if simpliefied or count parameters passed
1176        if ( ! $simplified && ! $count && ! $onlyColumns ) {
1177            $addrCustomFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_ADDRESS ) );
1178            if ( is_array( $addrCustomFields ) && count( $addrCustomFields ) > 0 ) {
1179
1180                foreach ( $addrCustomFields as $cK => $cF ) {
1181
1182                    // custom field key
1183                    $cfKey  = 'addr_' . $cK;
1184                    $cfKey2 = 'secaddr_' . $cK;
1185
1186                    // address custom field (e.g. 'house name') it'll be passed here as 'house-name'
1187                    // ... problem is mysql does not like that :) so we have to chage here:
1188                    // in this case we prepend address cf's with addr_ and we switch - for _
1189                    $cKey  = 'addrcf_' . str_replace( '-', '_', $cK );
1190                    $cKey2 = 'secaddrcf_' . str_replace( '-', '_', $cK );
1191
1192                    // we also check the $sortByField in case that's the same cf
1193                    if ( $cfKey == $sortByField ) {
1194                        $sortByField = $cKey;
1195                    }
1196                    if ( $cfKey2 == $sortByField ) {
1197                        $sortByField = $cKey2;
1198                    }
1199
1200                    // addr 1
1201                        // add as subquery
1202                        $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d) ' . $cKey;
1203                        // add params
1204                        $params[] = $cfKey;
1205                    $params[]     = ZBS_TYPE_COMPANY;
1206                    // addr 2
1207                        // add as subquery
1208                        $extraSelect .= ',(SELECT zbscf_objval FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objid = company.ID AND zbscf_objkey = %s AND zbscf_objtype = %d) ' . $cKey2;
1209                        // add params
1210                        $params[] = $cfKey2;
1211                    $params[]     = ZBS_TYPE_COMPANY;
1212
1213                }
1214            }
1215        }
1216
1217            // ==== TOTAL VALUES
1218
1219            // Calculate total vals etc. with SQL
1220        if ( ! $simplified && ! $count && $withValues && ! $onlyColumns ) {
1221
1222            // arguably, if getting $withInvoices etc. may be more performant to calc this in php in AFTER loop,
1223            // ... for now as a fair guess, this'll be most performant:
1224            // ... we calc total by adding invs + trans below :)
1225
1226            // only include transactions with statuses which should be included in total value:
1227            $transStatusQueryAdd = $this->DAL()->transactions->getTransactionStatusesToIncludeQuery();
1228            // include invoices without deleted status in the total value for invoices_total_inc_deleted:
1229            $inv_status_query_add = $this->DAL()->invoices->get_invoice_status_except_deleted_for_query();
1230
1231            // When Company<->Quotes:
1232            // $extraSelect .= ',(SELECT SUM(quotestotal.zbsq_value) FROM '.$ZBSCRM_t['quotes'].' as quotestotal WHERE quotestotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM '.$ZBSCRM_t['objlinks']." WHERE zbsol_objtype_from = ".ZBS_TYPE_QUOTE." AND zbsol_objtype_to = ".ZBS_TYPE_COMPANY." AND zbsol_objid_to = company.ID)) as quotes_total";
1233            // invs:
1234            $extraSelect .= ',(SELECT IFNULL(SUM(invstotal.zbsi_total),0) FROM ' . $ZBSCRM_t['invoices'] . ' as invstotal WHERE invstotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $inv_status_query_add . ') as invoices_total'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1235            // invs including deleted:
1236            $extraSelect .= ',(SELECT SUM(invstotalincdeleted.zbsi_total) FROM ' . $ZBSCRM_t['invoices'] . ' as invstotalincdeleted WHERE invstotalincdeleted.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)) as invoices_total_inc_deleted'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1237            // invs count:
1238            $extraSelect .= ',(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $inv_status_query_add . ') as invoices_count'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1239            // invs count including deleted:
1240            $extraSelect .= ',(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)) as invoices_count_inc_deleted'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1241
1242            // trans (with status):
1243            $extraSelect .= ',(SELECT SUM(transtotal.zbst_total) FROM ' . $ZBSCRM_t['transactions'] . ' as transtotal WHERE transtotal.ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $transStatusQueryAdd . ') as transactions_total';
1244            // paid balance against invs  (also in getCompany)
1245            // (this allows us to subtract from totals to get a true figure where transactions are part/whole payments for invs)
1246            /*
1247                This selects transactions
1248                    where there is a link to an invoice
1249                        where that invoice has a link to this contact:
1250
1251                ==========
1252
1253                SELECT * FROM wp_zbs_transactions trans
1254                WHERE trans.ID IN
1255
1256                    (
1257                        SELECT DISTINCT zbsol_objid_from FROM `wp_zbs_object_links`
1258                        WHERE zbsol_objtype_from = 5
1259                        AND zbsol_objtype_to = 4
1260                        AND zbsol_objid_to IN
1261
1262                            (
1263
1264                                SELECT DISTINCT zbsol_objid_from FROM `wp_zbs_object_links`
1265                                WHERE zbsol_objtype_from = 4 AND zbsol_objtype_to = 1 AND zbsol_objid_to = 1
1266
1267                            )
1268
1269                    )
1270
1271
1272            */
1273            $extraSelect .= ',(SELECT SUM(assignedtranstotal.zbst_total) FROM ' . $ZBSCRM_t['transactions'] . ' assignedtranstotal WHERE assignedtranstotal.ID IN ';
1274            $extraSelect .= '(SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objid_to IN ';
1275            $extraSelect .= '(SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)';
1276            $extraSelect .= ')' . $transStatusQueryAdd . ') as transactions_paid_total';
1277        }
1278
1279            // ==== / TOTAL VALUES
1280
1281        #} ============ / PRE-QUERY ===========
1282
1283        #} Build query
1284        $query = 'SELECT company.*' . $extraSelect . ' FROM ' . $ZBSCRM_t['companies'] . ' as company' . $joinQ;
1285
1286        #} Count override
1287        if ( $count ) {
1288            $query = 'SELECT COUNT(company.ID) FROM ' . $ZBSCRM_t['companies'] . ' as company' . $joinQ;
1289        }
1290
1291        #} simplified override
1292        if ( $simplified ) {
1293            $query = 'SELECT company.ID as id,company.zbsco_name as name,company.zbsco_created as created,company.zbsco_email as email FROM ' . $ZBSCRM_t['companies'] . ' as company' . $joinQ;
1294        }
1295
1296        #} onlyColumns override
1297        if ( $onlyColumns && is_array( $onlyColumnsFieldArr ) && count( $onlyColumnsFieldArr ) > 0 ) {
1298
1299            $columnStr = '';
1300            foreach ( $onlyColumnsFieldArr as $colDBKey => $colStr ) {
1301
1302                if ( ! empty( $columnStr ) ) {
1303                    $columnStr .= ',';
1304                }
1305                // this presumes str is db-safe? could do with sanitation?
1306                $columnStr .= $colDBKey;
1307
1308            }
1309
1310            $query = 'SELECT ' . $columnStr . ' FROM ' . $ZBSCRM_t['companies'] . ' as company' . $joinQ;
1311
1312        }
1313
1314        #} ============= WHERE ================
1315
1316            #} Add Search phrase
1317        if ( ! empty( $searchPhrase ) ) {
1318
1319            // search? - ALL THESE COLS should probs have index of FULLTEXT in db?
1320            $searchWheres                   = array();
1321            $searchWheres['search_ID']      = array( 'ID', '=', '%d', $searchPhrase );
1322            $searchWheres['search_name']    = array( 'zbsco_name', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1323            $searchWheres['search_email']   = array( 'zbsco_email', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1324            $searchWheres['search_maintel'] = array( 'zbsco_maintel', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1325            $searchWheres['search_sectel']  = array( 'zbsco_sectel', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1326
1327            // address elements
1328            $searchWheres['search_addr1']       = array( 'zbsco_addr1', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1329            $searchWheres['search_addr2']       = array( 'zbsco_addr2', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1330            $searchWheres['search_city']        = array( 'zbsco_city', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1331            $searchWheres['search_county']      = array( 'zbsco_county', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1332            $searchWheres['search_country']     = array( 'zbsco_country', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1333            $searchWheres['search_postcode']    = array( 'zbsco_postcode', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1334            $searchWheres['search_secaddr1']    = array( 'zbsco_secaddr1', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1335            $searchWheres['search_secaddr2']    = array( 'zbsco_secaddr2', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1336            $searchWheres['search_seccity']     = array( 'zbsco_seccity', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1337            $searchWheres['search_seccounty']   = array( 'zbsco_seccounty', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1338            $searchWheres['search_seccountry']  = array( 'zbsco_seccountry', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1339            $searchWheres['search_secpostcode'] = array( 'zbsco_secpostcode', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1340
1341            // social
1342            $searchWheres['search_tw'] = array( 'zbsco_tw', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1343            $searchWheres['search_li'] = array( 'zbsco_li', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1344            $searchWheres['search_fb'] = array( 'zbsco_fb', 'LIKE', '%s', '%' . $searchPhrase . '%' );
1345
1346            // 3.0.13 - Added ability to search custom fields (optionally)
1347            $customFieldSearch = zeroBSCRM_getSetting( 'customfieldsearch' );
1348            if ( $customFieldSearch == 1 ) {
1349
1350                // simplistic add
1351                // NOTE: This IGNORES ownership of custom field lines.
1352                $searchWheres['search_customfields'] = array( 'ID', 'IN', '(SELECT zbscf_objid FROM ' . $ZBSCRM_t['customfields'] . ' WHERE zbscf_objval LIKE %s AND zbscf_objtype = ' . ZBS_TYPE_COMPANY . ')', '%' . $searchPhrase . '%' );
1353
1354            }
1355
1356            // This generates a query like 'zbsco_fname LIKE %s OR zbsco_lname LIKE %s',
1357            // which we then need to include as direct subquery (below) in main query :)
1358            $searchQueryArr = $this->buildWheres( $searchWheres, '', array(), 'OR', false );
1359
1360            if ( is_array( $searchQueryArr ) && isset( $searchQueryArr['where'] ) && ! empty( $searchQueryArr['where'] ) ) {
1361
1362                // add it
1363                $wheres['direct'][] = array( '(' . $searchQueryArr['where'] . ')', $searchQueryArr['params'] );
1364
1365            }
1366        }
1367
1368            #} In array (if inCompany passed, this'll currently overwrite that?! (todo2.5))
1369        if ( is_array( $inArr ) && count( $inArr ) > 0 ) {
1370
1371            // clean for ints
1372            $inArrChecked = array();
1373            foreach ( $inArr as $x ) {
1374                $inArrChecked[] = (int) $x; }
1375
1376            // add where
1377            $wheres['inarray'] = array( 'ID', 'IN', '(' . implode( ',', $inArrChecked ) . ')' );
1378
1379        }
1380
1381            #} Owned by
1382        if ( is_int( $ownedBy ) && ! empty( $ownedBy ) && $ownedBy > 0 ) {
1383
1384            // would never hard-type this in (would make generic as in buildWPMetaQueryWhere)
1385            // but this is only here until MIGRATED to db2 globally
1386            // $wheres['incompany'] = array('ID','IN','(SELECT DISTINCT post_id FROM '.$wpdb->prefix."postmeta WHERE meta_key = 'zbs_company' AND meta_value = %d)",$inCompany);
1387            // Use obj links now
1388            $wheres['ownedBy'] = array( 'zbs_owner', '=', '%s', $ownedBy );
1389
1390        }
1391
1392            // External sources
1393        if ( ! empty( $externalSource ) ) {
1394
1395            // NO owernship built into this, check when roll out multi-layered ownsership
1396            $wheres['externalsource'] = array( 'ID', 'IN', '(SELECT DISTINCT zbss_objid FROM ' . $ZBSCRM_t['externalsources'] . ' WHERE zbss_objtype = ' . ZBS_TYPE_COMPANY . ' AND zbss_source = %s)', $externalSource );
1397
1398        }
1399
1400        // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1401        // quick addition for mike
1402        #} olderThan
1403        if ( ! empty( $olderThan ) && $olderThan > 0 ) {
1404            $wheres['olderThan'] = array( 'zbsco_created', '<=', '%d', $olderThan );
1405        }
1406        #} newerThan
1407        if ( ! empty( $newerThan ) && $newerThan > 0 ) {
1408            $wheres['newerThan'] = array( 'zbsco_created', '>=', '%d', $newerThan );
1409        }
1410
1411        // status
1412        if ( ! empty( $hasStatus ) ) {
1413            $wheres['hasStatus'] = array( 'zbsco_status', '=', '%s', $hasStatus );
1414        }
1415        if ( ! empty( $otherStatus ) ) {
1416            $wheres['otherStatus'] = array( 'zbsco_status', '<>', '%s', $otherStatus );
1417        }
1418
1419        #} inCounty
1420        if ( ! empty( $inCounty ) ) {
1421            $wheres['inCounty']      = array( 'zbsco_county', '=', '%s', $inCounty );
1422            $wheres['inCountyAddr2'] = array( 'zbsco_secaddrcounty', '=', '%s', $inCounty );
1423        }
1424        #} inPostCode
1425        if ( ! empty( $inPostCode ) ) {
1426            $wheres['inPostCode']      = array( 'zbsco_postcode', '=', '%s', $inPostCode );
1427            $wheres['inPostCodeAddr2'] = array( 'zbsco_secaddrpostcode', '=', '%s', $inPostCode );
1428        }
1429        #} inCountry
1430        if ( ! empty( $inCountry ) ) {
1431            $wheres['inCountry']      = array( 'zbsco_country', '=', '%s', $inCountry );
1432            $wheres['inCountryAddr2'] = array( 'zbsco_secaddrcountry', '=', '%s', $inCountry );
1433        }
1434        #} notInCounty
1435        if ( ! empty( $notInCounty ) ) {
1436            $wheres['notInCounty']      = array( 'zbsco_county', '<>', '%s', $notInCounty );
1437            $wheres['notInCountyAddr2'] = array( 'zbsco_secaddrcounty', '<>', '%s', $notInCounty );
1438        }
1439        #} notInPostCode
1440        if ( ! empty( $notInPostCode ) ) {
1441            $wheres['notInPostCode']      = array( 'zbsco_postcode', '<>', '%s', $notInPostCode );
1442            $wheres['notInPostCodeAddr2'] = array( 'zbsco_secaddrpostcode', '<>', '%s', $notInPostCode );
1443        }
1444        #} notInCountry
1445        if ( ! empty( $notInCountry ) ) {
1446            $wheres['notInCountry']      = array( 'zbsco_country', '<>', '%s', $notInCountry );
1447            $wheres['notInCountryAddr2'] = array( 'zbsco_secaddrcountry', '<>', '%s', $notInCountry );
1448        }
1449
1450        // has contact associated with it
1451        if ( ! empty( $hasContact ) && $hasContact > 0 ) {
1452            $wheres['hasContact'] = array(
1453                'ID',
1454                'IN',
1455                '(SELECT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_CONTACT . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)',
1456            );
1457        }
1458
1459        // generic obj links, e.g. quotes, invs, trans
1460        // e.g. contact(s) assigned to inv 123
1461        // Where the link relationship is OBJECT -> CONTACT
1462        if ( ! empty( $hasObjIDLinkedTo ) && $hasObjIDLinkedTo > 0 &&
1463                ! empty( $hasObjTypeLinkedTo ) && $hasObjTypeLinkedTo > 0 ) {
1464            $wheres['hasObjIDLinkedTo'] = array( 'ID', 'IN', '(SELECT zbsol_objid_to FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = %d AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_from = %d AND zbsol_objid_to = company.ID)', array( $hasObjTypeLinkedTo, $hasObjIDLinkedTo ) );
1465
1466        }
1467
1468        // generic obj links, e.g. companies
1469        // Where the link relationship is CONTACT -> OBJECT
1470        if ( ! empty( $isLinkedToObjID ) && $isLinkedToObjID > 0 &&
1471                ! empty( $isLinkedToObjType ) && $isLinkedToObjType > 0 ) {
1472            $wheres['isLinkedToObjID'] = array( 'ID', 'IN', '(SELECT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objtype_to = %d AND zbsol_objid_from = company.ID AND zbsol_objid_to = %d)', array( $isLinkedToObjType, $isLinkedToObjID ) );
1473
1474        }
1475        // phpcs:enable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1476
1477            #} Quick filters - adapted from DAL1 (probs can be slicker)
1478        if ( is_array( $quickFilters ) && count( $quickFilters ) > 0 ) {
1479
1480            // cycle through
1481            foreach ( $quickFilters as $qFilter ) {
1482
1483                    // where status = x
1484                    // USE hasStatus above now...
1485                if ( str_starts_with( $qFilter, 'status_' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1486
1487                    $quick_filter_status         = substr( $qFilter, 7 ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1488                    $wheres['quickfilterstatus'] = array( 'zbsco_status', '=', 'convert(%s using utf8mb4) collate utf8mb4_bin', $quick_filter_status );
1489
1490                } elseif ( str_starts_with( $qFilter, 'notcontactedin' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1491
1492                                // check
1493                                $notcontactedinDays        = (int) substr( $qFilter, 14 );
1494                                $notcontactedinDaysSeconds = $notcontactedinDays * 86400;
1495                                $wheres['notcontactedinx'] = array( 'zbsco_lastcontacted', '<', '%d', time() - $notcontactedinDaysSeconds );
1496
1497                } elseif ( str_starts_with( $qFilter, 'olderthan' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1498
1499                                // check
1500                                $olderThanDays        = (int) substr( $qFilter, 9 );
1501                                $olderThanDaysSeconds = $olderThanDays * 86400;
1502                                $wheres['olderthanx'] = array( 'zbsco_created', '<', '%d', time() - $olderThanDaysSeconds );
1503
1504                } else {
1505
1506                                // normal/hardtyped
1507
1508                    switch ( $qFilter ) {
1509
1510                        case 'lead':
1511                            // hack "leads only" - adapted from DAL1 (probs can be slicker)
1512                            $wheres['quickfilterlead'] = array( 'zbsco_status', 'LIKE', '%s', 'Lead' );
1513
1514                            break;
1515
1516                        case 'customer':
1517                            // hack - adapted from DAL1 (probs can be slicker)
1518                            $wheres['quickfiltercustomer'] = array( 'zbsco_status', 'LIKE', '%s', 'Customer' );
1519
1520                            break;
1521
1522                    }  // / switch
1523
1524                } // / hardtyped
1525
1526            }
1527        } // / quickfilters
1528
1529            #} Is Tagged (expects 1 tag ID OR array)
1530
1531                // catch 1 item arr
1532        if ( is_array( $isTagged ) && count( $isTagged ) == 1 ) {
1533            $isTagged = $isTagged[0];
1534        }
1535
1536        if ( ! empty( $isTagged ) && ! is_array( $isTagged ) && $isTagged > 0 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1537
1538                // add where tagged
1539                // 1 int:
1540                $wheres['direct'][] = array( '((SELECT COUNT(ID) FROM ' . $ZBSCRM_t['taglinks'] . ' WHERE zbstl_objtype = %d AND zbstl_objid = company.ID AND zbstl_tagid = %d) > 0)', array( ZBS_TYPE_COMPANY, $isTagged ) );
1541
1542        } elseif ( is_array( $isTagged ) && count( $isTagged ) > 0 ) {
1543
1544            // foreach in array :)
1545            $tagStr = '';
1546            foreach ( $isTagged as $iTag ) {
1547                $i = (int) $iTag;
1548                if ( $i > 0 ) {
1549
1550                    if ( $tagStr !== '' ) {
1551                        $tagStr . ',';
1552                    }
1553                    $tagStr .= $i;
1554                }
1555            }
1556            if ( ! empty( $tagStr ) ) {
1557
1558                $wheres['direct'][] = array( '((SELECT COUNT(ID) FROM ' . $ZBSCRM_t['taglinks'] . ' WHERE zbstl_objtype = %d AND zbstl_objid = company.ID AND zbstl_tagid IN (%s)) > 0)', array( ZBS_TYPE_COMPANY, $tagStr ) );
1559
1560            }
1561        }
1562            #} Is NOT Tagged (expects 1 tag ID OR array)
1563
1564                // catch 1 item arr
1565        if ( is_array( $isNotTagged ) && count( $isNotTagged ) == 1 ) {
1566            $isNotTagged = $isNotTagged[0];
1567        }
1568
1569        if ( ! empty( $isNotTagged ) && ! is_array( $isNotTagged ) && $isNotTagged > 0 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1570
1571                // add where tagged
1572                // 1 int:
1573                $wheres['direct'][] = array( '((SELECT COUNT(ID) FROM ' . $ZBSCRM_t['taglinks'] . ' WHERE zbstl_objtype = %d AND zbstl_objid = company.ID AND zbstl_tagid = %d) = 0)', array( ZBS_TYPE_COMPANY, $isNotTagged ) );
1574
1575        } elseif ( is_array( $isNotTagged ) && count( $isNotTagged ) > 0 ) {
1576
1577            // foreach in array :)
1578            $tagStr = '';
1579            foreach ( $isNotTagged as $iTag ) {
1580                $i = (int) $iTag;
1581                if ( $i > 0 ) {
1582
1583                    if ( $tagStr !== '' ) {
1584                        $tagStr . ',';
1585                    }
1586                    $tagStr .= $i;
1587                }
1588            }
1589            if ( ! empty( $tagStr ) ) {
1590
1591                $wheres['direct'][] = array( '((SELECT COUNT(ID) FROM ' . $ZBSCRM_t['taglinks'] . ' WHERE zbstl_objtype = %d AND zbstl_objid = company.ID AND zbstl_tagid IN (%s)) = 0)', array( ZBS_TYPE_COMPANY, $tagStr ) );
1592
1593            }
1594        }
1595
1596        #} ============ / WHERE ===============
1597
1598        #} ============   SORT   ==============
1599
1600            // Obj Model based sort conversion
1601            // converts 'addr1' => 'zbsco_addr1' generically
1602        if ( isset( $this->objectModel[ $sortByField ] ) && isset( $this->objectModel[ $sortByField ]['fieldname'] ) ) {
1603            $sortByField = $this->objectModel[ $sortByField ]['fieldname'];
1604        }
1605
1606            // include invoices without deleted status in the total value for invoices_total_inc_deleted:
1607            $inv_status_query_add = $this->DAL()->invoices->get_invoice_status_except_deleted_for_query();
1608
1609            // Mapped sorts
1610            // This catches listview and other exception sort cases
1611            $sort_map = array(
1612                'id'                       => 'ID',
1613                'assigned'                 => 'zbsco_owner',
1614                'fullname'                 => 'zbsco_name',
1615                'status'                   => 'zbsco_status',
1616                'email'                    => 'zbsco_email',
1617
1618                // contacts (count)
1619                'contacts'                 => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['contacts'] . ' WHERE ID IN (SELECT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_CONTACT . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID))',
1620
1621                // has & counts (same queries)
1622                // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1623                'hasinvoice'               => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID))',
1624                'hastransaction'           => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['transactions'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID))',
1625                'invoicecount_inc_deleted' => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID))',
1626                'invoicecount'             => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['invoices'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_INVOICE . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID)' . $inv_status_query_add . ')',
1627                'transactioncount'         => '(SELECT COUNT(ID) FROM ' . $ZBSCRM_t['transactions'] . ' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM ' . $ZBSCRM_t['objlinks'] . ' WHERE zbsol_objtype_from = ' . ZBS_TYPE_TRANSACTION . ' AND zbsol_objtype_to = ' . ZBS_TYPE_COMPANY . ' AND zbsol_objid_to = company.ID))',
1628                // phpcs:enable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1629                // following will only work if obj total value subqueries triggered above ^
1630                'totalvalue'               => '((IFNULL(invoices_total,0) + IFNULL(transactions_total,0)) - IFNULL(transactions_paid_total,0))', // custom sort by total invoice value + transaction value - paid transactions (as mimicking tidy_contact php logic into SQL)
1631                'transactiontotal'         => 'transactions_total',
1632                'invoicetotal'             => 'invoices_total',
1633
1634                // When Company<->Quotes:
1635                /*
1636                'hasquote'         => '(SELECT COUNT(ID) FROM '.$ZBSCRM_t['quotes'].' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM '.$ZBSCRM_t['objlinks'].' WHERE zbsol_objtype_from = '.ZBS_TYPE_QUOTE.' AND zbsol_objtype_to = '.ZBS_TYPE_COMPANY.' AND zbsol_objid_to = company.ID))',
1637                'quotetotal'       => 'quotes_total',
1638                'quotecount'       => '(SELECT COUNT(ID) FROM '.$ZBSCRM_t['quotes'].' WHERE ID IN (SELECT DISTINCT zbsol_objid_from FROM '.$ZBSCRM_t['objlinks'].' WHERE zbsol_objtype_from = '.ZBS_TYPE_QUOTE.' AND zbsol_objtype_to = '.ZBS_TYPE_COMPANY.' AND zbsol_objid_to = company.ID))',
1639                */
1640
1641            );
1642
1643            if ( array_key_exists( $sortByField, $sort_map ) ) {
1644
1645                $sortByField = $sort_map[ $sortByField ];
1646
1647            }
1648
1649            #} ============ / SORT   ==============
1650
1651            #} CHECK this + reset to default if faulty
1652            if ( ! in_array( $whereCase, array( 'AND', 'OR' ) ) ) {
1653                $whereCase = 'AND';
1654            }
1655
1656            #} Build out any WHERE clauses
1657            $wheresArr = $this->buildWheres( $wheres, $whereStr, $params, $whereCase );
1658            $whereStr  = $wheresArr['where'];
1659            $params    = $params + $wheresArr['params'];
1660            #} / Build WHERE
1661
1662            #} Ownership v1.0 - the following adds SITE + TEAM checks, and (optionally), owner
1663            $params = array_merge( $params, $this->ownershipQueryVars( $ignoreowner ) ); // merges in any req.
1664            $ownQ   = $this->ownershipSQL( $ignoreowner, 'contact' );
1665            if ( ! empty( $ownQ ) ) {
1666                $additionalWhere = $this->spaceAnd( $additionalWhere ) . $ownQ; // adds str to query
1667            }
1668            #} / Ownership
1669
1670            #} Append to sql (this also automatically deals with sortby and paging)
1671            $query .= $this->buildWhereStr( $whereStr, $additionalWhere ) . $this->buildSort( $sortByField, $sortOrder ) . $this->buildPaging( $page, $perPage );
1672
1673            try {
1674
1675                #} Prep & run query
1676                $queryObj = $this->prepare( $query, $params );
1677
1678                #} Catch count + return if requested
1679                if ( $count ) {
1680                    return $wpdb->get_var( $queryObj );
1681                }
1682
1683                #} else continue..
1684                $potentialRes = $wpdb->get_results( $queryObj, OBJECT );
1685
1686            } catch ( Exception $e ) {
1687
1688                #} General SQL Err
1689                $this->catchSQLError( $e );
1690
1691            }
1692
1693            #} Interpret results (Result Set - multi-row)
1694            if ( isset( $potentialRes ) && is_array( $potentialRes ) && count( $potentialRes ) > 0 ) {
1695
1696                #} Has results, tidy + return
1697                foreach ( $potentialRes as $resDataLine ) {
1698
1699                    #} simplified override
1700                    if ( $simplified ) {
1701
1702                        $resArr = array(
1703                            'id'      => $resDataLine->id,
1704                            'name'    => $resDataLine->name,
1705                            'created' => $resDataLine->created,
1706                            'email'   => $resDataLine->email,
1707                        );
1708
1709                    } elseif ( $onlyColumns && is_array( $onlyColumnsFieldArr ) && count( $onlyColumnsFieldArr ) > 0 ) {
1710
1711                        // only coumns return.
1712                        $resArr = array();
1713                        foreach ( $onlyColumnsFieldArr as $colDBKey => $colStr ) {
1714
1715                            if ( isset( $resDataLine->$colDBKey ) ) {
1716                                $resArr[ $colStr ] = $resDataLine->$colDBKey;
1717                            }
1718                        }
1719                    } else {
1720
1721                                    // tidy
1722                                    $resArr = $this->tidy_company( $resDataLine, $withCustomFields );
1723
1724                    }
1725
1726                    if ( $withTags ) {
1727
1728                        // add all tags lines
1729                        $resArr['tags'] = $this->DAL()->getTagsForObjID(
1730                            array(
1731                                'objtypeid' => ZBS_TYPE_COMPANY,
1732                                'objid'     => $resDataLine->ID,
1733                            )
1734                        );
1735
1736                    }
1737
1738                    #} With most recent log? #DB1LEGACY (TOMOVE)
1739                    if ( $withLastLog ) {
1740
1741                        // doesn't return singular, for now using arr
1742                        $potentialLogs = $this->DAL()->logs->getLogsForObj(
1743                            array(
1744
1745                                'objtype'     => ZBS_TYPE_COMPANY,
1746                                'objid'       => $resDataLine->ID,
1747
1748                                'incMeta'     => true,
1749
1750                                'sortByField' => 'zbsl_created',
1751                                'sortOrder'   => 'DESC',
1752                                'page'        => 0,
1753                                'perPage'     => 1,
1754
1755                            )
1756                        );
1757
1758                        if ( is_array( $potentialLogs ) && count( $potentialLogs ) > 0 ) {
1759                            $resArr['lastlog'] = $potentialLogs[0];
1760                        }
1761
1762                        // COMPANY logs specifically
1763                        // doesn't return singular, for now using arr
1764                        $potentialLogs = $this->DAL()->logs->getLogsForObj( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1765                            array(
1766
1767                                'objtype'     => ZBS_TYPE_COMPANY,
1768                                'objid'       => $resDataLine->ID, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1769
1770                                'notetypes'   => $zbs->DAL->logs->contact_log_types, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1771
1772                                'incMeta'     => true,
1773
1774                                'sortByField' => 'zbsl_created',
1775                                'sortOrder'   => 'DESC',
1776                                'page'        => 0,
1777                                'perPage'     => 1,
1778
1779                            )
1780                        );
1781
1782                        if ( is_array( $potentialLogs ) && count( $potentialLogs ) > 0 ) {
1783                            $resArr['lastcontactlog'] = $potentialLogs[0];
1784                        }
1785                    }
1786
1787                    #} With Assigned?
1788                    if ( $withOwner ) {
1789
1790                        $resArr['owner'] = zeroBS_getOwner( $resDataLine->ID, true, 'zerobs_company', $resDataLine->zbs_owner );
1791
1792                    }
1793
1794                    if ( $withContacts ) {
1795
1796                        // add all assigned contacts/companies
1797                        $resArr['contacts'] = $this->DAL()->contacts->getContacts(
1798                            array(
1799                                'isLinkedToObjType' => ZBS_TYPE_COMPANY,
1800                                'isLinkedToObjID'   => $resDataLine->ID,
1801                                'page'              => -1,
1802                                'perPage'           => -1,
1803                                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
1804                            )
1805                        );
1806
1807                    }
1808
1809                    if ( $withInvoices ) {
1810                        // DAL3 ver, more perf, gets all
1811                        $resArr['invoices'] = $zbs->DAL->invoices->getInvoices(
1812                            array(
1813
1814                                'assignedCompany' => $resDataLine->ID, // assigned to company id (int)
1815                                'page'            => -1,
1816                                'perPage'         => -1,
1817                                'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_INVOICE ),
1818
1819                            )
1820                        );
1821
1822                    }
1823
1824                    if ( $withQuotes ) {
1825
1826                        // DAL3 ver, more perf, gets all
1827                        $resArr['quotes'] = $zbs->DAL->quotes->getQuotes(
1828                            array(
1829
1830                                'assignedCompany' => $resDataLine->ID, // assigned to company id (int)
1831                                'page'            => -1,
1832                                'perPage'         => -1,
1833                                'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
1834
1835                            )
1836                        );
1837
1838                    }
1839
1840                        #} ... brutal for mvp #DB1LEGACY (TOMOVE)
1841                    if ( $withTransactions ) {
1842
1843                        // DAL3 ver, more perf, gets all
1844                        $resArr['transactions'] = $zbs->DAL->transactions->getTransactions(
1845                            array(
1846
1847                                'assignedCompany' => $resDataLine->ID, // assigned to company id (int)
1848                                'page'            => -1,
1849                                'perPage'         => -1,
1850                                'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
1851
1852                            )
1853                        );
1854
1855                    }
1856
1857                        #} ... brutal for mvp #DB1LEGACY (TOMOVE)
1858                    if ( $withTasks ) {
1859
1860                        $res['tasks'] = $zbs->DAL->events->getEvents(
1861                            array(
1862
1863                                'assignedCompany' => $resDataLine->ID, // assigned to company id (int)
1864                                'page'            => -1,
1865                                'perPage'         => -1,
1866                                'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
1867                                'sortByField'     => 'zbse_start',
1868                                'sortOrder'       => 'DESC',
1869                                'withAssigned'    => false, // no need, it's assigned to this obj already
1870
1871                            )
1872                        );
1873
1874                    }
1875
1876                        // simplistic, could be optimised (though low use means later.)
1877                    if ( $withExternalSources ) {
1878
1879                        $res['external_sources'] = $zbs->DAL->getExternalSources(
1880                            array(
1881
1882                                'objectID'    => $resDataLine->ID,
1883                                'objectType'  => ZBS_TYPE_COMPANY,
1884                                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
1885
1886                            )
1887                        );
1888
1889                    }
1890                    if ( $withExternalSourcesGrouped ) {
1891
1892                        $res['external_sources'] = $zbs->DAL->getExternalSources(
1893                            -1,
1894                            array(
1895
1896                                'objectID'          => $resDataLine->ID,
1897                                'objectType'        => ZBS_TYPE_COMPANY,
1898                                'grouped_by_source' => true,
1899                                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
1900
1901                            )
1902                        );
1903
1904                    }
1905
1906                    // }
1907
1908                    // ===================================================
1909                    // ========== / #DB1LEGACY (TOMOVE)
1910                    // ===================================================
1911
1912                    $res[] = $resArr;
1913
1914                }
1915            }
1916
1917            return $res;
1918    }
1919
1920    /**
1921     * Returns a count of companies (owned)
1922     * .. inc by status
1923     *
1924     * @return int count
1925     */
1926    public function getCompanyCount( $args = array() ) {
1927
1928        #} ============ LOAD ARGS =============
1929        $defaultArgs = array(
1930
1931            // Search/Filtering (leave as false to ignore)
1932            'withStatus'  => false, // will be str if used
1933
1934            // permissions
1935            'ignoreowner' => true, // this'll let you not-check the owner of obj
1936
1937        );
1938        foreach ( $defaultArgs as $argK => $argV ) {
1939            $$argK = $argV;
1940            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
1941                if ( is_array( $args[ $argK ] ) ) {
1942                    $newData = $$argK;
1943                    if ( ! is_array( $newData ) ) {
1944                        $newData = array();
1945                    } foreach ( $args[ $argK ] as $subK => $subV ) {
1946                        $newData[ $subK ] = $subV;
1947                    }$$argK = $newData;
1948                } else {
1949                    $$argK = $args[ $argK ]; }
1950            }
1951        }
1952        #} =========== / LOAD ARGS =============
1953
1954        $whereArr = array();
1955
1956        if ( $withStatus !== false && ! empty( $withStatus ) ) {
1957            $whereArr['status'] = array( 'zbsco_status', '=', '%s', $withStatus );
1958        }
1959
1960        return $this->DAL()->getFieldByWHERE(
1961            array(
1962                'objtype'     => ZBS_TYPE_COMPANY,
1963                'colname'     => 'COUNT(ID)',
1964                'where'       => $whereArr,
1965                'ignoreowner' => $ignoreowner,
1966            )
1967        );
1968    }
1969
1970    /**
1971     * adds or updates a company object
1972     *
1973     * @param array $args Associative array of arguments
1974     *              id (if update), owner, data (array of field data)
1975     *
1976     * @return int line ID
1977     */
1978    public function addUpdateCompany( $args = array() ) {
1979
1980        global $ZBSCRM_t, $wpdb, $zbs;
1981
1982        #} Retrieve any cf
1983        $customFields     = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_COMPANY ) );
1984        $addrCustomFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_ADDRESS ) );
1985
1986        #} ============ LOAD ARGS =============
1987        $defaultArgs = array(
1988
1989            'id'                   => -1,
1990            'owner'                => -1,
1991
1992            // fields (directly)
1993            'data'                 => array(
1994
1995                'status'          => '',
1996                'name'            => '',
1997                'email'           => '',
1998                'addr1'           => '',
1999                'addr2'           => '',
2000                'city'            => '',
2001                'county'          => '',
2002                'country'         => '',
2003                'postcode'        => '',
2004                'secaddr1'        => '',
2005                'secaddr2'        => '',
2006                'seccity'         => '',
2007                'seccounty'       => '',
2008                'seccountry'      => '',
2009                'secpostcode'     => '',
2010                'maintel'         => '',
2011                'sectel'          => '',
2012                'wpid'            => '',
2013                'avatar'          => '',
2014                'tw'              => '',
2015                'li'              => '',
2016                'fb'              => '',
2017                'lastcontacted'   => '',
2018
2019                // Note Custom fields may be passed here, but will not have defaults so check isset()
2020
2021                // tags
2022                'tags'            => -1, // pass an array of tag ids or tag strings
2023                'tag_mode'        => 'replace', // replace|append|remove
2024
2025                'externalSources' => -1, // if this is an array(array('source'=>src,'uid'=>uid),multiple()) it'll add :)
2026
2027                // allow this to be set for MS sync etc.
2028                'created'         => -1,
2029                'lastupdated'     => '',
2030
2031                // obj links:
2032                'contacts'        => false, // array of id's
2033
2034            ),
2035
2036            'limitedFields'        => -1, // if this is set it OVERRIDES data (allowing you to set specific fields + leave rest in tact)
2037            // ^^ will look like: array(array('key'=>x,'val'=>y,'type'=>'%s'))
2038
2039            // this function as DAL1 func did.
2040            'extraMeta'            => -1,
2041            'automatorPassthrough' => -1,
2042            'fallBackLog'          => -1,
2043
2044            'silentInsert'         => false, // this was for init Migration - it KILLS all IA for newCompany (because is migrating, not creating new :) this was -1 before
2045
2046            'do_not_update_blanks' => false, // this allows you to not update fields if blank (same as fieldoverride for extsource -> in)
2047
2048        );
2049        foreach ( $defaultArgs as $argK => $argV ) {
2050            $$argK = $argV;
2051            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
2052                if ( is_array( $args[ $argK ] ) ) {
2053                    $newData = $$argK;
2054                    if ( ! is_array( $newData ) ) {
2055                        $newData = array();
2056                    } foreach ( $args[ $argK ] as $subK => $subV ) {
2057                        $newData[ $subK ] = $subV;
2058                    }$$argK = $newData;
2059                } else {
2060                    $$argK = $args[ $argK ]; }
2061            }
2062        }
2063
2064            // Needs this to grab custom fields (if passed) too :)
2065        if ( is_array( $customFields ) ) {
2066            foreach ( $customFields as $cK => $cF ) {
2067
2068                // only for data, limited fields below
2069                if ( is_array( $data ) ) {
2070
2071                    if ( isset( $args['data'][ $cK ] ) ) {
2072                        $data[ $cK ] = $args['data'][ $cK ];
2073                    }
2074                }
2075            }
2076        }
2077
2078            // this takes limited fields + checks through for custom fields present
2079            // (either as key zbsco_source or source, for example)
2080            // then switches them into the $data array, for separate update
2081            // where this'll fall over is if NO normal contact data is sent to update, just custom fields
2082        if ( is_array( $limitedFields ) && is_array( $customFields ) ) {
2083
2084                // $customFieldKeys = array_keys($customFields);
2085                $newLimitedFields = array();
2086
2087                // cycle through
2088            foreach ( $limitedFields as $field ) {
2089
2090                // some weird case where getting empties, so added check
2091                if ( isset( $field['key'] ) && ! empty( $field['key'] ) ) {
2092
2093                    $dePrefixed = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2094                    if ( str_starts_with( $field['key'], 'zbsco_' ) ) {
2095                        $dePrefixed = substr( $field['key'], strlen( 'zbsco_' ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2096                    }
2097
2098                    if ( isset( $customFields[ $field['key'] ] ) ) {
2099
2100                        // is custom, move to data
2101                        $data[ $field['key'] ] = $field['val'];
2102
2103                    } elseif ( ! empty( $dePrefixed ) && isset( $customFields[ $dePrefixed ] ) ) {
2104
2105                        // is custom, move to data
2106                        $data[ $dePrefixed ] = $field['val'];
2107
2108                    } else {
2109
2110                        // add it to limitedFields (it's not dealt with post-update)
2111                        $newLimitedFields[] = $field;
2112                    }
2113                }
2114            }
2115
2116                // move this back in
2117                $limitedFields = $newLimitedFields;
2118                unset( $newLimitedFields );
2119
2120        }
2121
2122            #} =========== / LOAD ARGS ============
2123
2124            #} ========== CHECK FIELDS ============
2125
2126            $id = (int) $id;
2127
2128            // here we check that the potential owner CAN even own
2129        if ( $owner > 0 && ! user_can( $owner, 'admin_zerobs_usr' ) ) {
2130            $owner = -1;
2131        }
2132
2133            // if owner = -1, add current
2134        if ( ! isset( $owner ) || $owner === -1 ) {
2135            $owner = zeroBSCRM_user(); }
2136
2137        if ( is_array( $limitedFields ) ) {
2138
2139            // LIMITED UPDATE (only a few fields.)
2140            if ( ! is_array( $limitedFields ) || count( $limitedFields ) <= 0 ) {
2141                return false;
2142            }
2143            // REQ. ID too (can only update)
2144            if ( empty( $id ) || $id <= 0 ) {
2145                return false;
2146            }
2147        } else {
2148
2149            // NORMAL, FULL UPDATE
2150
2151            // check email + load that user if present
2152            if ( ! isset( $data['email'] ) || empty( $data['email'] ) ) {
2153
2154                // no email
2155                // Allow users without emails? WH removed this for db1->2 migration
2156                // leaving this in breaks MIGRATIONS from DAL 1
2157                // in that those companies without emails will not be copied in
2158                // return false;
2159
2160            } else {
2161
2162                // email present, check if it matches ID?
2163                if ( ! empty( $id ) && $id > 0 ) {
2164
2165                    // if ID + email, check if existing contact with email, (e.g. in use)
2166                    // ... allow it if the ID of that email contact matches the ID given here
2167                    // (else e.g. add email x to ID y without checking)
2168                    $potentialUSERID = (int) $this->getCompany(
2169                        -1,
2170                        array(
2171                            'email'       => $data['email'],
2172                            'ignoreOwner' => 1,
2173                            'onlyID'      => 1,
2174                        )
2175                    );
2176                    if ( ! empty( $potentialUSERID ) && $potentialUSERID > 0 && $id > 0 && $potentialUSERID != $id ) {
2177
2178                        // email doesn't match ID
2179                        return false;
2180                    }
2181
2182                    // also check if has rights?!? Could be just email passed here + therefor got around owner check? hmm.
2183
2184                } else {
2185
2186                    // no ID, check if email present, and then update that co if so
2187                    $potentialUSERID = (int) $this->getCompany(
2188                        -1,
2189                        array(
2190                            'email'       => $data['email'],
2191                            'ignoreOwner' => 1,
2192                            'onlyID'      => 1,
2193                        )
2194                    );
2195                    if ( isset( $potentialUSERID ) && ! empty( $potentialUSERID ) && $potentialUSERID > 0 ) {
2196                        $id = $potentialUSERID; }
2197                }
2198            }
2199        }
2200
2201            #} If no status, and default is specified in settings, add that in :)
2202        if ( $data['status'] === null || ! isset( $data['status'] ) || empty( $data['status'] ) ) {
2203
2204            // Default status for obj? -> this one gets for contacts -> $zbsCustomerMeta['status'] = zeroBSCRM_getSetting('defaultstatus');
2205
2206        }
2207
2208            #} ========= / CHECK FIELDS ===========
2209
2210            #} ========= OVERRIDE SETTING (Deny blank overrides) ===========
2211
2212            // this only functions if externalsource is set (e.g. api/form, etc.)
2213        if ( isset( $data['externalSources'] ) && is_array( $data['externalSources'] ) && count( $data['externalSources'] ) > 0 ) {
2214            if ( zeroBSCRM_getSetting( 'fieldoverride' ) == '1' ) {
2215
2216                $do_not_update_blanks = true;
2217
2218            }
2219        }
2220
2221            // either ext source + setting, or set by the func call
2222        if ( $do_not_update_blanks ) {
2223
2224                // this setting says 'don't override filled-out data with blanks'
2225                // so here we check through any passed blanks + convert to limitedFields
2226                // only matters if $id is set (there is somt to update not add
2227            if ( isset( $id ) && ! empty( $id ) && $id > 0 ) {
2228
2229                // get data to copy over (for now, this is required to remove 'fullname' etc.)
2230                $dbData = $this->db_ready_company( $data );
2231                // unset($dbData['id']); // this is unset because we use $id, and is update, so not req. legacy issue
2232                // unset($dbData['created']); // this is unset because this uses an obj which has been 'updated' against original details, where created is output in the WRONG format :)
2233
2234                $origData    = $data; // $data = array();
2235                $limitedData = array(); // array(array('key'=>'zbsco_x','val'=>y,'type'=>'%s'))
2236
2237                // cycle through + translate into limitedFields (removing any blanks, or arrays (e.g. externalSources))
2238                // we also have to remake a 'faux' data (removing blanks for tags etc.) for the post-update updates
2239                foreach ( $dbData as $k => $v ) {
2240
2241                    $intV = (int) $v;
2242
2243                    // only add if valuenot empty
2244                    if ( ! is_array( $v ) && ! empty( $v ) && $v != '' && $v !== 0 && $v !== -1 && $intV !== -1 ) {
2245
2246                        // add to update arr
2247                        $limitedData[] = array(
2248                            'key'  => 'zbsco_' . $k, // we have to add zbsco_ here because translating from data -> limited fields
2249                            'val'  => $v,
2250                            'type' => $this->getTypeStr( 'zbsco_' . $k ),
2251                        );
2252
2253                        // add to remade $data for post-update updates
2254                        $data[ $k ] = $v;
2255
2256                    }
2257                }
2258
2259                // copy over
2260                $limitedFields = $limitedData;
2261
2262            } // / if ID
2263
2264        } // / if do_not_update_blanks
2265
2266            #} ========= / OVERRIDE SETTING (Deny blank overrides) ===========
2267
2268            #} ========= BUILD DATA ===========
2269
2270            $update = false;
2271        $dataArr    = array();
2272        $typeArr    = array();
2273
2274        if ( is_array( $limitedFields ) ) {
2275
2276            // LIMITED FIELDS
2277            $update = true;
2278
2279            // cycle through
2280            foreach ( $limitedFields as $field ) {
2281
2282                // some weird case where getting empties, so added check
2283                if ( ! empty( $field['key'] ) ) {
2284                    $dataArr[ $field['key'] ] = $field['val'];
2285                    $typeArr[]                = $field['type'];
2286                }
2287            }
2288
2289            // add update time
2290            if ( ! isset( $dataArr['zbsco_lastupdated'] ) ) {
2291                $dataArr['zbsco_lastupdated'] = time();
2292                $typeArr[]                    = '%d'; }
2293        } else {
2294
2295                // FULL UPDATE/INSERT
2296
2297                    // contacts - avoid dupes
2298            if ( isset( $data['contacts'] ) && is_array( $data['contacts'] ) ) {
2299
2300                $coArr = array();
2301                foreach ( $data['contacts'] as $c ) {
2302                    $cI = (int) $c;
2303                    if ( $cI > 0 && ! in_array( $cI, $coArr ) ) {
2304                        $coArr[] = $cI;
2305                    }
2306                }
2307
2308                // reset the main
2309                if ( count( $coArr ) > 0 ) {
2310                    $data['contacts'] = $coArr;
2311                } else {
2312                    $data['contacts'] = 'unset';
2313                }
2314                unset( $coArr );
2315
2316            }
2317
2318                    // UPDATE
2319                    $dataArr = array(
2320
2321                        // ownership
2322                        // no need to update these (as of yet) - can't move teams etc.
2323                        // 'zbs_site' => zeroBSCRM_installSite(),
2324                        // 'zbs_team' => zeroBSCRM_installTeam(),
2325                        // 'zbs_owner' => $owner,
2326
2327                        'zbsco_status'        => $data['status'],
2328                        'zbsco_name'          => $data['name'],
2329                        'zbsco_email'         => $data['email'],
2330                        'zbsco_addr1'         => $data['addr1'],
2331                        'zbsco_addr2'         => $data['addr2'],
2332                        'zbsco_city'          => $data['city'],
2333                        'zbsco_county'        => $data['county'],
2334                        'zbsco_country'       => $data['country'],
2335                        'zbsco_postcode'      => $data['postcode'],
2336                        'zbsco_secaddr1'      => $data['secaddr1'],
2337                        'zbsco_secaddr2'      => $data['secaddr2'],
2338                        'zbsco_seccity'       => $data['seccity'],
2339                        'zbsco_seccounty'     => $data['seccounty'],
2340                        'zbsco_seccountry'    => $data['seccountry'],
2341                        'zbsco_secpostcode'   => $data['secpostcode'],
2342                        'zbsco_maintel'       => $data['maintel'],
2343                        'zbsco_sectel'        => $data['sectel'],
2344                        'zbsco_wpid'          => $data['wpid'],
2345                        'zbsco_avatar'        => $data['avatar'],
2346                        'zbsco_tw'            => $data['tw'],
2347                        'zbsco_li'            => $data['li'],
2348                        'zbsco_fb'            => $data['fb'],
2349                        'zbsco_lastupdated'   => time(),
2350                        'zbsco_lastcontacted' => $data['lastcontacted'],
2351
2352                    );
2353
2354                    $typeArr = array( // field data types
2355                                // '%d',  // site
2356                                // '%d',  // team
2357                                // '%d',  // owner
2358
2359                        '%s',
2360                        '%s',
2361                        '%s',
2362                        '%s',
2363                        '%s',
2364                        '%s',
2365                        '%s',
2366                        '%s',
2367                        '%s',
2368                        '%s',
2369                        '%s',
2370                        '%s',
2371                        '%s',
2372                        '%s',
2373                        '%s',
2374                        '%s',
2375                        '%s',
2376                        '%d',
2377                        '%s',
2378                        '%s',
2379                        '%s',
2380                        '%s',
2381                        '%d',
2382                        '%d',
2383
2384                    );
2385
2386                    if ( ! empty( $id ) && $id > 0 ) {
2387
2388                        // is update
2389                        $update = true;
2390
2391                        // got owner change? - allow all basically?
2392                        if ( isset( $owner ) && $owner > 0 ) {
2393
2394                            $dataArr['zbs_owner'] = $owner;
2395                            $typeArr[]            = '%d';
2396
2397                        }
2398                    } else {
2399
2400                        // INSERT (get's few extra :D)
2401                        $update               = false;
2402                        $dataArr['zbs_site']  = zeroBSCRM_site();
2403                        $typeArr[]            = '%d';
2404                        $dataArr['zbs_team']  = zeroBSCRM_team();
2405                        $typeArr[]            = '%d';
2406                        $dataArr['zbs_owner'] = $owner;
2407                        $typeArr[]            = '%d';
2408                        if ( isset( $data['created'] ) && ! empty( $data['created'] ) && $data['created'] !== -1 ) {
2409                            $dataArr['zbsco_created'] = $data['created'];
2410                            $typeArr[]                = '%d';
2411                        } else {
2412                            $dataArr['zbsco_created'] = time();
2413                            $typeArr[]                = '%d';
2414                        }
2415                    }
2416        }
2417
2418            #} ========= / BUILD DATA ===========
2419
2420            #} ============================================================
2421            #} ========= CHECK force_uniques & not_empty & max_len ========
2422
2423            // if we're passing limitedFields we skip these, for now
2424            // #v3.1 - would make sense to unique/nonempty check just the limited fields. #gh-145
2425        if ( ! is_array( $limitedFields ) ) {
2426
2427            // verify uniques
2428            if ( ! $this->verifyUniqueValues( $data, $id ) ) {
2429                return false; // / fails unique field verify
2430            }
2431
2432            // verify not_empty
2433            if ( ! $this->verifyNonEmptyValues( $data ) ) {
2434                return false; // / fails empty field verify
2435            }
2436        }
2437
2438            // whatever we do we check for max_len breaches and abbreviate to avoid wpdb rejections
2439            $dataArr = $this->wpdbChecks( $dataArr );
2440
2441            #} ========= / CHECK force_uniques & not_empty ================
2442            #} ============================================================
2443
2444            #} Check if ID present
2445        if ( $update ) {
2446
2447            #} Check if obj exists (here) - for now just brutal update (will error when doesn't exist)
2448            $originalStatus = $this->getCompanyStatus( $id );
2449
2450            $previous_company_obj = $this->getCompany( $id );
2451
2452            // log any change of status
2453            if ( isset( $dataArr['zbsco_status'] ) && ! empty( $dataArr['zbsco_status'] ) && ! empty( $originalStatus ) && $dataArr['zbsco_status'] != $originalStatus ) {
2454
2455                // status change
2456                $statusChange = array(
2457                    'from' => $originalStatus,
2458                    'to'   => $dataArr['zbsco_status'],
2459                );
2460            }
2461
2462            #} Attempt update
2463            if ( $wpdb->update(
2464                $ZBSCRM_t['companies'],
2465                $dataArr,
2466                array( // where
2467                    'ID' => $id,
2468                ),
2469                $typeArr,
2470                array( // where data types
2471                    '%d',
2472                )
2473            ) !== false ) {
2474
2475                        // if passing limitedFields instead of data, we ignore the following
2476                            // this doesn't work, because data is in args default as arr
2477                            // if (isset($data) && is_array($data)){
2478                            // so...
2479                if ( ! isset( $limitedFields ) || ! is_array( $limitedFields ) || $limitedFields == -1 ) {
2480
2481                    // OBJ LINKS - contacts -> companies
2482                    // This would link Company -> Contacts, $this->addUpdateObjectLinks($id,$data['contacts'],ZBS_TYPE_CONTACT);
2483                    // ... but we need it Contacts -> Company, so use a direct call in this case:
2484                    if ( is_array( $data['contacts'] ) ) {
2485                        foreach ( $data['contacts'] as $contactID ) {
2486
2487                            // if is ID
2488                            if ( $contactID > 0 ) {
2489
2490                                    // append to it's links (in case it has other companies too)
2491                                    $this->DAL()->addUpdateObjLinks(
2492                                        array(
2493                                            'objtypefrom' => ZBS_TYPE_CONTACT,
2494                                            'objtypeto'   => ZBS_TYPE_COMPANY,
2495                                            'objfromid'   => $contactID,
2496                                            'objtoids'    => array( $id ),
2497                                            'mode'        => 'append',
2498                                        )
2499                                    );
2500
2501                            }
2502                        }
2503                    }
2504
2505                    // tags
2506                    if ( isset( $data['tags'] ) && is_array( $data['tags'] ) ) {
2507
2508                        $this->addUpdateCompanyTags(
2509                            array(
2510                                'id'        => $id,
2511                                'tag_input' => $data['tags'],
2512                                'mode'      => $data['tag_mode'],
2513                            )
2514                        );
2515
2516                    }
2517
2518                    // externalSources
2519                    $approvedExternalSource = $this->DAL()->addUpdateExternalSources(
2520                        array(
2521                            'obj_id'           => $id,
2522                            'obj_type_id'      => ZBS_TYPE_COMPANY,
2523                            'external_sources' => isset( $data['externalSources'] ) ? $data['externalSources'] : array(),
2524                        )
2525                    ); // for IA below
2526
2527                    // Custom fields?
2528
2529                        #} Cycle through + add/update if set
2530                    if ( is_array( $customFields ) ) {
2531                        foreach ( $customFields as $cK => $cF ) {
2532
2533                            // any?
2534                            if ( isset( $data[ $cK ] ) ) {
2535
2536                                // add update
2537                                $cfID = $this->DAL()->addUpdateCustomField(
2538                                    array(
2539                                        'data' => array(
2540                                            'objtype' => ZBS_TYPE_COMPANY,
2541                                            'objid'   => $id,
2542                                            'objkey'  => $cK,
2543                                            'objval'  => $data[ $cK ],
2544                                        ),
2545                                    )
2546                                );
2547
2548                            }
2549                        }
2550                    }
2551
2552                        // Also got to catch any 'addr' custom fields :)
2553                    if ( is_array( $addrCustomFields ) && count( $addrCustomFields ) > 0 ) {
2554
2555                        // cycle through addr custom fields + save
2556                        // see #ZBS-518, not easy until addr's get DAL2
2557                        // WH deferring here
2558
2559                        // WH later added via the addUpdateContactField method - should work fine if we catch properly in get
2560                        foreach ( $addrCustomFields as $cK => $cF ) {
2561
2562                            // v2:
2563                            // $cKN = (int)$cK+1;
2564                            // $cKey = 'addr_cf'.$cKN;
2565                            // $cKey2 = 'secaddr_cf'.$cKN;
2566                            // v3:
2567                            $cKey  = 'addr_' . $cK;
2568                            $cKey2 = 'secaddr_' . $cK;
2569
2570                            // any?
2571                            if ( isset( $data[ $cKey ] ) ) {
2572
2573                                // add update
2574                                $cfID = $this->DAL()->addUpdateCustomField(
2575                                    array(
2576                                        'data' => array(
2577                                            'objtype' => ZBS_TYPE_COMPANY,
2578                                            'objid'   => $id,
2579                                            'objkey'  => $cKey,
2580                                            'objval'  => $data[ $cKey ],
2581                                        ),
2582                                    )
2583                                );
2584
2585                            }
2586
2587                            // any?
2588                            if ( isset( $data[ $cKey2 ] ) ) {
2589
2590                                // add update
2591                                $cfID = $this->DAL()->addUpdateCustomField(
2592                                    array(
2593                                        'data' => array(
2594                                            'objtype' => ZBS_TYPE_COMPANY,
2595                                            'objid'   => $id,
2596                                            'objkey'  => $cKey2,
2597                                            'objval'  => $data[ $cKey2 ],
2598                                        ),
2599                                    )
2600                                );
2601
2602                            }
2603                        }
2604                    }
2605
2606                        // / Custom Fields
2607
2608                } // / if $data
2609
2610                        #} Any extra meta keyval pairs?
2611                        // BRUTALLY updates (no checking)
2612                        $confirmedExtraMeta = false;
2613                if ( is_array( $extraMeta ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
2614
2615                            $confirmedExtraMeta = array();
2616
2617                    foreach ( $extraMeta as $k => $v ) {
2618
2619                        #} This won't fix stupid keys, just catch basic fails...
2620                        $cleanKey = strtolower( str_replace( ' ', '_', $k ) );
2621
2622                        #} Brutal update
2623                        // update_post_meta($postID, 'zbs_customer_extra_'.$cleanKey, $v);
2624                        $this->DAL()->updateMeta( ZBS_TYPE_COMPANY, $id, 'extra_' . $cleanKey, $v );
2625
2626                        #} Add it to this, which passes to IA
2627                        $confirmedExtraMeta[ $cleanKey ] = $v;
2628
2629                    }
2630                }
2631
2632                        #} INTERNAL AUTOMATOR
2633                        #} &
2634                        #} FALLBACKS
2635                        // UPDATING CONTACT
2636                if ( ! $silentInsert ) {
2637
2638                    #} FALLBACK
2639                    #} (This fires for customers that weren't added because they already exist.)
2640                    #} e.g. x@g.com exists, so add log "x@g.com filled out form"
2641                    #} Requires a type and a shortdesc
2642                    if (
2643                                                        is_array( $fallBackLog ) && ! empty( $fallBackLog['type'] ) && ! empty( $fallBackLog['shortdesc'] ) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2644                    ) {
2645
2646                        #} Brutal add, maybe validate more?!
2647
2648                        #} Long desc if present:
2649                        $zbsNoteLongDesc = '';
2650                        if ( isset( $fallBackLog['longdesc'] ) && ! empty( $fallBackLog['longdesc'] ) ) {
2651                            $zbsNoteLongDesc = $fallBackLog['longdesc'];
2652                        }
2653
2654                            #} Only raw checked... but proceed.
2655                            zeroBS_addUpdateObjLog(
2656                                ZBS_TYPE_COMPANY,
2657                                $id,
2658                                -1,
2659                                -1,
2660                                array(
2661                                    // Anything here will get wrapped into an array and added as the meta vals
2662                                    'type'      => $fallBackLog['type'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
2663                                    'shortdesc' => $fallBackLog['shortdesc'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
2664                                    'longdesc'  => $zbsNoteLongDesc, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2665                                )
2666                            );
2667
2668                    }
2669
2670                    // catch dirty flag (update of status) (note, after update_post_meta - as separate)
2671                    // if (isset($_POST['zbsco_status_dirtyflag']) && $_POST['zbsco_status_dirtyflag'] == "1"){
2672                    // actually here, it's set above
2673                    if ( isset( $statusChange ) && is_array( $statusChange ) ) {
2674
2675                        // status has changed
2676
2677                        // IA
2678                        zeroBSCRM_FireInternalAutomator(
2679                            'company.status.update',
2680                            array(
2681                                'id'        => $id,
2682                                'againstid' => $id,
2683                                'data'      => $dataArr,
2684                                'from'      => $statusChange['from'],
2685                                'to'        => $statusChange['to'],
2686                            )
2687                        );
2688
2689                    }
2690
2691                    // IA General company update (2.87+)
2692                    zeroBSCRM_FireInternalAutomator(
2693                        'company.update',
2694                        array(
2695                            'id'        => $id,
2696                            'againstid' => $id,
2697                            'data'      => $dataArr, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2698                        'prev_company'  => $previous_company_obj,
2699                        )
2700                    );
2701
2702                }
2703
2704                        // Successfully updated - Return id
2705                        return $id;
2706
2707            } else {
2708
2709                $msg = __( 'DB Update Failed', 'zero-bs-crm' );
2710                $zbs->DAL->addError( 302, $this->objectType, $msg, $dataArr );
2711
2712                // FAILED update
2713                return false;
2714
2715            }
2716        } else {
2717
2718                #} No ID - must be an INSERT
2719            if ( $wpdb->insert(
2720                $ZBSCRM_t['companies'],
2721                $dataArr,
2722                $typeArr
2723            ) > 0 ) {
2724
2725                #} Successfully inserted, lets return new ID
2726                $newID = $wpdb->insert_id;
2727
2728                // OBJ LINKS - contacts -> companies
2729                // This would link Company -> Contacts, $this->addUpdateObjectLinks($newID,$data['contacts'],ZBS_TYPE_CONTACT);
2730                // ... but we need it Contacts -> Company, so use a direct call in this case:
2731                if ( is_array( $data['contacts'] ) ) {
2732                    foreach ( $data['contacts'] as $contactID ) {
2733
2734                        // if is ID
2735                        if ( $contactID > 0 ) {
2736
2737                                // append to it's links (in case it has other companies too)
2738                                $this->DAL()->addUpdateObjLinks(
2739                                    array(
2740                                        'objtypefrom' => ZBS_TYPE_CONTACT,
2741                                        'objtypeto'   => ZBS_TYPE_COMPANY,
2742                                        'objfromid'   => $contactID,
2743                                        'objtoids'    => array( $newID ),
2744                                        'mode'        => 'append',
2745                                    )
2746                                );
2747
2748                        }
2749                    }
2750                }
2751
2752                // tags
2753                if ( isset( $data['tags'] ) && is_array( $data['tags'] ) ) {
2754
2755                    $this->addUpdateCompanyTags(
2756                        array(
2757                            'id'        => $newID,
2758                            'tag_input' => $data['tags'],
2759                            'mode'      => $data['tag_mode'],
2760                        )
2761                    );
2762
2763                }
2764
2765                // externalSources
2766                $approvedExternalSource = $this->DAL()->addUpdateExternalSources(
2767                    array(
2768                        'obj_id'           => $newID,
2769                        'obj_type_id'      => ZBS_TYPE_COMPANY,
2770                        'external_sources' => isset( $data['externalSources'] ) ? $data['externalSources'] : array(),
2771                    )
2772                ); // for IA below
2773
2774                    // Custom fields?
2775
2776                    #} Cycle through + add/update if set
2777                if ( is_array( $customFields ) ) {
2778                    foreach ( $customFields as $cK => $cF ) {
2779
2780                        // any?
2781                        if ( isset( $data[ $cK ] ) ) {
2782
2783                            // add update
2784                            $cfID = $this->DAL()->addUpdateCustomField(
2785                                array(
2786                                    'data' => array(
2787                                        'objtype' => ZBS_TYPE_COMPANY,
2788                                        'objid'   => $newID,
2789                                        'objkey'  => $cK,
2790                                        'objval'  => $data[ $cK ],
2791                                    ),
2792                                )
2793                            );
2794
2795                        }
2796                    }
2797                }
2798
2799                    // / Custom Fields
2800
2801                    // Also got to catch any 'addr' custom fields :)
2802                if ( is_array( $addrCustomFields ) && count( $addrCustomFields ) > 0 ) {
2803
2804                    foreach ( $addrCustomFields as $cK => $cF ) {
2805
2806                        $cKey  = 'addr_' . $cK;
2807                        $cKey2 = 'secaddr_' . $cK;
2808
2809                        if ( isset( $data[ $cKey ] ) ) {
2810
2811                            // add update
2812                            $cfID = $this->DAL()->addUpdateCustomField(
2813                                array(
2814                                    'data' => array(
2815                                        'objtype' => ZBS_TYPE_COMPANY,
2816                                        'objid'   => $newID,
2817                                        'objkey'  => $cKey,
2818                                        'objval'  => $data[ $cKey ],
2819                                    ),
2820                                )
2821                            );
2822
2823                        }
2824
2825                        // any?
2826                        if ( isset( $data[ $cKey2 ] ) ) {
2827
2828                            // add update
2829                            $cfID = $this->DAL()->addUpdateCustomField(
2830                                array(
2831                                    'data' => array(
2832                                        'objtype' => ZBS_TYPE_COMPANY,
2833                                        'objid'   => $newID,
2834                                        'objkey'  => $cKey2,
2835                                        'objval'  => $data[ $cKey2 ],
2836                                    ),
2837                                )
2838                            );
2839
2840                        }
2841                    }
2842                }
2843
2844                    #} Any extra meta keyval pairs?
2845                    // BRUTALLY updates (no checking)
2846                    $confirmedExtraMeta = false;
2847                if ( is_array( $extraMeta ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
2848
2849                    $confirmedExtraMeta = array();
2850
2851                    foreach ( $extraMeta as $k => $v ) {
2852
2853                        #} This won't fix stupid keys, just catch basic fails...
2854                        $cleanKey = strtolower( str_replace( ' ', '_', $k ) );
2855
2856                        #} Brutal update
2857                        // update_post_meta($postID, 'zbs_customer_extra_'.$cleanKey, $v);
2858                        $this->DAL()->updateMeta( ZBS_TYPE_COMPANY, $newID, 'extra_' . $cleanKey, $v );
2859
2860                        #} Add it to this, which passes to IA
2861                        $confirmedExtraMeta[ $cleanKey ] = $v;
2862
2863                    }
2864                }
2865
2866                    #} INTERNAL AUTOMATOR
2867                    #} &
2868                    #} FALLBACKS
2869                    // NEW CONTACT
2870                if ( ! $silentInsert ) {
2871
2872                    #} Add to automator
2873                    zeroBSCRM_FireInternalAutomator(
2874                        'company.new',
2875                        array(
2876                            'id'                   => $newID,
2877                            'data'                 => $dataArr,
2878                            'extsource'            => $approvedExternalSource,
2879                            'automatorpassthrough' => $automatorPassthrough, #} This passes through any custom log titles or whatever into the Internal automator recipe.
2880                            'extraMeta'            => $confirmedExtraMeta, #} This is the "extraMeta" passed (as saved)
2881                        )
2882                    );
2883
2884                }
2885
2886                    return $newID;
2887
2888            } else {
2889
2890                $msg = __( 'DB Insert Failed', 'zero-bs-crm' );
2891                $zbs->DAL->addError( 303, $this->objectType, $msg, $dataArr );
2892
2893                #} Failed to Insert
2894                return false;
2895
2896            }
2897        }
2898
2899            return false;
2900    }
2901
2902    /**
2903     * adds or updates a company's tags
2904     * ... this is really just a wrapper for addUpdateObjectTags
2905     *
2906     * @param array $args Associative array of arguments
2907     *              id (if update), owner, data (array of field data)
2908     *
2909     * @return int line ID
2910     */
2911    public function addUpdateCompanyTags( $args = array() ) {
2912
2913        global $ZBSCRM_t, $wpdb;
2914
2915        #} ============ LOAD ARGS =============
2916        $defaultArgs = array(
2917
2918            'id'        => -1, // co id
2919
2920            // generic pass-through (array of tag strings or tag IDs):
2921            'tag_input' => -1,
2922
2923            // or either specific:
2924            'tagIDs'    => -1,
2925            'tags'      => -1,
2926
2927            'mode'      => 'append',
2928
2929        );
2930        foreach ( $defaultArgs as $argK => $argV ) {
2931            $$argK = $argV;
2932            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
2933                if ( is_array( $args[ $argK ] ) ) {
2934                    $newData = $$argK;
2935                    if ( ! is_array( $newData ) ) {
2936                        $newData = array();
2937                    } foreach ( $args[ $argK ] as $subK => $subV ) {
2938                        $newData[ $subK ] = $subV;
2939                    }$$argK = $newData;
2940                } else {
2941                    $$argK = $args[ $argK ]; }
2942            }
2943        }
2944        #} =========== / LOAD ARGS ============
2945
2946        #} ========== CHECK FIELDS ============
2947
2948            // check id
2949            $id = (int) $id;
2950        if ( empty( $id ) || $id <= 0 ) {
2951            return false;
2952        }
2953
2954        #} ========= / CHECK FIELDS ===========
2955
2956        return $this->DAL()->addUpdateObjectTags(
2957            array(
2958                'objtype'   => ZBS_TYPE_COMPANY,
2959                'objid'     => $id,
2960                'tag_input' => $tag_input,
2961                'tags'      => $tags,
2962                'tagIDs'    => $tagIDs,
2963                'mode'      => $mode,
2964            )
2965        );
2966    }
2967
2968    /**
2969     * deletes a company object
2970     *
2971     * @param array $args Associative array of arguments
2972     *              id
2973     *
2974     * @return int success;
2975     */
2976    public function deleteCompany( $args = array() ) {
2977
2978        global $ZBSCRM_t, $wpdb, $zbs;
2979
2980        #} ============ LOAD ARGS =============
2981        $defaultArgs = array(
2982
2983            'id'          => -1,
2984            'saveOrphans' => true,
2985
2986        );
2987        foreach ( $defaultArgs as $argK => $argV ) {
2988            $$argK = $argV;
2989            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
2990                if ( is_array( $args[ $argK ] ) ) {
2991                    $newData = $$argK;
2992                    if ( ! is_array( $newData ) ) {
2993                        $newData = array();
2994                    } foreach ( $args[ $argK ] as $subK => $subV ) {
2995                        $newData[ $subK ] = $subV;
2996                    }$$argK = $newData;
2997                } else {
2998                    $$argK = $args[ $argK ]; }
2999            }
3000        }
3001        #} =========== / LOAD ARGS ============
3002
3003        #} Check ID & Delete :)
3004        $id = (int) $id;
3005        if ( ! empty( $id ) && $id > 0 ) {
3006
3007            // delete orphans?
3008            if ( $saveOrphans === false ) {
3009
3010                // delete any tag links
3011                $this->DAL()->deleteTagObjLinks(
3012                    array(
3013
3014                        'objtype' => ZBS_TYPE_COMPANY,
3015                        'objid'   => $id,
3016                    )
3017                );
3018
3019                // delete any external source information
3020                $this->DAL()->delete_external_sources(
3021                    array(
3022
3023                        'obj_type'   => ZBS_TYPE_COMPANY,
3024                        'obj_id'     => $id,
3025                        'obj_source' => 'all',
3026
3027                    )
3028                );
3029            }
3030
3031            $del = zeroBSCRM_db2_deleteGeneric( $id, 'companies' );
3032
3033            #} Add to automator
3034            zeroBSCRM_FireInternalAutomator(
3035                'company.delete',
3036                array(
3037                    'id'          => $id,
3038                    'saveOrphans' => $saveOrphans,
3039                )
3040            );
3041
3042            return $del;
3043
3044        }
3045
3046        return false;
3047    }
3048
3049    /**
3050     * tidy's the object from wp db into clean array
3051     *
3052     * @param array $obj (DB obj)
3053     *
3054     * @return array company (clean obj)
3055     */
3056    private function tidy_company( $obj = false, $withCustomFields = false ) {
3057
3058            $res = false;
3059
3060        if ( isset( $obj->ID ) ) {
3061            $res       = array();
3062            $res['id'] = $obj->ID;
3063            /*
3064            `zbs_site` INT NULL DEFAULT NULL,
3065            `zbs_team` INT NULL DEFAULT NULL,
3066            `zbs_owner` INT NOT NULL,
3067            */
3068            $res['owner'] = $obj->zbs_owner;
3069
3070            $res['status']             = $this->stripSlashes( $obj->zbsco_status );
3071            $res['name']               = $this->stripSlashes( $obj->zbsco_name );
3072            $res['email']              = $this->stripSlashes( $obj->zbsco_email );
3073            $res['addr1']              = $this->stripSlashes( $obj->zbsco_addr1 );
3074            $res['addr2']              = $this->stripSlashes( $obj->zbsco_addr2 );
3075            $res['city']               = $this->stripSlashes( $obj->zbsco_city );
3076            $res['county']             = $this->stripSlashes( $obj->zbsco_county );
3077            $res['country']            = $this->stripSlashes( $obj->zbsco_country );
3078            $res['postcode']           = $this->stripSlashes( $obj->zbsco_postcode );
3079            $res['secaddr1']           = $this->stripSlashes( $obj->zbsco_secaddr1 );
3080            $res['secaddr2']           = $this->stripSlashes( $obj->zbsco_secaddr2 );
3081            $res['seccity']            = $this->stripSlashes( $obj->zbsco_seccity );
3082            $res['seccounty']          = $this->stripSlashes( $obj->zbsco_seccounty );
3083            $res['seccountry']         = $this->stripSlashes( $obj->zbsco_seccountry );
3084            $res['secpostcode']        = $this->stripSlashes( $obj->zbsco_secpostcode );
3085            $res['maintel']            = $this->stripSlashes( $obj->zbsco_maintel );
3086            $res['sectel']             = $this->stripSlashes( $obj->zbsco_sectel );
3087            $res['wpid']               = (int) $obj->zbsco_wpid;
3088            $res['avatar']             = $this->stripSlashes( $obj->zbsco_avatar );
3089            $res['tw']                 = $this->stripSlashes( $obj->zbsco_tw );
3090            $res['li']                 = $this->stripSlashes( $obj->zbsco_li );
3091            $res['fb']                 = $this->stripSlashes( $obj->zbsco_fb );
3092            $res['created']            = (int) $obj->zbsco_created;
3093            $res['created_date']       = ( isset( $obj->zbsco_created ) && $obj->zbsco_created > 0 ) ? zeroBSCRM_date_i18n( -1, $obj->zbsco_created, false, true ) : false;
3094            $res['lastupdated']        = (int) $obj->zbsco_lastupdated;
3095            $res['lastupdated_date']   = ( isset( $obj->zbsco_lastupdated ) && $obj->zbsco_lastupdated > 0 ) ? zeroBSCRM_date_i18n( -1, $obj->zbsco_lastupdated, false, true ) : false;
3096            $res['lastcontacted']      = (int) $obj->zbsco_lastcontacted;
3097            $res['lastcontacted_date'] = ( isset( $obj->zbsco_lastcontacted ) && $obj->zbsco_lastcontacted > 0 ) ? zeroBSCRM_date_i18n( -1, $obj->zbsco_lastcontacted, false, true ) : false;
3098
3099            // if have totals, pass them :)
3100            if ( isset( $obj->quotes_total ) ) {
3101                $res['quotes_total'] = $obj->quotes_total;
3102            }
3103            if ( isset( $obj->invoices_total ) ) {
3104                $res['invoices_total']             = $obj->invoices_total;
3105                $res['invoices_total_inc_deleted'] = $obj->invoices_total_inc_deleted;
3106                $res['invoices_count']             = $obj->invoices_count;
3107                $res['invoices_count_inc_deleted'] = $obj->invoices_count_inc_deleted;
3108            }
3109            if ( isset( $obj->transactions_total ) ) {
3110                $res['transactions_total'] = $obj->transactions_total;
3111            }
3112            if ( isset( $obj->transactions_paid_total ) ) {
3113                $res['transactions_paid_total'] = $obj->transactions_paid_total;
3114            }
3115
3116            // and if have invs + trans totals, add to make total val
3117            // This now accounts for "part payments" where trans are part/whole payments against invs
3118            if ( isset( $res['invoices_total'] ) || isset( $res['transactions_total'] ) ) {
3119                        $res['total_value'] = jpcrm_get_total_value_from_contact_or_company( $res );
3120            }
3121
3122            // custom fields - tidy any that are present:
3123            if ( $withCustomFields ) {
3124                $res = $this->tidyAddCustomFields( ZBS_TYPE_COMPANY, $obj, $res, true );
3125            }
3126        }
3127
3128        return $res;
3129    }
3130
3131    /**
3132     * Wrapper, use $this->getCompanyMeta($contactID,$key) for easy retrieval of singular company
3133     * Simplifies $this->getMeta
3134     *
3135     * @param int objtype
3136     * @param int objid
3137     * @param string key
3138     *
3139     * @return array company meta result
3140     */
3141    public function getCompanyMeta( $id = -1, $key = '', $default = false ) {
3142
3143        global $zbs;
3144
3145        if ( ! empty( $key ) ) {
3146
3147            return $this->DAL()->getMeta(
3148                array(
3149
3150                    'objtype'     => ZBS_TYPE_COMPANY,
3151                    'objid'       => $id,
3152                    'key'         => $key,
3153                    'fullDetails' => false,
3154                    'default'     => $default,
3155                    'ignoreowner' => true, // for now !!
3156
3157                )
3158            );
3159
3160        }
3161
3162        return $default;
3163    }
3164
3165    /**
3166     * Returns an email addr against a Company
3167     *
3168     * @param int id Company ID
3169     *
3170     * @return string Company email
3171     */
3172    public function getCompanyEmail( $id = -1 ) {
3173
3174        global $zbs;
3175
3176        $id = (int) $id;
3177
3178        if ( $id > 0 ) {
3179
3180            return $this->DAL()->getFieldByID(
3181                array(
3182                    'id'          => $id,
3183                    'objtype'     => ZBS_TYPE_COMPANY,
3184                    'colname'     => 'zbsco_email',
3185                    'ignoreowner' => true,
3186                )
3187            );
3188
3189        }
3190
3191        return false;
3192    }
3193
3194    /**
3195     * Returns an ownerid against a company
3196     *
3197     * @param int id company ID
3198     *
3199     * @return int company owner id
3200     */
3201    public function getCompanyOwner( $id = -1 ) {
3202
3203        global $zbs;
3204
3205        $id = (int) $id;
3206
3207        if ( $id > 0 ) {
3208
3209            return $this->DAL()->getFieldByID(
3210                array(
3211                    'id'          => $id,
3212                    'objtype'     => ZBS_TYPE_COMPANY,
3213                    'colname'     => 'zbs_owner',
3214                    'ignoreowner' => true,
3215                )
3216            );
3217
3218        }
3219
3220        return false;
3221    }
3222
3223    /**
3224     * Returns an status against a company
3225     *
3226     * @param int $id company ID.
3227     *
3228     * @return string company status string
3229     */
3230    public function getCompanyStatus( $id = -1 ) {
3231
3232        global $zbs;
3233
3234        $id = (int) $id;
3235
3236        if ( $id > 0 ) {
3237
3238            return $this->DAL()->getFieldByID(
3239                array(
3240                    'id'          => $id,
3241                    'objtype'     => ZBS_TYPE_COMPANY,
3242                    'colname'     => 'zbsco_status',
3243                    'ignoreowner' => true,
3244                )
3245            );
3246
3247        }
3248
3249        return false;
3250    }
3251
3252    /**
3253     * Returns a formatted fullname (optionally including ID + first line of addr)
3254     * Replaces zeroBS_companyName
3255     *
3256     * @param int id Company ID
3257     * @param array Company array (if already loaded can pass)
3258     * @param array args (see format_fullname func)
3259     *
3260     * @return string Company full name
3261     */
3262    public function getCompanyNameEtc( $id = -1, $companyArr = false, $args = array() ) {
3263
3264        global $zbs;
3265
3266        $id = (int) $id;
3267
3268        // this makes sure it uses name not 'fname'
3269        $args['company'] = true;
3270
3271        if ( $id > 0 ) {
3272
3273                // get a limited-fields contact obj
3274                $company = $zbs->DAL->companies->getCompany(
3275                    $id,
3276                    array(
3277                        'withCustomFields' => false,
3278                        'fields'           => array( 'zbsco_addr1', 'zbsco_name' ),
3279                        'ignoreowner'      => true,
3280                    )
3281                );
3282            if ( isset( $company ) && is_array( $company ) && isset( $company['name'] ) ) {
3283                return $this->format_name_etc( $company, $args );
3284            }
3285        } elseif ( is_array( $companyArr ) ) {
3286
3287            // pass through
3288            return $this->format_name_etc( $companyArr, $args );
3289
3290        }
3291
3292        return false;
3293    }
3294
3295    /**
3296     * Returns a formatted address of a contact
3297     * Replaces zeroBS_companyAddr
3298     *
3299     * @param int id Company ID
3300     * @param array Company array (if already loaded can pass)
3301     * @param array args (see format_address func)
3302     *
3303     * @return string Company addr html
3304     */
3305    public function getCompanyAddress( $id = -1, $companyArr = false, $args = array() ) {
3306
3307        global $zbs;
3308
3309        $id = (int) $id;
3310
3311        if ( $id > 0 ) {
3312
3313            // get a limited-fields company obj
3314            // this is hacky, but basically get whole basic company record for this for now, because
3315            // this doesn't properly get addr custom fields:
3316            // $company = $this->getContact($id,array('withCustomFields' => false,'fields'=>$this->field_list_address2,'ignoreowner'=>true));
3317            $company = $this->getCompany(
3318                $id,
3319                array(
3320                    'withCustomFields' => true,
3321                    'ignoreowner'      => true,
3322                )
3323            );
3324            if ( isset( $company ) && is_array( $company ) && isset( $company['addr1'] ) ) {
3325                return $this->format_address( $company, $args );
3326            }
3327        } elseif ( is_array( $companyArr ) ) {
3328
3329            // pass through
3330            return $this->format_address( $companyArr, $args );
3331
3332        }
3333
3334        return false;
3335    }
3336
3337    /**
3338     * Returns a formatted address of a Company (2nd addr)
3339     * Replaces zeroBS_companySecondAddr
3340     *
3341     * @param int id Company ID
3342     * @param array Company array (if already loaded can pass)
3343     * @param array args (see format_address func)
3344     *
3345     * @return string Company addr html
3346     */
3347    public function getCompany2ndAddress( $id = -1, $companyArr = false, $args = array() ) {
3348
3349        global $zbs;
3350
3351        $id = (int) $id;
3352
3353        $args['secondaddr'] = true;
3354
3355        if ( $id > 0 ) {
3356
3357            // get a limited-fields company obj
3358            // this is hacky, but basically get whole basic company record for this for now, because
3359            // this doesn't properly get addr custom fields:
3360            // $company = $this->getContact($id,array('withCustomFields' => false,'fields'=>$this->field_list_address2,'ignoreowner'=>true));
3361            $company = $this->getCompany(
3362                $id,
3363                array(
3364                    'withCustomFields' => true,
3365                    'ignoreowner'      => true,
3366                )
3367            );
3368            if ( isset( $company ) && is_array( $company ) && isset( $company['addr1'] ) ) {
3369                return $this->format_address( $company, $args );
3370            }
3371        } elseif ( is_array( $companyArr ) ) {
3372
3373            // pass through
3374            return $this->format_address( $companyArr, $args );
3375
3376        }
3377
3378        return false;
3379    }
3380
3381    /**
3382     * Returns a Company's tag array
3383     *
3384     * @param int id Company ID
3385     *
3386     * @return mixed
3387     */
3388    public function getCompanyTags( $id = -1 ) {
3389
3390        global $zbs;
3391
3392        $id = (int) $id;
3393
3394        if ( $id > 0 ) {
3395
3396            return $this->DAL()->getTagsForObjID(
3397                array(
3398                    'objtypeid' => ZBS_TYPE_COMPANY,
3399                    'objid'     => $id,
3400                )
3401            );
3402
3403        }
3404
3405        return false;
3406    }
3407
3408    /**
3409     * Returns last contacted uts against a Company
3410     *
3411     * @param int id Company ID
3412     *
3413     * @return int Contact last contacted date as uts (or -1)
3414     */
3415    public function getCompanyLastContactUTS( $id = -1 ) {
3416
3417        global $zbs;
3418
3419        $id = (int) $id;
3420
3421        if ( $id > 0 ) {
3422
3423            return $this->DAL()->getFieldByID(
3424                array(
3425                    'id'          => $id,
3426                    'objtype'     => ZBS_TYPE_COMPANY,
3427                    'colname'     => 'zbsco_lastcontacted',
3428                    'ignoreowner' => true,
3429                )
3430            );
3431
3432        }
3433
3434        return false;
3435    }
3436
3437    /**
3438     * updates lastcontacted date for a Company
3439     *
3440     * @param int id Company ID
3441     * @param int uts last contacted
3442     *
3443     * @return bool
3444     */
3445    public function setCompanyLastContactUTS( $id = -1, $lastContactedUTS = -1 ) {
3446
3447        global $zbs;
3448
3449        $id = (int) $id;
3450
3451        if ( $id > 0 ) {
3452
3453            return $this->addUpdateCompany(
3454                array(
3455                    'id'            => $id,
3456                    'limitedFields' => array(
3457                        array(
3458                            'key'  => 'zbsco_lastcontacted',
3459                            'val'  => $lastContactedUTS,
3460                            'type' => '%d',
3461                        ),
3462                    ),
3463                )
3464            );
3465
3466        }
3467
3468        return false;
3469    }
3470
3471    /**
3472     * Returns a set of social accounts for a Company (tw,li,fb)
3473     *
3474     * @param int id Company ID
3475     *
3476     * @return array social acc's
3477     */
3478    // this is not used yet, ahead of time :) - will work tho ;)
3479    public function getCompanySocials( $id = -1 ) {
3480
3481        global $zbs;
3482
3483        $id = (int) $id;
3484
3485        if ( $id > 0 ) {
3486
3487            // lazy 3 queries, optimise later
3488
3489            $tw = $this->DAL()->getFieldByID(
3490                array(
3491                    'id'          => $id,
3492                    'objtype'     => ZBS_TYPE_COMPANY,
3493                    'colname'     => 'zbsco_tw',
3494                    'ignoreowner' => true,
3495                )
3496            );
3497
3498            $li = $this->DAL()->getFieldByID(
3499                array(
3500                    'id'          => $id,
3501                    'objtype'     => ZBS_TYPE_COMPANY,
3502                    'colname'     => 'zbsco_li',
3503                    'ignoreowner' => true,
3504                )
3505            );
3506
3507            $fb = $this->DAL()->getFieldByID(
3508                array(
3509                    'id'          => $id,
3510                    'objtype'     => ZBS_TYPE_COMPANY,
3511                    'colname'     => 'zbsco_fb',
3512                    'ignoreowner' => true,
3513                )
3514            );
3515
3516            return array(
3517                'tw' => $tw,
3518                'li' => $li,
3519                'fb' => $fb,
3520            );
3521
3522        }
3523
3524        return false;
3525    }
3526
3527    /**
3528     * Returns the next company ID and the previous company ID
3529     * Used for the navigation between companies.
3530     *
3531     * @param int id
3532     *
3533     * @return array int id
3534     */
3535    public function getCompanyPrevNext( $id = -1 ) {
3536
3537        global $ZBSCRM_t, $wpdb;
3538
3539        if ( $id > 0 ) {
3540            // then run the queries..
3541            $nextSQL = $this->prepare( 'SELECT MIN(ID) FROM ' . $ZBSCRM_t['companies'] . ' WHERE ID > %d', $id );
3542
3543            $res['next'] = $wpdb->get_var( $nextSQL );
3544
3545            $prevSQL = $this->prepare( 'SELECT MAX(ID) FROM ' . $ZBSCRM_t['companies'] . ' WHERE ID < %d', $id );
3546
3547            $res['prev'] = $wpdb->get_var( $prevSQL );
3548
3549            return $res;
3550
3551        }
3552
3553        return false;
3554    }
3555
3556    /**
3557     * Takes full object and makes a "list view" boiled down version
3558     * Used to generate listview objs
3559     *
3560     * @param array $obj (clean obj)
3561     *
3562     * @return array (listview ready obj)
3563     */
3564    public function listViewObj( $company = false, $columnsRequired = array() ) {
3565
3566        if ( is_array( $company ) && isset( $company['id'] ) ) {
3567
3568            // copy whole obj
3569            $resArr = $company;
3570
3571            // here I've translated from DAL2 version (from AJAX) so commented out is no longer req.
3572            // ... or they'll naturally be in DAL3 model already
3573
3574            // $resArr['id'] = $company['id'];
3575            // $resArr['name'] = $company['coname'];
3576            $resArr['avatar'] = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3577
3578            #} Format the date in the list view..
3579            // $formatted_date = zeroBSCRM_date_i18n(-1, strtotime($obj['created']));
3580            // let it use proper obj.
3581            // $resArr['added'] = $company['created_date'];
3582
3583            #} Custom columns
3584
3585                #} Tags
3586            if ( in_array( 'tagged', $columnsRequired ) ) {
3587
3588                $resArr['tags'] = $company['tags'];
3589
3590            }
3591
3592                // Total value
3593            if ( in_array( 'totalvalue', $columnsRequired ) ) {
3594
3595                #} Calc total value + add to return array
3596                $resArr['totalvalue'] = zeroBSCRM_formatCurrency( 0 );
3597                if ( isset( $company['total_value'] ) ) {
3598                    $resArr['totalvalue'] = zeroBSCRM_formatCurrency( $company['total_value'] );
3599                }
3600            }
3601
3602                // When Company<->Quotes:
3603                // Quotes
3604                /*
3605                if ( in_array( 'quotetotal', $columnsRequired ) ) {
3606                    if ( isset( $company['quotes_total'] ) ) {
3607                        $resArr['quotestotal'] = zeroBSCRM_formatCurrency( $company['quotes_total'] );
3608                    }
3609                    else {
3610                        $resArr['quotestotal'] = zeroBSCRM_formatCurrency( 0 );
3611                    }
3612                }*/
3613
3614                // Invoices
3615            if ( in_array( 'invoicetotal', $columnsRequired ) ) {
3616                if ( isset( $company['invoices_total'] ) ) {
3617
3618                    $resArr['invoicestotal'] = zeroBSCRM_formatCurrency( $company['invoices_total'] );
3619
3620                    // also pass total without formatting (used for hasinvoices check)
3621                    $resArr['invoices_total_value'] = $company['invoices_total'];
3622                } else {
3623                    $resArr['invoicestotal'] = zeroBSCRM_formatCurrency( 0 );
3624                }
3625            }
3626
3627                // Transactions
3628            if ( in_array( 'transactiontotal', $columnsRequired ) ) {
3629
3630                $resArr['transactionstotal'] = zeroBSCRM_formatCurrency( 0 );
3631                if ( isset( $company['transactions_value'] ) ) {
3632                    $resArr['transactionstotal'] = zeroBSCRM_formatCurrency( $company['transactions_value'] );
3633                }
3634            }
3635                // v3.0
3636            if ( isset( $company['transactions_total'] ) ) {
3637
3638                // DAL2 way, brutal effort.
3639                $resArr['transactions_total'] = zeroBSCRM_formatCurrency( $company['transactions_total'] );
3640
3641                // also pass total without formatting (used for hastransactions check)
3642                $resArr['transactions_total_value'] = $company['transactions_total'];
3643
3644            }
3645
3646                // avatar mode
3647                $avatarMode = zeroBSCRM_getSetting( 'avatarmode' );
3648
3649                #} Contacts at company
3650                $contactsAtCo = zeroBS_getCustomers( true, 1000, 0, false, false, '', false, false, $company['id'] );
3651
3652                // build as str
3653                $resArr['contacts'] = '';
3654
3655                // when a company had many contacts the UI was breached, here we max out at 4...
3656                $contactCount = 0;
3657
3658            foreach ( $contactsAtCo as $contact ) {
3659
3660                // stop at 4
3661                if ( $contactCount >= 4 ) {
3662
3663                    $resArr['contacts'] .= '<a href="' . jpcrm_esc_link( 'view', $company['id'], ZBS_TYPE_COMPANY ) . '" title="' . __( 'View all contacts at company', 'zero-bs-crm' ) . '">...</a>';
3664                    break;
3665                }
3666
3667                // add avatar/label
3668                if ( $avatarMode !== 3 ) {
3669                    $resArr['contacts'] .= zeroBS_getCustomerIcoLinked( $contact['id'] ); // or zeroBS_getCustomerIcoLinkedLabel?
3670                } else {
3671                    // no avatars, use labels
3672                    $resArr['contacts'] .= zeroBS_getCustomerLinkedLabel( $contact['id'] );
3673                }
3674
3675                ++$contactCount;
3676
3677            }
3678
3679                return $resArr;
3680
3681        }
3682
3683        return false;
3684    }
3685
3686    /**
3687     * remove any non-db fields from the object
3688     * basically takes array like array('owner'=>1,'fname'=>'x','fullname'=>'x')
3689     * and returns array like array('owner'=>1,'fname'=>'x')
3690     * This does so based on the objectModel!
3691     *
3692     * @param array $obj (clean obj)
3693     *
3694     * @return array (db ready arr)
3695     */
3696    private function db_ready_company( $obj = false ) {
3697
3698        // use the generic? (override here if necessary)
3699        return $this->db_ready_obj( $obj );
3700    }
3701
3702    // ===============================================================================
3703    // ============  Formatting    ===================================================
3704
3705    /**
3706     * Returns a formatted company name +- id, address (e.g. Automattic Ltd. 12 London Street #23)
3707     *
3708     * @param array $obj (tidied db obj)
3709     *
3710     * @return string name
3711     */
3712    public function format_name_etc( $companyArr = array(), $args = array() ) {
3713
3714        #} =========== LOAD ARGS ==============
3715        $defaultArgs = array(
3716
3717            'incFirstLineAddr' => false,
3718            'incID'            => false,
3719
3720        );
3721        foreach ( $defaultArgs as $argK => $argV ) {
3722            $$argK = $argV;
3723            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
3724                if ( is_array( $args[ $argK ] ) ) {
3725                    $newData = $$argK;
3726                    if ( ! is_array( $newData ) ) {
3727                        $newData = array();
3728                    } foreach ( $args[ $argK ] as $subK => $subV ) {
3729                        $newData[ $subK ] = $subV;
3730                    }$$argK = $newData;
3731                } else {
3732                    $$argK = $args[ $argK ]; }
3733            }
3734        }
3735        #} =========== / LOAD ARGS =============
3736
3737        $str = '';
3738        if ( isset( $companyArr['name'] ) ) {
3739            $str = $companyArr['name'];
3740        }
3741
3742        // First line of addr?
3743        if ( $incFirstLineAddr ) {
3744            if ( isset( $companyArr['addr1'] ) && ! empty( $companyArr['addr1'] ) ) {
3745                $str .= ' (' . $companyArr['addr1'] . ')';
3746            }
3747        }
3748
3749        // ID?
3750        if ( $incID ) {
3751            $str .= ' #' . $companyArr['id'];
3752        }
3753
3754        return $str;
3755    }
3756
3757    // =========== / Formatting    ===================================================
3758    // ===============================================================================
3759} // / class