Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 423
0.00% covered (danger)
0.00%
0 / 58
CRAP
0.00% covered (danger)
0.00%
0 / 1
zbsDAL_ObjectLayer
0.00% covered (danger)
0.00%
0 / 423
0.00% covered (danger)
0.00%
0 / 58
53130
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
210
 objTableName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 objType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 objModel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 linkedToObjectTypes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_included_in_templating
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 objModelIncCustomFields
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 hasCustomFields
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
90
 getCustomFields
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
132
 includesAddressFields
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 DAL
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 objFieldCSV
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 get
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
182
 generateFieldsGlobalArr
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
380
 getDAL1toDAL3ConversionMatrix
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getSingle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSingleCustomFields
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 getIDList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAll
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFullCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOwner
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 setOwner
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 updateMeta
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 tidy
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
 tidyAddCustomFields
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
342
 wpdbChecks
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
110
 verifyUniqueValues
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
342
 verifyNonEmptyValues
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
156
 db_ready_obj
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 addUpdateObjectLinks
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 lazyTable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lazyTidy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lazyTidyGeneric
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 space
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 spaceAnd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 spaceWhere
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 delimiterIf
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 stripSlashes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 decodeIfJSON
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 build_csv
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildWhereStr
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildWheres
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildPaging
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildWPMetaQueryWhere
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTypeStr
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prepare
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 catchSQLError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 format_fullname
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 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 format_address
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeSlug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeSlugCleanStr
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ownershipQueryVars
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ownershipSQL
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addUpdateCustomField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_name_clash_suffix_if_needed
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 fix_name_clash_if_needed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
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 >> Object Class
16 *
17 * @author   Woody Hayday <hello@jetpackcrm.com>
18 * @version  2.0
19 * @access   public
20 * @see      https://jetpackcrm.com/kb
21 */
22class zbsDAL_ObjectLayer {
23
24    protected $objectType  = -1; // e.g. ZBS_TYPE_CONTACT
25    protected $objectModel = -1; // array('DBFIELD(e.g. zbs_owner'=>array('Local field (e.g. owner)','format (int)'))
26            // formats:
27            // int = (int)
28            // uts = uts + converted to locale date _datestr
29            //
30    protected $objectTableName       = -1;
31    protected $objectFieldCSV        = -1; // assumes model wont change mid-load :)
32    protected $include_in_templating = false; // if true, object types fields will be accessible in templating
33
34    // hardtyped list of types this object type is commonly linked to
35    // Note that as of 4.1.1 this mechanism is only used via DAL3.Export to know what typical links to look for, it is not a hard rule, or currently respected anywhere else.
36    // e.g. Invoice object type may be commonly linked to 'contact' or 'company' object types
37    protected $linkedToObjectTypes = array();
38
39    /** This field is used to store name clashes so we can change custom field names
40     * with name clashes in function `fix_name_clash_if_needed()`
41     * See: https://github.com/Automattic/zero-bs-crm/issues/3477
42     *
43     * @var array
44     */
45    protected $name_clashes_temp_fix = array();
46
47    /** Suffix used to fix name clashes
48     * See: https://github.com/Automattic/zero-bs-crm/issues/3477
49     */
50    protected const NAME_CLASH_FIX_SUFFIX = '_zbs-name-clash-tmp-fix';
51
52    /**
53     * Prefix used in database table columns, etc.
54     *
55     * @var string
56     */
57    protected $objectDBPrefix; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
58
59    /**
60     * Whether object has addresses or not.
61     *
62     * @var boolean
63     */
64    protected $objectIncludesAddresses; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
65
66    function __construct( $args = array() ) {
67
68        #} =========== LOAD ARGS ==============
69        $defaultArgs = array(
70
71            // 'tag' => false,
72
73        );
74        foreach ( $defaultArgs as $argK => $argV ) {
75            $this->$argK = $argV;
76            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
77                if ( is_array( $args[ $argK ] ) ) {
78                    $newData = $this->$argK;
79                    if ( ! is_array( $newData ) ) {
80                        $newData = array();
81                    } foreach ( $args[ $argK ] as $subK => $subV ) {
82                        $newData[ $subK ] = $subV;
83                    }$this->$argK = $newData;
84                } else {
85                    $this->$argK = $args[ $argK ]; }
86            }
87        }
88        #} =========== / LOAD ARGS =============
89
90        // check objectModel + err if not legit
91
92        // ==== AUTO TRANSLATION
93        // Any labels passed with $objectModel need passing through translation :)
94        if ( is_array( $this->objectModel ) ) {
95            foreach ( $this->objectModel as $key => $fieldArr ) {
96
97                if ( isset( $fieldArr['label'] ) ) {
98                    $fieldArr['label'] = __( $fieldArr['label'], 'zero-bs-crm' );
99                }
100                if ( isset( $fieldArr['placeholder'] ) ) {
101                    $fieldArr['placeholder'] = __( $fieldArr['placeholder'], 'zero-bs-crm' );
102                }
103                if ( isset( $fieldArr['options'] ) && is_array( $fieldArr['options'] ) ) {
104
105                    $newOptions = array();
106                    foreach ( $fieldArr['options'] as $o ) {
107                        $newOptions[] = __( $o, 'zero-bs-crm' );
108                    }
109                    $fieldArr['options'] = $newOptions;
110                    unset( $newOptions );
111                }
112            }
113        }
114    }
115
116    // return core vars
117    public function objTableName() {
118        return $this->objectTableName;
119    }
120    public function objType() {
121        return $this->objectType;
122    }
123    public function objModel( $appendCustomFields = false ) {
124        return $this->objectModel;
125    }
126    public function linkedToObjectTypes() {
127        return $this->linkedToObjectTypes;
128    }
129    public function is_included_in_templating() {
130        return $this->include_in_templating;
131    }
132
133    // return the objModel with additional custom fields as if they were db-ready fields
134    // Note: This is a bridging function currently only used in DAL3.Exports.php,
135    // ... a refactoring of the dbmodel+customfields+globalfieldarrays is necessary pre v4.0
136    // ... in mean time, avoid usage in the initial field setup linkages
137    //
138    // Note: Also the format of the custom field arr will differ from the objmodel field arr's
139    // ... to distinguish, look for attribute 'custom-field'
140    // ... but probably best to generally avoid usage of this function until reconciled the above note.
141    // see gh-253
142    public function objModelIncCustomFields() {
143
144        global $zbs;
145
146        $customFields = false;
147        $model        = $this->objectModel;
148
149        // turn ZBS_TYPE_CONTACT (1) into "contact"
150        $typeStr = $this->DAL()->objTypeKey( $this->objectType );
151
152        // Direct retrieval v3+
153        if ( ! empty( $typeStr ) ) {
154            $customFields = $zbs->DAL->setting( 'customfields_' . $typeStr, array() );
155        }
156
157        // if is an obj which has custom field capacity:
158        if ( isset( $customFields ) && is_array( $customFields ) ) {
159
160            // add to model
161            foreach ( $customFields as $fieldKey => $field ) {
162
163                // Unpacks csv options and sets 'custom-field' attr
164                // Adds it to arr
165                // ignores potential collisions (e.g. custom field with key 'status'), these should be blocked by UI
166                $model[ $fieldKey ] = zeroBSCRM_customFields_processCustomField( $field );
167
168            }
169        }
170
171        // if obj also has addresses, check for address custom fields
172        // Adapted from DAL3.Fields.php
173        // see gh-253
174        if ( $this->includesAddressFields() ) {
175
176            #} Retrieve
177            $addrCustomFields = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_ADDRESS ) );
178
179            #} Addresses
180            if ( is_array( $addrCustomFields ) && count( $addrCustomFields ) > 0 ) {
181
182                $cfIndx = 1;
183                foreach ( $addrCustomFields as $fieldKey => $field ) {
184
185                    // unpacks csv options and sets 'custom-field' attr
186                    $fieldO = zeroBSCRM_customFields_processCustomField( $field );
187
188                    // splice them in to the end of the e.g. 'second address' group
189
190                        // Addr1
191
192                            // for this specifically, we also add '[area]'
193                            $fieldO['area'] = 'Main Address';
194
195                            // find index
196                            $mainAddrIndx = -1;
197                    $i                    = 0; foreach ( $model as $k => $f ) {
198                        if ( isset( $f['area'] ) && $f['area'] == 'Main Address' ) {
199                            $mainAddrIndx = $i;
200                        }
201                                ++$i;
202                    }
203
204                            // splice
205                            ++$mainAddrIndx; // req
206                            $model = array_merge(
207                                array_slice( $model, 0, $mainAddrIndx, true ),
208                                array( 'addr_' . $fieldKey => $fieldO ),
209                                array_slice( $model, $mainAddrIndx, null, true )
210                            );
211
212                        // Addr2
213
214                            // change area
215                            $fieldO['area'] = 'Second Address';
216
217                            // find index
218                            $secAddrIndx = -1;
219                    $i                   = 0; foreach ( $model as $k => $f ) {
220                        if ( isset( $f['area'] ) && $f['area'] == 'Second Address' ) {
221                            $secAddrIndx = $i;
222                        }
223                                ++$i;
224                    }
225
226                            // splice
227                            ++$secAddrIndx; // req
228                            $model = array_merge(
229                                array_slice( $model, 0, $secAddrIndx, true ),
230                                array( 'secaddr_' . $fieldKey => $fieldO ),
231                                array_slice( $model, $secAddrIndx, null, true )
232                            );
233
234                }
235            }
236        }
237
238        return $model;
239    }
240
241    /**
242     * returns bool depending on whether object has custom fields setup
243     *
244     * @return bool
245     */
246    public function hasCustomFields( $includeHidden = false ) {
247
248        global $zbs;
249
250        $fieldsToHide = array();
251
252        // turn ZBS_TYPE_CONTACT (1) into "contact"
253        $typeStr = $this->DAL()->objTypeKey( $this->objectType );
254
255        // any to hide?
256        if ( ! $includeHidden ) {
257
258            $fieldHideOverrides = $zbs->settings->get( 'fieldhides' );
259            if ( isset( $fieldHideOverrides[ $typeStr ] ) ) {
260                $fieldsToHide = $fieldHideOverrides[ $typeStr ];
261            }
262        }
263
264        // Direct retrieval v3+
265        if ( ! empty( $typeStr ) ) {
266
267            $customFields = $zbs->DAL->setting( 'customfields_' . $typeStr, array() );
268
269            // got custom fields?
270            if ( isset( $customFields ) && is_array( $customFields ) ) {
271
272                // cycle through custom fields
273                foreach ( $customFields as $fieldKey => $field ) {
274
275                    // hidden?
276                    if ( $includeHidden || ! in_array( $fieldKey, $fieldsToHide ) ) {
277
278                        return true;
279
280                    }
281                }
282            }
283        }
284
285        return false;
286    }
287
288    /**
289     * returns custom fields for an object
290     * .. optionally excluding hidden (from fieldsort page)
291     *
292     * @return array custom fields
293     */
294    public function getCustomFields( $includeHidden = false ) {
295
296        global $zbs;
297
298        $returnCustomFields = array();
299        $fieldsToHide       = array();
300
301        // turn ZBS_TYPE_CONTACT (1) into "contact"
302        $typeStr = $this->DAL()->objTypeKey( $this->objectType );
303
304        // any to hide?
305        if ( ! $includeHidden ) {
306
307            $fieldHideOverrides = $zbs->settings->get( 'fieldhides' );
308            if ( isset( $fieldHideOverrides[ $typeStr ] ) ) {
309                $fieldsToHide = $fieldHideOverrides[ $typeStr ];
310            }
311        }
312
313        // Direct retrieval v3+
314        if ( ! empty( $typeStr ) ) {
315
316            $customFields = $zbs->DAL->setting( 'customfields_' . $typeStr, array() );
317
318            // got custom fields?
319            if ( isset( $customFields ) && is_array( $customFields ) ) {
320
321                // cycle through custom fields
322                foreach ( $customFields as $fieldKey => $field ) {
323
324                    // hidden?
325                    if ( $includeHidden || ! $fieldsToHide || ( is_array( $fieldsToHide ) && ! in_array( $fieldKey, $fieldsToHide ) ) ) {
326
327                        // Unpacks csv options and sets 'custom-field' attr
328                        // Adds it to arr
329                        $returnCustomFields[ $fieldKey ] = $field;
330
331                    }
332                }
333            }
334        }
335
336        return $returnCustomFields;
337    }
338
339    public function includesAddressFields() {
340
341        if ( isset( $this->objectIncludesAddresses ) ) {
342            return true;
343        }
344
345        return false;
346    }
347
348    public function DAL() {
349
350        // hmm this is reference to a kind of 'parent' but not parent class. not sure best reference here.
351        // (to get back to $zbs->DAL)
352        // ... this allows us to centralise the reference in all children classes, at least, but probs a more oop logical way to do this
353        // is mostly helpers like buildWhere etc. but also other DAL funcs.
354        global $zbs;
355        return $zbs->DAL;
356    }
357
358    // internal sql helpers
359    private function objFieldCSV() {
360
361        // assumes model wont change :)
362        if ( $this->objectFieldCSV == -1 || $this->objectFieldCSV == '' ) {
363
364            $x = ''; if ( is_array( $this->objectModel ) ) {
365                foreach ( $this->objectModel as $k => $v ) {
366                    if ( ! empty( $x ) ) {
367                        $x .= ',';
368                    }
369                    $x .= $k;
370                }
371            }
372
373            $this->objectFieldCSV = $x;
374
375        }
376
377        return $this->objectFieldCSV;
378    }
379
380    // basic get any (first 10 paged)
381    public function get( $args = array() ) {
382
383        // hmm this is reference to a kind of 'parent' but not parent class. not sure best reference here.
384        // (to get back to $zbs->DAL)
385        global $zbs;
386
387        #} =========== LOAD ARGS ==============
388        $defaultArgs = array(
389
390            'sortByField' => 'ID',
391            'sortOrder'   => 'ASC',
392            'page'        => 0,
393            'perPage'     => 10,
394
395        );
396        foreach ( $defaultArgs as $argK => $argV ) {
397            $$argK = $argV;
398            if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
399                if ( is_array( $args[ $argK ] ) ) {
400                    $newData = $$argK;
401                    if ( ! is_array( $newData ) ) {
402                        $newData = array();
403                    } foreach ( $args[ $argK ] as $subK => $subV ) {
404                        $newData[ $subK ] = $subV;
405                    }$$argK = $newData;
406                } else {
407                    $$argK = $args[ $argK ]; }
408            }
409        }
410        #} =========== / LOAD ARGS =============
411
412        #} ========== CHECK FIELDS ============
413
414            // always ignore owner for now (settings global)
415            $ignoreowner = true;
416
417        #} ========= / CHECK FIELDS ===========
418
419        global $ZBSCRM_t, $wpdb;
420        $wheres          = array( 'direct' => array() );
421        $whereStr        = '';
422        $additionalWhere = '';
423        $params          = array();
424        $res             = array();
425
426        #} Build query
427        $query = 'SELECT ' . $this->objFieldCSV() . ' FROM ' . $this->objectTableName; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
428
429        #} ============= WHERE ================
430
431        #} ============ / WHERE ===============
432
433        #} Build out any WHERE clauses
434        $wheresArr = $zbs->DAL2->buildWheres( $wheres, $whereStr, $params );
435        $whereStr  = $wheresArr['where'];
436        $params    = $params + $wheresArr['params'];
437        #} / Build WHERE
438
439        #} Ownership v1.0 - the following adds SITE + TEAM checks, and (optionally), owner
440        $params = array_merge( $params, $zbs->DAL->tools->ownershipQueryVars( $ignoreowner ) ); // merges in any req.
441        $ownQ   = $zbs->DAL->ownershipSQL( $ignoreowner );
442        if ( ! empty( $ownQ ) ) {
443            $additionalWhere = $zbs->DAL->spaceAnd( $additionalWhere ) . $ownQ; // adds str to query
444        }
445        #} / Ownership
446
447        #} Append to sql (this also automatically deals with sortby and paging)
448        $query .= $zbs->DAL->buildWhereStr( $whereStr, $additionalWhere ) . $zbs->DAL->buildSort( $sortByField, $sortOrder ) . $zbs->DAL->buildPaging( $page, $perPage ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
449
450        try {
451
452            #} Prep & run query
453            $queryObj     = $zbs->DAL->prepare( $query, $params );
454            $potentialRes = $zbs->DAL->get_results( $queryObj, OBJECT );
455
456        } catch ( Exception $e ) {
457
458            #} General SQL Err
459            $zbs->DAL->catchSQLError( $e );
460
461        }
462
463        #} Interpret results (Result Set - multi-row)
464        if ( isset( $potentialRes ) && is_array( $potentialRes ) && count( $potentialRes ) > 0 ) {
465
466            #} Has results, tidy + return
467            foreach ( $potentialRes as $resDataLine ) {
468
469                // tidy (simple)
470                $resArr = $this->tidy( $resDataLine );
471                $res[]  = $resArr;
472
473            }
474        }
475
476        return $res;
477    }
478
479    // takes $this->objectModel and converts any applicable fields
480    // into old-form (legacy) $globalFieldArr (as used to be hard-typed in Fields.php Pre DAL3)
481    #} #FIELDLOADING
482    public function generateFieldsGlobalArr() {
483
484        if ( isset( $this->objectModel ) && is_array( $this->objectModel ) ) {
485
486            // build it
487            $retArr = array();
488
489            // cycle through fields
490            foreach ( $this->objectModel as $dal3key => $fieldModel ) {
491
492                // if they have 'input_type' then they're designed to be loaded into the global field var
493                // .. if not, they're DB-only / loaded elsewhere stuff
494                if ( is_array( $fieldModel ) && isset( $fieldModel['input_type'] ) ) {
495
496                    // should be loaded. Build it.
497                    $retArr[ $dal3key ] = array();
498
499                    // Old format as follows:
500                    /*
501                    'fname' => array(
502                        'text',                         // input type
503                        __('First Name',"zero-bs-crm"), // label
504                        'e.g. John',                    // placeholder
505                        // extra options: +-
506                        'options'=>array('Mr', 'Mrs', 'Ms', 'Miss', 'Dr', 'Prof','Mr & Mrs'),
507                        'essential' => true
508                        'area'=>__('Main Address',"zero-bs-crm"),
509                        'migrate'=>'addresses'
510                        'opt'=>'secondaddress',
511                    ) */
512
513                    // map them in
514
515                    // input type = [0]
516                    $retArr[ $dal3key ][0] = $fieldModel['input_type'];
517
518                    // input label = [1]
519                    $retArr[ $dal3key ][1] = '';
520                    if ( isset( $fieldModel['label'] ) ) {
521                        $retArr[ $dal3key ][1] = __( $fieldModel['label'], 'zero-bs-crm' );
522                        $second_address_label  = zeroBSCRM_getSetting( 'secondaddresslabel' );
523                        if ( empty( $second_address_label ) ) {
524                            $second_address_label = __( 'Second Address', 'zero-bs-crm' );
525                        }
526                        if ( ! empty( $fieldModel['area'] ) && $fieldModel['area'] == 'Second Address' ) {
527                            $retArr[ $dal3key ][1] .= ' (' . esc_html( $second_address_label ) . ')';
528                        }
529                    }
530
531                    // input placeholder = [2]
532                    $retArr[ $dal3key ][2] = ( isset( $fieldModel['placeholder'] ) ) ? $fieldModel['placeholder'] : '';
533
534                    // extra options (all key-referenced)
535                    // if (isset($fieldModel['options']))      $retArr[$dal3key]['options'] = $fieldModel['options'];
536                        // [options] == [3] in old global obj model world
537                    if ( isset( $fieldModel['options'] ) ) {
538                        $retArr[ $dal3key ][3] = $fieldModel['options'];
539                    }
540
541                    if ( isset( $fieldModel['essential'] ) ) {
542                        $retArr[ $dal3key ]['essential'] = $fieldModel['essential'];
543                    }
544                    if ( isset( $fieldModel['area'] ) ) {
545                        $retArr[ $dal3key ]['area'] = $fieldModel['area'];
546                    }
547                    if ( isset( $fieldModel['migrate'] ) ) {
548                        $retArr[ $dal3key ]['migrate'] = $fieldModel['migrate'];
549                    }
550                    if ( isset( $fieldModel['opt'] ) ) {
551                        $retArr[ $dal3key ]['opt'] = $fieldModel['opt'];
552                    }
553                    if ( isset( $fieldModel['nocolumn'] ) ) {
554                        $retArr[ $dal3key ]['nocolumn'] = $fieldModel['nocolumn'];
555                    }
556                    if ( isset( $fieldModel['default'] ) ) {
557                        $retArr[ $dal3key ]['default'] = $fieldModel['default'];
558                    }
559
560                    // should all have (where different from dal3key):
561                    if ( isset( $fieldModel['dal1key'] ) ) {
562                        $retArr[ $dal3key ]['dal1key'] = $fieldModel['dal1key'];
563                    }
564                }
565            } // / foreach field
566
567            // return built fieldGlobalArr (should mimic old DAL1/2 Fields.php)
568            return $retArr;
569
570        }
571
572        return array();
573    }
574
575    // returns a translation matrix of DAL1key => DAL3key, where possible, using 'dal1key' attribute in data model
576    public function getDAL1toDAL3ConversionMatrix() {
577
578        $ret = array();
579
580        if ( isset( $this->objectModel ) && is_array( $this->objectModel ) ) {
581
582                // foreach ($arraySource as $k => $v){
583            foreach ( $this->objectModel as $v3Key => $fieldObj ) {
584
585                if ( isset( $fieldObj['dal1key'] ) ) {
586
587                    $ret[ $fieldObj['dal1key'] ] = $v3Key;
588
589                }
590            }
591        }
592
593        return $ret;
594    }
595
596    // generic get X (by ID)
597    // designed to be overriden by each child.
598    public function getSingle( $ID = -1 ) {
599
600        return false;
601    }
602
603    /**
604     * Helper to retrieve Custom Fields with Data for an object
605     *
606     * @return array summarised custom fields including values, for object
607     */
608    public function getSingleCustomFields( $ID = -1, $includeHidden = false ) {
609
610        global $zbs;
611
612        if ( $ID > 0 ) {
613
614            // retrieve custom fields
615            $customFields = $this->getCustomFields( $includeHidden );
616
617            // retrieve object data
618            $objectData = $this->getSingle( $ID );
619            if ( ! is_array( $objectData ) ) {
620                $objectData = array();
621            }
622
623            // Build return
624            $return = array();
625            if ( is_array( $customFields ) ) {
626                foreach ( $customFields as $k => $v ) {
627
628                    $return[] = array(
629                        'id'    => $v[3],
630                        'name'  => $v[1],
631                        'value' => ( isset( $objectData[ $v[3] ] ) ? $objectData[ $v[3] ] : '' ),
632                        'type'  => $v[0],
633                    );
634
635                }
636            }
637
638            return $return;
639
640        }
641
642        return false;
643    }
644
645    // generic get X (by IDs)
646    // designed to be overriden by each child.
647    public function getIDList( $IDs = array() ) {
648
649        return false;
650    }
651
652    // generic get X (EVERYTHING)
653    // designed to be overriden by each child.
654    // expect heavy load!
655    public function getAll( $IDs = array() ) {
656
657        return false;
658    }
659
660    // generic get count of (EVERYTHING)
661    // designed to be overriden by each child.
662    public function getFullCount() {
663
664        return false;
665    }
666
667    // Ownership - simplistic GET owner of obj
668    public function getOwner( $objID = -1 ) {
669
670        // check
671        if ( $objID < 1 ) {
672            return false;
673        }
674
675        return $this->DAL()->getObjectOwner(
676            array(
677
678                'objID'     => $objID,
679                'objTypeID' => $this->objectType,
680
681            )
682        );
683    }
684
685    // Ownership - simplistic SET owner of obj
686    public function setOwner( $objID = -1, $ownerID = -1 ) {
687
688        // check
689        if ( $objID < 1 || $ownerID < 1 ) {
690            return false;
691        }
692
693        // set owner
694        return $this->DAL()->setObjectOwner(
695            array(
696
697                'objID'     => $objID,
698                'objTypeID' => $this->objectType,
699                'ownerID'   => $ownerID,
700
701            )
702        );
703    }
704
705    /**
706     * Wrapper, use $this->updateMeta($objid,$key,$val) for easy update of obj meta :)
707     * (Uses built in type)
708     *
709     * @param string key
710     * @param string value
711     *
712     * @return bool result
713     */
714    public function updateMeta( $objid = -1, $key = '', $val = '' ) {
715
716        if ( ! empty( $key ) && isset( $this->objectType ) && $this->objectType > 0 ) { // && !empty($val)
717
718            return $this->DAL()->addUpdateMeta(
719                array(
720
721                    'data' => array(
722
723                        'objid'   => $objid,
724                        'objtype' => $this->objectType,
725                        'key'     => $key,
726                        'val'     => $val,
727                    ),
728
729                )
730            );
731
732        }
733
734        return false;
735    }
736
737    /**
738     * tidy's the object from wp db into clean array
739     * ... also converts uts to local datetime etc. politely
740     *
741     * @param array $obj (DB obj)
742     *
743     * @return array (clean obj)
744     */
745    public function tidy( $obj = false ) {
746
747            global $zbs;
748
749            $res = false;
750
751        if ( isset( $obj->ID ) ) {
752
753            // THESE must be standard :)
754            $res       = array();
755            $res['id'] = (int) $obj->ID;
756            /*
757                `zbs_site` INT NULL DEFAULT NULL,
758                `zbs_team` INT NULL DEFAULT NULL,
759                `zbs_owner` INT NOT NULL,
760            */
761            $res['owner'] = (int) $obj->zbs_owner;
762
763            // cycle through + pull in
764            foreach ( $this->objectModel as $dbkey => $val ) {
765
766                // if not already set
767                if ( ! isset( $res[ $val['fieldname'] ] ) ) {
768
769                    switch ( $val['format'] ) {
770
771                        case 'int':
772                            $res[ $val['fieldname'] ] = (int) $obj->$dbkey;
773                            break;
774
775                        case 'uts':
776                            // normal return
777                            $res[ $val['fieldname'] ] = (int) $obj->$dbkey;
778
779                            // auto add locale str
780                            $res[ $val['fieldname'] . '_datestr' ] = zeroBSCRM_locale_utsToDatetime( $obj->$dbkey );
781                            break;
782
783                        default:
784                            $res[ $val['fieldname'] ] = $obj->$dbkey;
785                            break;
786
787                    }
788                }
789            }
790        } // if is obj id
791
792        return $res;
793    }
794
795    /**
796     * Offers generic custom field tidying, where an obj and it's cleaned version are passed
797     * ... centralised here as all objects (which have custom fields) had this repeated
798     *
799     * @param int    $objTypeID e.g. 1 = ZBS_TYPE_CONTACT.
800     * @param object $obj (DB obj).
801     * @param array  $res (tidied DB obj).
802     * @param bool   $includeAddrCustomFields (whether or not to also probe + tidy custom fields for addrs (mainly contacts + company tidying)).
803     *
804     * @return array (clean obj)
805     */
806    public function tidyAddCustomFields( $objTypeID = ZBS_TYPE_CONTACT, $obj = false, $res = false, $includeAddrCustomFields = false ) {
807
808        // vague catch
809        if ( $obj == false || $res == false ) {
810            return $res;
811        }
812
813        #} Retrieve any cf
814        $customFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => $objTypeID ) );
815
816        if ( is_array( $customFields ) ) {
817            foreach ( $customFields as $cK => $cF ) {
818
819                // custom field (e.g. 'third name') it'll be passed here as 'third-name'
820                // ... problem is mysql does not like that :) so we have to chage here:
821                // in this case we REVERSE this: prepend cf's with cf_ and we switch - for _
822                // ... by using $cKey below, instead of cK
823                $cKey = 'cf_' . str_replace( '-', '_', $cK );
824
825                $res[ $cK ] = '';
826
827                // if normal
828                if ( isset( $obj->$cK ) ) {
829                    $res[ $cK ] = $this->stripSlashes( $obj->$cK );
830                }
831
832                // if cf
833                if ( isset( $obj->$cKey ) ) {
834                    $res[ $cK ] = $this->stripSlashes( $obj->$cKey );
835                }
836
837                // if date_type, format
838                if ( isset( $cF[0] ) && $cF[0] === 'date' ) {
839
840                    // make a _date field
841                    if ( '' === $res[ $cK ] ) {
842                        $res[ $cK . '_cfdate' ] = '';
843                    } else {
844                        $res[ $cK . '_cfdate' ]       = zeroBSCRM_date_i18n( -1, $res[ $cK ], false, true );
845                        $res[ $cK . '_datetime_str' ] = jpcrm_uts_to_datetime_str( $res[ $cK ] );
846                        $res[ $cK . '_date_str' ]     = jpcrm_uts_to_date_str( $res[ $cK ] );
847                    }
848                }
849            }
850        }
851
852        #} Retrieve addr custfiedls
853        $addrCustomFields = $this->DAL()->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_ADDRESS ) );
854
855        if ( is_array( $addrCustomFields ) ) {
856            foreach ( $addrCustomFields as $cK => $cF ) {
857
858                // v2:
859                // $cKN = (int)$cK+1;
860                // $cKey = 'addr_cf'.$cKN;
861                // $cKey2 = 'secaddr_cf'.$cKN;
862                // v3:
863                // $cKey = 'addr_'.$cK;
864                // $cKey2 = 'secaddr_'.$cK;
865                // v4:
866                // These keys were causing alias collisions in mysql when keys ended up like 'addr_house-type'
867                // ... where the sort couldn't be fired for that key due to the - character
868                // ... so from 4.0.7+ we processed these adding a prefix `addrcf_` (and `secaddrcf_`) and replacing - for _
869                $cKey  = 'addrcf_' . str_replace( '-', '_', $cK );
870                $cKey2 = 'secaddrcf_' . str_replace( '-', '_', $cK );
871
872                // Note we still want to return as `addr_house-type` not `addrcf_house_type`
873                $res[ 'addr_' . $cK ]    = '';
874                $res[ 'secaddr_' . $cK ] = '';
875
876                // retrieve
877                if ( isset( $obj->$cKey ) ) {
878                    $res[ 'addr_' . $cK ] = $this->stripSlashes( $obj->$cKey );
879                }
880                if ( isset( $obj->$cKey2 ) ) {
881                    $res[ 'secaddr_' . $cK ] = $this->stripSlashes( $obj->$cKey2 );
882                }
883
884                // if date_type, format
885                if ( isset( $cF[0] ) && $cF[0] == 'date' ) {
886
887                    // make a _date field
888                    if ( isset( $res[ 'addr_' . $cK ] ) ) {
889                        $res[ 'addr_' . $cK . '_cfdate' ]       = zeroBSCRM_date_i18n( -1, $res[ 'addr_' . $cK ], false, true );
890                        $res[ 'addr_' . $cK . '_datetime_str' ] = jpcrm_uts_to_datetime_str( $res[ 'addr_' . $cK ] );
891                        $res[ 'addr_' . $cK . '_date_str' ]     = jpcrm_uts_to_date_str( $res[ 'addr_' . $cK ] );
892                    }
893                    if ( isset( $res[ 'secaddr_' . $cK ] ) ) {
894                        $res[ 'secaddr_' . $cK . '_cfdate' ]       = zeroBSCRM_date_i18n( -1, $res[ 'secaddr_' . $cK ], false, true );
895                        $res[ 'secaddr_' . $cK . '_datetime_str' ] = jpcrm_uts_to_datetime_str( $res[ 'secaddr_' . $cK ] );
896                        $res[ 'secaddr_' . $cK . '_date_str' ]     = jpcrm_uts_to_date_str( $res[ 'secaddr_' . $cK ] );
897                    }
898                }
899            }
900        }
901
902        return $res;
903    }
904
905    /**
906     * this takes a dataArray passed for update/insert and works through
907     * the fields, checking against the obj model for compliance
908     * initially this only includes max_len checks as fix for gh-270
909     *
910     * @param array $dataArr (pre-insert obj)
911     *
912     * @return bool
913     */
914    public function wpdbChecks( $dataArr = array() ) {
915
916        // req.
917        global $zbs;
918
919        if ( $this->objectType > 0 && is_array( $dataArr ) && isset( $this->objectDBPrefix ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
920
921            // new return
922            $retArr = $dataArr;
923
924            foreach ( $dataArr as $key => $val ) {
925
926                // use $objectDBPrefix to retrieve the objmodel key
927                // zbsi_id_override => id_override
928                $fieldKey = str_replace( $this->objectDBPrefix, '', $key );
929
930                // max length check?
931                if ( ! empty( $fieldKey ) && isset( $this->objectModel[ $fieldKey ] ) && isset( $this->objectModel[ $fieldKey ]['max_len'] ) ) {
932
933                    // check length
934                    if ( strlen( $val ) > $this->objectModel[ $fieldKey ]['max_len'] ) {
935
936                        // > max_len
937                        // .. abbreviate
938                        $retArr[ $key ] = substr( $val, 0, ( $this->objectModel[ $fieldKey ]['max_len'] - 3 ) ) . '...';
939
940                        // Add notice
941                        $label = $fieldKey;
942                        if ( isset( $this->objectModel[ $fieldKey ]['label'] ) ) {
943                            $label = $this->objectModel[ $fieldKey ]['label'];
944                        }
945                        $msg = __( 'The value for the field:', 'zero-bs-crm' ) . ' "' . $label . '" ' . __( 'was too long and has been abbreviated', 'zero-bs-crm' );
946                        $zbs->DAL->addError( 305, $this->objectType, $msg, $fieldKey );
947
948                    }
949                }
950            }
951
952            // return (possibly modified arr)
953            return $retArr;
954
955        }
956
957        return $dataArr;
958    }
959
960    /**
961     * this takes the current database insert/update or any object
962     * and validates it against the dbmodel for that objtype for uniqueness
963     * e.g. if a field in the dbmodel has force_unique, it's checked that that field is in fact unique,
964     * returning false if so
965     * Note: Blanks side-step this check if attribute 'can_be_blank', but are still are still subject to 'not_empty' check
966     * ... (verifyNonEmptyValues) if that attribute is specified in the obj model
967     *
968     * This'll also add an error to the stack, if it can
969     *
970     * @param array $obj (clean obj)
971     *
972     * @return bool
973     */
974    public function verifyUniqueValues( $objArr = array(), $id = -1 ) {
975
976        // req.
977        global $zbs;
978
979        $checksFailed = array();
980
981        if ( $this->objectType > 0 && is_array( $objArr ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
982
983            // DAL3+ we now have proper object models, so can check for 'force_unique' flags against field
984
985                // get an obj model, if set
986                // note: importantly, v2->v3 migration in v3.0 uses DAL2 objModel drop-in function here, so be aware this may be used during v2->v3 migration
987                $potentialModel = $zbs->DAL->objModel( $this->objectType );
988
989                // will be objlayer model if set
990            if ( is_array( $potentialModel ) ) {
991
992                // cycle through each field verify where necessary
993                foreach ( $potentialModel as $fieldKey => $fieldDetail ) {
994
995                    // there's a few we ignore :)
996                    if ( in_array( $fieldKey, array( 'ID', 'zbs_site', 'zbs_team', 'zbs_owner' ) ) ) {
997                        continue;
998                    }
999
1000                    // verify unique fields are unique + unused
1001                    // note. If 'can_be_blank' is also set against the field, blank doesn't get checked here
1002                    if ( isset( $fieldDetail['force_unique'] ) && $fieldDetail['force_unique'] ) {
1003
1004                        if ( isset( $fieldDetail['can_be_blank'] ) && $fieldDetail['can_be_blank'] && empty( $objArr[ $fieldKey ] ) ) {
1005
1006                            // field is blank, and is allowed to be!
1007
1008                        } else {
1009
1010                            // needs to ensure field is unique.
1011
1012                                // get existing id, if set
1013                                $whereArr                = array(); // colname zbsc_email
1014                                $whereArr['uniquecheck'] = array( $fieldDetail['fieldname'], '=', '%s', $objArr[ $fieldKey ] );
1015
1016                                $potentialID = $zbs->DAL->getFieldByWHERE(
1017                                    array(
1018                                        'objtype'     => $this->objectType, // ZBS_TYPE_CONTACT
1019                                        'colname'     => 'ID',
1020                                        'where'       => $whereArr,
1021                                        'ignoreowner' => true,
1022                                    )
1023                                );
1024
1025                                // catch dupes (exists, but it's not this)
1026                            if ( $potentialID > 0 && $potentialID != $id ) {
1027
1028                                // pass back the failed field.
1029                                $checksFailed[ $fieldKey ] = $fieldDetail;
1030
1031                            }
1032                        }
1033                    }
1034                } // / foreach
1035
1036            } // / if has model
1037
1038        }
1039
1040        // got any fails?
1041        if ( count( $checksFailed ) > 0 ) {
1042
1043            // can't update, some non-uniques.
1044
1045            // set reason msg
1046            if ( is_array( $checksFailed ) ) {
1047                foreach ( $checksFailed as $fieldKey => $fieldDetail ) {
1048                    $fk = $fieldKey;
1049                    if ( isset( $fieldDetail['label'] ) ) {
1050                        $fk = $fieldDetail['label'];
1051                    }
1052                    if ( $fk === 'id_override' ) {
1053                        $msg = __( 'Duplicated reference. The reference should be unique', 'zero-bs-crm' );
1054                    } else {
1055                        $msg = __( 'The value for the field:', 'zero-bs-crm' ) . ' "' . $fk . '" ' . __( 'was not unique (exists)', 'zero-bs-crm' );
1056                    }
1057
1058                    $zbs->DAL->addError( 301, $this->objectType, $msg, $fieldKey );
1059                }
1060            }
1061
1062                // return fail
1063                return false;
1064
1065        } // / fails unique field verify
1066
1067        return true;
1068    }
1069
1070    /**
1071     * this takes the current database insert/update or any object
1072     * and validates it against the dbmodel for that objtype for empties
1073     * e.g. if a field in the dbmodel has not_empty, it's checked that that field is in fact not empty
1074     * returning false if so
1075     * Note this is inverse to 'can_be_blank' flag
1076     *
1077     * This'll also add an error to the stack, if it can
1078     *
1079     * @param array $obj (clean obj)
1080     *
1081     * @return bool
1082     */
1083    public function verifyNonEmptyValues( $objArr = array() ) {
1084
1085        // req.
1086        global $zbs;
1087
1088        $checksFailed = array();
1089
1090        if ( $this->objectType > 0 && is_array( $objArr ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1091
1092            // DAL3+ we now have proper object models, so can check for 'force_unique' flags against field
1093
1094                // get an obj model, if set
1095                // note: importantly, v2->v3 migration in v3.0 uses DAL2 objModel drop-in function here, so be aware this may be used during v2->v3 migration
1096                $potentialModel = $zbs->DAL->objModel( $this->objectType );
1097
1098                // will be objlayer model if set
1099            if ( is_array( $potentialModel ) ) {
1100
1101                // cycle through each field verify where necessary
1102                foreach ( $potentialModel as $fieldKey => $fieldDetail ) {
1103
1104                    // verify fields are not empty
1105                    // note. This ignores 'can_be_blank', if is somehow set despite setting not_empty
1106                    if ( isset( $fieldDetail['not_empty'] ) && $fieldDetail['not_empty'] ) {
1107
1108                        // needs to ensure field is not empty
1109                        if ( empty( $objArr[ $fieldKey ] ) ) {
1110
1111                            // pass back the failed field.
1112                            $checksFailed[ $fieldKey ] = $fieldDetail;
1113
1114                        }
1115                    }
1116                } // / foreach
1117
1118            } // / if has model
1119
1120        }
1121
1122        // got any fails?
1123        if ( count( $checksFailed ) > 0 ) {
1124
1125                // can't update, some empties.
1126
1127                // set reason msg
1128            if ( is_array( $checksFailed ) ) {
1129                foreach ( $checksFailed as $fieldKey => $fieldDetail ) {
1130                    $fk = $fieldKey;
1131                    if ( isset( $fieldDetail['label'] ) ) {
1132                        $fk = $fieldDetail['label'];
1133                    }
1134                    $msg = __( 'The field:', 'zero-bs-crm' ) . ' "' . $fk . '" ' . __( 'is required', 'zero-bs-crm' );
1135                    $zbs->DAL->addError( 304, $this->objectType, $msg, $fieldKey );
1136                }
1137            }
1138
1139                // return fail
1140                return false;
1141
1142        } // / fails non blank field verify
1143
1144        return true;
1145    }
1146
1147    /**
1148     * remove any non-db fields from the object
1149     * basically takes array like array('owner'=>1,'fname'=>'x','fullname'=>'x')
1150     * and returns array like array('owner'=>1,'fname'=>'x')
1151     *
1152     * @param array $obj (clean obj)
1153     *
1154     * @return array (db ready arr)
1155     */
1156    public function db_ready_obj( $obj = false ) {
1157
1158            global $zbs;
1159
1160            // here it has to use the KEY which is without prefix (e.g. status, not zbsc_status) :)
1161        if ( isset( $this->objectModel ) && is_array( $this->objectModel ) ) {
1162
1163            $ret = array();
1164            if ( is_array( $obj ) ) {
1165
1166                foreach ( $this->objectModel as $fKey => $fObj ) {
1167
1168                    if ( isset( $obj[ $fKey ] ) ) {
1169                        $ret[ $fKey ] = $obj[ $fKey ];
1170                    }
1171                }
1172
1173                // gross backward compat - long may this die.
1174                if ( $zbs->db2CompatabilitySupport ) {
1175                    $ret['meta'] = $ret;
1176                }
1177            }
1178        }
1179
1180            return $ret;
1181    }
1182
1183    /**
1184     * genericified link array of id's with this obj
1185     * Takes current area (e.g. EVENT) as first type, and assigns objlinks EVENT -> $toObjType
1186     *
1187     * @param int   $objectID (int of this obj id)
1188     * @param array $objectIDsArr (array of ints (IDs)) - NOTE: can pass 'unset' as str to wipe links
1189     * @param int   $toObjectType (int of obj link type)
1190     *
1191     * @return bool of action
1192     */
1193    public function addUpdateObjectLinks( $objectID = -1, $objectIDsArr = false, $toObjectType = false ) {
1194        if ( $toObjectType > 0 && $objectID > 0 ) {
1195
1196            // GENERICIFIED OBJ LINKS
1197            if ( isset( $objectIDsArr ) && is_array( $objectIDsArr ) && count( $objectIDsArr ) > 0 ) {
1198
1199                // replace existing
1200                $this->DAL()->addUpdateObjLinks(
1201                    array(
1202                        'objtypefrom' => $this->objectType,
1203                        'objtypeto'   => $toObjectType,
1204                        'objfromid'   => $objectID,
1205                        'objtoids'    => $objectIDsArr,
1206                        'mode'        => 'replace',
1207                    )
1208                );
1209
1210                return true;
1211
1212            } elseif ( isset( $objectIDsArr ) && $objectIDsArr == 'unset' ) {
1213
1214                // wipe previous links
1215                $deleted = $this->DAL()->deleteObjLinks(
1216                    array(
1217                        'objtypefrom' => $this->objectType,
1218                        'objtypeto'   => $toObjectType,
1219                        'objfromid'   => $objectID,
1220                    )
1221                ); // where id =
1222
1223                return true;
1224
1225            }
1226        }
1227
1228            return false;
1229    }
1230
1231    // ===============================================================================
1232    // =========== DAL2 WRAPPERS =====================================================
1233    // These are dumb, and pass back directly to parent $zbs->DAL equivilents, centralising them
1234    // ... this is so we can keep $this->lazyTable etc. usage which is simpler than $this->DAL()->lazyTable
1235
1236    public function lazyTable( $objType = -1 ) {
1237
1238        // pass back to main $zbs->DAL
1239        return $this->DAL()->lazyTable( $objType );
1240    }
1241    public function lazyTidy( $objType = -1, $obj = false ) {
1242
1243        // pass back to main $zbs->DAL
1244        return $this->DAL()->lazyTidy( $objType, $obj );
1245    }
1246    public function lazyTidyGeneric( $obj = false ) {
1247
1248        // pass back to main $zbs->DAL
1249        return $this->DAL()->lazyTidyGeneric( $obj );
1250    }
1251    public function space( $str = '', $pre = false ) {
1252
1253        // pass back to main $zbs->DAL
1254        return $this->DAL()->space( $str, $pre );
1255    }
1256    public function spaceAnd( $str = '' ) {
1257
1258        // pass back to main $zbs->DAL
1259        return $this->DAL()->spaceAnd( $str );
1260    }
1261    public function spaceWhere( $str = '' ) {
1262
1263        // pass back to main $zbs->DAL
1264        return $this->DAL()->spaceWhere( $str );
1265    }
1266    public function delimiterIf( $delimiter, $ifStr = '' ) {
1267
1268        // pass back to main $zbs->DAL
1269        return $this->DAL()->delimiterIf( $delimiter, $ifStr );
1270    }
1271    public function stripSlashes( $obj = false ) {
1272        return zeroBSCRM_stripSlashes( $obj );
1273    }
1274    public function decodeIfJSON( $str = '' ) {
1275
1276        // pass back to main $zbs->DAL
1277        return $this->DAL()->decodeIfJSON( $str );
1278    }
1279    public function build_csv( $array = array() ) {
1280
1281        // pass back to main $zbs->DAL
1282        return $this->DAL()->build_csv( $array );
1283    }
1284    public function buildWhereStr( $whereStr = '', $additionalWhere = '' ) {
1285
1286        // pass back to main $zbs->DAL
1287        return $this->DAL()->buildWhereStr( $whereStr, $additionalWhere );
1288    }
1289    public function buildWheres( $wheres = array(), $whereStr = '', $params = array(), $andOr = 'AND', $includeInitialWHERE = true ) {
1290
1291        // pass back to main $zbs->DAL
1292        return $this->DAL()->buildWheres( $wheres, $whereStr, $params, $andOr, $includeInitialWHERE );
1293    }
1294    public function buildSort( $sortByField = '', $sortOrder = 'ASC' ) {
1295
1296        // pass back to main $zbs->DAL
1297        return $this->DAL()->buildSort( $sortByField, $sortOrder );
1298    }
1299    public function buildPaging( $page = -1, $perPage = -1 ) {
1300
1301        // pass back to main $zbs->DAL
1302        return $this->DAL()->buildPaging( $page, $perPage );
1303    }
1304    public function buildWPMetaQueryWhere( $metaKey = -1, $metaVal = -1 ) {
1305
1306        // pass back to main $zbs->DAL
1307        return $this->DAL()->buildWPMetaQueryWhere( $metaKey, $metaVal );
1308    }
1309    public function getTypeStr( $fieldKey = '' ) {
1310
1311        // pass back to main $zbs->DAL
1312        return $this->DAL()->getTypeStr( $fieldKey );
1313    }
1314    public function prepare( $sql = '', $params = array() ) {
1315
1316        // pass back to main $zbs->DAL
1317        return $this->DAL()->prepare( $sql, $params );
1318    }
1319    public function catchSQLError( $errObj = -1 ) {
1320
1321        // pass back to main $zbs->DAL
1322        return $this->DAL()->catchSQLError( $errObj );
1323    }
1324        // legacy signpost, this is now overwritten by DAL->contacts->fullname
1325    public function format_fullname( $contactArr = array() ) {
1326
1327        // pass back to main $zbs->DAL
1328        return $this->DAL()->format_fullname( $contactArr );
1329    }
1330        // legacy signpost, this is now overwritten by DAL->[contacts|companies]->format_name_etc
1331    public function format_name_etc( $contactArr = array(), $args = array() ) {
1332
1333        // pass back to main $zbs->DAL
1334        return $this->DAL()->format_name_etc( $contactArr, $args );
1335    }
1336    public function format_address( $contactArr = array(), $args = array() ) {
1337
1338        // pass back to main $zbs->DAL
1339        return $this->DAL()->format_address( $contactArr, $args );
1340    }
1341    public function makeSlug( $string, $replace = array(), $delimiter = '-' ) {
1342
1343        // pass back to main $zbs->DAL
1344        return $this->DAL()->makeSlug( $string, $replace, $delimiter );
1345    }
1346    public function makeSlugCleanStr( $string = '', $delimiter = '-' ) {
1347
1348        // pass back to main $zbs->DAL
1349        return $this->DAL()->makeSlugCleanStr( $string, $delimiter );
1350    }
1351    public function ownershipQueryVars( $ignoreOwner = false ) {
1352
1353        // pass back to main $zbs->DAL
1354        return $this->DAL()->ownershipQueryVars( $ignoreOwner );
1355    }
1356    public function ownershipSQL( $ignoreOwner = false, $table = '' ) {
1357
1358        // pass back to main $zbs->DAL
1359        return $this->DAL()->ownershipSQL( $ignoreOwner, $table );
1360    }
1361    public function addUpdateCustomField( $args = array() ) {
1362
1363        // pass back to main $zbs->DAL
1364        return $this->DAL()->addUpdateCustomField( $args );
1365    }
1366
1367    /**
1368     * Fixes name clashes between linked objects (e.g. company) and custom fields with the same slug.
1369     * Adds a suffix to conflicting field names to prevent clashes.
1370     *
1371     * @param array    &$array The array containing the fields that need to be checked for name clashes.
1372     * @param string[] $keys An array of keys to check within the provided array for potential name clashes.
1373     *
1374     * @return void
1375     *
1376     * @see https://github.com/Automattic/zero-bs-crm/issues/3477
1377     */
1378    public function add_name_clash_suffix_if_needed( &$array, $keys ) {
1379        foreach ( $keys as $key ) {
1380            if ( isset( $array[ $key ] ) ) {
1381                $this->name_clashes_temp_fix[] = $key;
1382                $new_key                       = $key . self::NAME_CLASH_FIX_SUFFIX;
1383                $array[ $new_key ]             = $array[ $key ];
1384            }
1385        }
1386    }
1387
1388    /**
1389     * Fixes a field name if it has been identified as having a name clash by appending a suffix.
1390     *
1391     * @param string &$field_name The field name to check and potentially modify to avoid a name clash.
1392     *
1393     * @return void
1394     *
1395     * @see https://github.com/Automattic/zero-bs-crm/issues/3477
1396     */
1397    public function fix_name_clash_if_needed( &$field_name ) {
1398        if ( in_array( $field_name, $this->name_clashes_temp_fix, true ) ) {
1399            $field_name = $field_name . self::NAME_CLASH_FIX_SUFFIX;
1400        }
1401    }
1402
1403    // =========== / DAL2 WRAPPERS ===================================================
1404    // ===============================================================================
1405} // / class