Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.07% covered (danger)
0.07%
2 / 2837
0.51% covered (danger)
0.51%
1 / 198
CRAP
n/a
0 / 0
zeroBS_getCustomer
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerName
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerName
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerNameShort
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerAddr
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerSecondAddr
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerMobile
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerAvatarHTML
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_customerCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerCompanyID
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_setCustomerCompanyID
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
zbsCRM_addUpdateCustomerCompany
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getCustomerWPID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerIDFromWPID
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_setCustomerWPID
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_getCustomerTags
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_setContactTags
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_getContactTagsArr
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerIcoHTML
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCustomerIDWithEmail
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_searchCustomers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_customerPortalDisableEnable
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
zeroBSCRM_customerPortalPWReset
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
90
zeroBSCRM_isCustomerPortalDisabled
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_createClientPortalUserFromRecord
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_getClientPortalUserID
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_getClientPortalUserWPObj
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_setClientPortalUser
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_createClientPortalUser
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
156
zeroBS_getCustomerMeta
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getDemoCustomer
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getCustomerExtraMetaVal
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_setCustomerExtraMetaVal
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_updateCustomerSocialAccounts
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getCustomerFiles
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getCustomerPortalFiles
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_updateCustomerFiles
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCustomers
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
182
zeroBS_getCustomersCountIncParams
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCustomerIcoLinked
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getCustomerIcoLinkedLabel
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getCustomerLinkedLabel
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_deleteCustomer
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCustomerIDWithExternalSource
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_getCustomerTagsByID
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getOwner
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
zeroBS_getOwnerObj
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_setOwner
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_mergeCustomers
0.00% covered (danger)
0.00%
0 / 153
0.00% covered (danger)
0.00%
0 / 1
6320
zeroBS_addUpdateCustomer
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 1
506
zeroBS_canUseCustomerAlias
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerAlias
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerAliasByID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCustomerAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_addCustomerAlias
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_removeCustomerAlias
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_removeCustomerAliasByID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_getLog
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getContactLogs
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getAllContactLogs
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_getCompanyLogs
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getObjCreationLog
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getMostRecentContactLog
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getMostRecentCompanyLog
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_searchLogs
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_allLogs
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_addUpdateLog
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_addUpdateContactLog
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_addUpdateObjLog
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_DAL2_set_post_terms
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_DAL2_set_object_terms
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_buildContactMeta
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_translateDAL1toDAL3Obj
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
zeroBS_buildObjArr
0.00% covered (danger)
0.00%
0 / 149
0.00% covered (danger)
0.00%
0 / 1
6320
zeroBSCRM_getSimplyFormattedContact
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
zeroBS_companyCount
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCompany
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCompanies
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
zeroBS_companyEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCompanyIDWithName
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getCompanyIDWithExternalSource
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getCompaniesForTypeahead
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getCompanyIDWithEmail
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_addUpdateCompany
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 1
650
zeroBS_buildCompanyMeta
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_deleteCompany
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_companyName
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_companyAddr
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_companySecondAddr
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_setCompanyTags
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_getCompanyTagsByID
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_getSimplyFormattedCompany
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
zeroBS_getQuoteStatus
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_getNextQuoteID
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_setMaxQuoteID
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getQuoteOffset
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getQuote
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_markQuoteAccepted
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_markQuoteUnAccepted
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getQuotes
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getQuotesCountIncParams
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getQuotesForCustomer
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getQuoteTemplate
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getQuoteTemplates
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getQuoteTemplatesCountIncParams
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_changeQuoteCustomer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getNextInvoiceID
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_setMaxInvoiceID
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getInvoiceOffset
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getInvoices
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getInvoicesCountIncParams
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getInvoice
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getInvoicesForCustomer
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_changeInvoiceCustomer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getInvoiceCustomer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_updateInvoiceStatus
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_get_invoice_defaults
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_get_invoice_settings
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getProductIndex
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_quotes_getFromHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_invoicing_getInvoiceData
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
870
zeroBSCRM_check_amount_due_mark_paid
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getTransaction
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getTransactions
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getTransactionsCountIncParams
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getTransactionsForCustomer
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getTransactionIDWithExternalSource
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_addUpdateTransaction
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 1
650
zeroBS_getTransactionMeta
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_buildTransactionMeta
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_changeTransactionCustomer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_changeTransactionCompany
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_getTaskList
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getEvents
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getEventsByCustomerID
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_changeEventCustomer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_addUpdateEvent
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
210
zeroBS_getForm
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getForms
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getFormsCountIncParams
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_getSetting
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
zeroBS_canUseAlias
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
zeroBS_getObjAlias
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getAliasByID
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getObjAliases
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_addObjAlias
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
zeroBS_removeObjAlias
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_removeObjAliasByID
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
jpcrm_get_total_value_from_contact_or_company
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
156
zeroBS_customerTotalValue
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_customerQuotesValue
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_customerInvoicesValue
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_customerTransactionsValue
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_security_logRequest
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_security_finiRequest
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_security_blockRequest
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_clearSecurityLogs
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_hashes_GetHashForObj
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_hashes_GetObjFromHash
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
240
zeroBSCRM_taxRates_getTaxValue
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_taxRates_getTaxRate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
zeroBSCRM_taxRates_getTaxTableArr
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
jpcrm_tax_rates_generate_lookup_key
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_taxRates_addUpdateTaxRate
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 1
650
zeroBSCRM_taxRates_deleteTaxRate
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
90
zeroBSCRM_taxRates_tidy_taxRate
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_files_getFiles
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
132
zeroBSCRM_files_updateFiles
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
zeroBSCRM_files_key
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
90
zeroBSCRM_checkValidTempHash
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
zeroBSCRM_getTempHash
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_addUpdateTempHash
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_deleteTempHash
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
zeroBS_tidy_temphash
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_GenerateTempHash
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBS_getAssigneeEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jpcrm_get_obj_owner_wordpress_email
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getAssigneeMobile
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBS_getObjOwnerWPMobile
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
zeroBS_getWPUsersMobile
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
jpcrm_wp_user_name
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
zeroBS_getTransactionsStatuses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_getCustomerStatuses
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_getTransactionsStatuses
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_getInvoicesStatuses
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_getCompanyStatusesCSV
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
zeroBSCRM_getCompanyStatuses
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_db2_deleteGeneric
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
zeroBSCRM_clickToCallPrefix
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
zeroBS_getCurrentUserUsername
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_crm_users_list
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_DAL2_ignoreOwnership
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_DEPRECATEDMSG
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
jpcrm_upconvert_obj_type
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
zbsLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jpcrm_esc_link
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
1560
zeroBSCRM_installDefaultContent
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
42
1<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 * V1.20
6 *
7 * Copyright 2020 Automattic
8 *
9 * Date: 01/11/16
10 */
11
12defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
13
14    /*
15        This file contains functions from DAL1 & DAL2 which have been translated into DAL2.5
16        This, along with DAL2, provide backward compatability with all other extensions etc.
17        - Note: First half of this file (customer funcs etc.) is the same as DAL2.LegacySupport.php first half, but going forwards, we only maintain this file.
18
19        Leaving peeps in a few states (until migrated):
20        1) Still on DAL1, using DAL.LegacySuport.php
21            -> Requires Migration ->
22        2) Still on DAL2, using DAL2.PHP, DAL.LegacySupport.php & DAL2.LegacySupport.php, & loading DAL objs but not using (except for contacts, logs, segments)
23            -> Requires Migration ->
24        3) Up to date fully, using DAL2.Helpers.php & fully using DAL objs.
25
26        .. fresh installs will silently migrate up to 3), older ones will manually have to run the wizs
27
28    */
29
30// ====================================================================================================================================
31// ====================================================================================================================================
32// ==================== DAL 2.0 FUNCS =================================================================================================
33// ====================================================================================================================================
34// ====================================================================================================================================
35
36/*
37======================================================
38    Unchanged DAL2->3 (Mostly customer/contact + log relatead)
39====================================================== */
40
41function zeroBS_getCustomer( $cID = -1, $withInvoices = false, $withQuotes = false, $withTransactions = false ) {
42
43    global $zbs;
44    return $zbs->DAL->contacts->getContact(
45        $cID,
46        array(
47
48            // with what?
49            'withCustomFields' => true,
50            'withQuotes'       => $withQuotes,
51            'withInvoices'     => $withInvoices,
52            'withTransactions' => $withTransactions,
53            'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
54
55        )
56    );
57}
58
59function zeroBS_getCustomerName( $contactID = -1 ) {
60
61    global $zbs;
62    return $zbs->DAL->contacts->getContactFullNameEtc(
63        $contactID,
64        array(),
65        array(
66            'incFirstLineAddr' => true,
67            'incID'            => true,
68        )
69    );
70}
71function zeroBS_customerName( $contactID = '', $contactArr = false, $incFirstLineAddr = true, $incID = true ) {
72
73    global $zbs;
74    return $zbs->DAL->contacts->getContactFullNameEtc(
75        $contactID,
76        $contactArr,
77        array(
78            'incFirstLineAddr' => $incFirstLineAddr,
79            'incID'            => $incID,
80        )
81    );
82}
83function zeroBS_getCustomerNameShort( $contactID = -1 ) {
84
85    global $zbs;
86    return $zbs->DAL->contacts->getContactFullNameEtc(
87        $contactID,
88        array(),
89        array(
90            'incFirstLineAddr' => false,
91            'incID'            => false,
92        )
93    );
94}
95
96function zeroBS_customerAddr( $contactID = '', $contactArr = array(), $addrFormat = 'short', $delimiter = ', ' ) {
97
98    global $zbs;
99    return $zbs->DAL->contacts->getContactAddress(
100        $contactID,
101        array(),
102        array(
103            'addrFormat' => $addrFormat,
104            'delimiter'  => $delimiter,
105        )
106    );
107}
108
109#} Returns a str of address, ($third param = 'short','full')
110#} Pass an ID OR a customerMeta array (saves loading ;) - in fact doesn't even work with ID yet... lol)
111function zeroBS_customerSecondAddr( $contactID = '', $contactArr = array(), $addrFormat = 'short', $delimiter = ', ' ) {
112
113    global $zbs;
114    return $zbs->DAL->contacts->getContact2ndAddress(
115        $contactID,
116        array(),
117        array(
118            'addrFormat' => $addrFormat,
119            'delimiter'  => $delimiter,
120        )
121    );
122}
123
124function zeroBS_customerEmail( $contactID = '', $contactArr = false ) {
125
126    global $zbs;
127    return $zbs->DAL->contacts->getContactEmail( $contactID );
128}
129
130function zeroBS_customerMobile( $contactID = '', $contactArr = false ) {
131
132    global $zbs;
133    return $zbs->DAL->contacts->getContactMobile( $contactID );
134}
135
136function zeroBS_customerAvatarHTML( $contactID = '', $contactArr = false, $size = 100, $extraClasses = '' ) {
137
138    global $zbs;
139    return $zbs->DAL->contacts->getContactAvatarHTML( $contactID, $size, $extraClasses );
140}
141
142function zeroBS_customerCount() {
143
144    global $zbs;
145    return $zbs->DAL->contacts->getContactCount( array( 'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ) ) );
146}
147
148#} Retrieves company post id if associated with customer
149// note 2.5+ can have multiple co's, but this'll only return first ID, need to move away from this
150function zeroBS_getCustomerCompanyID( $cID = -1 ) {
151
152    global $zbs;
153    $coArr = $zbs->DAL->contacts->getContactCompanies( $cID );
154    if ( is_array( $coArr ) && count( $coArr ) > 0 ) {
155        return $coArr[0]['id'];
156    }
157
158    return false;
159}
160
161#} sets company id associated with customer (note this'll override any existing val)
162// note 2.5+ can have multiple co's, but this'll only add first ID, need to move away from this
163function zeroBS_setCustomerCompanyID( $cID = -1, $coID = -1 ) {
164
165    global $zbs;
166    if ( ! empty( $cID ) && ! empty( $coID ) ) {
167
168        return $zbs->DAL->contacts->addUpdateContactCompanies(
169            array(
170                'id'         => $cID,
171                'companyIDs' => array( $coID ),
172            )
173        );
174
175    }
176
177    return false;
178}
179function zbsCRM_addUpdateCustomerCompany( $customerID = -1, $companyID = -1 ) {
180
181    global $zbs;
182    if ( ! empty( $customerID ) && ! empty( $companyID ) ) {
183
184        return $zbs->DAL->contacts->addUpdateContactCompanies(
185            array(
186                'id'         => $customerID,
187                'companyIDs' => array( $companyID ),
188            )
189        );
190
191    }
192
193    return false;
194}
195
196/**
197 * Returns a WP ID for a given contact ID, or false if no match.
198 *
199 * @param int $cID Contact ID.
200 * @return int|false
201 */
202function zeroBS_getCustomerWPID( $cID = -1 ) {
203
204    global $zbs;
205    return $zbs->DAL->contacts->getContactWPID( $cID );
206}
207
208/**
209 * Returns a contact ID for a given WP ID, or false if no match.
210 *
211 * @param int $wpID WordPress ID.
212 * @return int|false
213 */
214function zeroBS_getCustomerIDFromWPID( $wpID = -1 ) {
215
216    global $zbs;
217    return $zbs->DAL->contacts->getContact(
218        -1,
219        array(
220            'WPID'        => $wpID,
221            'onlyID'      => 1,
222            'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
223        )
224    );
225}
226
227#} Sets a WP id against a customer
228function zeroBS_setCustomerWPID( $cID = -1, $wpID = -1 ) {
229
230    global $zbs;
231    return $zbs->DAL->contacts->addUpdateContactWPID(
232        array(
233            'id'   => $cID,
234            'WPID' => $wpID,
235        )
236    );
237}
238
239function zeroBSCRM_getCustomerTags( $hide_empty = false ) {
240
241    global $zbs;
242
243    return $zbs->DAL->getTagsForObjType(
244        array(
245            'objtypeid'    => ZBS_TYPE_CONTACT,
246            'excludeEmpty' => $hide_empty,
247            'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
248        )
249    );
250}
251
252// either or
253function zeroBSCRM_setContactTags( $cID = -1, $tags = array(), $tagIDs = array(), $mode = 'replace' ) {
254
255    if ( $cID > 0 ) {
256
257        $args = array(
258
259            'id'   => $cID,
260            'mode' => $mode,
261        );
262
263        // got tags?
264        if ( is_array( $tags ) && ! empty( $tags ) ) {
265            $args['tags'] = $tags;
266        } elseif ( is_array( $tagIDs ) && ! empty( $tagIDs ) ) {
267            $args['tagIDs'] = $tagIDs;
268        } else {
269            return false;
270        }
271
272        global $zbs;
273
274        return $zbs->DAL->contacts->addUpdateContactTags( $args );
275
276    }
277
278    return false;
279}
280function zeroBSCRM_getContactTagsArr( $hide_empty = true ) {
281
282    global $zbs;
283
284    return $zbs->DAL->getTagsForObjType(
285        array(
286            'objtypeid'    => ZBS_TYPE_CONTACT,
287            'excludeEmpty' => $hide_empty,
288            'withCount'    => true,
289            'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
290        )
291    );
292}
293function zeroBS_getCustomerIcoHTML( $cID = -1, $additionalClasses = '' ) {
294
295    $thumbHTML = '<i class="fa fa-user" aria-hidden="true"></i>';
296
297    global $zbs;
298    $thumbURL = $zbs->DAL->contacts->getContactAvatarURL( $cID );
299    if ( ! empty( $thumbURL ) ) {
300
301        $thumbHTML = '<img src="' . $thumbURL . '" alt="" />'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
302
303    }
304
305    return '<div class="zbs-co-img ' . $additionalClasses . '">' . $thumbHTML . '</div>';
306}
307
308function zeroBS_getCustomerIDWithEmail( $custEmail = '' ) {
309    /**
310     *  @var $custEmail the customer email you want to check if a contact exists for
311     *
312     *  @return returns return $potentialRes->ID from $zbs->DAL->contacts->getContact()..
313     */
314
315    global $zbs;
316    return $zbs->DAL->contacts->getContact(
317        -1,
318        array(
319            'email'       => $custEmail,
320            'onlyID'      => true,
321            'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
322        )
323    );
324}
325
326function zeroBS_searchCustomers( $args = array(), $withMoneyData = false ) {
327
328    // here I've shoehorned old into new,
329    // NOTE:
330    // this WONT return same exact fields
331
332    $args['ignoreowner'] = zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT );
333
334    if ( $withMoneyData ) {
335
336            $args['withInvoices']     = true;
337            $args['withTransactions'] = true;
338    }
339
340    global $zbs;
341    return $zbs->DAL->contacts->getContacts( $args );
342}
343
344/**
345 * Enables or disables the client portal access for a contact, by ID.
346 *
347 * @param int    $contact_id The id of the CRM Contact to be enabled or disabled.
348 * @param string $enable_or_disable String indicating if the selected contact should be enabled or disabled. Use 'disable' to disable, otherwise the contact will be enabled.
349 *
350 * @return bool True in case of success, false otherwise.
351 */
352function zeroBSCRM_customerPortalDisableEnable( $contact_id = -1, $enable_or_disable = 'disable' ) {
353    global $zbs;
354
355    if ( jpcrm_perms_manage_options() && ! empty( $contact_id ) ) {
356        // Verify this user can be changed.
357        // Has to have singular role of `zerobs_customer`. This helps to avoid users changing each others accounts via crm.
358        $wp_user_id  = zeroBSCRM_getClientPortalUserID( $contact_id );
359        $user_object = get_userdata( $wp_user_id );
360        if ( jpcrm_role_check( $user_object, array(), array(), array( 'zerobs_customer' ) ) ) {
361            if ( $enable_or_disable === 'disable' ) {
362                return $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $contact_id, 'portal_disabled', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
363            } else {
364                return $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $contact_id, 'portal_disabled', false ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
365            }
366        }
367    }
368
369    return false;
370}
371
372/*
373 * Resets the password for client portal access for a contact, by ID
374 */
375function zeroBSCRM_customerPortalPWReset( $contact_id = -1 ) {
376
377    global $zbs;
378
379    if ( jpcrm_perms_manage_options() && ! empty( $contact_id ) ) {
380
381        $wp_user_id    = zeroBS_getCustomerWPID( $contact_id );
382        $contact       = $zbs->DAL->contacts->getContact( $contact_id );
383        $contact_email = $contact['email'];
384        $user_object   = get_userdata( $wp_user_id );
385
386        if ( $wp_user_id > 0 && ! empty( $contact_email ) ) {
387
388            // Verify this user can be changed.
389            // Has to have singular role of `zerobs_customer`. This helps to avoid
390            // users resetting each others passwords via the CRM.
391            if ( ! jpcrm_role_check( $user_object, array(), array(), array( 'zerobs_customer' ) ) ) {
392
393                return false;
394
395            }
396
397            // generate new pw
398            $new_password = wp_generate_password( 12, false );
399
400            // update
401            wp_set_password( $new_password, $wp_user_id );
402
403            // email?
404
405            // check if the email is active..
406            $active = zeroBSCRM_get_email_status( ZBSEMAIL_CLIENTPORTALPWREST );
407
408            if ( $active ) {
409
410                // send welcome email (tracking will now be dealt with by zeroBSCRM_mailDelivery_sendMessage)
411
412                // ==========================================================================================
413                // =================================== MAIL SENDING =========================================
414
415                // generate html
416                $emailHTML = zeroBSCRM_Portal_generatePWresetNotificationHTML( $new_password, true, $contact );
417
418                // build send array
419                $mailArray = array(
420                    'toEmail'  => $contact_email,
421                    'toName'   => '',
422                    'subject'  => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_CLIENTPORTALPWREST ),
423                    'headers'  => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_CLIENTPORTALPWREST ),
424                    'body'     => $emailHTML,
425                    'textbody' => '',
426                    'options'  => array(
427                        'html' => 1,
428                    ),
429                    'tracking' => array(
430                        // tracking :D (auto-inserted pixel + saved in history db)
431                        'emailTypeID'     => ZBSEMAIL_CLIENTPORTALPWREST,
432                        'targetObjID'     => $contact_id,
433                        'senderWPID'      => -10,
434                        'associatedObjID' => -1, // none
435                    ),
436                );
437
438                // Sends email, including tracking, via setting stored route out, (or default if none)
439                // and logs trcking :)
440
441                // discern del method
442                $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_CLIENTPORTALPWREST );
443                if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
444                    $mailDeliveryMethod = -1;
445                }
446
447                // send
448                $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
449
450                // =================================== / MAIL SENDING =======================================
451                // ==========================================================================================
452
453            }
454
455            return true;
456
457        } // if wpid
458
459    }
460
461    return false;
462}
463
464// Returns bool of whether or not a specific customer can access client portal
465function zeroBSCRM_isCustomerPortalDisabled( $contact_id = -1 ) {
466
467    // No Contact ID, no entry, unless Admin or Jetpack CRM Admin we can let those in.
468    $contact_id = (int) $contact_id;
469    if ( $contact_id < 1 ) {
470        if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
471            return false;
472        }
473        return true;
474    } else {
475
476        // return check
477        global $zbs;
478        return $zbs->DAL->contacts->getContactMeta( $contact_id, 'portal_disabled' );
479
480    }
481
482    // default = closed door
483    return true;
484}
485
486// loads customer record + creates a portal user for record
487// replaces zeroBSCRM_genPortalUser
488function zeroBSCRM_createClientPortalUserFromRecord( $cID = -1 ) {
489
490    if ( ! empty( $cID ) ) {
491
492        global $zbs;
493
494        // existing?
495        $existing = zeroBSCRM_getClientPortalUserID( $cID );
496        if ( ! empty( $existing ) || $existing > 0 ) {
497            return false;
498        }
499
500        $email   = $zbs->DAL->contacts->getContactEmail( $cID );
501        $contact = $zbs->DAL->contacts->getContact( $cID, array( 'fields' => array( 'zbsc_fname', 'zbsc_lname' ) ) );
502        $fname   = '';
503        if ( isset( $contact['fname'] ) && ! empty( $contact['fname'] ) ) {
504            $fname = $contact['fname'];
505        }
506        $lname = '';
507        if ( isset( $contact['lname'] ) && ! empty( $contact['lname'] ) ) {
508            $lname = $contact['lname'];
509        }
510
511        // fire
512        return zeroBSCRM_createClientPortalUser( $cID, $email, 12, $fname, $lname );
513
514    }
515
516    return false;
517}
518
519function zeroBSCRM_getClientPortalUserID( $cID = -1 ) {
520
521    if ( ! empty( $cID ) ) {
522
523        global $zbs;
524
525        // first lets check if a user already exists with that email..
526        $email = $zbs->DAL->contacts->getContactEmail( $cID );
527        if ( ! empty( $email ) ) {
528            $userID = email_exists( $email );
529            if ( $userID != null ) {
530                // update_post_meta($cID, 'zbs_portal_wpid', $userID);
531                $zbs->DAL->contacts->addUpdateContactWPID(
532                    array(
533                        'id'   => $cID,
534                        'WPID' => $userID,
535                    )
536                );
537            }
538        } else {
539            // no email in meta, but might be linked?
540            // $userID = get_post_meta($cID, 'zbs_portal_wpid', true);
541            $userID = $zbs->DAL->contacts->getContactWPID( $cID );
542        }
543        return $userID;
544    }
545    return false;
546}
547
548function zeroBSCRM_getClientPortalUserWPObj( $cID = -1 ) {
549
550    if ( ! empty( $cID ) ) {
551
552        global $zbs;
553
554        // $user_id = zeroBSCRM_getClientPortalUserID($cID);
555        $user_id = $zbs->DAL->contacts->getContactWPID( $cID );
556
557        return new WP_User( $user_id );
558
559    }
560
561    return false;
562}
563
564// Function to update the zbs<->wp user link
565function zeroBSCRM_setClientPortalUser( $cID = -1, $wpUserID = -1 ) {
566
567    if ( $cID > 0 && $wpUserID > 0 ) {
568
569        global $zbs;
570        $zbs->DAL->contacts->addUpdateContactWPID(
571            array(
572                'id'   => $cID,
573                'WPID' => $wpUserID,
574            )
575        );
576
577        return true;
578
579    }
580
581    return false;
582}
583
584function zeroBSCRM_createClientPortalUser( $cID = -1, $email = '', $passwordLength = 12, $first_name = '', $last_name = '' ) {
585
586    // fail if bad params
587    if ( empty( $cID ) || empty( $email ) || ! zeroBSCRM_validateEmail( $email ) ) {
588        return false;
589    }
590
591    // fail if email already exists as a WP user
592    if ( email_exists( $email ) ) {
593        return false;
594    }
595
596    global $zbs;
597
598    $password = wp_generate_password( $passwordLength, false );
599
600    // organise WP user details
601    $wpUserDeets = array(
602        'user_email' => $email,
603        'user_login' => $email,
604        'user_pass'  => $password,
605        'nickname'   => $email,
606        'first_name' => empty( $first_name ) ? '' : $first_name,
607        'last_name'  => empty( $last_name ) ? '' : $last_name,
608    );
609
610    // create WP user
611    $user_id = wp_insert_user( $wpUserDeets );
612
613    // retrieve created user
614    $user = new WP_User( $user_id );
615
616    // fail if the user doesn't exist
617    if ( ! $user->exists() ) {
618        return false;
619    }
620
621    // link WP user ID to contact
622    $zbs->DAL->contacts->addUpdateContactWPID(
623        array(
624            'id'   => $cID,
625            'WPID' => $user_id,
626        )
627    );
628
629    // any extra assigned role? (from settings)
630    $extraRole = zeroBSCRM_getSetting( 'portalusers_extrarole' );
631
632    // add role(s)
633    if ( ! empty( $extraRole ) ) {
634        // Set the WP role first, then the JPCRM role
635        $user->set_role( $extraRole );
636        $user->add_role( 'zerobs_customer' );
637    } else {
638        $user->set_role( 'zerobs_customer' );
639    }
640
641    // check if the email template is active, and if it is, send...
642    $active = zeroBSCRM_get_email_status( ZBSEMAIL_CLIENTPORTALWELCOME );
643    if ( $active ) {
644
645        // generate html
646        $emailHTML = zeroBSCRM_Portal_generateNotificationHTML( $password, true, $email, $cID );
647
648        // build send array
649        $mailArray = array(
650            'toEmail'  => $email,
651            'toName'   => '',
652            'subject'  => zeroBSCRM_mailTemplate_getSubject( ZBSEMAIL_CLIENTPORTALWELCOME ),
653            'headers'  => zeroBSCRM_mailTemplate_getHeaders( ZBSEMAIL_CLIENTPORTALWELCOME ),
654            'body'     => $emailHTML,
655            'textbody' => '',
656            'options'  => array(
657                'html' => 1,
658            ),
659            'tracking' => array(
660                // tracking :D (auto-inserted pixel + saved in history db)
661                'emailTypeID'     => ZBSEMAIL_CLIENTPORTALWELCOME,
662                'targetObjID'     => $cID,
663                'senderWPID'      => -10,
664                'associatedObjID' => -1, // none
665            ),
666        );
667
668        // Sends email, including tracking, via setting stored route out, (or default if none)
669        // and logs tracking :)
670
671        // discern del method
672        $mailDeliveryMethod = zeroBSCRM_mailTemplate_getMailDelMethod( ZBSEMAIL_CLIENTPORTALWELCOME );
673        if ( ! isset( $mailDeliveryMethod ) || empty( $mailDeliveryMethod ) ) {
674            $mailDeliveryMethod = -1;
675        }
676
677        // send
678        $sent = zeroBSCRM_mailDelivery_sendMessage( $mailDeliveryMethod, $mailArray );
679
680    }
681
682    // IA
683    zeroBSCRM_FireInternalAutomator(
684        'clientwpuser.new',
685        array(
686            'id'        => $user_id,
687            'againstid' => $cID,
688            'userEmail' => $email,
689        )
690    );
691}
692
693// THIS IS NOW DEPRECATED db2+
694// (META used to be all deets, it's now normal deets - as table)
695#} Quick wrapper to future-proof.
696#} Should later replace all get_post_meta's with this
697function zeroBS_getCustomerMeta( $cID = -1 ) {
698
699    global $zbs;
700
701    // if (!empty($cID)) return get_post_meta($cID, 'zbs_customer_meta', true);
702    // Return contact directly DB2+
703    if ( ! empty( $cID ) ) {
704        return $zbs->DAL->contacts->getContact( $cID, array( 'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ) ) );
705    }
706
707    return false;
708}
709// generates a 'demo' customer object (excluding custom fields)
710function zeroBS_getDemoCustomer() {
711
712    global $zbs, $zbsCustomerFields;
713
714    $ret = array();
715
716    $demoData = array(
717
718        'status'           => array( 'Lead', 'Customer' ),
719        'prefix'           => array( 'Mr', 'Mrs', 'Miss' ),
720        'fname'            => array( 'John', 'Jim', 'Mike', 'Melvin', 'Janet', 'Jennifer', 'Judy', 'Julie' ),
721        'lname'            => array( 'Smith', 'Jones', 'Scott', 'Filbert' ),
722        'fullname'         => array( 'John Smith', 'Jim Ellison', 'Mike Myers', 'Melvin Malcolms' ),
723        'addr1'            => array( '101 Red Street', '26 Somerset Street', '1 London Road' ),
724        'addr2'            => array( 'Winchester', 'Leeds Village', 'Webleck' ),
725        'city'             => array( 'London', 'Los Angeles', 'Leeds', 'Exeter' ),
726        'county'           => array( 'London', 'Hertfordshire', 'California', 'Montana' ),
727        'postcode'         => array( 'A1 1XU', 'AO12 3RR', 'E1 3XG', 'M1 3LF' ),
728        'secaddr_addr1'    => array( '101 Red Street', '26 Somerset Street', '1 London Road' ),
729        'secaddr_addr2'    => array( 'Winchester', 'Leeds Village', 'Webleck' ),
730        'secaddr_city'     => array( 'London', 'Los Angeles', 'Leeds', 'Exeter' ),
731        'secaddr_county'   => array( 'London', 'Hertfordshire', 'California', 'Buckinghamshire' ),
732        'secaddr_postcode' => array( 'A1 1XU', 'AO12 3RR', 'E1 3XG', 'M1 3LF' ),
733        // dirty repetition...
734        'secaddr1'         => array( '101 Red Street', '26 Somerset Street', '1 London Road' ),
735        'secaddr2'         => array( 'Winchester', 'Leeds Village', 'Webleck' ),
736        'seccity'          => array( 'London', 'Los Angeles', 'Leeds', 'Exeter' ),
737        'seccounty'        => array( 'London', 'Hertfordshire', 'California', 'Buckinghamshire' ),
738        'secpostcode'      => array( 'A1 1XU', 'AO12 3RR', 'E1 3XG', 'M1 3LF' ),
739        'hometel'          => array( '01010 123 345', '01234 546 789' ),
740        'worktel'          => array( '01010 123 345', '01234 546 789' ),
741        'mobtel'           => array( '07812 345 678' ),
742        'email'            => array( 'random@email.com', 'not.real@gmail.com', 'nonsense@email.com' ),
743
744    );
745
746    foreach ( $zbsCustomerFields as $key => $value ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
747        $ret[ $key ] = '';
748        if ( isset( $demoData[ $key ] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
749            $ret[ $key ] = $demoData[ $key ][ wp_rand( 0, count( $demoData[ $key ] ) - 1 ) ]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
750        }
751    }
752
753    // add fullname
754    $ret['fullname'] = $demoData['fullname'][ wp_rand( 0, count( $demoData['fullname'] ) - 1 ) ]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
755
756    // fill in some randoms
757    $ret['status'] = $demoData['status'][ wp_rand( 0, count( $demoData['status'] ) - 1 ) ]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
758
759    return $ret;
760}
761function zeroBS_getCustomerExtraMetaVal( $cID = -1, $extraMetaKey = false ) {
762
763    if ( ! empty( $cID ) && ! empty( $extraMetaKey ) ) {
764
765        global $zbs;
766
767        // quick
768        $cleanKey = strtolower( str_replace( ' ', '_', $extraMetaKey ) );
769
770        // return get_post_meta($cID, 'zbs_customer_extra_'.$cleanKey, true);
771        return $zbs->DAL->contacts->getContactMeta( $cID, 'extra_' . $cleanKey );
772
773    }
774
775    return false;
776}
777
778#} sets an extra meta val
779function zeroBS_setCustomerExtraMetaVal( $cID = -1, $extraMetaKey = false, $extraMetaVal = false ) {
780
781    if ( ! empty( $cID ) && ! empty( $extraMetaKey ) ) {
782
783        // quick
784        $cleanKey = strtolower( str_replace( ' ', '_', $extraMetaKey ) );
785
786        global $zbs;
787
788        // return update_post_meta($cID, 'zbs_customer_extra_'.$cleanKey, $extraMetaVal);
789        return $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $cID, 'extra_' . $extraMetaKey, $extraMetaVal );
790
791    }
792
793    return false;
794}
795
796function zeroBS_updateCustomerSocialAccounts( $cID = -1, $accArray = array() ) {
797
798    if ( ! empty( $cID ) && is_array( $accArray ) ) { // return update_post_meta( $cID, 'zbs_customer_socials', $accArray);
799
800        global $zbs;
801        #} Enact
802        return $zbs->DAL->contacts->addUpdateContact(
803            array(
804                'id'            => $cID,
805                'limitedFields' => array(
806                    array(
807                        'key'  => 'zbsc_tw',
808                        'val'  => $accArray['tw'],
809                        'type' => '%s',
810                    ),
811                    array(
812                        'key'  => 'zbsc_li',
813                        'val'  => $accArray['li'],
814                        'type' => '%s',
815                    ),
816                    array(
817                        'key'  => 'zbsc_fb',
818                        'val'  => $accArray['fb'],
819                        'type' => '%s',
820                    ),
821                ),
822            )
823        );
824
825    }
826
827    return false;
828}
829
830function zeroBSCRM_getCustomerFiles( $cID = -1 ) {
831
832    if ( ! empty( $cID ) ) {
833
834        global $zbs;
835
836        // return get_post_meta($cID, 'zbs_customer_files', true);
837        // return $zbs->DAL->contacts->getContactMeta($cID,'files');
838        return zeroBSCRM_files_getFiles( 'customer', $cID );
839
840    }
841
842    return false;
843}
844// maintainIndexs keeps the files original index .e.g. 1,2 so that can match when doing portal stuff (as we're using legacy indx)
845function zeroBSCRM_getCustomerPortalFiles( $cID = -1 ) {
846
847    if ( ! empty( $cID ) ) {
848
849        global $zbs;
850
851        // return get_post_meta($cID, 'zbs_customer_files', true);
852        // return $zbs->DAL->contacts->getContactMeta($cID,'files');
853        $ret       = array();
854        $files     = zeroBSCRM_files_getFiles( 'customer', $cID );
855        $fileIndex = 0;
856        if ( is_array( $files ) ) {
857            foreach ( $files as $f ) {
858
859                        // APPROVED portal files
860                if ( isset( $f['portal'] ) && $f['portal'] == 1 ) {
861                    $ret[ $fileIndex ] = $f;
862                }
863
864                ++$fileIndex;
865
866            }
867        }
868        return $ret;
869
870    }
871
872    return false;
873}
874function zeroBSCRM_updateCustomerFiles( $cID = -1, $filesArray = false ) {
875
876    if ( ! empty( $cID ) ) {
877
878        global $zbs;
879
880        // return update_post_meta($cID, 'zbs_customer_files', $filesArray);
881        return $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $cID, 'files', $filesArray );
882
883    }
884
885    return false;
886}
887
888#} As of v1.1 can pass searchphrase
889#} As of v1.2 can pass tags
890#} As of v2.2 has associated func getCustomersCountIncParams for getting the TOTAL for a search (ignoring pages)
891#} As of v2.2 can also get ,$withTags=false,$withAssigned=false,$withLastLog=false
892#} As of v2.2 can also pass quickfilters (Damn this has got messy): lead, customer, over100, over200, over300, over400, over500
893    // ... in array like ('lead')
894#} 2.52+ AVOID using this, call getContacts directly plz, this is just for backward compatibility :)
895function zeroBS_getCustomers(
896    $withFullDetails = false,
897    $perPage = 10,
898    $page = 0,
899    $withInvoices = false,
900    $withQuotes = false,
901    $searchPhrase = '',
902    $withTransactions = false,
903    $argsOverride = false,
904    $companyID = false,
905    $hasTagIDs = '',
906    $inArr = '',
907    $withTags = false,
908    $withAssigned = false,
909    $withLastLog = false,
910    $sortByField = 'ID',
911    $sortOrder = 'DESC',
912    $quickFilters = false,
913    $ownedByID = false,
914    $withValues = false
915) {
916    /* DAL3.0: $withValues */
917
918    #} Query Performance index
919    #global $zbsQPI;
920    #if (!isset($zbsQPI)) {
921    #   $zbsQPI = array();
922    #}
923    #$zbsQPI['retrieveCustomers2getCustomers'] = zeroBSCRM_mtime_float();
924
925    // $withFullDetails = irrelevant with new DB2 (always returns)
926    // $argsOverride CAN NO LONGER WORK :)
927    if ( $argsOverride !== false ) {
928        zeroBSCRM_DEPRECATEDMSG( 'Use of $argsOverride in zeroBS_getCustomers is no longer relevant (DB2)' );
929    }
930
931    global $zbs;
932
933    // this needs translating for new dbfields:
934    // FOR NOW
935    if ( $sortByField == 'post_id' ) {
936        $sortByField = 'ID';
937    }
938    if ( $sortByField == 'post_title' ) {
939        $sortByField = 'zbsc_lname';
940    }
941    if ( $sortByField == 'post_excerpt' ) {
942        $sortByField = 'zbsc_lname';
943    }
944
945    /* we need to prepend zbsc_ when not using cf */
946    $custFields = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_CONTACT ) );
947
948        // needs to check if field name is custom field:
949        $sortIsCustomField = false;
950    if ( is_array( $custFields ) && array_key_exists( $sortByField, $custFields ) ) {
951        $sortIsCustomField = true;
952    }
953    if ( ! $sortIsCustomField && $sortByField != 'ID' ) {
954        $sortByField = 'zbsc_' . $sortByField;
955    }
956
957    // catch empties
958    if ( empty( $sortByField ) ) {
959        $sortByField = 'ID';
960    }
961    if ( empty( $sortOrder ) ) {
962        $sortOrder = 'desc';
963    }
964
965    // legacy from dal1
966    $actualPage = $page;
967    if ( $actualPage < 0 ) {
968        $actualPage = 0;
969    }
970
971    // make ARGS
972    $args = array(
973
974        // Search/Filtering (leave as false to ignore)
975        'searchPhrase'     => $searchPhrase,
976        'inCompany'        => $companyID,
977        'inArr'            => $inArr,
978        'quickFilters'     => $quickFilters,
979        'isTagged'         => $hasTagIDs,
980        'ownedBy'          => $ownedByID,
981
982        'withCustomFields' => true,
983        'withQuotes'       => $withQuotes,
984        'withInvoices'     => $withInvoices,
985        'withTransactions' => $withTransactions,
986        'withLogs'         => false,
987        'withLastLog'      => $withLastLog,
988        'withTags'         => $withTags,
989        'withOwner'        => $withAssigned,
990        'withValues'       => $withValues,
991
992        'sortByField'      => $sortByField,
993        'sortOrder'        => $sortOrder,
994        'page'             => $actualPage,
995        'perPage'          => $perPage,
996
997        'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
998
999    );
1000
1001    // here ignore owners = true the default, because we're not really forcing ownership anywhere overall,
1002    // when we do, we should change this/make it check
1003    if ( $ownedByID !== false ) {
1004
1005        $args['ignoreowner'] = false;
1006
1007    }
1008
1009    return $zbs->DAL->contacts->getContacts( $args );
1010}
1011
1012#} As of 2.2 - matches getCustomers but returns a total figure (no deets)
1013// NOTE, params are same except first 5 + withTransactions removed:
1014// $withFullDetails=false,$perPage=10,$page=0,$withInvoices=false,$withQuotes=false,$withTransactions=false,
1015// - trimmed returns for efficiency (is just a count really :o dirty.)
1016// https://codex.wordpress.org/Class_Reference/WP_Query
1017function zeroBS_getCustomersCountIncParams(
1018    $searchPhrase = '',
1019    $argsOverride = false,
1020    $companyID = false,
1021    $hasTagIDs = '',
1022    $inArr = '',
1023    $quickFilters = ''
1024) {
1025
1026    // $withFullDetails = irrelevant with new DB2 (always returns)
1027    // $argsOverride CAN NO LONGER WORK :)
1028    if ( $argsOverride !== false ) {
1029        zeroBSCRM_DEPRECATEDMSG( 'Use of $argsOverride in zeroBS_getCustomersCountIncParams is no longer relevant (DB2)' );
1030    }
1031
1032    global $zbs;
1033
1034    // make ARGS
1035    $args = array(
1036
1037        // Search/Filtering (leave as false to ignore)
1038        'searchPhrase' => $searchPhrase,
1039        'inCompany'    => $companyID,
1040        'inArr'        => $inArr,
1041        'quickFilters' => $quickFilters,
1042        'isTagged'     => $hasTagIDs,
1043
1044        // just count
1045        'count'        => true,
1046
1047        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
1048
1049    );
1050
1051    return (int) $zbs->DAL->contacts->getContacts( $args );
1052}
1053
1054#} same as above but wrapped in contact view link
1055function zeroBS_getCustomerIcoLinked( $cID = -1, $incName = false, $extraClasses = '', $maxSize = 100 ) {
1056
1057    $extraHTML = '';
1058    if ( $incName ) {
1059
1060        $cName = zeroBS_getCustomerNameShort( $cID );
1061
1062        if ( ! empty( $cName ) ) {
1063            $extraHTML = '<span class="">' . esc_html( $cName ) . '</span>';
1064        }
1065    }
1066
1067    return '<div class="zbs-co-img' . esc_attr( $extraClasses ) . '"><a href = "' . jpcrm_esc_link( 'view', $cID, 'zerobs_customer' ) . '">' . zeroBS_customerAvatarHTML( $cID, -1, $maxSize ) . '</a>' . $extraHTML . '</div>';
1068}
1069
1070#} same as above but wrapped in contact view link + semantic ui label img link
1071function zeroBS_getCustomerIcoLinkedLabel( $cID = -1 ) {
1072
1073    $extraHTML = '';
1074    $cName     = zeroBS_getCustomerNameShort( $cID );
1075    if ( ! empty( $cName ) ) {
1076        $extraHTML = '<span>' . esc_html( $cName ) . '</span>';
1077    } else {
1078        $cEmail = zeroBS_customerEmail( $cID );
1079        if ( ! empty( $cEmail ) ) {
1080            $extraHTML = '<span>' . esc_html( $cEmail ) . '</span>';
1081        }
1082    }
1083
1084    $extraClasses = ' ui image label';
1085
1086    return '<a href="' . jpcrm_esc_link( 'view', $cID, 'zerobs_customer' ) . '" class="' . $extraClasses . '">' . zeroBS_customerAvatarHTML( $cID ) . $extraHTML . '</a>';
1087}
1088
1089#} same as above but with no image (for non-avatar mode)
1090function zeroBS_getCustomerLinkedLabel( $cID = -1 ) {
1091
1092    $extraHTML = '';
1093    $cName     = zeroBS_getCustomerNameShort( $cID );
1094    if ( ! empty( $cName ) ) {
1095        $extraHTML = '<span>' . esc_html( $cName ) . '</span>';
1096    } else {
1097        $cEmail = zeroBS_customerEmail( $cID );
1098        if ( ! empty( $cEmail ) ) {
1099            $extraHTML = '<span>' . esc_html( $cEmail ) . '</span>';
1100        }
1101    }
1102    // for empties, add no
1103    if ( empty( $extraHTML ) ) {
1104        $extraHTML = '<span>#' . $cID . '</span>';
1105    }
1106
1107    $extraClasses = ' ui label';
1108
1109    return '<a href="' . jpcrm_esc_link( 'view', $cID, 'zerobs_customer' ) . '" class="' . $extraClasses . '">' . $extraHTML . '</a>';
1110}
1111
1112/* Centralised delete customer func, including sub-element removal */
1113function zeroBS_deleteCustomer( $id = -1, $saveOrphans = true ) {
1114
1115    if ( ! empty( $id ) ) {
1116
1117        global $zbs;
1118
1119        return $zbs->DAL->contacts->deleteContact(
1120            array(
1121                'id'          => $id,
1122                'saveOrphans' => $saveOrphans,
1123            )
1124        );
1125
1126    }
1127
1128    return false;
1129}
1130
1131function zeroBS_getCustomerIDWithExternalSource( $externalSource = '', $externalID = '' ) {
1132
1133    global $zbs;
1134
1135    #} No empties, no random externalSources :)
1136    if ( ! empty( $externalSource ) && ! empty( $externalID ) && array_key_exists( $externalSource, $zbs->external_sources ) ) {
1137
1138        #} If here, is legit.
1139        $approvedExternalSource = $externalSource;
1140
1141        global $zbs;
1142
1143        return $zbs->DAL->contacts->getContact(
1144            -1,
1145            array(
1146                'externalSource'    => $approvedExternalSource,
1147                'externalSourceUID' => $externalID,
1148                'onlyID'            => true,
1149                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
1150            )
1151        );
1152
1153    }
1154
1155    return false;
1156}
1157
1158function zeroBSCRM_getCustomerTagsByID( $cID = -1, $justIDs = false ) {
1159
1160    if ( ! empty( $cID ) ) {
1161
1162        global $zbs;
1163
1164        return $zbs->DAL->getTagsForObjID(
1165            array(
1166                'objtypeid'   => ZBS_TYPE_CONTACT,
1167                'objid'       => $cID,
1168                'onlyID'      => $justIDs,
1169                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
1170            )
1171        );
1172
1173    }
1174}
1175
1176// NOTE: $objType is temporary until DB2 fully rolled out all tables
1177function zeroBS_getOwner( $objID = -1, $withDeets = true, $objType = -1, $knownOwnerID = -1 ) {
1178
1179    if ( $objID !== -1 && $objType !== -1 ) {
1180
1181        $objType = jpcrm_upconvert_obj_type( $objType );
1182
1183        global $zbs;
1184        $retObj = false;
1185
1186        // if passed, save the db call
1187        if ( $knownOwnerID > 0 ) {
1188
1189            $userIDofOwner = $knownOwnerID;
1190
1191        } else {
1192
1193            $userIDofOwner = $zbs->DAL->getObjectOwner(
1194                array(
1195                    'objID'     => $objID,
1196                    'objTypeID' => $objType,
1197                )
1198            );
1199
1200        }
1201
1202        if ( isset( $userIDofOwner ) && ! empty( $userIDofOwner ) ) {
1203
1204            // check if user can be owner (is zbs admin)
1205            // check on the assign, is less performance impacting
1206            // if (! user_can($userIDofOwner,'admin_zerobs_usr') return false;
1207
1208            if ( $withDeets ) {
1209
1210                #} Retrieve owner deets
1211                $retObj = zeroBS_getOwnerObj( $userIDofOwner );
1212
1213            } else {
1214                return $userIDofOwner;
1215            }
1216        }
1217
1218        return $retObj;
1219
1220    }
1221
1222    return false;
1223}
1224
1225/**
1226 * Retrieves the owner object based on a given WP user ID.
1227 *
1228 * This function gets the owner's data without revealing sensitive information
1229 * (e.g. `user_pass`).
1230 *
1231 * @param int $wp_user_id The WordPress user ID. Default is -1.
1232 *
1233 * @return array|bool Returns an associative array containing the 'ID' and 'OBJ' (user data object) if successful, false otherwise.
1234 */
1235function zeroBS_getOwnerObj( $wp_user_id = -1 ) {
1236    if ( $wp_user_id > 0 ) {
1237
1238        $user = get_userdata( $wp_user_id );
1239
1240        if ( ! isset( $user->ID ) || ! isset( $user->data ) ) {
1241            return false;
1242        }
1243
1244        /**
1245         * Ideally we'd restructure this, but the return result is used extensively,
1246         * particularly from `zeroBS_getOwner` calls. For now we'll explicitly set what
1247         * fields are provided (e.g. don't show `user_pass`).
1248         */
1249        $user_data = (object) array(
1250            'ID'            => $user->data->ID,
1251            'user_login'    => $user->data->user_login,
1252            'user_nicename' => $user->data->user_nicename,
1253            'display_name'  => $user->data->display_name,
1254        );
1255
1256        return array(
1257            'ID'  => $wp_user_id,
1258            'OBJ' => $user_data,
1259        );
1260
1261    }
1262
1263    return false;
1264}
1265
1266// NOTE - this is very generic & not to be used in future code
1267// Use the direct $zbs->DAL->contacts->addUpdateContact code example in below rather than this generic.
1268// kthx.
1269function zeroBS_setOwner( $objID = -1, $ownerID = -1, $objTypeID = false ) {
1270
1271    if ( $objID !== -1 && $objTypeID !== false ) {
1272
1273        // here we check that the potential owner CAN even own
1274        if ( ! user_can( $ownerID, 'admin_zerobs_usr' ) ) {
1275            return false;
1276        }
1277
1278        global $zbs;
1279
1280        return $zbs->DAL->setFieldByID(
1281            array(
1282
1283                'objID'       => $objID,
1284                'objTypeID'   => $objTypeID,
1285
1286                'colname'     => 'zbs_owner',
1287                'coldatatype' => '%d', // %d/s
1288                'newValue'    => $ownerID,
1289
1290            )
1291        );
1292
1293    }
1294
1295    return false;
1296}
1297
1298function zeroBSCRM_mergeCustomers( $dominantID = -1, $slaveID = -1 ) {
1299
1300    if ( ! empty( $dominantID ) && ! empty( $slaveID ) ) {
1301
1302        // load both
1303        $master = zeroBS_getCustomer( $dominantID );
1304        $slave  = zeroBS_getCustomer( $slaveID, true, true, true );
1305
1306        if ( isset( $master['id'] ) && isset( $slave['id'] ) ) {
1307
1308            global $zbs;
1309
1310            try {
1311
1312                // all set, merge
1313                $changes            = array();
1314                $conflictingChanges = array();
1315
1316                $fieldPrefix = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1317
1318                // copy details from slave fields -> master fields
1319                // where detail not present?
1320                // into second address?
1321
1322                $masterNewMeta       = false;
1323                $masterHasSecondAddr = false;  // this'll let us copy over first from slave if empty :)
1324                $slaveHasFirstAddr   = false;
1325                $slaveHasSecondAddr  = false;
1326                $slaveFirstAddrStr   = '';
1327                $slaveSecondAddrStr  = '';
1328
1329                // if this gets filled, it'll be added as aka below
1330                $slaveEmailAddress = false;
1331
1332                // because these are just arrays (in meta) - we do a kind of compare, save a new ver,
1333                // ..and add any mismatches to conflicting changes in a meaningful way
1334
1335                // DB2 converted these from obj[meta] -> obj
1336
1337                // first, just copy through slave email if present
1338                if ( isset( $slave['email'] ) && ! empty( $slave['email'] ) ) {
1339                    $slaveEmailAddress = $slave['email'];
1340                }
1341
1342                // we start with the master :)
1343                    $masterNewMeta = $master;
1344
1345                    global $zbsCustomerFields, $zbsAddressFields;
1346
1347                // first, any empties (excluding addr) in master, get patched from secondary
1348                foreach ( $zbsCustomerFields as $fieldKey => $fieldDeets ) {
1349
1350                    // ignore addrs here
1351                    if ( ! isset( $fieldDeets['migrate'] ) || $fieldDeets['migrate'] != 'addresses' ) {
1352                        // present in master?
1353                        if ( ! isset( $master[ $fieldKey ] ) || empty( $master[ $fieldKey ] ) ) {
1354
1355                            // NOT PRESENT IN MASTER
1356
1357                            // was not set, or empty, in master
1358                            // present in slave?
1359                            if ( isset( $slave[ $fieldKey ] ) && ! empty( $slave[ $fieldKey ] ) ) {
1360
1361                                // a change :) - note requires zbsc_ here for some annoying reason, leaving for now
1362                                $masterNewMeta[ $fieldPrefix . $fieldKey ] = $slave[ $fieldKey ];
1363
1364                                // hopefully DB2 doesnt..
1365                                // Does for now lol $masterNewMeta[$fieldKey] = $slave[$fieldKey];
1366                                $changes[] = __( 'Copied field', 'zero-bs-crm' ) . ' "' . $fieldDeets[1] . '" ' . __( 'from secondary record over main record, (main was empty).', 'zero-bs-crm' );
1367
1368                            }
1369                        } else {
1370
1371                            // if slave had value?
1372                            // (no need to worry about emails, dealt with separately)
1373                            if ( isset( $slave[ $fieldKey ] ) && ! empty( $slave[ $fieldKey ] ) && $fieldKey !== 'email' ) {
1374
1375                                // master val already present, conflicting change:
1376                                $conflictingChanges[] = __( 'Field not copied', 'zero-bs-crm' ) . ' "' . $fieldDeets[1] . '" ' . __( 'from secondary record over main record, (main had value). Value was', 'zero-bs-crm' ) . ' "' . $slave[ $fieldKey ] . '"';
1377
1378                            }
1379                        }
1380                    } else {
1381
1382                        // ADDRESSES. Here we just use the foreach to check if the master has any secaddr fields
1383                        // just sets a flag used below in logic :)
1384                        if ( str_starts_with( $fieldKey, 'secaddr_' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1385
1386                            // check presence (of any secaddr_ field)
1387                            if ( isset( $master[ $fieldKey ] ) && ! empty( $master[ $fieldKey ] ) ) {
1388                                $masterHasSecondAddr = true;
1389                            }
1390
1391                                // does slave have secondary?
1392                            if ( isset( $slave[ $fieldKey ] ) && ! empty( $slave[ $fieldKey ] ) ) {
1393
1394                                // clearly has (bits of) second addr
1395                                $slaveHasSecondAddr = true;
1396
1397                                // we also build this str which'll be shown as conflicting change (so we don't "loose" this data)
1398                                if ( ! empty( $slaveSecondAddrStr ) ) {
1399                                    $slaveSecondAddrStr .= ', ';
1400                                }
1401                                $slaveSecondAddrStr .= $slave[ $fieldKey ];
1402
1403                            }
1404                        } else {
1405
1406                            // first address
1407                            if ( isset( $slave[ $fieldKey ] ) && ! empty( $slave[ $fieldKey ] ) ) {
1408
1409                                // clearly has (bits of) first addr
1410                                $slaveHasFirstAddr = true;
1411
1412                                // we also build this str which'll be shown as conflicting change (so we don't "loose" this data)
1413                                if ( ! empty( $slaveFirstAddrStr ) ) {
1414                                    $slaveFirstAddrStr .= ', ';
1415                                }
1416                                $slaveFirstAddrStr .= $slave[ $fieldKey ];
1417
1418                            }
1419                        }
1420                    }
1421                }
1422
1423                // addr's
1424
1425                // if master has no sec addr, just copy first addr from slave :)
1426                if ( ! $masterHasSecondAddr ) {
1427
1428                    // copy first addr from slave
1429                    foreach ( $zbsAddressFields as $addrFieldKey => $addrFieldDeets ) {
1430
1431                        // from slave first to master second - note requires zbsc_ here for some annoying reason, leaving for now
1432                        // Hopefully db2 doesnt
1433                        $masterNewMeta[ $fieldPrefix . 'secaddr_' . $addrFieldKey ] = $slave[ $addrFieldKey ];
1434                        // Does for now lol $masterNewMeta['secaddr_'.$addrFieldKey] = $slave[$addrFieldKey];
1435
1436                    }
1437                    $changes[] = __( 'Copied address from secondary record into "secondary address" for main record', 'zero-bs-crm' );
1438
1439                    // any second addr from slave just goes into logs
1440                    if ( $slaveHasSecondAddr ) {
1441
1442                            // provide old addr string
1443                            $conflictingChanges[] = __( 'Address not copied. Secondary address from secondary record could not be copied (master already had two addresses).', 'zero-bs-crm' ) . "\r\n" . __( 'Address', 'zero-bs-crm' ) . ': ' . "\r\n" . $slaveSecondAddrStr;
1444
1445                    }
1446                } else {
1447
1448                    // master already has two addresses, dump (any) secondary addresses into conflicting changes
1449
1450                    if ( $slaveHasFirstAddr ) {
1451
1452                        // provide old addr string
1453                        $conflictingChanges[] = __( 'Address not copied. Address from secondary record could not be copied (master already had two addresses).', 'zero-bs-crm' ) . "\r\n" . __( 'Address', 'zero-bs-crm' ) . ': ' . "\r\n" . $slaveFirstAddrStr;
1454
1455                    }
1456                    if ( $slaveHasSecondAddr ) {
1457
1458                            // provide old addr string
1459                            $conflictingChanges[] = __( 'Address not copied. Secondary address from secondary record could not be copied (master already had two addresses).', 'zero-bs-crm' ) . "\r\n" . __( 'Address', 'zero-bs-crm' ) . ': ' . "\r\n" . $slaveSecondAddrStr;
1460
1461                    }
1462                }
1463
1464                // assign social profiles from slave -> master
1465                // GET THESE BEFORE updating!
1466                    $masterSocial = $zbs->DAL->contacts->getContactSocials( $dominantID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1467
1468                    $slaveSocial = $zbs->DAL->contacts->getContactSocials( $slaveID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1469
1470                // UPDATE MASTER META:
1471                zeroBS_addUpdateCustomer( $dominantID, $masterNewMeta, '', '', '', false, false, false, -1, $fieldPrefix );
1472
1473                $masterNewSocial = $masterSocial;
1474
1475                global $zbsSocialAccountTypes;
1476
1477                if ( count( $zbsSocialAccountTypes ) > 0 ) {
1478
1479                    foreach ( $zbsSocialAccountTypes as $socialKey => $socialAccType ) {
1480
1481                        // master / slave has this acc?
1482                        // for simplicity (not perf.) we grab which has which, first
1483                        $masterHas = false;
1484                        $slaveHas  = false;
1485                        if ( is_array( $masterSocial ) && isset( $masterSocial[ $socialKey ] ) && ! empty( $masterSocial[ $socialKey ] ) ) {
1486                            $masterHas = true;
1487                        }
1488                        if ( is_array( $slaveSocial ) && isset( $slaveSocial[ $socialKey ] ) && ! empty( $slaveSocial[ $socialKey ] ) ) {
1489                            $slaveHas = true;
1490                        }
1491
1492                        // what's up.
1493                        if ( $masterHas && $slaveHas ) {
1494
1495                            // conflicting change
1496                            $conflictingChanges[] = __( 'Social account not copied.', 'zero-bs-crm' ) . ' "' . $socialAccType['name'] . '" of "' . $slaveSocial[ $socialKey ] . '" ' . __( 'from secondary record (master already has a ', 'zero-bs-crm' ) . $socialAccType['name'] . ' ' . __( 'account.', 'zero-bs-crm' );
1497
1498                        } elseif ( $masterHas && ! $slaveHas ) {
1499
1500                            // no change
1501
1502                        } elseif ( $slaveHas && ! $masterHas ) {
1503
1504                            // copy slave -> master
1505                            $masterNewSocial[ $socialKey ] = $slaveSocial[ $socialKey ];
1506                            $changes[]                     = __( 'Copied social account from secondary record into main record', 'zero-bs-crm' ) . ' (' . $socialAccType['name'] . ').';
1507
1508                        }
1509                    }
1510
1511                    // UPDATE SOCIAL
1512                    zeroBS_updateCustomerSocialAccounts( $dominantID, $masterNewSocial );
1513
1514                }
1515
1516                // assign files from slave -> master
1517
1518                /*
1519                Array
1520                (
1521                    [0] => Array
1522                        (
1523                            [file] => /app/public/wp-content/uploads/zbscrm-store/aa250965422e9aea-Document-20243.pdf
1524                            [url] => http://zbsphp5.dev/wp-content/uploads/zbscrm-store/aa250965422e9aea-Document-20243.pdf
1525                            [type] => application/pdf
1526                            [error] =>
1527                            [priv] => 1
1528                        )
1529
1530                )
1531                */
1532
1533                    $slaveFiles = zeroBSCRM_getCustomerFiles( $slaveID );
1534
1535                if ( is_array( $slaveFiles ) && count( $slaveFiles ) > 0 ) {
1536
1537                    $masterFiles = zeroBSCRM_getCustomerFiles( $dominantID );
1538
1539                    if ( ! is_array( $masterFiles ) ) {
1540                        $masterFiles = array();
1541                    }
1542
1543                    foreach ( $slaveFiles as $zbsFile ) {
1544
1545                        // add
1546                        $masterFiles[] = $zbsFile;
1547
1548                        // changelog
1549
1550                            $filename = basename( $zbsFile['file'] );
1551
1552                            // if in privatised system, ignore first hash in name
1553                        if ( isset( $zbsFile['priv'] ) ) {
1554
1555                            $filename = substr( $filename, strpos( $filename, '-' ) + 1 );
1556                        }
1557
1558                        $changes[] = __( 'Moved file to main record', 'zero-bs-crm' ) . ' (' . $filename . ')';
1559
1560                    }
1561
1562                    // save master files
1563                    zeroBSCRM_updateCustomerFiles( $dominantID, $masterFiles );
1564
1565                }
1566
1567                // assign company from slave -> master
1568
1569                $masterCompany = zeroBS_getCustomerCompanyID( $dominantID );
1570                $slaveCompany  = zeroBS_getCustomerCompanyID( $slaveID );
1571                if ( empty( $masterCompany ) ) {
1572
1573                    // slave co present, update main
1574                    if ( ! empty( $slaveCompany ) ) {
1575
1576                        zeroBS_setCustomerCompanyID( $dominantID, $slaveCompany );
1577                        $changes[] = __( 'Assigned main record to secondary record\'s ' . jpcrm_label_company(), 'zero-bs-crm' ) . ' (#' . $slaveCompany . ').';
1578
1579                    }
1580                } else {
1581
1582                    // master has co already, does slave?
1583                    if ( ! empty( $slaveCompany ) && $slaveCompany != $masterCompany ) {
1584
1585                        // conflicting change
1586                        $conflictingChanges[] = __( 'Secondary contact was assigned to ' . jpcrm_label_company() . ', whereas main record was assigned to another ' . jpcrm_label_company() . '.', 'zero-bs-crm' ) . ' (#' . $slaveCompany . ').';
1587
1588                    }
1589                }
1590
1591                // assign quotes from slave -> master
1592
1593                // got quotes?
1594                if ( is_array( $slave['quotes'] ) && count( $slave['quotes'] ) > 0 ) {
1595
1596                    $quoteOffset = zeroBSCRM_getQuoteOffset();
1597
1598                    foreach ( $slave['quotes'] as $quote ) {
1599
1600                        // id for passing to logs
1601                        $qID = '';
1602                        #TRANSITIONTOMETANO
1603                        if ( isset( $quote['zbsid'] ) ) {
1604                            $qID = $quote['zbsid'];
1605                        }
1606
1607                        // for quotes, we just "switch" the owner meta :)
1608                        zeroBSCRM_changeQuoteCustomer( $quote['id'], $dominantID );
1609                        $changes[] = __( 'Assigned quote from secondary record onto main record', 'zero-bs-crm' ) . ' (#' . $qID . ').';
1610
1611                    }
1612                } // / has quotes
1613
1614                // assign invs from slave -> master
1615
1616                // got invoices?
1617                if ( is_array( $slave['invoices'] ) && count( $slave['invoices'] ) > 0 ) {
1618
1619                    foreach ( $slave['invoices'] as $invoice ) {
1620
1621                        // for invs, we just "switch" the owner meta :)
1622                        zeroBSCRM_changeInvoiceCustomer( $invoice['id'], $dominantID );
1623                        $changes[] = __( 'Assigned invoice from secondary record onto main record', 'zero-bs-crm' ) . ' (#' . $invoice['id'] . ').';
1624
1625                    }
1626                } // / has invoices
1627
1628                // assign trans from slave -> master
1629
1630                // got invoices?
1631                if ( is_array( $slave['transactions'] ) && count( $slave['transactions'] ) > 0 ) {
1632
1633                    foreach ( $slave['transactions'] as $transaction ) {
1634
1635                        // for trans, we just "switch" the owner meta :)
1636                        zeroBSCRM_changeTransactionCustomer( $transaction['id'], $dominantID );
1637                        $changes[] = __( 'Assigned transaction from secondary record onto main record', 'zero-bs-crm' ) . ' (#' . $transaction['id'] . ').';
1638
1639                    }
1640                } // / has invoices
1641
1642                // assign events from slave -> master
1643
1644                // get events
1645                $events = zeroBS_getEventsByCustomerID( $slaveID, true, 10000, 0 );
1646                if ( is_array( $events ) && count( $events ) > 0 ) {
1647
1648                    foreach ( $events as $event ) {
1649
1650                        // for events, we just "switch" the meta val :)
1651                        zeroBSCRM_changeEventCustomer( $event['id'], $dominantID );
1652                        $changes[] = __( 'Assigned task from secondary record onto main record', 'zero-bs-crm' ) . ' (#' . $event['id'] . ').';
1653
1654                    }
1655                } // / has invoices
1656
1657                // assign logs(?) from slave -> master
1658
1659                // for now save these as a random text meta against customer (not sure how to expose as of yet, but don't want to loose)
1660                $slaveLogs = zeroBSCRM_getContactLogs( $slaveID, true, 10000, 0 ); // id created name meta
1661                if ( is_array( $slaveLogs ) && count( $slaveLogs ) > 0 ) {
1662
1663                    // no $change here, as this is kinda secret, kthx
1664                    $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $dominantID, 'merged_customer_log_bk_' . time(), $slaveLogs );
1665
1666                }
1667
1668                // assign tags(?) from slave -> master
1669
1670                // get slave tags as ID array
1671                $slaveTagsIDs = zeroBSCRM_getCustomerTagsByID( $slaveID, true );
1672                if ( is_array( $slaveTagsIDs ) && count( $slaveTagsIDs ) > 0 ) {
1673
1674                    // add tags to master (append mode)
1675                    // wp_set_object_terms($dominantID, $slaveTagsIDs, 'zerobscrm_customertag', true );
1676                    $zbs->DAL->addUpdateObjectTags(
1677                        array(
1678                            'objid'   => $dominantID,
1679                            'objtype' => ZBS_TYPE_CONTACT,
1680                            'tagIDs'  => $slaveTagsIDs,
1681                            'mode'    => 'append',
1682                        )
1683                    );
1684                    $changes[] = __( 'Tagged main record with', 'zero-bs-crm' ) . ' ' . count( $slaveTagsIDs ) . ' ' . __( 'tags from secondary record.', 'zero-bs-crm' );
1685
1686                }
1687
1688                // AKA / alias
1689
1690                // second email -> alias first
1691                if ( ! empty( $slaveEmailAddress ) ) {
1692
1693                    // add as alias
1694                    zeroBS_addCustomerAlias( $dominantID, $slaveEmailAddress );
1695                    $changes[] = __( 'Added secondary record email as alias/aka of main record', 'zero-bs-crm' ) . ' (' . $slaveEmailAddress . ')';
1696
1697                }
1698
1699                // Customer image
1700
1701                // (for now, left to die)
1702
1703                // delete slave
1704                zeroBS_deleteCustomer( $slaveID, false );
1705                $changes[] = __( 'Removed secondary record', 'zero-bs-crm' ) . ' (#' . $slaveID . ')';
1706
1707                // assign log for changes + conflicting changes
1708
1709                // strbuild
1710                $shortDesc = '"' . $slave['name'] . '" (#' . $slave['id'] . ') ' . __( 'into this record', 'zero-bs-crm' );
1711                $longDesc  = '';
1712
1713                        // changes
1714                if ( is_array( $changes ) && count( $changes ) > 0 ) {
1715
1716                    $longDesc .= '<strong>' . __( 'Record Changes', 'zero-bs-crm' ) . ':</strong><br />';
1717
1718                    // cycle through em
1719                    foreach ( $changes as $c ) {
1720
1721                        $longDesc .= '<br />' . $c;
1722
1723                    }
1724                } else {
1725
1726                    $longDesc .= '<strong>' . __( 'No Changes', 'zero-bs-crm' ) . '</strong>';
1727
1728                }
1729
1730                        // conflicting changes
1731                if ( is_array( $conflictingChanges ) && count( $conflictingChanges ) > 0 ) {
1732
1733                    $longDesc .= '<br />=============================<br /><strong>' . __( 'Conflicting Changes', 'zero-bs-crm' ) . ':</strong><br />';
1734
1735                    // cycle through em
1736                    foreach ( $conflictingChanges as $c ) {
1737
1738                        $longDesc .= '<br />' . $c;
1739
1740                    }
1741                } else {
1742
1743                    $longDesc .= '<br />=============================<br /><strong>' . __( 'No Conflicting Changes', 'zero-bs-crm' ) . '</strong>';
1744
1745                }
1746
1747                // MASTER LOG :D
1748                zeroBS_addUpdateContactLog(
1749                    $dominantID,
1750                    -1,
1751                    -1,
1752                    array(
1753                        'type'      => 'Bulk Action: Merge',
1754                        'shortdesc' => $shortDesc,
1755                        'longdesc'  => $longDesc,
1756                    )
1757                );
1758
1759                return true;
1760
1761            } catch ( Exception $e ) {
1762
1763                // failed somehow!
1764                echo 'ERROR:' . $e->getMessage();
1765
1766            }
1767        } // / if id's
1768
1769    }
1770
1771    return false;
1772}
1773
1774function zeroBS_addUpdateCustomer(
1775    $cID = -1,
1776    $cFields = array(),
1777    $externalSource = '',
1778    $externalID = '',
1779    $customerDate = '',
1780    $fallBackLog = false,
1781    $extraMeta = false,
1782    $automatorPassthrough = false,
1783    $owner = -1,
1784    $metaBuilderPrefix = 'zbsc_'
1785) {
1786
1787    #} return
1788    $ret = false;
1789
1790    if ( isset( $cFields ) && count( $cFields ) > 0 ) {
1791
1792        #} New flag
1793        $newCustomer    = false;
1794        $originalStatus = '';
1795
1796        global $zbs;
1797
1798        if ( $cID > 0 ) {
1799
1800            #} Retrieve / check?
1801            $postID = $cID;
1802
1803            #} Build "existing meta" to pass, (so we only update fields pushed here)
1804            $existingMeta = $zbs->DAL->contacts->getContact( $postID, array() );
1805
1806            $originalDate = time();
1807            if ( isset( $existingMeta ) && is_array( $existingMeta ) && isset( $existingMeta['createduts'] ) && ! empty( $existingMeta['createduts'] ) ) {
1808                $originalDate = $existingMeta['createduts'];
1809            }
1810
1811            if ( ! empty( $customerDate ) && $customerDate != '' ) {
1812
1813                #} DATE PASSED TO THE FUNCTION
1814                $customerDateTimeStamp = strtotime( $customerDate );
1815                #} ORIGINAL POST CREATION DATE
1816                // no need, db2 = UTS $originalDateTimeStamp = strtotime($originalDate);
1817                $originalDateTimeStamp = $originalDate;
1818
1819                #} Compare, if $customerDateTimeStamp < then update with passed date
1820                if ( $customerDateTimeStamp < $originalDateTimeStamp ) {
1821
1822                    // straight in there :)
1823                    $zbs->DAL->contacts->addUpdateContact(
1824                        array(
1825                            'id'            => $postID,
1826                            'limitedFields' => array(
1827                                array(
1828                                    'key'  => 'zbsc_created',
1829                                    'val'  => $customerDateTimeStamp,
1830                                    'type' => '%d',
1831                                ),
1832                            ),
1833                        )
1834                    );
1835                }
1836            }
1837
1838            // WH changed 20/05/18
1839            // 20/05/18 - Previously this would reload the EXISTING database data
1840            // THEN 'override' any passed fields
1841            // THEN save that down
1842            // ... this was required when we used old meta objs. (pre db2)
1843            // ... so if we're now DAL2, we can do away with that and simply pass what's to be updated and mode do_not_update_blanks
1844            $existingMeta = array();
1845
1846        } else {
1847
1848            // DB2: Probably can rethink this whole func, (do we even need it?) e.g. header post mentality used here
1849            // for now I've just edited in place, but def refactor in time
1850
1851            #} Set flag
1852            $newCustomer = true;
1853
1854            #} Set up empty meta arr
1855
1856            #} DATE PASSED TO THE FUNCTION
1857            $customerDateTimeStamp = strtotime( $customerDate );
1858            #} DAL2 needs timestamp :)
1859            $existingMeta = array( 'created' => $customerDateTimeStamp );
1860
1861        }
1862
1863        #} Build using centralised func below, passing any existing meta (updates not overwrites)
1864        $zbsCustomerMeta = zeroBS_buildContactMeta( $cFields, $existingMeta, $metaBuilderPrefix, '', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1865
1866        $we_have_tags = false; // set to false.. duh..
1867
1868        // TAG customer (if exists) - clean etc here too
1869        if ( ! empty( $cFields['tags'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1870            $tags = $cFields['tags']; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1871            // Sanitize tags
1872            if ( is_array( $tags ) ) {
1873                $customer_tags = filter_var_array( $tags, FILTER_UNSAFE_RAW );
1874                // Formerly this used FILTER_SANITIZE_STRING, which is now deprecated as it was fairly broken. This is basically equivalent.
1875                // @todo Replace this with something more correct.
1876                foreach ( $customer_tags as $k => $v ) {
1877                    $customer_tags[ $k ] = strtr(
1878                        strip_tags( $v ), // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1879                        array(
1880                            "\0" => '',
1881                            '"'  => '&#34;',
1882                            "'"  => '&#39;',
1883                            '<'  => '',
1884                        )
1885                    );
1886                }
1887                $we_have_tags = true;
1888            }
1889
1890            if ( $we_have_tags ) {
1891
1892                $zbsCustomerMeta['tags'] = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1893                foreach ( $customer_tags as $tag_name ) {
1894
1895                    // Check for existing tag under this name.
1896                    $tag_id = $zbs->DAL->getTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1897                        -1,
1898                        array(
1899                            'objtype' => ZBS_TYPE_CONTACT,
1900                            'name'    => $tag_name,
1901                            'onlyID'  => true,
1902                        )
1903                    );
1904
1905                    // If tag doesn't exist, create one.
1906                    if ( empty( $tag_id ) ) {
1907                        $tag_id = $zbs->DAL->addUpdateTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1908                            array(
1909                                'data' => array(
1910                                    'objtype' => ZBS_TYPE_CONTACT,
1911                                    'name'    => $tag_name,
1912                                ),
1913                            )
1914                        );
1915                    }
1916
1917                    // Add tag to list.
1918                    if ( ! empty( $tag_id ) ) {
1919                        $zbsCustomerMeta['tags'][] = $tag_id; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1920                    }
1921                }
1922            }
1923        }
1924
1925        #} Add external source/externalid
1926        #} No empties, no random externalSources :)
1927        $extSourceArr           = -1;
1928        $approvedExternalSource = ''; #} As this is passed to automator :)
1929        if ( ! empty( $externalSource ) && ! empty( $externalID ) && array_key_exists( $externalSource, $zbs->external_sources ) ) {
1930
1931            #} If here, is legit.
1932            $approvedExternalSource = $externalSource;
1933
1934            #} Add/Update record flag
1935            // 2.4+ Migrated away from this method to new update_post_meta($postID, 'zbs_customer_ext_'.$approvedExternalSource, $externalID);
1936            // 2.52+ Moved to new DAL method :)
1937
1938            $extSourceArr = array(
1939                'source' => $approvedExternalSource,
1940                'uid'    => $externalID,
1941            );
1942
1943            // add/update
1944            // DB2, this is just used below :)zeroBS_updateExternalSource($postID,$extSourceArr);
1945            $zbsCustomerMeta['externalSources'] = array( $extSourceArr );
1946
1947        } #} Otherwise will just be a random customer no ext source
1948
1949        #} Got owner?
1950        if ( $owner !== -1 ) {
1951            $zbsCustomerMeta['owner'] = $owner;
1952        }
1953
1954        #} Update record (All IA is now fired intrinsicly )
1955        // DB2 update_post_meta($postID, 'zbs_customer_meta', $zbsCustomerMeta);
1956        return $zbs->DAL->contacts->addUpdateContact(
1957            array(
1958                'id'                   => $cID,
1959                'data'                 => $zbsCustomerMeta,
1960                'extraMeta'            => $extraMeta,
1961                'automatorPassthrough' => $automatorPassthrough,
1962                'fallBackLog'          => $fallBackLog,
1963            )
1964        );
1965
1966            /*
1967            This now get's passed above, and dealt with by DAL
1968            #} Any extra meta keyval pairs?
1969            $confirmedExtraMeta = false;
1970            if ( isset( $extraMeta) && is_array( $extraMeta) ) {
1971
1972                $confirmedExtraMeta = array();
1973
1974                    foreach ( $extraMeta as $k => $v){
1975
1976                    #} This won't fix stupid keys, just catch basic fails...
1977                    $cleanKey = strtolower(str_replace(' ','_',$k));
1978
1979                    #} Brutal update
1980                    //update_post_meta($postID, 'zbs_customer_extra_'.$cleanKey, $v);
1981                    $zbs->DAL->updateMeta(ZBS_TYPE_CONTACT,$postID,'extra_'.$cleanKey,$v);
1982
1983                    #} Add it to this, which passes to IA
1984                    $confirmedExtraMeta[$cleanKey] = $v;
1985
1986                }
1987
1988            } */
1989
1990            /*
1991            NOW DEALT WITH IN DAL2 :)
1992
1993            #} INTERNAL AUTOMATOR
1994            #} &
1995            #} FALLBACKS
1996
1997            if ( $newCustomer){
1998
1999                #} Add to automator
2000                zeroBSCRM_FireInternalAutomator('customer.new',array(
2001                    'id'=>$postID,
2002                    'customerMeta'=>$zbsCustomerMeta,
2003                    'extsource'=>$approvedExternalSource,
2004                    'automatorpassthrough'=>$automatorPassthrough, #} This passes through any custom log titles or whatever into the Internal automator recipe.
2005                    'customerExtraMeta'=>$confirmedExtraMeta #} This is the "extraMeta" passed (as saved)
2006                ));
2007
2008                // (WH) Moved this to fire on the IA...
2009                // do_action('zbs_new_customer', $postID);   //fire the hook here...
2010
2011            } else {
2012
2013                #} Customer Update here (automator)?
2014                #} TODO
2015
2016                #} FALLBACK
2017                #} (This fires for customers that weren't added because they already exist.)
2018                #} e.g. x@g.com exists, so add log "x@g.com filled out form"
2019                #} Requires a type and a shortdesc
2020                if (
2021                    isset($fallBackLog) && is_array($fallBackLog)
2022                    && isset($fallBackLog['type']) && !empty($fallBackLog['type'])
2023                    && isset($fallBackLog['shortdesc']) && !empty($fallBackLog['shortdesc'])
2024                ) {
2025
2026                    #} Brutal add, maybe validate more?!
2027
2028                    #} Long desc if present:
2029                    $zbsNoteLongDesc = '';
2030                    if ( isset( $fallBackLog['longdesc']) && !empty( $fallBackLog['longdesc']) ) {
2031                        $zbsNoteLongDesc = $fallBackLog['longdesc'];
2032                    }
2033
2034                        #} Only raw checked... but proceed.
2035                        $newOrUpdatedLogID = zeroBS_addUpdateContactLog($postID,-1,-1,array(
2036                            #} Anything here will get wrapped into an array and added as the meta vals
2037                            'type' => $fallBackLog['type'],
2038                            'shortdesc' => $fallBackLog['shortdesc'],
2039                            'longdesc' => $zbsNoteLongDesc
2040                        ));
2041
2042                }
2043
2044                // catch dirty flag (update of status) (note, after update_post_meta - as separate)
2045                //if (isset($_POST['zbsc_status_dirtyflag']) && $_POST['zbsc_status_dirtyflag'] == "1"){
2046                // actually here, it's set above
2047                if ( isset( $statusChange) && is_array( $statusChange)){
2048
2049                    // status has changed
2050
2051                    // IA
2052                    zeroBSCRM_FireInternalAutomator('customer.status.update',array(
2053                        'id'=>$postID,
2054                        'againstid' => $postID,
2055                        'userMeta'=> $zbsCustomerMeta,
2056                        'from' => $statusChange['from'],
2057                        'to' => $statusChange['to']
2058                        ));
2059
2060                }
2061
2062            } */
2063
2064            #} REQ?
2065            #} MAKE SURE if you change any post_name features you also look at: "NAMECHANGES" in this file (when a post updates it'll auto replace these...)
2066            #$newCName = zeroBS_customerName('',$zbsMeta,true,false)
2067
2068            #} Return customerID if success :)
2069            // $ret = $postID;
2070
2071    }
2072
2073    return $ret;
2074}
2075
2076/*
2077======================================================
2078    Contact -> aliases
2079    ====================================================== */
2080#} See if already in use/exists
2081function zeroBS_canUseCustomerAlias( $alias = '' ) {
2082
2083    // now can call this generic:
2084    return zeroBS_canUseAlias( ZBS_TYPE_CONTACT, $alias );
2085}
2086
2087#} Get specific alias if exists
2088function zeroBS_getCustomerAlias( $cID = -1, $alias = '' ) {
2089
2090    return zeroBS_getObjAlias( ZBS_TYPE_CONTACT, $cID, $alias );
2091}
2092
2093#} Get specific alias if exists
2094function zeroBS_getCustomerAliasByID( $cID = -1, $aliasID = -1 ) {
2095
2096    return zeroBS_getAliasByID( ZBS_TYPE_CONTACT, $cID, $aliasID );
2097}
2098
2099#} Get All Aliases against a contact.
2100function zeroBS_getCustomerAliases( $cID = -1 ) {
2101
2102    return zeroBS_getObjAliases( ZBS_TYPE_CONTACT, $cID );
2103}
2104
2105#} add Aliases to a contact.
2106function zeroBS_addCustomerAlias( $cID = -1, $alias = '' ) {
2107
2108    return zeroBS_addObjAlias( ZBS_TYPE_CONTACT, $cID, $alias );
2109}
2110
2111#} remove Alias from an contact
2112function zeroBS_removeCustomerAlias( $cID = -1, $alias = '' ) {
2113
2114    return zeroBS_removeObjAlias( ZBS_TYPE_CONTACT, $cID, $alias );
2115}
2116
2117#} remove Alias from a contact.
2118function zeroBS_removeCustomerAliasByID( $cID = -1, $aliasID = -1 ) {
2119
2120    return zeroBS_removeObjAliasByID( ZBS_TYPE_CONTACT, $cID, $aliasID );
2121}
2122
2123/*
2124======================================================
2125    / Contact -> aliases
2126====================================================== */
2127
2128function zeroBSCRM_getLog( $lID = -1 ) {
2129
2130    if ( $lID !== -1 ) {
2131
2132        global $zbs;
2133
2134        return $zbs->DAL->logs->getLog(
2135            array(
2136                'id'          => $lID,
2137                'incMeta'     => true,
2138                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2139            )
2140        );
2141
2142    }
2143
2144    return false;
2145}
2146
2147function zeroBSCRM_getContactLogs( $customerID = -1, $withFullDetails = false, $perPage = 100, $page = 0, $searchPhrase = '', $argsOverride = false ) {
2148
2149    if ( ! empty( $customerID ) && $customerID !== -1 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2150
2151        global $zbs;
2152        return $zbs->DAL->logs->getLogsForObj(
2153            array(
2154
2155                'objtype'      => ZBS_TYPE_CONTACT,
2156                'objid'        => $customerID,
2157
2158                'searchPhrase' => $searchPhrase,
2159
2160                'incMeta'      => $withFullDetails,
2161
2162                'sortByField'  => 'zbsl_created',
2163                'sortOrder'    => 'DESC',
2164                'page'         => $page,
2165                'perPage'      => $perPage,
2166                'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2167
2168            )
2169        );
2170
2171    }
2172    return array();
2173}
2174
2175function zeroBSCRM_getAllContactLogs( $withFullDetails = false, $perPage = 100, $page = 0, $searchPhrase = '', $argsOverride = false ) {
2176
2177    global $zbs;
2178    return $zbs->DAL->logs->getLogsForANYObj(
2179        array(
2180
2181            'objtype'      => ZBS_TYPE_CONTACT,
2182
2183            'searchPhrase' => $searchPhrase,
2184
2185            'incMeta'      => $withFullDetails,
2186
2187            'sortByField'  => 'zbsl_created',
2188            'sortOrder'    => 'DESC',
2189            'page'         => $page,
2190            'perPage'      => $perPage,
2191            'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2192
2193        )
2194    );
2195}
2196function zeroBSCRM_getCompanyLogs( $companyID = false, $withFullDetails = false, $perPage = 100, $page = 0, $searchPhrase = '', $argsOverride = false ) {
2197
2198    // DAL 3+ :)
2199    if ( ! empty( $companyID ) ) {
2200        global $zbs;
2201
2202        return $zbs->DAL->logs->getLogsForObj(
2203            array(
2204
2205                'objtype'      => ZBS_TYPE_COMPANY,
2206                'objid'        => $companyID,
2207
2208                'searchPhrase' => $searchPhrase,
2209
2210                'incMeta'      => $withFullDetails,
2211
2212                'sortByField'  => 'zbsl_created',
2213                'sortOrder'    => 'DESC',
2214                'page'         => $page,
2215                'perPage'      => $perPage,
2216                'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2217            )
2218        );
2219
2220    }
2221    return array();
2222}
2223
2224function zeroBSCRM_getObjCreationLog( $objID = -1, $objType = ZBS_TYPE_CONTACT ) {
2225
2226    if ( ! empty( $objID ) && $objID !== -1 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2227
2228        global $zbs;
2229        return $zbs->DAL->logs->getLogsForObj( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
2230            array(
2231
2232                'objtype'     => $objType,
2233                'objid'       => $objID,
2234
2235                'notetype'    => 'Created',
2236
2237                'incMeta'     => true,
2238
2239                'sortByField' => 'zbsl_created',
2240                'sortOrder'   => 'ASC',
2241                'page'        => 0,
2242                'perPage'     => 1,
2243                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2244
2245            )
2246        );
2247
2248    }
2249}
2250
2251function zeroBSCRM_getMostRecentContactLog( $objID = false, $withFullDetails = false, $restrictToTypes = false ) {
2252
2253    if ( ! empty( $objID ) ) {
2254
2255        global $zbs;
2256
2257        return $zbs->DAL->logs->getLogsForObj(
2258            array(
2259
2260                'objtype'     => ZBS_TYPE_COMPANY,
2261                'objid'       => $objID,
2262
2263                'notetypes'   => $restrictToTypes,
2264
2265                'incMeta'     => $withFullDetails,
2266
2267                'sortByField' => 'zbsl_created',
2268                'sortOrder'   => 'DESC',
2269                'page'        => 0,
2270                'perPage'     => 1,
2271                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2272
2273            )
2274        );
2275
2276    }
2277}
2278
2279function zeroBSCRM_getMostRecentCompanyLog( $objID = false, $withFullDetails = false, $restrictToTypes = false ) {
2280
2281    if ( ! empty( $objID ) ) {
2282
2283        global $zbs;
2284
2285        return $zbs->DAL->logs->getLogsForObj(
2286            array(
2287
2288                'objtype'     => ZBS_TYPE_COMPANY,
2289                'objid'       => $objID,
2290
2291                'notetypes'   => $restrictToTypes,
2292
2293                'incMeta'     => $withFullDetails,
2294
2295                'sortByField' => 'zbsl_created',
2296                'sortOrder'   => 'DESC',
2297                'page'        => 0,
2298                'perPage'     => 1,
2299                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2300
2301            )
2302        );
2303
2304    }
2305}
2306
2307function zeroBS_searchLogs( $querystring ) {
2308
2309    global $zbs;
2310
2311    return $zbs->DAL->logs->getLogsForANYObj(
2312        array(
2313            'searchPhrase' => $querystring,
2314            'perPage'      => 100,
2315            'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2316        )
2317    );
2318}
2319
2320function zeroBS_allLogs() {
2321
2322    global $zbs;
2323
2324    return $zbs->DAL->logs->getLogsForANYObj(
2325        array(
2326            'perPage'     => 100,
2327            'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT ),
2328        )
2329    );
2330}
2331
2332/**
2333 * Adds or updates a log entry.
2334 *
2335 * @param int    $cID        Contact ID.
2336 * @param int    $logID      Log ID.
2337 * @param int    $logDate    Log date timestamp.
2338 * @param array  $noteFields Note fields array. Expected keys: zbsNoteAgainstPostID, zbsNoteType,
2339 *                           zbsNoteShortDesc, zbsNoteLongDesc. May also include 'meta_assoc_id'
2340 *                           (e.g. campaign ID for 'email sent' logs) and 'meta_assoc_src'
2341 *                           (e.g. 'mailcamp').
2342 * @param string $objType    Object type â€” e.g. 'contact' or 'zerobs_customer'.
2343 * @param int    $owner      Owner ID.
2344 */
2345function zeroBS_addUpdateLog(
2346    $cID = -1,
2347    $logID = -1,
2348    $logDate = -1,
2349    $noteFields = array(),
2350    $objType = '',
2351    $owner = -1
2352) {
2353
2354    global $zbs;
2355
2356    // DAL3 all obj logs: if ($objType == 'zerobs_customer'){
2357        // zeroBSCRM_DEPRECATEDMSG('zeroBS_addUpdateLog has been replaced by zeroBS_addUpdateContactLog etc. or (better still) DAL2 calls direct');
2358        // return zeroBS_addUpdateContactLog($cID,$logID,$logDate,$noteFields,$owner);
2359    // DAL3 NO CPT LOGS:
2360        // } else
2361        // fallback
2362
2363    // translate zerobs_customer to 1
2364    $typeID = $zbs->DAL->objTypeID( $objType );
2365
2366        // got type?
2367    if ( $typeID !== -1 ) {
2368
2369        // assume this'll work. (should do.)
2370        return zeroBS_addUpdateObjLog( $typeID, $cID, $logID, $logDate, $noteFields, $owner );
2371
2372    }
2373
2374    // no TYPE
2375    zeroBSCRM_DEPRECATEDMSG( 'zeroBS_addUpdateLog has been replaced by DAL3 logging. Please do not use, or at least pass an object type.' );
2376    return false;
2377}
2378// really should just be calling direct at this point, or zeroBS_addUpdateObjLog at least
2379function zeroBS_addUpdateContactLog(
2380    $cID = -1,
2381    $logID = -1,
2382    $logDate = -1,
2383    $noteFields = array(),
2384    $owner = -1
2385) {
2386
2387    // wrapper for this:
2388    return zeroBS_addUpdateObjLog( ZBS_TYPE_CONTACT, $cID, $logID, $logDate, $noteFields, $owner );
2389}
2390
2391// generic add obj log
2392function zeroBS_addUpdateObjLog(
2393    $objTypeID = -1,
2394    $objID = -1,
2395    $logID = -1,
2396    $logDate = -1,
2397    $noteFields = array(),
2398    $owner = -1
2399) {
2400
2401    if ( $objTypeID > 0 ) {
2402
2403        $logType      = '';
2404        $logShortDesc = '';
2405        $logLongDesc  = '';
2406        $logMeta      = -1;
2407        $logCreated   = -1;
2408        $pinned       = -1;
2409        if ( isset( $noteFields['type'] ) ) {
2410            $logType = zeroBSCRM_permifyLogType( $noteFields['type'] );
2411        }
2412        if ( isset( $noteFields['shortdesc'] ) ) {
2413            $logShortDesc = $noteFields['shortdesc'];
2414        }
2415        if ( isset( $noteFields['longdesc'] ) ) {
2416            $logLongDesc = $noteFields['longdesc'];
2417        }
2418        if ( isset( $noteFields['meta'] ) ) {
2419            $logMeta = $noteFields['meta'];
2420        }
2421        if ( isset( $noteFields['pinned'] ) ) {
2422            $pinned = $noteFields['pinned'];
2423        }
2424        if ( $logDate !== -1 ) {
2425            $logCreated = strtotime( $logDate );
2426        } else {
2427            $logCreated = -1;
2428        }
2429
2430        global $zbs;
2431
2432        return $zbs->DAL->logs->addUpdateLog(
2433            array(
2434
2435                'id'    => $logID,
2436                'owner' => $owner,
2437
2438                // fields (directly)
2439                'data'  => array(
2440
2441                    'objtype'   => $objTypeID,
2442                    'objid'     => $objID,
2443                    'type'      => $logType,
2444                    'shortdesc' => $logShortDesc,
2445                    'longdesc'  => $logLongDesc,
2446
2447                    'meta'      => $logMeta,
2448                    'pinned'    => $pinned,
2449
2450                    'created'   => $logCreated,
2451
2452                ),
2453            )
2454        );
2455
2456    }
2457
2458        return false;
2459}
2460
2461// allows us to lazily 'hotswap' wp_set_post_terms in extensions (e.g. pre DAL2 it'll just fire wp_set_post_terms)
2462// ... here it does DAL2 equiv
2463// WH Note: if using old WP method (wp_set_post_terms) can pass tags or tagIDS - DB2 currently only accepts tagIDs - to add in
2464// ... to get around this I've temp added $usingTagIDS=true flag
2465// still used in bulk-tagger and groove-connect extensions as of 9 May 1923
2466function zeroBSCRM_DAL2_set_post_terms( $cID = -1, $tags = array(), $taxonomy = 'zerobscrm_customertag', $append = true, $usingTagIDS = true ) {
2467
2468    zeroBSCRM_DEPRECATEDMSG( 'zeroBSCRM_DAL2_set_post_terms has been replaced by DAL3 tagging. Please do not use.' );
2469
2470    global $zbs;
2471
2472    // if we have tooo....
2473    $possibleObjTypeID = $zbs->DAL->cptTaxonomyToObjID( $taxonomy );
2474
2475    if ( $possibleObjTypeID > 0 ) {
2476
2477        $mode = 'replace';
2478        if ( $append ) {
2479            $mode = 'append';
2480        }
2481
2482        $fieldName = 'tagIDs';
2483        if ( ! $usingTagIDS ) {
2484            $fieldName = 'tags';
2485        }
2486
2487        return $zbs->DAL->addUpdateObjectTags(
2488            array(
2489                'objid'    => $cID,
2490                'objtype'  => $possibleObjTypeID,
2491                $fieldName => $tags,
2492                'mode'     => $mode,
2493            )
2494        );
2495
2496    }
2497    return false;
2498}
2499
2500// allows us to lazily 'hotswap' wp_set_object_terms in extensions (e.g. pre DAL2 it'll just fire wp_set_object_terms)
2501// ... here it does DAL2 equiv
2502// WH Note: if using old WP method (wp_set_object_terms) can pass tags or tagIDS - DB2 currently only accepts tagIDs - to add in
2503// ... to get around this I've temp added $usingTagIDS=true flag
2504// still used in several extensions as of 9 May 1923
2505function zeroBSCRM_DAL2_set_object_terms( $cID = -1, $tags = array(), $taxonomy = 'zerobscrm_customertag', $append = true, $usingTagIDS = true ) {
2506
2507    zeroBSCRM_DEPRECATEDMSG( 'zeroBSCRM_DAL2_set_object_terms has been replaced by DAL3 tagging. Please do not use.' );
2508
2509    global $zbs;
2510
2511    // if we have tooo....
2512    $possibleObjTypeID = $zbs->DAL->cptTaxonomyToObjID( $taxonomy );
2513
2514    if ( $possibleObjTypeID > 0 ) {
2515
2516        $mode = 'replace';
2517        if ( $append ) {
2518            $mode = 'append';
2519        }
2520
2521        $fieldName = 'tagIDs';
2522        if ( ! $usingTagIDS ) {
2523            $fieldName = 'tags';
2524        }
2525
2526        return $zbs->DAL->addUpdateObjectTags(
2527            array(
2528                'objid'    => $cID,
2529                'objtype'  => $possibleObjTypeID,
2530                $fieldName => $tags,
2531                'mode'     => $mode,
2532            )
2533        );
2534
2535    }
2536    return false;
2537    /*
2538    // we only switch out for customer tags, rest just go old way
2539    if ( $taxonomy == 'zerobscrm_customertag'){
2540
2541        global $zbs;
2542
2543        $mode = 'replace';
2544        if ( $append ) {
2545            $mode = 'append';
2546        }
2547
2548        $fieldName = 'tagIDs';
2549        if ( !$usingTagIDS ) {
2550            $fieldName = 'tags';
2551        }
2552
2553        return $zbs->DAL->addUpdateObjectTags(array(
2554                                                        'objid'         => $cID,
2555                                                        'objtype'       => ZBS_TYPE_CONTACT,
2556                                                        $fieldName      => $tags,
2557                                                        'mode'          => $mode
2558                                                ));
2559
2560    } else {
2561
2562        //https://codex.wordpress.org/Function_Reference/wp_set_object_terms
2563        return wp_set_object_terms($cID,$tags,$taxonomy,$append);
2564
2565    } */
2566}
2567
2568#} This takes an array source (can be $_POST) and builds out a meta field array for it..
2569#} This lets us use the same fields array for Metaboxes.php and any custom integrations
2570#} e.g. $zbsCustomerMeta = zeroBS_buildContactMeta($_POST);
2571#} e.g. $zbsCustomerMeta = zeroBS_buildContactMeta($importedMetaFields);
2572#} e.g. $zbsCustomerMeta = zeroBS_buildContactMeta(array('zbsc_fname'=>'Woody'));
2573#} 27/09/16: Can now also pass starting array, which lets you "override" fields present in $arraySource, without loosing originals not passed
2574#} 12/04/18: Added prefix so as to be able to pass normal array e.g. fname (by passing empty fieldPrefix)
2575#} 3.0: this was moved to generic zeroBS_buildObjArr :)
2576function zeroBS_buildContactMeta( $arraySource = array(), $startingArray = array(), $fieldPrefix = 'zbsc_', $outputPrefix = '', $removeEmpties = false, $autoGenAutonumbers = false ) {
2577
2578    // moved to generic, just return that :)
2579    return zeroBS_buildObjArr( $arraySource, $startingArray, $fieldPrefix, $outputPrefix, $removeEmpties, ZBS_TYPE_CONTACT, $autoGenAutonumbers );
2580}
2581
2582/*
2583======================================================
2584    / Unchanged DAL2->3 (Mostly customer/contact + log relatead)
2585====================================================== */
2586
2587// ====================================================================================================================================
2588// ====================================================================================================================================
2589// ==================== / DAL 2.0 FUNCS ===============================================================================================
2590// ====================================================================================================================================
2591// ====================================================================================================================================
2592
2593// ====================================================================================================================================
2594// ====================================================================================================================================
2595// ==================== DAL 3.0 FUNCS ===============================================================================================
2596// ====================================================================================================================================
2597// ====================================================================================================================================
2598
2599/*
2600======================================================
2601    GENERIC helpers
2602====================================================== */
2603
2604    #} This is a fill-in until we deprecate addUpdateTransaction etc. (3.5 or so)
2605    #} it'll take a DAL1 obj (e.g. transaction with 'orderid') and produce a v3 translated field variant (e.g. orderid => ref (via 'dal1key' attr on obj model))
2606    #} param $objType = ZBS_TYPE_TRANSACTION
2607    #} param $fieldPrefix = zbst_ if fields are prefixed with
2608function zeroBS_translateDAL1toDAL3Obj( $arraySource = array(), $objType = -1, $fieldPrefix = '' ) {
2609
2610    if ( $objType > 0 ) {
2611
2612        global $zbs;
2613
2614        // $objectModel = $zbs->DAL->objModel($objType);
2615        $objectLayer = $zbs->DAL->getObjectLayerByType( $objType );
2616
2617        if ( isset( $objectLayer ) ) {
2618
2619            $ret                  = array();
2620            $objTranslationMatrix = $objectLayer->getDAL1toDAL3ConversionMatrix();
2621            if ( ! is_array( $objTranslationMatrix ) ) {
2622                $objTranslationMatrix = array();
2623            }
2624
2625            foreach ( $arraySource as $k => $v ) {
2626
2627                $kClean = $k;
2628                if ( ! empty( $fieldPrefix ) ) {
2629                    $kClean = str_replace( $fieldPrefix, '', $k );
2630                }
2631
2632                if ( isset( $objTranslationMatrix[ $kClean ] ) ) {
2633
2634                    // is translatable
2635                    $ret[ $fieldPrefix . $objTranslationMatrix[ $kClean ] ] = $v;
2636
2637                } else {
2638
2639                    // isn't translatable
2640                    $ret[ $k ] = $v;
2641
2642                }
2643            }
2644        } // / has object layer
2645
2646    } // / has objtype
2647
2648    return $ret;
2649}
2650
2651    #} This takes an array source (can be $_POST) and builds out a meta field array for it..
2652    #} ... this is a generalised postarray->objarray creator, built from zeroBS_buildContactMeta,
2653    #} ... now produces all "meta" (objarrays) for all objs. Centralised to keep DRY
2654    #} 13/03/19: Added $autoGenAutonumbers - if TRUE, empty/non-passed autonumber custom fields will assume fresh + autogen (useful for PORTAL/SYNC generated)
2655function zeroBS_buildObjArr( $arraySource = array(), $startingArray = array(), $fieldPrefix = 'zbsc_', $outputPrefix = '', $removeEmpties = false, $objType = ZBS_TYPE_CONTACT, $autoGenAutonumbers = false ) {
2656
2657    #} def
2658    $retArray = array();
2659
2660    #} if passed...
2661    if ( isset( $startingArray ) && is_array( $startingArray ) ) {
2662        $retArray = $startingArray;
2663    }
2664
2665    #} go
2666
2667    // req.
2668    global $zbs;
2669
2670    // DAL3 notes: (See #globalfieldobjsdal3 in fields.php)
2671    // .. ultimately we default to using the $fields globals, then fallback to the objmodels
2672    // introduced in DAL3 objs. This allows coverage of both, for now
2673    // v3.0 RC+ this can be refactored :)
2674    // Note: To make RC1 I also added in translation, which is perhaps a step toward refactoring this:
2675
2676    // Some RC1 field translations (requires dal1key against changed obj model fields)
2677    $arraySource = zeroBS_translateDAL1toDAL3Obj( $arraySource, $objType, $fieldPrefix );
2678
2679    // retrieve global var name
2680    $globFieldVarName = $zbs->DAL->objFieldVarName( $objType );
2681
2682    // nope. (for events in DAL3)
2683    // ... potentially can turn this off for all non DAL3? may be redundant inside next {}
2684    if ( $objType !== ZBS_TYPE_TASK && $objType !== ZBS_TYPE_QUOTETEMPLATE && isset( $GLOBALS[ $globFieldVarName ] ) ) {
2685
2686        $i = 0;
2687
2688        foreach ( $GLOBALS[ $globFieldVarName ] as $fK => $fV ) {
2689
2690            ++$i;
2691
2692            // if it's not an autonumber (which generates new on blank passes), set it to empty
2693            // ... or if it has $autoGenAutonumbers = true,
2694            if (
2695                ( $fV[0] !== 'autonumber' && ! isset( $retArray[ $outputPrefix . $fK ] ) )
2696                ||
2697                $autoGenAutonumbers
2698                ) {
2699                $retArray[ $outputPrefix . $fK ] = '';
2700            }
2701
2702            // two EXCEPTIONS:
2703            // 1) custom field type checkbox, because it adds -0 -1 etc. to options, so this wont fire,
2704            // 2) Autonumbers which are blank to start with get caught beneath
2705            // ... see below for checkbox catch
2706            if ( isset( $arraySource[ $fieldPrefix . $fK ] ) ) {
2707
2708                switch ( $fV[0] ) {
2709
2710                    case 'tel':
2711                        // validate tel? Should be an user option, allow validation.
2712                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2713                        // $retArray[$outputPrefix.$fK] = preg_replace("/[^0-9 .+\-()]/", '', $retArray[$outputPrefix.$fK]);
2714                        break;
2715
2716                    case 'price':
2717                    case 'numberfloat':
2718                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2719                        $retArray[ $outputPrefix . $fK ] = preg_replace( '@[^0-9\.]+@i', '-', $retArray[ $outputPrefix . $fK ] );
2720                        $retArray[ $outputPrefix . $fK ] = floatval( $retArray[ $outputPrefix . $fK ] );
2721                        break;
2722
2723                    case 'numberint':
2724                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2725                        $retArray[ $outputPrefix . $fK ] = preg_replace( '@[^0-9]+@i', '-', $retArray[ $outputPrefix . $fK ] );
2726                        $retArray[ $outputPrefix . $fK ] = intval( $retArray[ $outputPrefix . $fK ] );
2727                        break;
2728
2729                    case 'textarea':
2730                        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2731                        $retArray[ $outputPrefix . $fK ] = sanitize_textarea_field( $arraySource[ $fieldPrefix . $fK ] );
2732                        break;
2733
2734                    case 'date':
2735                        $safe_text = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2736
2737                        $retArray[ $outputPrefix . $fK ] = jpcrm_date_str_to_uts( $safe_text, '!Y-m-d', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2738                        break;
2739
2740                    case 'datetime':
2741                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2742
2743                        // translate datetime to UTS (without time)
2744                        // ... by default from DAL3.0
2745                        $retArray[ $outputPrefix . $fK ] = zeroBSCRM_locale_dateToUTS( $retArray[ $outputPrefix . $fK ], true );
2746
2747                        break;
2748
2749                    case 'radio':
2750                    case 'select':
2751                        // just get value, easy.
2752                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2753
2754                        break;
2755
2756                    // autonumber dealt with below this if {}
2757                    case 'autonumber':
2758                        // pass it along :)
2759                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2760
2761                        break;
2762
2763                    // checkbox dealt with below this if {}
2764
2765                    default:
2766                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2767
2768                        break;
2769
2770                } // / switch type
2771
2772            } // / if isset (simple) $arraySource[$fieldPrefix.$fK]
2773
2774            // catch checkboxes
2775            if ( $fV[0] == 'checkbox' ) {
2776
2777                // there are several ways that checkbox (multiselect) inputs may be passed, depending on source
2778                // because this function catches from:
2779                // - edit page post
2780                // - client portal profile page
2781                // - Gravity forms/extension calls
2782                // - API
2783                // ... to name a few, it's sensible that we try and catch the variants (low risk/cost here)
2784                $checkboxArr = array();
2785
2786                // Checkbox input: Iterative
2787                // This cycles through `checkboxkey-$i` (up to 64 options) and includes if they're set
2788                // This is used by our edit pages, client portal profile page etc.
2789                for ( $checkboxI = 0; $checkboxI < 64; $checkboxI++ ) {
2790
2791                    if ( isset( $arraySource[ $fieldPrefix . $fK . '-' . $checkboxI ] ) ) {
2792
2793                        // retrieve
2794                        $checkboxArr[] = $arraySource[ $fieldPrefix . $fK . '-' . $checkboxI ];
2795
2796                    }
2797                }
2798
2799                // Checkbox input: CSV
2800                // This can be exploded
2801                // This is used by gravity forms, when multiple 1 word options are checked (and probably elsewhere)
2802                if ( isset( $arraySource[ $fieldPrefix . $fK ] ) && is_string( $arraySource[ $fieldPrefix . $fK ] ) ) {
2803
2804                    // one option or multi?
2805                    if ( strpos( $arraySource[ $fieldPrefix . $fK ], ',' ) ) {
2806                        $checkboxArr = explode( ',', $arraySource[ $fieldPrefix . $fK ] );
2807                    } else {
2808                        $checkboxArr = array( $arraySource[ $fieldPrefix . $fK ] );
2809                    }
2810                }
2811
2812                // Checkbox input: Array
2813                // This can be straight passed
2814                // This is used by gravity forms, when at least one option with multiple words are checked (and probably elsewhere, is good to support pass through)
2815                if ( isset( $arraySource[ $fieldPrefix . $fK ] ) && is_array( $arraySource[ $fieldPrefix . $fK ] ) ) {
2816                    $checkboxArr = $arraySource[ $fieldPrefix . $fK ];
2817                }
2818
2819                if ( is_array( $checkboxArr ) ) {
2820
2821                    // sanitize
2822                    $checkboxArr = array_map( 'sanitize_text_field', $checkboxArr );
2823
2824                    // csv em
2825                    $retArray[ $outputPrefix . $fK ] = implode( ',', $checkboxArr );
2826
2827                } else {
2828
2829                    // none selected, set blank
2830                    $retArray[ $outputPrefix . $fK ] = '';
2831
2832                }
2833            } // / if checkbox
2834
2835            // if autonumber
2836            if ( $fV[0] == 'autonumber' ) {
2837
2838                // this is a generated field.
2839                // if was previously set, sticks with that, if not set, will generate new, based on custom field rule
2840                // NOTE!!!! if this is NOT SET in customerMeta, it WILL NOT be updated
2841                // ... this is because when passing incomplete update records (e.g. not passing autonumber)
2842                // ... it doesn't need a new AUTONUMBER
2843                // ... so if you want a fresh autonumber, you need to pass with $startingArray[] EMPTY value set
2844
2845                // if not yet set
2846                if ( isset( $retArray[ $outputPrefix . $fK ] ) && empty( $retArray[ $outputPrefix . $fK ] ) ) {
2847
2848                    // retrieve based on custom field rule
2849                    $autono = '';
2850
2851                    // retrieve rule
2852                    $formatExample = '';
2853                    if ( isset( $fV[2] ) ) {
2854                        $formatExample = $fV[2];
2855                    }
2856                    if ( ! empty( $formatExample ) && str_contains( $formatExample, '#' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2857
2858                        // has a rule at least
2859                        $formatParts = explode( '#', $formatExample );
2860
2861                        // build
2862
2863                        // prefix
2864                        if ( ! empty( $formatParts[0] ) ) {
2865                            $autono .= zeroBSCRM_customFields_parseAutoNumberStr( $formatParts[0] );
2866                        }
2867
2868                        // number
2869                        $no = zeroBSCRM_customFields_getAutoNumber( $objType, $fK );
2870                        if ( $no > 0 && $no !== false ) {
2871                            $autono .= $no;
2872                        }
2873
2874                        // suffix
2875                        if ( ! empty( $formatParts[2] ) ) {
2876                            $autono .= zeroBSCRM_customFields_parseAutoNumberStr( $formatParts[2] );
2877                        }
2878
2879                            // if legit, add
2880                        if ( $no > 0 && $no !== false ) {
2881                            $retArray[ $outputPrefix . $fK ] = $autono;
2882                        }
2883                    }
2884                }
2885            } // / if autonumber
2886
2887        } // / foreach field
2888
2889    } // / if global-based-fill-out
2890
2891            $replaceMap = array(
2892                'secaddr1'    => 'secaddr_addr1',
2893                'secaddr2'    => 'secaddr_addr2',
2894                'seccity'     => 'secaddr_city',
2895                'seccounty'   => 'secaddr_county',
2896                'seccountry'  => 'secaddr_country',
2897                'secpostcode' => 'secaddr_postcode',
2898            );
2899
2900            foreach ( $replaceMap as $d2key => $d1key ) {
2901                if ( isset( $retArray[ $outputPrefix . $d1key ] ) ) {
2902                    $retArray[ $outputPrefix . $d2key ] = $retArray[ $outputPrefix . $d1key ];
2903                    unset( $retArray[ $outputPrefix . $d1key ] );
2904                }
2905            }
2906
2907            // can also pass some extras :) /social
2908            // for co + contact
2909            if ( $objType == ZBS_TYPE_CONTACT || $objType == ZBS_TYPE_COMPANY ) {
2910
2911                $extras = array( 'tw', 'fb', 'li' );
2912                foreach ( $extras as $fK ) {
2913
2914                    if ( ! isset( $retArray[ $outputPrefix . $fK ] ) ) {
2915                        $retArray[ $outputPrefix . $fK ] = '';
2916                    }
2917
2918                    if ( isset( $arraySource[ $fieldPrefix . $fK ] ) ) {
2919
2920                        $retArray[ $outputPrefix . $fK ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fK ] );
2921
2922                    }
2923                }
2924            }
2925
2926            // ... Further, from DAL3+ we now have proper object models, which probably should replace "fields"
2927            // above, but for now, churning through both, as sensitively as possible.
2928
2929            // get an obj model, if set
2930            $potentialModel = $zbs->DAL->objModel( $objType );
2931
2932            // will be objlayer model if set
2933            if ( is_array( $potentialModel ) ) {
2934
2935                // cycle through each field + set, if not already set by the above.
2936                foreach ( $potentialModel as $fieldKey => $fieldDetail ) {
2937
2938                    // there's a few we ignore :)
2939                    if ( in_array( $fieldKey, array( 'ID', 'zbs_site', 'zbs_team' ) ) ) {
2940                        continue;
2941                    }
2942
2943                    // if not already set
2944                    if ( ! isset( $retArray[ $outputPrefix . $fieldKey ] ) ) {
2945
2946                        // retrieve based on type
2947                        switch ( $fieldDetail['format'] ) {
2948
2949                            case 'str':
2950                            case 'curr': // for now, process curr as str. (probs needs to just validate IS CURR)
2951                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
2952                                    $retArray[ $outputPrefix . $fieldKey ] = zeroBSCRM_textProcess( $arraySource[ $fieldPrefix . $fieldKey ] );
2953                                }
2954                                break;
2955
2956                            case 'int':
2957                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
2958
2959                                    $retArray[ $outputPrefix . $fieldKey ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fieldKey ] );
2960                                    $retArray[ $outputPrefix . $fieldKey ] = preg_replace( '@[^0-9]+@i', '-', $retArray[ $outputPrefix . $fieldKey ] );
2961                                    $retArray[ $outputPrefix . $fieldKey ] = intval( $retArray[ $outputPrefix . $fieldKey ] );
2962
2963                                }
2964                                break;
2965                            case 'uts':
2966                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
2967
2968                                    $retArray[ $outputPrefix . $fieldKey ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fieldKey ] );
2969
2970                                    // in case of UTS dates, the $_POST likely passed may be in date format
2971                                    // ... if so, take the model default + translate (if set)
2972                                    if ( isset( $fieldDetail['autoconvert'] ) && $fieldDetail['autoconvert'] == 'date' ) {
2973
2974                                        // translate "01/12/2018" to UTS (without time)
2975                                        $retArray[ $outputPrefix . $fieldKey ] = zeroBSCRM_locale_dateToUTS( $retArray[ $outputPrefix . $fieldKey ], false );
2976
2977                                    }
2978                                    if ( isset( $fieldDetail['autoconvert'] ) && $fieldDetail['autoconvert'] == 'datetime' ) {
2979
2980                                        // translate datetime to UTS (with time)
2981                                        $retArray[ $outputPrefix . $fieldKey ] = zeroBSCRM_locale_dateToUTS( $retArray[ $outputPrefix . $fieldKey ], true );
2982
2983                                    }
2984
2985                                    $retArray[ $outputPrefix . $fieldKey ] = preg_replace( '@[^0-9]+@i', '-', $retArray[ $outputPrefix . $fieldKey ] );
2986                                    $retArray[ $outputPrefix . $fieldKey ] = intval( $retArray[ $outputPrefix . $fieldKey ] );
2987
2988                                }
2989                                break;
2990
2991                            case 'bool':
2992                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
2993                                    $retArray[ $outputPrefix . $fieldKey ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fieldKey ] );
2994                                    $retArray[ $outputPrefix . $fieldKey ] = preg_replace( '@[^0-9]+@i', '-', $retArray[ $outputPrefix . $fieldKey ] );
2995                                    $retArray[ $outputPrefix . $fieldKey ] = boolval( $retArray[ $outputPrefix . $fieldKey ] );
2996                                }
2997                                break;
2998
2999                            case 'decimal':
3000                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
3001                                    $retArray[ $outputPrefix . $fieldKey ] = sanitize_text_field( $arraySource[ $fieldPrefix . $fieldKey ] );
3002                                    $retArray[ $outputPrefix . $fieldKey ] = preg_replace( '@[^0-9]+@i', '-', $retArray[ $outputPrefix . $fieldKey ] );
3003                                    $retArray[ $outputPrefix . $fieldKey ] = floatval( $retArray[ $outputPrefix . $fieldKey ] );
3004                                }
3005                                break;
3006
3007                            default: // basically str.
3008                                if ( isset( $arraySource[ $fieldPrefix . $fieldKey ] ) ) {
3009                                    $retArray[ $outputPrefix . $fieldKey ] = zeroBSCRM_textProcess( $arraySource[ $fieldPrefix . $fieldKey ] );
3010                                }
3011                                break;
3012
3013                        } // / format switch
3014
3015                    } // / not isset
3016
3017                } // / foreach
3018
3019            } // / if has model
3020
3021            // $removeEmpties
3022            if ( $removeEmpties ) {
3023
3024                $ret = array();
3025                foreach ( $retArray as $k => $v ) {
3026
3027                    $intV = (int) $v;
3028
3029                    if ( ! is_array( $v ) && ! empty( $v ) && $v != '' && $v !== 0 && $v !== -1 && $intV !== -1 ) {
3030                        $ret[ $k ] = $v;
3031                    }
3032                }
3033
3034                $retArray = $ret;
3035
3036            }
3037
3038            return $retArray;
3039}
3040
3041    // generally used for list view reformatting - cleans a contact array into simple format
3042    // here it takes an array of contacts, and (currently) returns 1 contact simplified
3043    // This may make more sense in the contact DAL obj layer?
3044    // >> this has a company variant too, in this file.
3045function zeroBSCRM_getSimplyFormattedContact( $contacts = array(), $requireOwner = false ) {
3046
3047    $return = false;
3048
3049    // DAL3 + has potential for multi-links, so here we just grab first if there
3050    if ( isset( $contacts ) && is_array( $contacts ) && count( $contacts ) > 0 ) {
3051
3052        // first only for now...
3053            $contact = $contacts[0];
3054
3055            // w adapted so same func can be used (generic) js side
3056            // works with zeroBSCRMJS_listView_generic_customer
3057            // provides a simplified ver of customer obj (4 data transit efficiency/exposure)
3058            $email = '';
3059        if ( isset( $contact['email'] ) && ! empty( $contact['email'] ) ) {
3060            $email = $contact['email'];
3061        }
3062        global $zbs;
3063        $return = array(
3064            'id'       => $contact['id'],
3065            'avatar'   => $zbs->DAL->contacts->getContactAvatar( $contact['id'] ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3066            'fullname' => zeroBS_customerName( '', $contact, false, false ),
3067            'email'    => $email,
3068        );
3069
3070        if ( $requireOwner ) {
3071            $return['owner'] = zeroBS_getOwner( $contact['id'], true, 'zerobs_customer' );
3072        }
3073    }
3074
3075    return $return;
3076}
3077
3078/*
3079======================================================
3080    / GENERIC helpers
3081====================================================== */
3082
3083/*
3084======================================================
3085    Company helpers
3086====================================================== */
3087
3088    #} Get the COUNT of companies.
3089function zeroBS_companyCount( $status = false ) {
3090
3091    global $zbs;
3092    return $zbs->DAL->companies->getCompanyCount(
3093        array(
3094            'withStatus'  => $status,
3095            'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3096        )
3097    );
3098}
3099
3100    // another func which should be skipped 3.0+, just do direct call :)
3101function zeroBS_getCompany( $coID = -1, $withObjs = false ) {
3102
3103    if ( $coID !== -1 ) {
3104
3105        global $zbs;
3106
3107        #} Super rough. Not sure where we use this, but shouldn't.
3108        return $zbs->DAL->companies->getCompany(
3109            $coID,
3110            array(
3111                'withQuotes'       => $withObjs,
3112                'withInvoices'     => $withObjs,
3113                'withTransactions' => $withObjs,
3114                'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3115            )
3116        );
3117
3118    }
3119
3120    return false;
3121}
3122
3123    #} Wrapper func for "company" type customers
3124    // note: $inCountry returns with address 1 or 2 in country (added for logReporter for Miguel (custom extension WH))
3125    // note: $withStatus returns with specific status  (added for logReporter for Miguel (custom extension WH))
3126    #} Adapted for 3.0+, note deprecated really, should be using DAL->companies->getCompanies directly
3127function zeroBS_getCompanies(
3128    $withFullDetails = false,
3129    $perPage = 10,
3130    $page = 0,
3131    $searchPhrase = '',
3132    $argsOverride = false,
3133    $withInvoices = false,
3134    $withQuotes = false,
3135    $withTransactions = false,
3136    $inCountry = false,
3137    $ownedByID = false,
3138    $withStatus = false,
3139    $inArr = false
3140) {
3141
3142    // $withFullDetails = irrelevant with new DB2 (always returns)
3143    // $argsOverride CAN NO LONGER WORK :)
3144    if ( $argsOverride !== false ) {
3145        zeroBSCRM_DEPRECATEDMSG( 'Use of $argsOverride in zeroBS_getCompanies is no longer relevant (DAL3.0)' );
3146    }
3147
3148    global $zbs;
3149
3150        $actualPage = $page;
3151    if ( $actualPage < 0 ) {
3152        $actualPage = 0;
3153    }
3154
3155    // make ARGS
3156    $args = array(
3157
3158        // Search/Filtering (leave as false to ignore)
3159        'searchPhrase'     => $searchPhrase,
3160        'inArr'            => $inArr,
3161        'inCountry'        => $inCountry,
3162        'hasStatus'        => $withStatus,
3163        'ownedBy'          => $ownedByID,
3164
3165        'withCustomFields' => true,
3166        'withQuotes'       => $withQuotes,
3167        'withInvoices'     => $withInvoices,
3168        'withTransactions' => $withTransactions,
3169        'withLogs'         => false,
3170        'withLastLog'      => false,
3171        'withTags'         => false, // $withTags,
3172        'withOwner'        => false,
3173
3174        // 'sortByField'     => $sortByField,
3175        // 'sortOrder'   => $sortOrder,
3176        'page'             => $actualPage,
3177        'perPage'          => $perPage,
3178
3179        'ignoreowner'      => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3180
3181    );
3182
3183    // here ignore owners = true the default, because we're not really forcing ownership anywhere overall,
3184    // when we do, we should change this/make it check
3185    if ( $ownedByID !== false && is_int( $ownedByID ) && $ownedByID > 0 ) {
3186
3187        $args['ignoreowner'] = false;
3188
3189    }
3190
3191    return $zbs->DAL->companies->getCompanies( $args );
3192}
3193
3194    // returns email for a company
3195function zeroBS_companyEmail( $companyID = '', $companyArr = false ) {
3196
3197    global $zbs;
3198    return $zbs->DAL->companies->getCompanyEmail( $companyID );
3199}
3200
3201/**
3202 * Retrieves the company ID based on its name.
3203 *
3204 * @param  string $company_name  The name of the company for which the ID is required.
3205 * @return int|bool              Returns the ID of the company if found, false otherwise.
3206 */
3207function zeroBS_getCompanyIDWithName( $company_name = '' ) {
3208    if ( ! empty( $company_name ) ) {
3209        global $zbs;
3210        return $zbs->DAL->companies->get_company_id_by_name( $company_name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3211    }
3212    return false;
3213}
3214
3215    #} ExternalID is name in this case :)
3216function zeroBS_getCompanyIDWithExternalSource( $externalSource = '', $externalID = '' ) {
3217
3218    global $zbs;
3219
3220    #} No empties, no random externalSources :)
3221    if ( ! empty( $externalSource ) && ! empty( $externalID ) && array_key_exists( $externalSource, $zbs->external_sources ) ) {
3222
3223        #} If here, is legit.
3224        $approvedExternalSource = $externalSource;
3225
3226        global $zbs;
3227
3228        return $zbs->DAL->companies->getCompany(
3229            -1,
3230            array(
3231                'externalSource'    => $approvedExternalSource,
3232                'externalSourceUID' => $externalID,
3233                'onlyID'            => true,
3234                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3235            )
3236        );
3237
3238    }
3239
3240    return false;
3241}
3242
3243    // This should probably be deprecated and just called directly.
3244    // for now just translating
3245function zeroBS_getCompaniesForTypeahead( $searchQueryStr = '' ) {
3246
3247    /*
3248    //gets them all, from a brutal SQL
3249    global $wpdb;
3250
3251        if ( !empty( $searchQueryStr)){
3252
3253            // param query
3254            $sql = "SELECT ID as id, post_title as name, post_date as created FROM $wpdb->posts WHERE post_type = 'zerobs_company' AND post_status = 'publish' AND post_title LIKE %s";
3255            $q = $wpdb->prepare($sql,'%'.$searchQueryStr.'%');
3256            $results = $wpdb->get_results($q, ARRAY_A);
3257
3258        } else {
3259
3260            // straight query
3261            $sql = "SELECT ID as id, post_title as name, post_date as created FROM $wpdb->posts WHERE post_type = 'zerobs_company' AND post_status = 'publish'";
3262            $results = $wpdb->get_results($sql, ARRAY_A);
3263        }
3264
3265    return $results;
3266    */
3267    global $zbs;
3268
3269    return $zbs->DAL->companies->getCompanies(
3270        array(
3271            'searchPhrase' => $searchQueryStr,
3272            'simplified'   => true,
3273            'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3274        )
3275    );
3276}
3277
3278function zeroBS_getCompanyIDWithEmail( $custEmail = '' ) {
3279
3280    if ( ! empty( $custEmail ) ) {
3281
3282        global $zbs;
3283        return $zbs->DAL->companies->getCompany(
3284            -1,
3285            array(
3286                'email'       => $custEmail,
3287                'onlyID'      => true,
3288                'ignoreowner' => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_COMPANY ),
3289            )
3290        );
3291
3292    }
3293
3294    return false;
3295}
3296
3297    #} Add or Update a Company - ideally use $zbs->DAL->companies->addUpdateCompany() rather than this wrapper, in proper code now :)
3298function zeroBS_addUpdateCompany(
3299    $coID = -1,
3300    $coFields = array(),
3301    $externalSource = '',
3302    $externalID = '',
3303    $companyDate = '',
3304    $fallBackLog = false,
3305    $extraMeta = false,
3306    $automatorPassthrough = false,
3307    $owner = -1,
3308    $metaBuilderPrefix = 'zbsc_'
3309) {
3310
3311    #} Basics - /--needs status
3312    #} 27/09/16 - WH - Removed need for zeroBS_addUpdateCustomer to have a "status" passed with customer (defaults to lead for now if not present)
3313    if ( isset( $coFields ) && count( $coFields ) > 0 ) { #} && isset( $coFields['zbsc_status'])
3314
3315        global $zbs;
3316
3317        #} New flag
3318        $newCompany   = false;
3319        $existingMeta = array();
3320
3321        if ( $coID > 0 ) {
3322
3323            #} Build "existing meta" to pass, (so we only update fields pushed here)
3324            $existingMeta = $zbs->DAL->companies->getCompany( $coID, array() );
3325
3326            #} need to check the dates here. If a date is passed which is BEFORE the current "created" date then overwrite the date with the new date. If a date is passed which is AFTER the current "created" date, then do not update the date..
3327            #} date changed - created is only in the wp_posts table in DB v1.0
3328            $originalDate = time();
3329            if ( isset( $existingMeta ) && is_array( $existingMeta ) && isset( $existingMeta['created'] ) && ! empty( $existingMeta['created'] ) ) {
3330                $originalDate = $existingMeta['created'];
3331            }
3332
3333            if ( ! empty( $companyDate ) && $companyDate != '' ) {
3334
3335                #} DATE PASSED TO THE FUNCTION
3336                $companyDateTimeStamp = strtotime( $companyDate );
3337                #} ORIGINAL POST CREATION DATE
3338                // no need, db2 = UTS $originalDateTimeStamp = strtotime($originalDate);
3339                $originalDateTimeStamp = $originalDate;
3340
3341                #} Compare, if $companyDateTimeStamp < then update with passed date
3342                if ( $companyDateTimeStamp < $originalDateTimeStamp ) {
3343
3344                    // straight in there :)
3345                    $zbs->DAL->companies->addUpdateCompany(
3346                        array(
3347                            'id'            => $coID,
3348                            'limitedFields' => array(
3349                                array(
3350                                    'key'  => 'zbsco_created',
3351                                    'val'  => $companyDateTimeStamp,
3352                                    'type' => '%d',
3353                                ),
3354                            ),
3355                        )
3356                    );
3357                }
3358            }
3359
3360            // WH changed 20/05/18
3361            // 20/05/18 - Previously this would reload the EXISTING database data
3362            // THEN 'override' any passed fields
3363            // THEN save that down
3364            // ... this was required when we used old meta objs. (pre db2)
3365            // ... so if we're now DAL2, we can do away with that and simply pass what's to be updated and mode do_not_update_blanks
3366            $existingMeta = array();
3367
3368        } else {
3369
3370            #} Set flag
3371            $newCompany = true;
3372
3373            if ( ! empty( $companyDate ) ) {
3374
3375                #} DATE PASSED TO THE FUNCTION
3376                $companyDateTimeStamp = strtotime( $companyDate );
3377                if ( $companyDateTimeStamp > 0 ) {
3378                    $existingMeta = array( 'created' => $companyDateTimeStamp );
3379                }
3380            }
3381        }
3382
3383        #} Build using centralised func below, passing any existing meta (updates not overwrites)
3384        $zbsCompanyMeta = zeroBS_buildCompanyMeta( $coFields, $existingMeta, $metaBuilderPrefix, '', true );
3385
3386        $we_have_tags = false; // set to false.. duh..
3387
3388        # TAG company (if exists) - clean etc here too
3389        if ( ! empty( $coFields['tags'] ) ) {
3390
3391            $tags = $coFields['tags'];
3392
3393            #} Santize tags
3394            if ( is_array( $tags ) && count( $tags ) > 0 ) {
3395                $company_tags = filter_var_array( $tags, FILTER_UNSAFE_RAW );
3396                // Formerly this used FILTER_SANITIZE_STRING, which is now deprecated as it was fairly broken. This is basically equivalent.
3397                // @todo Replace this with something more correct.
3398                foreach ( $company_tags as $k => $v ) {
3399                    $company_tags[ $k ] = strtr(
3400                        strip_tags( $v ),
3401                        array(
3402                            "\0" => '',
3403                            '"'  => '&#34;',
3404                            "'"  => '&#39;',
3405                            '<'  => '',
3406                        )
3407                    );
3408                }
3409                $we_have_tags = true;
3410            }
3411
3412            if ( $we_have_tags ) {
3413
3414                $zbsCompanyMeta['tags'] = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3415                foreach ( $company_tags as $tag_name ) {
3416
3417                    // Check for existing tag under this name.
3418                    $tag_id = $zbs->DAL->getTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3419                        -1,
3420                        array(
3421                            'objtype' => ZBS_TYPE_COMPANY,
3422                            'name'    => $tag_name,
3423                            'onlyID'  => true,
3424                        )
3425                    );
3426
3427                    // If tag doesn't exist, create one.
3428                    if ( empty( $tag_id ) ) {
3429                        $tag_id = $zbs->DAL->addUpdateTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
3430                            array(
3431                                'data' => array(
3432                                    'objtype' => ZBS_TYPE_COMPANY,
3433                                    'name'    => $tag_name,
3434                                ),
3435                            )
3436                        );
3437                    }
3438
3439                    // Add tag to list.
3440                    if ( ! empty( $tag_id ) ) {
3441                        $zbsCompanyMeta['tags'][] = $tag_id; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3442                    }
3443                }
3444            }
3445        }
3446
3447        #} Add external source/externalid
3448        #} No empties, no random externalSources :)
3449        $extSourceArr           = -1;
3450        $approvedExternalSource = ''; #} As this is passed to automator :)
3451        if ( ! empty( $externalSource ) && ! empty( $externalID ) && array_key_exists( $externalSource, $zbs->external_sources ) ) {
3452
3453            #} If here, is legit.
3454            $approvedExternalSource = $externalSource;
3455
3456            #} Add/Update record flag
3457            // 2.4+ Migrated away from this method to new update_post_meta($postID, 'zbs_customer_ext_'.$approvedExternalSource, $externalID);
3458            // 2.52+ Moved to new DAL method :)
3459
3460            $extSourceArr = array(
3461                'source' => $approvedExternalSource,
3462                'uid'    => $externalID,
3463            );
3464
3465            // add/update
3466            // DB2, this is just used below :)zeroBS_updateExternalSource($postID,$extSourceArr);
3467            $zbsCompanyMeta['externalSources'] = array( $extSourceArr );
3468
3469        } #} Otherwise will just be a random customer no ext source
3470
3471        #} Got owner?
3472        if ( $owner !== -1 ) {
3473            $zbsCompanyMeta['owner'] = $owner;
3474        }
3475
3476        #} Update record (All IA is now fired intrinsicly )
3477        return $zbs->DAL->companies->addUpdateCompany(
3478            array(
3479                'id'                   => $coID,
3480                'data'                 => $zbsCompanyMeta,
3481                'extraMeta'            => $extraMeta,
3482                'automatorPassthrough' => $automatorPassthrough,
3483                'fallBackLog'          => $fallBackLog,
3484            )
3485        );
3486
3487    } // if fields
3488
3489    return false;
3490}
3491
3492    // v3.0+ this uses the generic zeroBS_buildObjArr, and accepts full args as per contact meta DAL2:
3493function zeroBS_buildCompanyMeta( $arraySource = array(), $startingArray = array(), $fieldPrefix = 'zbsco_', $outputPrefix = '', $removeEmpties = false, $autoGenAutonumbers = false ) {
3494
3495    return zeroBS_buildObjArr( $arraySource, $startingArray, $fieldPrefix, $outputPrefix, $removeEmpties, ZBS_TYPE_COMPANY, $autoGenAutonumbers );
3496}
3497
3498    /* Centralised delete company func, including sub-element removal */
3499function zeroBS_deleteCompany( $id = -1, $saveOrphans = true ) {
3500
3501    if ( ! empty( $id ) ) {
3502
3503        global $zbs;
3504
3505        return $zbs->DAL->companies->deleteCompany(
3506            array(
3507                'id'          => $id,
3508                'saveOrphans' => $saveOrphans,
3509            )
3510        );
3511
3512    }
3513
3514    return false;
3515}
3516
3517    // adapted company name builder to use proper DAL3 func
3518function zeroBS_companyName( $companyID = '', $companyArr = array(), $incFirstLineAddr = true, $incID = true ) {
3519
3520    global $zbs;
3521    return $zbs->DAL->companies->getCompanyNameEtc(
3522        $companyID,
3523        $companyArr,
3524        array(
3525            'incFirstLineAddr' => $incFirstLineAddr,
3526            'incID'            => $incID,
3527        )
3528    );
3529}
3530
3531    // adapted company name builder to use proper DAL3 func
3532function zeroBS_companyAddr( $companyID = '', $companyArr = array(), $addrFormat = 'short', $delimiter = ', ' ) {
3533
3534    global $zbs;
3535    return $zbs->DAL->companies->getCompanyAddress(
3536        $companyID,
3537        $companyArr,
3538        array(
3539            'addrFormat' => $addrFormat,
3540            'delimiter'  => $delimiter,
3541        )
3542    );
3543}
3544
3545    // adapted company name builder to use proper DAL3 func
3546function zeroBS_companySecondAddr( $companyID = '', $companyArr = array(), $addrFormat = 'short', $delimiter = ', ' ) {
3547
3548    global $zbs;
3549    return $zbs->DAL->companies->getCompany2ndAddress(
3550        $companyID,
3551        $companyArr,
3552        array(
3553            'addrFormat' => $addrFormat,
3554            'delimiter'  => $delimiter,
3555        )
3556    );
3557}
3558
3559    // sets tags, in future just use direct DAL func plz
3560function zeroBSCRM_setCompanyTags( $coID = -1, $tags = array(), $tagIDs = array(), $mode = 'replace' ) {
3561
3562    if ( $coID > 0 ) {
3563
3564        $args = array(
3565
3566            'id'   => $coID,
3567
3568            // EITHER of the following:
3569                // 'tagIDs'        => -1,
3570                // 'tags'          => -1,
3571
3572            'mode' => $mode,
3573        );
3574
3575        // got tags?
3576        if ( is_array( $tags ) && count( $tags ) > 0 ) {
3577            $args['tags'] = $tags;
3578        } elseif ( is_array( $tagIDs ) && count( $tagIDs ) > 0 ) {
3579            $args['tagIDs'] = $tagIDs;
3580        } else {
3581            return false;
3582        }
3583
3584        global $zbs;
3585        return $zbs->DAL->companies->addUpdateCompanyTags( $args );
3586
3587    }
3588
3589    return false;
3590}
3591
3592    // gets tags, in future just use direct DAL func plz
3593function zeroBSCRM_getCompanyTagsByID( $coID = -1, $justIDs = false ) {
3594
3595    global $zbs;
3596    $tags = $zbs->DAL->companies->getCompanyTags( $coID );
3597
3598    // lazy here, but shouldn't use these old funcs anyhow!
3599    if ( $justIDs ) {
3600
3601        $ret = array();
3602        if ( is_array( $tags ) ) {
3603            foreach ( $tags as $t ) {
3604                $ret[] = $t['id'];
3605            }
3606        }
3607        return $ret;
3608
3609    }
3610
3611    return $tags;
3612}
3613
3614    // generally used for list view reformatting - cleans a company array into simple format
3615    // here it takes an array of contacts, and (currently) returns 1 company simplified
3616    // This may make more sense in the company DAL obj layer?
3617    // >> this has a contact variant too, in this file.
3618function zeroBSCRM_getSimplyFormattedCompany( $companies = array(), $requireOwner = false ) {
3619
3620    $return = false;
3621
3622    // DAL3 + has potential for multi-links, so here we just grab first if there
3623    if ( isset( $companies ) && is_array( $companies ) && count( $companies ) > 0 ) {
3624
3625        // first only for now...
3626        $company = $companies[0];
3627
3628        // w adapted so same func can be used (generic) js side
3629        // works with zeroBSCRMJS_listView_generic_customer
3630        // provides a simplified ver of customer obj (4 data transit efficiency/exposure)
3631        $email = '';
3632        if ( isset( $company['email'] ) && ! empty( $company['email'] ) ) {
3633            $email = $company['email'];
3634        }
3635        $return = array(
3636
3637            // company only has name, id, email currently
3638            'id'       => $company['id'],
3639            'fullname' => $company['name'],
3640            'email'    => $email,
3641
3642        );
3643        if ( $requireOwner ) {
3644            $return['owner'] = zeroBS_getOwner( $company['id'], true, 'zerobs_company' );
3645        }
3646    }
3647
3648    return $return;
3649}
3650/*
3651======================================================
3652    / Company helpers
3653====================================================== */
3654
3655/*
3656======================================================
3657    Quote helpers
3658====================================================== */
3659
3660    # Quote Status (from list view)
3661    // WH note - not sure why we're building HTML here, allowing for now.
3662    // if returnAsInt - will return -1 for not published, -2 for not accepted, or 14int timestamp for accepted
3663function zeroBS_getQuoteStatus( $item = false, $returnAsInt = false ) {
3664
3665    #} marked accepted?
3666    $accepted = false;
3667    if ( is_array( $item ) && isset( $item['accepted'] ) ) {
3668        $accepted = $item['accepted'];
3669    }
3670
3671    # HERE TODO:
3672    # if acceptedArr = output "accepted xyz"
3673    # else if !templated outut "not yet published"
3674    # else if templated output "not yet accepted"
3675
3676    if ( $accepted > 0 ) {
3677
3678        if ( $returnAsInt ) {
3679            return $accepted;
3680        }
3681
3682        $td = '<strong>' . __( 'Accepted', 'zero-bs-crm' ) . ' ' . date( zeroBSCRM_getDateFormat(), $accepted ) . '</strong>';
3683
3684    } else {
3685
3686        #} get extra deets
3687        $zbsTemplated = $item['template'];
3688        if ( ! empty( $zbsTemplated ) ) {
3689
3690            if ( $returnAsInt ) {
3691                return -2;
3692            }
3693
3694            #} is published
3695            $td = '<strong>' . __( 'Created, not yet accepted', 'zero-bs-crm' ) . '</strong>';
3696
3697        } else {
3698
3699            if ( $returnAsInt ) {
3700                return -1;
3701            }
3702
3703            #} not yet published
3704            $td = '<strong>' . __( 'Not yet published', 'zero-bs-crm' ) . '</strong>';
3705
3706        }
3707    }
3708
3709    return $td;
3710}
3711
3712    // Get next available sequential quote ID
3713function zeroBSCRM_getNextQuoteID() {
3714
3715    #} Retrieves option, and returns, is dumb for now.
3716    // DAL1+2: return (int)get_option('quoteindx',$defaultStartingQuoteID)+1;
3717
3718    // DAL3:
3719    $potential = (int) zeroBSCRM_getSetting( 'quoteindx', true );
3720    if ( $potential > 0 ) {
3721        return $potential + 1;
3722    } else {
3723        return zeroBSCRM_getQuoteOffset() + 1;
3724    }
3725}
3726
3727    // set the current max used quoteid
3728function zeroBSCRM_setMaxQuoteID( $newMax = 0 ) {
3729
3730    $existingMax = zeroBSCRM_getNextQuoteID();
3731
3732    if ( $newMax >= $existingMax ) {
3733
3734        // DAL3:
3735        global $zbs;
3736        return $zbs->settings->update( 'quoteindx', $newMax );
3737
3738    }
3739
3740    return false;
3741}
3742
3743    #} Minified get offset func
3744function zeroBSCRM_getQuoteOffset() {
3745
3746    global $zbs;
3747    $offset = (int) $zbs->settings->get( 'quoteoffset' );
3748
3749    if ( empty( $offset ) || $offset < 0 ) {
3750        $offset = 0;
3751    }
3752
3753    return $offset;
3754}
3755
3756#} Old get func, use proper form if writing fresh code
3757// used to return array('id','meta','customerid','quotebuilder')
3758// ... so any existing use may be broken (have mass replaced in core at this point)
3759// ... use direct ->getQuotes in future anyhow.
3760// (which is diff format! any use of zeroBS_getQuote is now borked. - couldn't find any though + did proper search.)
3761function zeroBS_getQuote( $qID = -1, $withQuoteBuilderData = false ) {
3762
3763    if ( $qID !== -1 ) {
3764
3765        global $zbs;
3766
3767        #} Super rough. Not sure where we use this, but shouldn't.
3768        return $zbs->DAL->quotes->getQuote(
3769            $qID,
3770            array(
3771                'withLineItems' => $withQuoteBuilderData,
3772                'ignoreowner'   => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
3773            )
3774        );
3775
3776    }
3777
3778    return false;
3779}
3780
3781    #} Marks a quote as "accepted" and saves as much related data as poss on accepter
3782    // again, use DAL to do this in future (zbs->DAL->quotes->addUpdateQuoteStatus directly)
3783function zeroBS_markQuoteAccepted( $qID = -1, $quoteSignedBy = '' ) {
3784
3785    if ( $qID !== -1 ) {
3786
3787        global $zbs;
3788
3789        return $zbs->DAL->quotes->addUpdateQuoteAccepted(
3790            array(
3791                'id'       => $qID,
3792                'accepted' => time(),
3793                'signedby' => $quoteSignedBy,
3794                'ip'       => zeroBSCRM_getRealIpAddr(),
3795            )
3796        );
3797
3798    }
3799
3800    return false;
3801}
3802
3803    #} UNMarks a quote as "accepted" and saves as much related data as poss on accepter
3804    // again, use DAL to do this in future (zbs->DAL->quotes->addUpdateQuoteStatus directly)
3805function zeroBS_markQuoteUnAccepted( $qID = -1 ) {
3806
3807    if ( $qID !== -1 ) {
3808
3809        global $zbs;
3810
3811        return $zbs->DAL->quotes->addUpdateQuoteAccepted(
3812            array(
3813                'id'       => $qID,
3814                'accepted' => '',
3815            )
3816        );
3817
3818    }
3819
3820    return false;
3821}
3822
3823// Please use direct dal calls in future work.
3824function zeroBS_getQuotes(
3825    $withFullDetails = false,
3826    $perPage = 10,
3827    $page = 0,
3828    $withCustomerDeets = false,
3829    $searchPhrase = '',
3830    $inArray = array(),
3831    $sortByField = '',
3832    $sortOrder = 'DESC',
3833    $quickFilters = array(),
3834    $hasTagIDs = array()
3835) {
3836
3837    // $withFullDetails = irrelevant with new DB2 (always returns)
3838    global $zbs;
3839
3840    $actualPage = $page;
3841    if ( $actualPage < 0 ) {
3842        $actualPage = 0;
3843    }
3844
3845    // make ARGS
3846    $args = array(
3847
3848        // Search/Filtering (leave as false to ignore)
3849        'searchPhrase' => $searchPhrase,
3850        'inArr'        => $inArray,
3851        'quickFilters' => $quickFilters,
3852        'isTagged'     => $hasTagIDs,
3853
3854        'withAssigned' => $withCustomerDeets,
3855
3856        'sortByField'  => $sortByField,
3857        'sortOrder'    => $sortOrder,
3858        'page'         => $actualPage,
3859        'perPage'      => $perPage,
3860
3861        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
3862
3863    );
3864
3865    return $zbs->DAL->quotes->getQuotes( $args );
3866}
3867
3868// Please use direct dal calls in future work.
3869function zeroBS_getQuotesCountIncParams(
3870    $withFullDetails = false,
3871    $perPage = 10,
3872    $page = 0,
3873    $withCustomerDeets = false,
3874    $searchPhrase = '',
3875    $inArray = array(),
3876    $sortByField = '',
3877    $sortOrder = 'DESC',
3878    $quickFilters = array(),
3879    $hasTagIDs = array()
3880) {
3881
3882    // $withFullDetails = irrelevant with new DB2 (always returns)
3883    global $zbs;
3884
3885    $actualPage = $page;
3886    if ( $actualPage < 0 ) {
3887        $actualPage = 0;
3888    }
3889
3890    // make ARGS
3891    $args = array(
3892
3893        // Search/Filtering (leave as false to ignore)
3894        'searchPhrase' => $searchPhrase,
3895        'inArr'        => $inArray,
3896        'quickFilters' => $quickFilters,
3897        'isTagged'     => $hasTagIDs,
3898
3899        // just count thx
3900        'count'        => true,
3901        'withAssigned' => false,
3902
3903        // 'sortByField'     => $sortByField,
3904        // 'sortOrder'   => $sortOrder,
3905        'page'         => -1,
3906        'perPage'      => -1,
3907
3908        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
3909
3910    );
3911
3912    return $zbs->DAL->quotes->getQuotes( $args );
3913}
3914
3915// Please use direct dal calls in future work.
3916function zeroBS_getQuotesForCustomer(
3917    $customerID = -1,
3918    $withFullDetails = false,
3919    $perPage = 10,
3920    $page = 0,
3921    $withCustomerDeets = false,
3922    $withQuoteBuilderData = true // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- legacy API parameter.
3923) {
3924
3925    global $zbs;
3926
3927    $actualPage = $page;
3928    if ( $actualPage < 0 ) {
3929        $actualPage = 0;
3930    }
3931
3932    // make ARGS
3933    $args = array(
3934
3935        // Search/Filtering (leave as false to ignore)
3936        'assignedContact' => $customerID,
3937
3938        // with contact?
3939        'withAssigned'    => $withCustomerDeets,
3940
3941        'sortByField'     => 'ID',
3942        'sortOrder'       => 'DESC',
3943        'page'            => $actualPage,
3944        'perPage'         => $perPage,
3945
3946        'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_QUOTE ),
3947
3948    );
3949
3950    return $zbs->DAL->quotes->getQuotes( $args );
3951}
3952
3953// Please use direct dal calls in future work.
3954// phpcs:ignore Squiz.Commenting.FunctionComment.WrongStyle
3955function zeroBS_getQuoteTemplate( $quoteTemplateID = -1 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,Squiz.Commenting.FunctionComment.Missing
3956
3957    if ( $quoteTemplateID > 0 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3958
3959        global $zbs;
3960
3961        return $zbs->DAL->quotetemplates->getQuotetemplate( $quoteTemplateID ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase,WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
3962
3963    }
3964
3965    return false;
3966}
3967
3968// Please use direct dal calls in future work.
3969function zeroBS_getQuoteTemplates( $withFullDetails = false, $perPage = 10, $page = 0, $searchPhrase = '' ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,Squiz.Commenting.FunctionComment.WrongStyle
3970
3971    global $zbs;
3972    return $zbs->DAL->quotetemplates->getQuotetemplates(
3973        array(
3974            'searchPhrase'  => $searchPhrase,
3975            'page'          => $page,
3976            'perPage'       => $perPage,
3977            'checkDefaults' => true,
3978        )
3979    );
3980
3981    /*
3982    was returning
3983
3984        core post +
3985            $retObj['meta']             = get_post_meta($ele->ID, 'zbs_quotemplate_meta', true);
3986            $retObj['zbsdefault']       = get_post_meta($ele->ID, 'zbsdefault', true);
3987
3988    */
3989}
3990
3991// retrieves a count for listview retrievedata, really
3992function zeroBS_getQuoteTemplatesCountIncParams( $withFullDetails = false, $perPage = 10, $page = 0, $searchPhrase = '' ) {
3993
3994    global $zbs;
3995
3996    return $zbs->DAL->quotetemplates->getQuotetemplates(
3997        array(
3998            'searchPhrase' => $searchPhrase,
3999            'count'        => 1,
4000            'page'         => -1,
4001            'perPage'      => -1,
4002        )
4003    );
4004}
4005
4006// moves a quote from being assigned to one cust, to another
4007// this is a fill-in to match old DAL2 func, however DAL3+ can accept customer/company,
4008// ... so use the proper $DAL->addUpdateObjectLinks for fresh code
4009function zeroBSCRM_changeQuoteCustomer( $id = -1, $contactID = 0 ) {
4010
4011    if ( ! empty( $id ) && $contactID > 0 ) {
4012
4013        global $zbs;
4014        return $zbs->DAL->quotes->addUpdateObjectLinks( $id, array( $contactID ), ZBS_TYPE_CONTACT );
4015
4016    }
4017
4018    return false;
4019}
4020
4021/*
4022======================================================
4023    / Quote helpers
4024====================================================== */
4025
4026/*
4027======================================================
4028    Invoice helpers
4029====================================================== */
4030
4031    // Get next available sequential invoice ID
4032function zeroBSCRM_getNextInvoiceID() {
4033
4034    // DAL3:
4035    $potential = (int) zeroBSCRM_getSetting( 'invoiceindx', true );
4036    if ( $potential > 0 ) {
4037        return $potential + 1;
4038    } else {
4039        return zeroBSCRM_getInvoiceOffset() + 1;
4040    }
4041}
4042
4043    // set the current max used invid
4044function zeroBSCRM_setMaxInvoiceID( $newMax = 0 ) {
4045
4046    $existingMax = zeroBSCRM_getNextInvoiceID();
4047
4048    if ( $newMax >= $existingMax ) {
4049
4050        // DAL3:
4051        global $zbs;
4052        return $zbs->settings->update( 'invoiceindx', $newMax );
4053
4054    }
4055
4056    return false;
4057}
4058
4059    // Minified get offset func
4060function zeroBSCRM_getInvoiceOffset() {
4061
4062    global $zbs;
4063    // this only exists on legacy sites
4064    $offset = (int) $zbs->settings->get( 'invoffset' );
4065
4066    if ( empty( $offset ) || $offset < 0 ) {
4067        $offset = 0;
4068    }
4069
4070    return $offset;
4071}
4072
4073    // outdated, outmoded, use proper ->DAL calls not this in fresh code
4074function zeroBS_getInvoices(
4075    $withFullDetails = false,
4076    $perPage = 10,
4077    $page = 0,
4078    $withCustomerDeets = false,
4079    $searchPhrase = '',
4080    $inArray = array(),
4081    $sortByField = '',
4082    $sortOrder = 'DESC',
4083    $quickFilters = array(),
4084    $hasTagIDs = array()
4085) {
4086
4087    // $withFullDetails = irrelevant with new DB2 (always returns)
4088    global $zbs;
4089
4090    $actualPage = $page;
4091    if ( $actualPage < 0 ) {
4092        $actualPage = 0;
4093    }
4094
4095    // make ARGS
4096    $args = array(
4097
4098        // Search/Filtering (leave as false to ignore)
4099        'searchPhrase' => $searchPhrase,
4100        'inArr'        => $inArray,
4101        'quickFilters' => $quickFilters,
4102        'isTagged'     => $hasTagIDs,
4103
4104        'withAssigned' => $withCustomerDeets,
4105
4106        'sortByField'  => $sortByField,
4107        'sortOrder'    => $sortOrder,
4108        'page'         => $actualPage,
4109        'perPage'      => $perPage,
4110
4111        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_INVOICE ),
4112
4113    );
4114
4115    return $zbs->DAL->invoices->getInvoices( $args );
4116}
4117
4118    // outdated, outmoded, use proper ->DAL calls not this in fresh code
4119function zeroBS_getInvoicesCountIncParams(
4120    $withFullDetails = false,
4121    $perPage = 10,
4122    $page = 0,
4123    $withCustomerDeets = false,
4124    $searchPhrase = '',
4125    $inArray = array(),
4126    $sortByField = '',
4127    $sortOrder = 'DESC',
4128    $quickFilters = array(),
4129    $hasTagIDs = array()
4130) {
4131
4132    // $withFullDetails = irrelevant with new DB2 (always returns)
4133    global $zbs;
4134
4135    $actualPage = $page;
4136    if ( $actualPage < 0 ) {
4137        $actualPage = 0;
4138    }
4139
4140    // make ARGS
4141    $args = array(
4142
4143        // Search/Filtering (leave as false to ignore)
4144        'searchPhrase' => $searchPhrase,
4145        'inArr'        => $inArray,
4146        'quickFilters' => $quickFilters,
4147        'isTagged'     => $hasTagIDs,
4148
4149        // just count thx
4150        'count'        => true,
4151        'withAssigned' => false,
4152
4153        // 'sortByField'     => $sortByField,
4154        // 'sortOrder'   => $sortOrder,
4155        'page'         => -1,
4156        'perPage'      => -1,
4157
4158        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_INVOICE ),
4159
4160    );
4161
4162    return $zbs->DAL->invoices->getInvoices( $args );
4163}
4164
4165    // DAL3 translated.
4166    // invs model now looks diff so adapt everywhere with getInvoice
4167function zeroBS_getInvoice( $invoiceID = -1 ) {
4168
4169    if ( $invoiceID > 0 ) {
4170
4171        global $zbs;
4172
4173        return $zbs->DAL->invoices->getInvoice( $invoiceID );
4174
4175    }
4176
4177    return false;
4178}
4179
4180    // just do direct call in future, plz
4181function zeroBS_getInvoicesForCustomer(
4182    $customerID = -1,
4183    $withFullDetails = false,
4184    $perPage = 10,
4185    $page = 0,
4186    $withCustomerDeets = false,
4187    $orderBy = 'ID',
4188    $order = 'DESC'
4189) {
4190
4191    // $withFullDetails = irrelevant with new DB2 (always returns)
4192    global $zbs;
4193
4194    $actualPage = $page;
4195    if ( $actualPage < 0 ) {
4196        $actualPage = 0;
4197    }
4198
4199    // make ARGS
4200    $args = array(
4201
4202        // Search/Filtering (leave as false to ignore)
4203        'assignedContact' => $customerID,
4204
4205        // with contact?
4206        'withAssigned'    => $withCustomerDeets,
4207
4208        'sortByField'     => $orderBy,
4209        'sortOrder'       => $order,
4210        'page'            => $actualPage,
4211        'perPage'         => $perPage,
4212
4213        'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_INVOICE ),
4214
4215    );
4216
4217    return $zbs->DAL->invoices->getInvoices( $args );
4218}
4219
4220    // moves a inv from being assigned to one cust, to another
4221    // this is a fill-in to match old DAL2 func, however DAL3+ can accept customer/company,
4222    // ... so use the proper $DAL->addUpdateObjectLinks for fresh code
4223function zeroBSCRM_changeInvoiceCustomer( $id = -1, $contactID = 0 ) {
4224
4225    if ( ! empty( $id ) && $contactID > 0 ) {
4226
4227        global $zbs;
4228        return $zbs->DAL->invoices->addUpdateObjectLinks( $id, array( $contactID ), ZBS_TYPE_CONTACT );
4229
4230    }
4231
4232    return false;
4233}
4234
4235    // grabs invoice customer
4236function zeroBSCRM_getInvoiceCustomer( $invID = -1 ) {
4237
4238    if ( ! empty( $invWPID ) ) {
4239
4240        global $zbs;
4241        return $zbs->DAL->invoices->getInvoiceContact( $invID );
4242
4243    }
4244
4245    return false;
4246}
4247
4248    // updates a stauts, if inv exists.
4249    // use $zbs->DAL->invoices->setInvoiceStatus($invoiceID,$statusStr); for new code
4250function zeroBS_updateInvoiceStatus( $invoiceID = -1, $statusStr = 'Draft' ) {
4251
4252    if ( in_array( $statusStr, zeroBSCRM_getInvoicesStatuses() ) ) {
4253
4254        $potentialInvoice = zeroBS_getInvoice( $invoiceID );
4255        if ( isset( $potentialInvoice ) && is_array( $potentialInvoice ) ) {
4256
4257            // dal3
4258            global $zbs;
4259            return $zbs->DAL->invoices->setInvoiceStatus( $invoiceID, $statusStr );
4260
4261        }
4262    }
4263
4264    return false;
4265}
4266
4267/*
4268======================================================
4269    Invoice 3.0 helpers
4270====================================================== */
4271
4272// this function probably becomes defunct when DAL ready. Is cos right now, if invoice_meta = '' then it's a new invoice, so should return defaults.
4273function zeroBSCRM_get_invoice_defaults( $obj_id = -1 ) {
4274
4275    // Settings
4276    $settings = zeroBSCRM_get_invoice_settings();
4277
4278    $now          = time();
4279    $default_date = jpcrm_uts_to_date_str( $now, 'Y-m-d' );
4280
4281    // If it has reference as autonumber, determine next number.
4282    if ( $settings['reftype'] === 'autonumber' ) {
4283        $next_number = $settings['refnextnum'];
4284        $prefix      = $settings['refprefix'];
4285        $suffix      = $settings['refsuffix'];
4286        $id_override = $prefix . $next_number . $suffix;
4287    } else {
4288        $id_override = $settings['defaultref'];
4289    }
4290
4291    $defaults = array(
4292        'status'                    => 'Draft',
4293        'status_label'              => __( 'Draft', 'zero-bs-crm' ),
4294        'new_invoice'               => true,
4295        'id'                        => $obj_id,
4296        'invoice_items'             => array(),
4297        'invoice_hours_or_quantity' => 'quantity',
4298        'invoice_contact'           => -1,
4299        'invoice_company'           => -1,
4300        'id_override'               => $id_override,
4301        'date_date'                 => $default_date, // need to sort this out on the way out (in TS to outputtable date for Date Picker)
4302        'date'                      => $now,
4303        'due'                       => 0,
4304        'hash'                      => zeroBSCRM_hashes_GetHashForObj( $obj_id, ZBS_TYPE_INVOICE ),
4305        'logo_url'                  => $settings['logo'],
4306        'bill'                      => '',
4307        'bill_name'                 => '',
4308        'settings'                  => $settings,
4309        'product_index'             => zeroBSCRM_getProductIndex(),
4310        'preview_link'              => '/invoices/hash',
4311        'pdf_installed'             => zeroBSCRM_isExtensionInstalled( 'pdfinv' ),
4312        'portal_installed'          => zeroBSCRM_isExtensionInstalled( 'portal' ),
4313        'totals'                    => array(
4314            'invoice_discount_total' => 0,
4315            'invoice_discount_type'  => '%',
4316            'invoice_postage_total'  => 0,
4317        ),
4318    );
4319    return $defaults;
4320}
4321
4322#} wrapper as right now it was loading the full settings into the page. Tidy up page to have the translations here.
4323#} WH - is it possible that some languages here will mess with the output? character encoding wise?
4324function zeroBSCRM_get_invoice_settings() {
4325
4326    global $zbs;
4327
4328    $all_settings = $zbs->settings->getAll();
4329
4330    $reference_label = zbs_ifAV( $all_settings, 'reflabel', '' );
4331    if ( empty( $reference_label ) ) {
4332        $reference_label = __( 'Reference', 'zero-bs-crm' );
4333    }
4334
4335    // Check if it is the first invoice
4336    $first_invoice = ! $zbs->DAL->invoices->getFullCount();
4337
4338    $invoice_settings = array(
4339        'b2bmode'           => zbs_ifAV( $all_settings, 'companylevelcustomers', false ),
4340        'invtax'            => zbs_ifAV( $all_settings, 'invtax', '' ),
4341        'invpandp'          => zbs_ifAV( $all_settings, 'invpandp', '' ),
4342        'invdis'            => zbs_ifAV( $all_settings, 'invdis', '' ),
4343        'logo'              => zbs_ifAV( $all_settings, 'invoicelogourl', '' ),
4344        'bizname'           => zbs_ifAV( $all_settings, 'businessname', '' ),
4345        'yourname'          => zbs_ifAV( $all_settings, 'businessyourname', '' ),
4346        'defaultref'        => zbs_ifAV( $all_settings, 'defaultref', '' ),
4347        'reftype'           => zbs_ifAV( $all_settings, 'reftype', '' ),
4348        'refprefix'         => zbs_ifAV( $all_settings, 'refprefix', '' ),
4349        'refnextnum'        => zbs_ifAV( $all_settings, 'refnextnum', '' ),
4350        'refsuffix'         => zbs_ifAV( $all_settings, 'refsuffix', '' ),
4351        'isfirstinv'        => $first_invoice,
4352        'invhash'           => zbs_ifAV( $all_settings, 'easyaccesslinks', '' ),
4353        'hideid'            => zbs_ifAV( $all_settings, 'invid', false ),
4354        'businessextra'     => nl2br( zeroBSCRM_textExpose( zbs_ifAV( $all_settings, 'businessextra', '' ) ) ),
4355        'businessyouremail' => zbs_ifAV( $all_settings, 'businessyouremail', '' ),
4356        'businessyoururl'   => zbs_ifAV( $all_settings, 'businessyoururl', '' ),
4357        'settings_slug'     => admin_url( 'admin.php?page=' . $zbs->slugs['settings'] ) . '&tab=invbuilder',
4358        'biz_settings_slug' => admin_url( 'admin.php?page=' . $zbs->slugs['settings'] ) . '&tab=bizinfo',
4359        'addnewcontacturl'  => jpcrm_esc_link( 'create', -1, 'zerobs_customer' ),
4360        'addnewcompanyurl'  => jpcrm_esc_link( 'create', -1, 'zerobs_company' ),
4361        'contacturlprefix'  => jpcrm_esc_link( 'edit', -1, 'zerobs_customer', true ),
4362        'companyurlprefix'  => jpcrm_esc_link( 'edit', -1, 'zerobs_company', true ),
4363        'lang'              => array(
4364            'invoice_number'      => zeroBSCRM_slashOut( __( 'ID', 'zero-bs-crm' ), true ),
4365            'invoice_date'        => zeroBSCRM_slashOut( __( 'Invoice date', 'zero-bs-crm' ), true ),
4366            'invoice_status'      => zeroBSCRM_slashOut( __( 'Status', 'zero-bs-crm' ), true ),
4367            'status_unpaid'       => zeroBSCRM_slashOut( __( 'Unpaid', 'zero-bs-crm' ), true ),
4368            'status_paid'         => zeroBSCRM_slashOut( __( 'Paid', 'zero-bs-crm' ), true ),
4369            'status_overdue'      => zeroBSCRM_slashOut( __( 'Overdue', 'zero-bs-crm' ), true ),
4370            'status_draft'        => zeroBSCRM_slashOut( __( 'Draft', 'zero-bs-crm' ), true ),
4371            'status_deleted'      => zeroBSCRM_slashOut( __( 'Deleted', 'zero-bs-crm' ), true ),
4372            'reference'           => zeroBSCRM_slashOut( $reference_label, true ),
4373            'autogenerated'       => zeroBSCRM_slashOut( __( 'Generated on save', 'zero-bs-crm' ), true ),
4374            'refsettings'         => zeroBSCRM_slashOut( __( 'Set up your reference type here', 'zero-bs-crm' ), true ),
4375            'nextref'             => zeroBSCRM_slashOut( __( 'Next reference expected', 'zero-bs-crm' ), true ),
4376            'due_date'            => zeroBSCRM_slashOut( __( 'Due date', 'zero-bs-crm' ), true ),
4377            'frequency'           => zeroBSCRM_slashOut( __( 'Frequency', 'zero-bs-crm' ), true ),
4378            'update'              => zeroBSCRM_slashOut( __( 'Update', 'zero-bs-crm' ), true ),
4379            'remove'              => zeroBSCRM_slashOut( __( 'Remove', 'zero-bs-crm' ), true ),
4380            'biz_info'            => zeroBSCRM_slashOut( __( 'Your business information', 'zero-bs-crm' ), true ),
4381            'add_edit'            => zeroBSCRM_slashOut( __( 'Edit ' . jpcrm_label_company() . ' Details', 'zero-bs-crm' ), true ),
4382            'add_logo'            => zeroBSCRM_slashOut( __( 'Add your logo', 'zero-bs-crm' ), true ),
4383            'send_to'             => zeroBSCRM_slashOut( __( 'Assign invoice to', 'zero-bs-crm' ), true ),
4384            'customise'           => zeroBSCRM_slashOut( __( 'Customise', 'zero-bs-crm' ), true ),
4385            'hours'               => zeroBSCRM_slashOut( __( 'Hours', 'zero-bs-crm' ), true ),
4386            'quantity'            => zeroBSCRM_slashOut( __( 'Quantity', 'zero-bs-crm' ), true ),
4387            'description'         => zeroBSCRM_slashOut( __( 'Description', 'zero-bs-crm' ), true ),
4388            'price'               => zeroBSCRM_slashOut( __( 'Price', 'zero-bs-crm' ), true ),
4389            'rate'                => zeroBSCRM_slashOut( __( 'Rate', 'zero-bs-crm' ), true ),
4390            'tax'                 => zeroBSCRM_slashOut( __( 'Tax', 'zero-bs-crm' ), true ),
4391            'add_row'             => zeroBSCRM_slashOut( __( 'Add row', 'zero-bs-crm' ), true ),
4392            'remove_row'          => zeroBSCRM_slashOut( __( 'Remove row', 'zero-bs-crm' ), true ),
4393            'amount'              => zeroBSCRM_slashOut( __( 'Amount', 'zero-bs-crm' ), true ),
4394            'discount'            => zeroBSCRM_slashOut( __( 'Discount', 'zero-bs-crm' ), true ),
4395            'shipping'            => zeroBSCRM_slashOut( __( 'Shipping', 'zero-bs-crm' ), true ),
4396            'tax_on_shipping'     => zeroBSCRM_slashOut( __( 'Tax on shipping', 'zero-bs-crm' ), true ),
4397            'due'                 => array(
4398                'none'      => zeroBSCRM_slashOut( __( 'No due date', 'zero-bs-crm' ), true ),
4399                'on'        => zeroBSCRM_slashOut( __( 'Due on receipt', 'zero-bs-crm' ), true ),
4400                'ten'       => zeroBSCRM_slashOut( __( 'Due in 10 days', 'zero-bs-crm' ), true ),
4401                'fifteen'   => zeroBSCRM_slashOut( __( 'Due in 15 days', 'zero-bs-crm' ), true ),
4402                'thirty'    => zeroBSCRM_slashOut( __( 'Due in 30 days', 'zero-bs-crm' ), true ),
4403                'fortyfive' => zeroBSCRM_slashOut( __( 'Due in 45 days', 'zero-bs-crm' ), true ),
4404                'sixty'     => zeroBSCRM_slashOut( __( 'Due in 60 days', 'zero-bs-crm' ), true ),
4405                'ninety'    => zeroBSCRM_slashOut( __( 'Due in 90 days', 'zero-bs-crm' ), true ),
4406            ),
4407            'preview'             => zeroBSCRM_slashOut( __( 'Preview', 'zero-bs-crm' ), true ),
4408            'dl_pdf'              => zeroBSCRM_slashOut( __( 'Download PDF', 'zero-bs-crm' ), true ),
4409            'bill_to'             => zeroBSCRM_slashOut( __( 'Enter email address or name', 'zero-bs-crm' ), true ),
4410            'edit_record'         => zeroBSCRM_slashOut( __( 'Edit record', 'zero-bs-crm' ), true ),
4411            'no_tax'              => zeroBSCRM_slashOut( __( 'None', 'zero-bs-crm' ), true ),
4412            'taxgrouplabel'       => zeroBSCRM_slashOut( __( 'Rates', 'zero-bs-crm' ), true ),
4413            'subtotal'            => zeroBSCRM_slashOut( __( 'Subtotal', 'zero-bs-crm' ), true ),
4414            'total'               => zeroBSCRM_slashOut( __( 'Total', 'zero-bs-crm' ), true ),
4415            'amount_due'          => zeroBSCRM_slashOut( __( 'Amount due', 'zero-bs-crm' ), true ),
4416            'partial_table'       => zeroBSCRM_slashOut( __( 'Payments', 'zero-bs-crm' ), true ),
4417            'incomplete'          => zeroBSCRM_slashOut( __( 'Incomplete', 'zero-bs-crm' ), true ),
4418            'rowtitleplaceholder' => zeroBSCRM_slashOut( __( 'Item title', 'zero-bs-crm' ), true ),
4419            'rowdescplaceholder'  => zeroBSCRM_slashOut( __( 'Item description', 'zero-bs-crm' ), true ),
4420            'noname'              => zeroBSCRM_slashOut( __( 'Unnamed', 'zero-bs-crm' ), true ), // no name on typeahead,
4421            'noemail'             => zeroBSCRM_slashOut( __( 'No email', 'zero-bs-crm' ), true ), // no email on typeahead,
4422            'contact'             => zeroBSCRM_slashOut( __( 'Contact', 'zero-bs-crm' ), true ), // contact view button (if assigned)
4423            'company'             => zeroBSCRM_slashOut( jpcrm_label_company(), true ), // contact view button (if assigned)
4424            'view'                => zeroBSCRM_slashOut( __( 'View', 'zero-bs-crm' ), true ),
4425            'addnewcontact'       => zeroBSCRM_slashOut( __( 'Add new contact', 'zero-bs-crm' ), true ),
4426            'newcompany'          => zeroBSCRM_slashOut( __( 'new ' . jpcrm_label_company(), 'zero-bs-crm' ), true ),
4427            'or'                  => zeroBSCRM_slashOut( __( 'or', 'zero-bs-crm' ), true ),
4428
4429            // send email modal
4430            'send_email'          => zeroBSCRM_slashOut( __( 'Email invoice', 'zero-bs-crm' ), true ),
4431            'sendthisemail'       => zeroBSCRM_slashOut( __( 'Send this invoice via email:', 'zero-bs-crm' ), true ),
4432            'toemail'             => zeroBSCRM_slashOut( __( 'To email:', 'zero-bs-crm' ), true ),
4433            'toemailplaceholder'  => zeroBSCRM_slashOut( __( 'e.g. mike@gmail.com', 'zero-bs-crm' ), true ),
4434            'attachassoc'         => zeroBSCRM_slashOut( __( 'Attach associated files', 'zero-bs-crm' ), true ),
4435            'attachpdf'           => zeroBSCRM_slashOut( __( 'Attach as PDF', 'zero-bs-crm' ), true ),
4436            'sendthemail'         => zeroBSCRM_slashOut( __( 'Send', 'zero-bs-crm' ), true ),
4437            'sendneedsassignment' => zeroBSCRM_slashOut( __( 'To send an email, this invoice needs to be assigned to a contact or company with a valid email address.', 'zero-bs-crm' ), true ),
4438            'sendingemail'        => zeroBSCRM_slashOut( __( 'Sending email...', 'zero-bs-crm' ), true ),
4439            'senttitle'           => zeroBSCRM_slashOut( __( 'Invoice sent', 'zero-bs-crm' ), true ),
4440            'sent'                => zeroBSCRM_slashOut( __( 'Your invoice has been sent by email', 'zero-bs-crm' ), true ),
4441            'senderrortitle'      => zeroBSCRM_slashOut( __( 'Error sending', 'zero-bs-crm' ), true ),
4442            'senderror'           => zeroBSCRM_slashOut( __( 'There was an error sending this invoice via email.', 'zero-bs-crm' ), true ),
4443
4444        ),
4445    );
4446    return $invoice_settings;
4447}
4448
4449    #} Invoicing Pro - needs product index
4450    // WH: Don't like the lazy naming
4451function zeroBSCRM_getProductIndex() {
4452    $product_index = array();
4453    apply_filters( 'zbs_product_index_array', $product_index );
4454    return $product_index;
4455}
4456
4457    // wrapper now for zeroBSCRM_hashes_GetObjFromHash
4458function zeroBSCRM_quotes_getFromHash( $hash = '' ) {
4459
4460    return zeroBSCRM_hashes_GetObjFromHash( $hash, -1, ZBS_TYPE_QUOTE );
4461}
4462
4463    // NOTE ON FOLLOWING:
4464    // ... this is MS's centralised func which centralises Inv data req. but it's still clunky.
4465    // ... here I've shimmed in DAL3 data -> this clunky centralised model.
4466    // ... could do with a complete rewrite to use proper DAL3 models tbh.
4467    // for now doing what can without taking months over it.
4468
4469    /**
4470     *  This file has the various functions used to control the invoice metaboxes
4471     *  Wrappers so can be used throughout and switched over when it comes to it
4472     *
4473     *  The current metabox output, has also been changed to draw with JS now given
4474     *  the added complexity of the tax table and discount per line
4475     *
4476     *  The calculation routine has also been reviewed to calculate the tax due
4477     *  AFTER the line items discount has been applied
4478     *
4479     *  Drawing a new line was already available in JS, but the initial load (new) and edit
4480     *  were messily drawn in PHP
4481     *
4482     *  Now it simply stores the invoice meta as one big data JSON structure outlined below
4483     *  data format described below
4484     *
4485     *  JSON object for invoice
4486     *
4487     *  invoiceObj = {
4488     * !                 invoice_id: 5,        // ID in the database - usually the invoice ID.
4489     * !                 invoice_custom_id: -1 // the ID if over-written by settings (WH REMOVED, use id_override)
4490     *
4491     * !                 status:  paid,        // not defined in settings - should be? (draft, unpaid, paid, overdue)
4492     *
4493     *                  preview_link:         // generated from hash
4494     *                  pdf_dl_link:          // downloaded on fly
4495     *
4496     * !                  hash:                 // the invoice hash (for front end accessible pages)
4497     *
4498     *                  pdf_template:         // the template to use
4499     *                  portal_template:      // allow the choice of portal template (0 = default)
4500     *                  email_template:       // allow the choice of email template (0 = default)
4501     *
4502     *                  invoice_frequency:    // invoicing pro only (0 = once only, 1 = week, 2 = month, 3 = year)
4503     *
4504     *                  invoice_number:       // this is over-ridable in settings
4505     *                  invoice_date:         // date of the invoice
4506     *                  invoice_due:          // when due -1 (no due date), 0 (on receipt), 10, 15, 30, 45, 60, 90 (days in advance of invoice date)
4507     *
4508     * !                  invoice_ref:          // internal reference number
4509     *
4510     *                  invoice_parent:       // invoice pro only (0 for parent), id of parent if child
4511     *
4512     *                  invoice_pay_via:      // 0 online, 1 bank transfer, 2 (both) - Zak addition to show online payment only for some
4513     *
4514     *
4515     * !                  invoice_logo_url:    // url of the invoice logo (default, or custom per invoice)
4516     *                  invoice_business_details:   // the details from settings to go on the invoice (also in settings obj)
4517     *
4518     *
4519     *                  invoice_send_to:       // email to send the invoice to
4520     * !                 invoice_contact:       // 0 or contact ID
4521     * !                 invoice_company:       // 0 or company ID
4522     *                  invoice_address_to:    // 0 contact or 1 company. So if assigned to Mike, can be address to a company (i.e. Mike Stott: Jetpack CRM, Mike Stott: Epic Plugins) etc
4523     *
4524     *
4525     *                  invoice_hours_or_quantity:    0 for hours, 1 for quantity
4526     *
4527     *                  invoice_items:   {
4528     *                                      item_id: (line_item ID)
4529     *                                      order:   (order in list, i.e. 0,1,2,3,4,5)
4530     *                                      title:
4531     *                                      description:
4532     *                                      unit:
4533     *                                      price:
4534     *                                      tax_ids: {
4535     *                                              id: 1, rate: 20,
4536     *                                              id: 2, rate: 19
4537     *                                      },
4538     *
4539     *                                    },{
4540     *
4541     *                                    }
4542     *
4543     *                  invoice_discount:   0,
4544     *                  invoice_shipping:   0,
4545     *                  invoice_shipping_tax: {
4546     *                                      tax_ids:{
4547     *                                             id: 1, rate: 20,
4548     *                                             id: 2, rate: 19
4549     *                                      }
4550     *                  },
4551     *
4552     *                  invoice_tip:        0, 1 (allow tip) - not in UI yet
4553     *                  invoice_partial:    0, 1 (allow partial payment) - in UI already (i.e. can assign multiple transactions) need to handle it via checkout (i.e. pay full amount, or pay instalments)
4554     *
4555     *                  transactions: {                             //the transactions against the invoice (array to allow for partial payments)
4556     *                                      transaction_id: 5,
4557     *                                      amount: 200,
4558     *                                      status: paid,
4559     *                                  },
4560     *                  invoice_attachments: {
4561     *                              id: 1,
4562     *                              url:  uploaded_url
4563     *                              send: 0,1
4564     *                  },
4565     *                  invoice_custom_fields: {
4566     *                          id: 1,
4567     *                          label: "vesting period",
4568     *                          type:  "date",
4569     *                          value: "20/10/2019"
4570     *                  },
4571     *                  //what the invoice settings are (biz info, tax etc)
4572     *                  settings: {
4573     *
4574     *                  }
4575     *
4576     *                }
4577     *
4578     *
4579     *   tax_linesObj = {
4580     *                      id:
4581     *                      name:    (e.g. VAT, GST)
4582     *                      rate:    (%)
4583     *                  }
4584     */
4585
4586    // this gets the data (from the current DAL and outputs it to the UI) - can get via jQuery
4587    // once happy it works and fills the current databse. This will need switching over come
4588    // the new DAL database structure but allows me to work with the UI now ahead of time.
4589
4590// wh Centralised, is ultimately output via ajax zeroBSCRM_AJAX_getInvoice function in Control.Invoices
4591// was called zeroBSCRM_getInvoiceData -> zeroBSCRM_invoicing_getInvoiceData
4592function zeroBSCRM_invoicing_getInvoiceData( $invID = -1 ) {
4593
4594    global $zbs;
4595
4596    $data = array();
4597
4598    // viable id?
4599    if ( $invID > 0 ) {
4600
4601        // build response
4602        $data['invoiceObj'] = array();
4603
4604        $invoice = $zbs->DAL->invoices->getInvoice(
4605            $invID,
4606            array(
4607
4608                // if these two passed, will search based on these
4609                'idOverride'        => false, // direcetly checks 1:1 match id_override
4610                'searchPhrase'      => false, // more generic, searches id_override (reference) (and not lineitems (toadd?))
4611
4612                'externalSource'    => false,
4613                'externalSourceUID' => false,
4614
4615                // with what?
4616                'withLineItems'     => true,
4617                'withCustomFields'  => true,
4618                'withTransactions'  => true, // gets trans associated with inv as well
4619                'withAssigned'      => true, // return ['contact'] & ['company'] objs if has link
4620                'withTags'          => true,
4621                'withOwner'         => true,
4622
4623                // returns scalar ID of line
4624                'onlyID'            => false,
4625
4626                'fields'            => false, // false = *, array = fieldnames
4627
4628            )
4629        );
4630
4631        if ( ! is_array( $invoice ) ) {
4632
4633            // get blank defaults - this is for a de-headed serpent? (don't think should ever exist)
4634            $data['invoiceObj'] = zeroBSCRM_get_invoice_defaults( $invID );
4635
4636        } else {
4637
4638            // process the loaded data
4639            // ... this made a lot of sense Pre DAL3, but much should be dealt with by DAL now
4640            // ... wh done best to leave only necessary here:
4641            $now = time();
4642
4643            $invoice_date_uts     = isset( $invoice['date_date'] ) ? $invoice['date'] : $now;
4644            $invoice['date_date'] = jpcrm_uts_to_date_str( $invoice_date_uts, 'Y-m-d' );
4645
4646            $invoice_due_date_uts     = isset( $invoice['due_date'] ) ? $invoice['due_date'] : $now;
4647            $invoice['due_date_date'] = jpcrm_uts_to_date_str( $invoice_due_date_uts, 'Y-m-d' );
4648
4649            // this should load it all anyhow :) (DAL3+)
4650            $data['invoiceObj'] = $invoice;
4651
4652            // Settings
4653            $settings = zeroBSCRM_get_invoice_settings();
4654
4655            // catch any empty shiz? seems to be what was happening.
4656            if ( ! isset( $data['invoiceObj']['invoice_logo_url'] ) ) {
4657                $data['invoiceObj']['invoice_logo_url'] = $settings['logo'];
4658            }
4659
4660            // these two are kind of just aliases? Use straight $invoiceObj[contact] etc.
4661            $data['invoiceObj']['invoice_contact'] = false;
4662            if ( isset( $invoice['contact'] ) && is_array( $invoice['contact'] ) && count( $invoice['contact'] ) > 0 ) {
4663                $data['invoiceObj']['invoice_contact'] = $invoice['contact'][0];
4664            }
4665            $data['invoiceObj']['invoice_company'] = false;
4666            if ( isset( $invoice['company'] ) && is_array( $invoice['company'] ) && count( $invoice['company'] ) > 0 ) {
4667                $data['invoiceObj']['invoice_company'] = $invoice['company'][0];
4668            }
4669            $data['invoiceObj']['new_invoice'] = false;
4670
4671            // these should probs use $invoice['contact'] etc. leaving for now for time.
4672            $billing_email = '';
4673            $billing_name  = '';
4674            if ( isset( $data['invoiceObj']['invoice_contact'] ) && is_array( $data['invoiceObj']['invoice_contact'] ) && isset( $data['invoiceObj']['invoice_contact']['id'] ) ) {
4675
4676                if ( isset( $data['invoiceObj']['invoice_contact']['email'] ) ) {
4677                    $billing_email = $data['invoiceObj']['invoice_contact']['email'];
4678                }
4679                if ( isset( $data['invoiceObj']['invoice_contact']['name'] ) ) {
4680                    $billing_name = $data['invoiceObj']['invoice_contact']['name'];
4681                }
4682                if ( empty( $billing_name ) ) {
4683                    $billing_name = $zbs->DAL->contacts->getContactNameWithFallback( $data['invoiceObj']['invoice_contact']['id'] );
4684                }
4685            } elseif ( isset( $data['invoiceObj']['invoice_company'] ) && is_array( $data['invoiceObj']['invoice_company'] ) && isset( $data['invoiceObj']['invoice_company']['id'] ) ) {
4686
4687                if ( isset( $data['invoiceObj']['invoice_company']['email'] ) ) {
4688                    $billing_email = $data['invoiceObj']['invoice_company']['email'];
4689                }
4690                if ( isset( $data['invoiceObj']['invoice_company']['name'] ) ) {
4691                    $billing_name = $data['invoiceObj']['invoice_company']['name'];
4692                }
4693            }
4694            $data['invoiceObj']['bill'] = $billing_email;
4695            // add billing name here
4696            $data['invoiceObj']['bill_name'] = $billing_name;
4697
4698            // handle if due is not set
4699            $data['invoiceObj']['due'] = -1; // default
4700            if ( isset( $invoice['due'] ) ) {
4701                $data['invoiceObj']['due'] = $invoice['due'];
4702            }
4703
4704            $data['invoiceObj']['invoice_items'] = $invoice['lineitems'];
4705
4706            // needs translating
4707            $hoursOrQuantity    = (int) $invoice['hours_or_quantity']; // 0 = hours, 1 = quantity
4708            $hoursOrQuantityStr = 'hours';
4709            if ( $hoursOrQuantity > 0 ) {
4710                $hoursOrQuantityStr = 'quantity';
4711            }
4712            $data['invoiceObj']['invoice_hours_or_quantity'] = $hoursOrQuantityStr;
4713
4714            // are PDF engine and Client Portal installed?
4715            $data['invoiceObj']['pdf_installed']    = zeroBSCRM_isExtensionInstalled( 'pdfinv' );
4716            $data['invoiceObj']['portal_installed'] = zeroBSCRM_isExtensionInstalled( 'portal' );
4717
4718            // if we have Client Portal installed, build URLS
4719            $preview_link = null;
4720
4721            if ( $data['invoiceObj']['portal_installed'] ) {
4722
4723                // Retrieve invoice endpoint & portal root URL
4724                $invoice_endpoint = $zbs->modules->portal->get_endpoint( ZBS_TYPE_INVOICE );
4725                $portalLink       = zeroBS_portal_link();
4726                if ( ! str_ends_with( $portalLink, '/' ) ) {  // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
4727                    $portalLink .= '/';
4728                }
4729
4730                // if invoice has a hash this will be a hash URL, otherwise it uses the invoice ID
4731                if ( $settings['invhash'] ) {
4732                    $preview_link = esc_url( $portalLink . $invoice_endpoint . '/zh-' . $invoice['hash'] );
4733                } else {
4734                    $preview_link = esc_url( $portalLink . $invoice_endpoint . '/' . $invID );
4735                }
4736            }
4737
4738            // if a hash is set (or admin) load the invoice for logged out users, if agreed
4739            // will still need to load the contactID and info in the Stripe call too even if logged out
4740            $data['invoiceObj']['preview_link'] = $preview_link;
4741
4742            // urgh. this was how we got the settings object.
4743            // refine this too.
4744
4745            // SETTINGS array (process from main ZBS settings object)
4746            // Add our custom setting here
4747            $settings['invoicing_disable_partial_payments'] = zeroBSCRM_getSetting( 'invoicing_disable_partial_payments' );
4748            $data['invoiceObj']['settings']                 = $settings;
4749
4750            // WH shim - converts DAL3 single record attrs into an array as MS expects?
4751            $data['invoiceObj']['totals']                           = array();
4752            $data['invoiceObj']['totals']['invoice_discount_total'] = $invoice['discount'];
4753            $data['invoiceObj']['totals']['invoice_discount_type']  = $invoice['discount_type'];
4754            $data['invoiceObj']['totals']['invoice_postage_total']  = $invoice['shipping'];
4755            $data['invoiceObj']['totals']['tax']                    = $invoice['tax'];
4756
4757            // shipping total needs to return 0 in some cases if not set it is empty. GRR @ mike DB1.0 data.
4758            if ( ! array_key_exists( 'invoice_postage_total', $data['invoiceObj']['totals'] ) ) {
4759                $data['invoiceObj']['totals']['invoice_postage_total'] = 0;
4760            }
4761
4762            // Invoice PARTIALS
4763            $data['invoiceObj']['partials'] = $invoice['transactions'];
4764
4765        }
4766
4767        // update to get from tax table UI. Below is dummy data for UI work (UI tax table TO DO)
4768        $data['tax_linesObj'] = zeroBSCRM_taxRates_getTaxTableArr();
4769
4770        return $data;
4771
4772    }
4773
4774    return false;
4775}
4776
4777/*
4778======================================================
4779        / Invoice 3.0 helpers
4780    ====================================================== */
4781
4782#} General function to check the amount due on an invoice, if <= mark as paid.
4783// Adapted to work V3.0+
4784// ... ultimately just uses zeroBSCRM_invoicing_invOutstandingBalance to check for balance + marks if paid off
4785function zeroBSCRM_check_amount_due_mark_paid( $invoice_id = -1 ) {
4786
4787    if ( $invoice_id > 0 ) {
4788
4789        global $zbs;
4790
4791        $outstandingBalance = $zbs->DAL->invoices->getOutstandingBalance( $invoice_id );
4792
4793        // got balance?
4794        if ( $outstandingBalance <= 0 && $outstandingBalance !== false ) {
4795
4796            // mark invoice as paid
4797            $status_str     = 'Paid';
4798            $invoice_update = $zbs->DAL->invoices->setInvoiceStatus( $invoice_id, $status_str ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
4799
4800            return $invoice_update;
4801
4802        }
4803    }
4804
4805    return false;
4806}
4807
4808/*
4809======================================================
4810        / Invoice helpers
4811    ====================================================== */
4812
4813/*
4814======================================================
4815        Transactions helpers
4816    ====================================================== */
4817
4818// Please use direct dal calls in future work.
4819function zeroBS_getTransaction( $tID = -1 ) {
4820
4821    if ( $tID !== -1 ) {
4822
4823        /*
4824        return array(
4825            'id'=>$tID,
4826            'meta'=>get_post_meta($tID, 'zbs_transaction_meta', true),
4827            'customerid'=>get_post_meta($tID, 'zbs_parent_cust', true),
4828            'companyid'=>get_post_meta($tID, 'zbs_parent_co', true)
4829            );
4830        */
4831
4832        global $zbs;
4833
4834        return $zbs->DAL->transactions->getTransaction( $tID );
4835
4836    } else {
4837        return false;
4838    }
4839}
4840
4841// Please use direct dal calls in future work.
4842function zeroBS_getTransactions(
4843    $withFullDetails = false,
4844    $perPage = 10,
4845    $page = 0,
4846    $withCustomerDeets = false,
4847    $searchPhrase = '',
4848    $hasTagIDs = array(),
4849    $inArray = array(),
4850    $sortByField = '',
4851    $sortOrder = 'DESC',
4852    $withTags = false,
4853    $quickFilters = array(),
4854    $external_source_uid = false
4855) {
4856
4857    // $withFullDetails = irrelevant with new DB2 (always returns)
4858    global $zbs;
4859
4860    $actualPage = $page;
4861    if ( $actualPage < 0 ) {
4862        $actualPage = 0;
4863    }
4864
4865    // make ARGS
4866    $args = array(
4867
4868        // Search/Filtering (leave as false to ignore)
4869        'searchPhrase'        => $searchPhrase,
4870        'inArr'               => $inArray,
4871        'isTagged'            => $hasTagIDs,
4872        'quickFilters'        => $quickFilters,
4873
4874        'withAssigned'        => $withCustomerDeets,
4875        'withTags'            => $withTags,
4876
4877        'sortByField'         => $sortByField,
4878        'sortOrder'           => $sortOrder,
4879        'page'                => $actualPage,
4880        'perPage'             => $perPage,
4881
4882        'ignoreowner'         => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
4883
4884        'external_source_uid' => $external_source_uid,
4885
4886    );
4887
4888    return $zbs->DAL->transactions->getTransactions( $args );
4889}
4890
4891// Please use direct dal calls in future work.
4892function zeroBS_getTransactionsCountIncParams(
4893    $withFullDetails = false,
4894    $perPage = 10,
4895    $page = 0,
4896    $withCustomerDeets = false,
4897    $searchPhrase = '',
4898    $hasTagIDs = array(),
4899    $inArray = array(),
4900    $sortByField = '',
4901    $sortOrder = 'DESC',
4902    $withTags = false,
4903    $quickFilters = array()
4904) {
4905
4906    // $withFullDetails = irrelevant with new DB2 (always returns)
4907    global $zbs;
4908
4909    $actualPage = $page;
4910    if ( $actualPage < 0 ) {
4911        $actualPage = 0;
4912    }
4913
4914    // make ARGS
4915    $args = array(
4916
4917        // Search/Filtering (leave as false to ignore)
4918        'searchPhrase' => $searchPhrase,
4919        'inArr'        => $inArray,
4920        'isTagged'     => $hasTagIDs,
4921        'quickFilters' => $quickFilters,
4922
4923        // just count thx
4924        'count'        => true,
4925        'withAssigned' => false,
4926
4927        // 'sortByField'     => $sortByField,
4928        // 'sortOrder'   => $sortOrder,
4929        'page'         => -1,
4930        'perPage'      => -1,
4931
4932        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
4933
4934    );
4935
4936    return $zbs->DAL->transactions->getTransactions( $args );
4937}
4938
4939    // Please use direct dal calls in future work.
4940function zeroBS_getTransactionsForCustomer(
4941    $customerID = -1,
4942    $withFullDetails = false,
4943    $perPage = 10,
4944    $page = 0,
4945    $withCustomerDeets = false
4946) {
4947    // $withFullDetails = irrelevant with new DB2 (always returns)
4948    global $zbs;
4949
4950    $actualPage = $page;
4951    if ( $actualPage < 0 ) {
4952        $actualPage = 0;
4953    }
4954
4955    // make ARGS
4956    $args = array(
4957
4958        // Search/Filtering (leave as false to ignore)
4959        'assignedContact' => $customerID,
4960
4961        // with contact?
4962        'withAssigned'    => $withCustomerDeets,
4963
4964        // 'sortByField'     => $orderBy,
4965        // 'sortOrder'   => $order,
4966        'page'            => $actualPage,
4967        'perPage'         => $perPage,
4968
4969        'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
4970
4971    );
4972
4973    return $zbs->DAL->transactions->getTransactions( $args );
4974}
4975
4976    // Please use direct dal calls in future work.
4977function zeroBS_getTransactionIDWithExternalSource( $transactionExternalSource = '', $transactionExternalID = '' ) {
4978
4979    // retrieve external sources from $zbs now
4980    global $zbs;
4981
4982    #} No empties, no random externalSources :)
4983    if ( ! empty( $transactionExternalSource ) && ! empty( $transactionExternalID ) && array_key_exists( $transactionExternalSource, $zbs->external_sources ) ) {
4984
4985        // return id if exists
4986        return $zbs->DAL->transactions->getTransaction(
4987            -1,
4988            array(
4989                'externalSource'    => $transactionExternalSource,
4990                'externalSourceUID' => $transactionExternalID,
4991                'onlyID'            => true,
4992                'ignoreowner'       => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TRANSACTION ),
4993            )
4994        );
4995
4996    }
4997
4998    return false;
4999}
5000
5001/**
5002 * Adds or updates a transaction.
5003 *
5004 * Deprecated in v3.0+ â€” avoid using these centralised funcs, use direct DAL calls instead.
5005 *
5006 * @param int    $tID                       Transaction ID.
5007 * @param array  $tFields                   Transaction fields array. Required keys: 'orderid', 'customer',
5008 *                                          'status', 'total'. Recommended keys: 'date', 'currency', 'item',
5009 *                                          'net', 'tax', 'fee', 'discount', 'tax_rate'.
5010 * @param string $transactionExternalSource External source identifier.
5011 * @param string $transactionExternalID     External ID.
5012 * @param string $transactionDate           Transaction date.
5013 * @param array  $transactionTags           Transaction tags.
5014 * @param bool   $fallBackLog               Whether to create a fallback log.
5015 * @param bool   $extraMeta                 Extra metadata.
5016 * @param bool   $automatorPassthrough      Automator passthrough flag.
5017 * @param string $arrBuilderPrefix          Array builder prefix.
5018 */
5019function zeroBS_addUpdateTransaction(
5020    $tID = -1,
5021    $tFields = array(),
5022    $transactionExternalSource = '',
5023    $transactionExternalID = '',
5024    $transactionDate = '',
5025    $transactionTags = array(),
5026    $fallBackLog = false,
5027    $extraMeta = false,
5028    $automatorPassthrough = false,
5029    $arrBuilderPrefix = 'zbst_'
5030) {
5031
5032    // zeroBSCRM_DEPRECATEDMSG('ZBS Function Deprecated in v3.0+. zeroBS_addUpdateTransaction should now be replaced with proper zbs->DAL->calls');
5033
5034    global $zbs;
5035
5036    #} Basics - /--needs unique ID, total MINIMUM
5037    if ( isset( $tFields ) && count( $tFields ) > 0 ) {
5038
5039        #} New flag
5040        $newTrans = false;
5041
5042        if ( $tID > 0 ) {
5043
5044            #} Build "existing meta" to pass, (so we only update fields pushed here)
5045            $existingMeta = $zbs->DAL->transactions->getTransaction( $tID, array() );
5046
5047            // Do date comparison + update that where relevant
5048            $originalDate = time();
5049            if ( isset( $existingMeta ) && is_array( $existingMeta ) && isset( $existingMeta['created'] ) && ! empty( $existingMeta['created'] ) ) {
5050                $originalDate = $existingMeta['created'];
5051            }
5052            if ( ! empty( $transactionDate ) && $transactionDate != '' ) {
5053
5054                #} DATE PASSED TO THE FUNCTION
5055                $transactionDateTimestamp = strtotime( $transactionDate );
5056                #} ORIGINAL POST CREATION DATE
5057                // no need, db2 = UTS $originalDateTimeStamp = strtotime($originalDate);
5058                $originalDateTimeStamp = $originalDate;
5059
5060                #} Compare, if $transactionDateTimestamp < then update with passed date
5061                if ( $transactionDateTimestamp < $originalDateTimeStamp ) {
5062
5063                    // straight in there :)
5064                    $zbs->DAL->transactions->addUpdateTransaction(
5065                        array(
5066                            'id'            => $tID,
5067                            'limitedFields' => array(
5068                                array(
5069                                    'key'  => 'zbst_created',
5070                                    'val'  => $transactionDateTimestamp,
5071                                    'type' => '%d',
5072                                ),
5073                            ),
5074                        )
5075                    );
5076                }
5077            }
5078        } else {
5079
5080            #} Set flag
5081            $newTrans = true;
5082
5083            #} DATE PASSED TO THE FUNCTION
5084            $transactionDateTimestamp = strtotime( $transactionDate );
5085            $tFields['created']       = $transactionDateTimestamp;
5086
5087        }
5088
5089        // this is a DAL2 legacy:
5090        $existingMeta = array();
5091
5092        #} Build using centralised func below, passing any existing meta (updates not overwrites)
5093        $transactionMeta = zeroBS_buildTransactionMeta( $tFields, $existingMeta, $arrBuilderPrefix );
5094
5095        // format it for DAL3 addition
5096        $args = array(
5097
5098            'id'                   => $tID,
5099            'data'                 => $transactionMeta,
5100            'extraMeta'            => $extraMeta,
5101            'automatorPassthrough' => $automatorPassthrough,
5102            'fallBackLog'          => $fallBackLog,
5103
5104        );
5105        // few DAL2 -> DAL3 translations:
5106
5107        // owner?
5108        if ( isset( $tFields['owner'] ) > 0 ) {
5109            $args['owner'] = $tFields['owner'];
5110        }
5111
5112        // contact/companies?
5113        if ( isset( $tFields['customer'] ) && $tFields['customer'] > 0 ) {
5114            $args['data']['contacts'] = array( (int) $tFields['customer'] );
5115        }
5116        if ( isset( $tFields['company'] ) && $tFields['company'] > 0 ) {
5117            $args['data']['companies'] = array( (int) $tFields['company'] );
5118        }
5119
5120        #} Add external source/externalid
5121        #} No empties, no random externalSources :)
5122        $approvedExternalSource = ''; #} As this is passed to automator :)
5123
5124        if ( ! empty( $transactionExternalSource ) && ! empty( $transactionExternalID ) && array_key_exists( $transactionExternalSource, $zbs->external_sources ) ) {
5125
5126            #} If here, is legit.
5127            $approvedExternalSource = $transactionExternalSource;
5128
5129            $extSourceArr = array(
5130                'source' => $approvedExternalSource,
5131                'uid'    => $transactionExternalID,
5132            );
5133
5134            $args['data']['externalSources'] = array( $extSourceArr );
5135
5136        } #} Otherwise will just be a random obj no ext source
5137
5138        #} For now a brutal pass through:
5139        // wh: not sure why this was here? if (isset($tFields['trans_time']) && !empty($tFields['trans_time'])) $zbsTransactionMeta['trans_time'] = (int)$tFields['trans_time'];
5140
5141        # TAG obj (if exists) - clean etc here too
5142        if ( isset( $transactionTags ) && is_array( $transactionTags ) ) {
5143
5144            $transactionTags = filter_var_array( $transactionTags, FILTER_UNSAFE_RAW );
5145            // Formerly this used FILTER_SANITIZE_STRING, which is now deprecated as it was fairly broken. This is basically equivalent.
5146            // @todo Replace this with something more correct.
5147            foreach ( $transactionTags as $k => $v ) {
5148                $transactionTags[ $k ] = strtr(
5149                    strip_tags( $v ),
5150                    array(
5151                        "\0" => '',
5152                        '"'  => '&#34;',
5153                        "'"  => '&#39;',
5154                        '<'  => '',
5155                    )
5156                );
5157            }
5158
5159            $args['data']['tags'] = array();
5160            foreach ( $transactionTags as $tag_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5161
5162                // Check for existing tag under this name.
5163                $tag_id = $zbs->DAL->getTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5164                    -1,
5165                    array(
5166                        'objtype' => ZBS_TYPE_TRANSACTION,
5167                        'name'    => $tag_name,
5168                        'onlyID'  => true,
5169                    )
5170                );
5171
5172                // If tag doesn't exist, create one.
5173                if ( empty( $tag_id ) ) {
5174                    $tag_id = $zbs->DAL->addUpdateTag( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5175                        array(
5176                            'data' => array(
5177                                'objtype' => ZBS_TYPE_TRANSACTION,
5178                                'name'    => $tag_name,
5179                            ),
5180                        )
5181                    );
5182                }
5183
5184                // Add tag to list.
5185                if ( ! empty( $tag_id ) ) {
5186                    $args['data']['tags'][] = $tag_id;
5187                }
5188            }
5189        }
5190
5191        // Update record (All IA is now fired intrinsicaly)
5192        return $zbs->DAL->transactions->addUpdateTransaction( $args ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5193    }
5194
5195    return false;
5196}
5197
5198    // Please use direct dal calls in future work, not this.
5199    #} Quick wrapper to future-proof.
5200    #} Should later replace all get_post_meta's with this
5201function zeroBS_getTransactionMeta( $tID = -1 ) {
5202
5203    global $zbs;
5204
5205    // in DAL3 it's just a normal get
5206    if ( ! empty( $tID ) ) {
5207        return $zbs->DAL->transactions->getTransaction( $tID );
5208    }
5209
5210    return false;
5211}
5212
5213    // filters array for fields currently used in fields.php
5214    // v3.0+ this uses the generic zeroBS_buildObjArr, and accepts full args as per contact meta DAL2:
5215function zeroBS_buildTransactionMeta( $arraySource = array(), $startingArray = array(), $fieldPrefix = 'zbst_', $outputPrefix = '', $removeEmpties = false, $autoGenAutonumbers = false ) {
5216
5217    return zeroBS_buildObjArr( $arraySource, $startingArray, $fieldPrefix, $outputPrefix, $removeEmpties, ZBS_TYPE_TRANSACTION, $autoGenAutonumbers );
5218}
5219
5220    // moves a tran from being assigned to one cust, to another
5221    // this is a fill-in to match old DAL2 func, however DAL3+ can accept customer/company,
5222    // ... so use the proper $DAL->addUpdateObjectLinks for fresh code
5223function zeroBSCRM_changeTransactionCustomer( $id = -1, $contactID = 0 ) {
5224
5225    if ( ! empty( $id ) && $contactID > 0 ) {
5226
5227        global $zbs;
5228        return $zbs->DAL->transactions->addUpdateObjectLinks( $id, array( $contactID ), ZBS_TYPE_CONTACT );
5229
5230    }
5231
5232    return false;
5233}
5234
5235    // moves a tran from being assigned to one company, to another
5236    // this is a fill-in to match old DAL2 func, however DAL3+ can accept customer/company,
5237    // ... so use the proper $DAL->addUpdateObjectLinks for fresh code
5238function zeroBSCRM_changeTransactionCompany( $id = -1, $companyID = 0 ) {
5239
5240    if ( ! empty( $id ) && $companyID > 0 ) {
5241
5242        global $zbs;
5243        return $zbs->DAL->transactions->addUpdateObjectLinks( $id, array( $companyID ), ZBS_TYPE_COMPANY );
5244
5245    }
5246
5247    return false;
5248}
5249
5250/*
5251======================================================
5252    / Transactions helpers
5253====================================================== */
5254
5255/*
5256======================================================
5257    Event helpers
5258====================================================== */
5259
5260    // old way of doing - also should really be "get list of events/tasks for a contact"
5261function zeroBSCRM_getTaskList( $cID = -1 ) {
5262
5263    $ret = array();
5264
5265    if ( $cID > 0 ) {
5266
5267        global $zbs;
5268
5269        return $zbs->DAL->events->getEvents(
5270            array(
5271                'assignedContact' => $cID,
5272                'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
5273            )
5274        );
5275
5276        /*
5277        these translated for DAL3
5278
5279        $i = 0;
5280        foreach ( $tasks as $task){
5281            $ret[$i]['title'] = $task->post_title;
5282            $ret[$i]['ID'] = $task->ID;
5283            $ret[$i]['meta'] = get_post_meta($task->ID,'zbs_event_meta',true);
5284            $ret[$i]['actions'] = get_post_meta($task->ID,'zbs_event_actions',true);
5285
5286            // titles moved into meta with MS new task ui, wh bringing them out here:
5287            if ( empty( $task->post_title) && is_array( $ret[$i]['meta']) && isset( $ret[$i]['meta']['title']) && !empty( $ret[$i]['meta']['title'])){
5288                $ret[$i]['title'] = $ret[$i]['meta']['title'];
5289            }
5290
5291            $i++;
5292        }*/
5293
5294        return $ret;
5295    }
5296
5297    return array();
5298}
5299
5300    // adapted to DAL3
5301    // NOTE: $withFullDetails is redundant here
5302    // NOTE: as with all dal3 translations, objs no longer have ['meta'] etc.
5303    // USE direct DAL calls in code, not this, for future proofing
5304function zeroBS_getEvents(
5305    $withFullDetails = false,
5306    $perPage = 10,
5307    $page = 0,
5308    $ownedByID = false,
5309    $search_term = '',
5310    $sortByField = '',
5311    $sortOrder = 'DESC',
5312    $hasTagIDs = array()
5313) {
5314
5315    global $zbs;
5316
5317    $actualPage = $page;
5318    if ( $actualPage < 0 ) {
5319        $actualPage = 0;
5320    }
5321
5322    // make ARGS
5323    $args = array(
5324
5325        'withAssigned' => true,
5326        'withOwner'    => true,
5327
5328        'isTagged'     => $hasTagIDs,
5329
5330        'sortByField'  => $sortByField,
5331        'sortOrder'    => $sortOrder,
5332
5333        'page'         => $actualPage,
5334        'perPage'      => $perPage,
5335
5336        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
5337
5338    );
5339    if ( $ownedByID > 0 ) {
5340        $args['ownedBy'] = $ownedByID;
5341    }
5342    if ( ! empty( $search_term ) ) {
5343        $args['searchPhrase'] = $search_term;
5344    }
5345
5346    return $zbs->DAL->events->getEvents( $args );
5347}
5348
5349    // adapted to DAL3
5350    // NOTE: $withFullDetails is redundant here
5351    // NOTE: as with all dal3 translations, objs no longer have ['meta'] etc.
5352    // USE direct DAL calls in code, not this, for future proofing
5353function zeroBS_getEventsByCustomerID( $cID = -1, $withFullDetails = false, $perPage = 10, $page = 0 ) {
5354
5355    global $zbs;
5356
5357    $actualPage = $page;
5358    if ( $actualPage < 0 ) {
5359        $actualPage = 0;
5360    }
5361
5362    // make ARGS
5363    $args = array(
5364
5365        'assignedContact' => $cID,
5366
5367        'page'            => $actualPage,
5368        'perPage'         => $perPage,
5369
5370        'ignoreowner'     => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_TASK ),
5371
5372    );
5373
5374    return $zbs->DAL->events->getEvents( $args );
5375}
5376
5377    // moves an event from being assigned to one cust, to another
5378    // this is a fill-in to match old DAL2 func, however DAL3+ can accept customer/company,
5379    // ... so use the proper $DAL->addUpdateObjectLinks for fresh code
5380function zeroBSCRM_changeEventCustomer( $id = -1, $contactID = 0 ) {
5381
5382    if ( ! empty( $id ) && $contactID > 0 ) {
5383
5384        global $zbs;
5385        return $zbs->DAL->events->addUpdateObjectLinks( $id, array( $contactID ), ZBS_TYPE_CONTACT );
5386
5387    }
5388
5389    return false;
5390}
5391
5392    // Add an event
5393function zeroBS_addUpdateEvent( $eventID = -1, $eventFields = array(), $reminders = array() ) {
5394
5395    // if using 'from' and 'to', probably using v1 dal, so translate dates:
5396    if ( isset( $eventFields['from'] ) ) {
5397        $eventFields['from'] = strtotime( $eventFields['from'] );
5398    }
5399    if ( isset( $eventFields['to'] ) ) {
5400        $eventFields['to'] = strtotime( $eventFields['to'] );
5401    }
5402
5403    #} Build using centralised func below, passing any existing meta (updates not overwrites)
5404    $removeEmpties = false;
5405    $zbsEventMeta  = zeroBS_buildObjArr( $eventFields, array(), '', '', $removeEmpties, ZBS_TYPE_TASK );
5406
5407    // Some sanitation MS has added. Really, DAL isn't place to sanitize,
5408    // ... by time it gets here it should be sanitized (e.g. a level up)
5409    // ... leaving as I translate this to DAL3
5410    // $zbsEventMeta = filter_var_array($eventFields,FILTER_SANITIZE_STRING);
5411
5412    // format it for DAL3 addition
5413    $args = array(
5414
5415        'data' => $zbsEventMeta,
5416
5417    );
5418
5419    global $zbs;
5420
5421    // few DAL2 -> DAL3 translations:
5422
5423    // owner?
5424    if ( isset( $eventFields['owner'] ) > 0 ) {
5425        $args['owner'] = $eventFields['owner'];
5426    }
5427
5428    // contact/companies?
5429    if ( isset( $eventFields['customer'] ) && $eventFields['customer'] > 0 ) {
5430        $args['data']['contacts'] = array( $eventFields['customer'] );
5431    }
5432    if ( isset( $eventFields['company'] ) && $eventFields['company'] > 0 ) {
5433        $args['data']['companies'] = array( $eventFields['company'] );
5434    }
5435
5436    $args['data']['reminders'] = array();
5437
5438    // reminders into new DAL2 eventreminder format:
5439    if ( is_array( $reminders ) && count( $reminders ) > 0 ) {
5440        foreach ( $reminders as $reminder ) {
5441
5442            // this just adds with correct fields
5443            $args['data']['reminders'][] = array(
5444
5445                'event'     => (int) $eventID,
5446                'remind_at' => (int) $reminder['remind_at'], // just assume is int - garbage in, garbage out ($reminder['remind_at']) ? $reminder['remind_at'] : false; // if int, this
5447                'sent'      => ( isset( $reminder['sent'] ) && $reminder['sent'] > 0 ) ? $reminder['sent'] : -1,
5448
5449            );
5450
5451        }
5452    }
5453
5454    // updating....
5455    if ( $eventID > 0 ) {
5456        $args['id'] = (int) $eventID;
5457    }
5458
5459    // simples
5460    return $zbs->DAL->events->addUpdateEvent( $args );
5461}
5462
5463/*
5464======================================================
5465        / Event helpers
5466    ====================================================== */
5467
5468/*
5469======================================================
5470        Form helpers
5471    ====================================================== */
5472
5473    // Please use direct dal calls in future work.
5474    // simple wrapper for Form
5475function zeroBS_getForm( $formID = -1 ) {
5476
5477    if ( $formID > 0 ) {
5478
5479        /*
5480        return array(
5481            'id'=>$fID,
5482
5483            // mikes init fields
5484            'meta'=>get_post_meta($fID,'zbs_form_field_meta',true),
5485            'style'=>get_post_meta($fID, 'zbs_form_style', true),
5486            'views'=>get_post_meta($fID, 'zbs_form_views', true),
5487            'conversions'=>get_post_meta($fID, 'zbs_form_conversions', true)
5488
5489            );
5490        */
5491
5492        global $zbs;
5493
5494        return $zbs->DAL->forms->getForm( $formID );
5495
5496    }
5497
5498    return false;
5499}
5500
5501    // Please use direct dal calls in future work.
5502function zeroBS_getForms(
5503    $withFullDetails = false,
5504    $perPage = 10,
5505    $page = 0,
5506    $searchPhrase = '',
5507    $inArray = array(),
5508    $sortByField = '',
5509    $sortOrder = 'DESC',
5510    $quickFilters = array(),
5511    $hasTagIDs = array()
5512) {
5513
5514    // quickFilters not used for forms :) *yet
5515
5516    // $withFullDetails = irrelevant with new DB2 (always returns)
5517    global $zbs;
5518
5519    $actualPage = $page;
5520    if ( $actualPage < 0 ) {
5521        $actualPage = 0;
5522    }
5523
5524    // make ARGS
5525    $args = array(
5526
5527        // Search/Filtering (leave as false to ignore)
5528        'searchPhrase' => $searchPhrase,
5529        'inArr'        => $inArray,
5530        'isTagged'     => $hasTagIDs,
5531
5532        'sortByField'  => $sortByField,
5533        'sortOrder'    => $sortOrder,
5534        'page'         => $actualPage,
5535        'perPage'      => $perPage,
5536
5537        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_FORM ),
5538
5539    );
5540
5541    return $zbs->DAL->forms->getForms( $args );
5542}
5543
5544    // Please use direct dal calls in future work.
5545function zeroBS_getFormsCountIncParams(
5546    $withFullDetails = false,
5547    $perPage = 10,
5548    $page = 0,
5549    $searchPhrase = '',
5550    $inArray = array(),
5551    $sortByField = '',
5552    $sortOrder = 'DESC',
5553    $quickFilters = array(),
5554    $hasTagIDs = array()
5555) {
5556
5557    // quickFilters not used for forms :) *yet
5558
5559    // $withFullDetails = irrelevant with new DB2 (always returns)
5560    global $zbs;
5561
5562    $actualPage = $page;
5563    if ( $actualPage < 0 ) {
5564        $actualPage = 0;
5565    }
5566
5567    // make ARGS
5568    $args = array(
5569
5570        // Search/Filtering (leave as false to ignore)
5571        'searchPhrase' => $searchPhrase,
5572        'inArr'        => $inArray,
5573        'isTagged'     => $hasTagIDs,
5574
5575        // just count thx
5576        'count'        => true,
5577
5578        'page'         => -1,
5579        'perPage'      => -1,
5580
5581        'ignoreowner'  => zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_FORM ),
5582
5583    );
5584
5585    return $zbs->DAL->forms->getForms( $args );
5586}
5587
5588/*
5589======================================================
5590    / Form helpers
5591====================================================== */
5592
5593/*
5594======================================================
5595    Settings helpers
5596====================================================== */
5597
5598    #} Minified get setting func
5599function zeroBSCRM_getSetting( $key, $freshFromDB = false ) {
5600
5601    global $zbs;
5602    $zbs->checkSettingsSetup();
5603    return $zbs->settings->get( $key, $freshFromDB );
5604}
5605
5606/*
5607======================================================
5608    / Settings helpers
5609====================================================== */
5610
5611/*
5612======================================================
5613    Alias / AKA helpers
5614====================================================== */
5615    // Aliases - direct SQL here, could do with moving to DAL3
5616
5617    #} (Generic) See if already in use/exists
5618function zeroBS_canUseAlias( $objType = ZBS_TYPE_CONTACT, $alias = '' ) {
5619
5620    if ( ! empty( $alias ) ) {
5621
5622        // verify email?
5623
5624        if ( ! zeroBSCRM_validateEmail( $alias ) ) {
5625            return false;
5626        }
5627
5628        // is in use?
5629
5630        // is customer with this email?
5631        $existing = zeroBS_getCustomerIDWithEmail( $alias );
5632
5633        if ( ! empty( $existing ) ) {
5634            return false;
5635        }
5636
5637        global $wpdb, $ZBSCRM_t;
5638
5639        $query = $wpdb->prepare( 'SELECT ID FROM ' . $ZBSCRM_t['aka'] . ' WHERE aka_type = %d AND aka_alias = %s', $objType, $alias );
5640
5641        $aliasID = $wpdb->get_var( $query );
5642
5643        // has alias in there already?
5644        if ( ! empty( $aliasID ) ) {
5645            return false;
5646        }
5647
5648        // usable
5649        return true;
5650
5651    }
5652
5653    return false;
5654}
5655
5656    #} Get specific alias if exists
5657function zeroBS_getObjAlias( $objType = ZBS_TYPE_CONTACT, $objID = -1, $alias = '' ) {
5658
5659    if ( ! empty( $objID ) && ! empty( $alias ) ) {
5660
5661        global $wpdb, $ZBSCRM_t;
5662
5663        $query = $wpdb->prepare( 'SELECT ID,aka_alias,aka_created,aka_lastupdated FROM ' . $ZBSCRM_t['aka'] . ' WHERE aka_type = %d AND aka_id = %d AND aka_alias = %s', $objType, $objID, $alias );
5664
5665        $alias = $wpdb->get_row( $query, ARRAY_A );
5666
5667        // check it + return
5668        if ( is_array( $alias ) ) {
5669            return $alias;
5670        }
5671    }
5672
5673    return false;
5674}
5675
5676    #} Get specific alias if exists
5677function zeroBS_getAliasByID( $objType = ZBS_TYPE_CONTACT, $objID = -1, $aliasID = -1 ) {
5678
5679    if ( ! empty( $objID ) && ! empty( $aliasID ) ) {
5680
5681        global $wpdb, $ZBSCRM_t;
5682
5683        $query = $wpdb->prepare( 'SELECT ID,aka_alias,aka_created,aka_lastupdated FROM ' . $ZBSCRM_t['aka'] . ' WHERE aka_type = %d AND aka_id = %d AND ID = %d', $objType, $objID, $aliasID );
5684
5685        $alias = $wpdb->get_row( $query, ARRAY_A );
5686
5687        // check it + return
5688        if ( is_array( $alias ) ) {
5689            return $alias;
5690        }
5691    }
5692
5693    return false;
5694}
5695
5696    #} Get All Aliases against an obj.
5697function zeroBS_getObjAliases( $objType = ZBS_TYPE_CONTACT, $objID = -1 ) {
5698
5699    if ( ! empty( $objID ) ) {
5700
5701        global $wpdb, $ZBSCRM_t;
5702
5703        $query = $wpdb->prepare( 'SELECT ID,aka_alias,aka_created,aka_lastupdated FROM ' . $ZBSCRM_t['aka'] . ' WHERE aka_type = %d AND aka_id = %d', $objType, $objID );
5704
5705        $aliases = $wpdb->get_results( $query, ARRAY_A );
5706
5707        // check it + return
5708        if ( is_array( $aliases ) && count( $aliases ) > 0 ) {
5709            return $aliases;
5710        }
5711    }
5712
5713    return false;
5714}
5715
5716#} add Aliases to an obj.
5717function zeroBS_addObjAlias( $objType = ZBS_TYPE_CONTACT, $objID = -1, $alias = '' ) {
5718
5719    if ( ! empty( $objID ) && ! empty( $alias ) ) {
5720
5721        // check not already there
5722        $existing = zeroBS_getObjAlias( $objType, $objID, $alias );
5723        if ( ! is_array( $existing ) ) {
5724
5725            // insert
5726
5727            global $wpdb, $ZBSCRM_t;
5728
5729            if ( $wpdb->insert(
5730                $ZBSCRM_t['aka'],
5731                array(
5732                    'aka_type'        => $objType,
5733                    'aka_id'          => $objID,
5734                    'aka_alias'       => $alias,
5735                    'aka_created'     => time(),
5736                    'aka_lastupdated' => time(),
5737                ),
5738                array(
5739                    '%d',
5740                    '%d',
5741                    '%s',
5742                    '%d',
5743                    '%d',
5744                )
5745            ) ) {
5746
5747                // success
5748                return $wpdb->insert_id;
5749
5750            } else {
5751                return false;
5752            }
5753        } else {
5754
5755            // return true, already exists
5756            return true;
5757
5758        }
5759    }
5760
5761    return false;
5762}
5763
5764#} remove Alias from an obj.
5765function zeroBS_removeObjAlias( $objType = ZBS_TYPE_CONTACT, $objID = -1, $alias = '' ) {
5766
5767    if ( ! empty( $objID ) && ! empty( $alias ) ) {
5768
5769        // check there/find ID
5770        $existing = zeroBS_getObjAlias( $objType, $objID, $alias );
5771
5772        if ( is_array( $existing ) ) {
5773
5774            // just brutal :)
5775
5776            global $wpdb, $ZBSCRM_t;
5777
5778            return $wpdb->delete( $ZBSCRM_t['aka'], array( 'ID' => $existing['ID'] ), array( '%d' ) );
5779
5780        }
5781    }
5782
5783    return false;
5784}
5785
5786#} remove Alias from an obj.
5787function zeroBS_removeObjAliasByID( $objType = ZBS_TYPE_CONTACT, $objID = -1, $aliasID = -1 ) {
5788
5789    if ( ! empty( $objID ) && ! empty( $aliasID ) ) {
5790
5791        // check there/find ID
5792        $existing = zeroBS_getAliasByID( $objType, $objID, $aliasID );
5793
5794        if ( is_array( $existing ) ) {
5795
5796            // just brutal :)
5797
5798            global $wpdb, $ZBSCRM_t;
5799
5800            return $wpdb->delete( $ZBSCRM_t['aka'], array( 'ID' => $existing['ID'] ), array( '%d' ) );
5801
5802        }
5803    }
5804
5805    return false;
5806}
5807
5808/*
5809======================================================
5810    / Alias / AKA helpers
5811====================================================== */
5812
5813/*
5814======================================================
5815    Value Calculator / helpers
5816====================================================== */
5817
5818/**
5819 * Calculates the total value associated with a contact or company entity.
5820 *
5821 * This function sums the total of invoices and transactions associated with a given entity.
5822 * It also accounts for the 'jpcrm_total_value_fields' settings to determine whether to include
5823 * invoices and transactions in the total value. Additionally, if both invoices and transactions
5824 * are included, and the 'transactions_paid_total' is set and greater than 0, it subtracts this
5825 * value from the total.
5826 *
5827 * @param array $entity The entity array containing 'invoices_total', 'transactions_total', and optionally 'transactions_paid_total'.
5828 *
5829 * @return float The calculated total value. It includes the invoices and transactions totals based on the settings,
5830 *               and adjusts for 'transactions_paid_total' if applicable.
5831 */
5832function jpcrm_get_total_value_from_contact_or_company( $entity ) {
5833    global $zbs;
5834    $total_value        = 0.0;
5835    $invoices_total     = isset( $entity['invoices_total'] ) ? $entity['invoices_total'] : 0.0;
5836    $transactions_total = isset( $entity['transactions_total'] ) ? $entity['transactions_total'] : 0.0;
5837    // For compatibility reasons we include all values if the jpcrm_total_value_fields setting is inexistent.
5838    $settings                     = $zbs->settings->getAll();
5839    $include_invoices_in_total    = true;
5840    $include_transations_in_total = true;
5841    if ( isset( $settings['jpcrm_total_value_fields'] ) ) {
5842        $include_invoices_in_total    = isset( $settings['jpcrm_total_value_fields']['invoices'] ) && $settings['jpcrm_total_value_fields']['invoices'] === 1;
5843        $include_transations_in_total = isset( $settings['jpcrm_total_value_fields']['transactions'] ) && $settings['jpcrm_total_value_fields']['transactions'] === 1;
5844    }
5845    $total_value  = 0;
5846    $total_value += $include_invoices_in_total ? $invoices_total : 0;
5847    $total_value += $include_transations_in_total ? $transactions_total : 0;
5848    if ( $include_invoices_in_total && $include_transations_in_total && isset( $entity['transactions_paid_total'] ) && $entity['transactions_paid_total'] > 0 ) {
5849        $total_value -= $entity['transactions_paid_total'];
5850    }
5851
5852    return $total_value;
5853}
5854
5855    // evolved for dal3.0
5856    // left in place + translated, but FAR better to just use 'withValues' => true on a getContact call directly.
5857    // THIS STAYS THE SAME FOR DB2 until trans+invoices MOVED OVER #DB2ROUND2
5858    #} Main function to return a customers "total value"
5859    #} At MVP that means Invoices + Transactions
5860function zeroBS_customerTotalValue( $contactID = '', $customerInvoices = array(), $customerTransactions = array() ) {
5861
5862    global $zbs;
5863
5864    $contactWithVals = $zbs->DAL->contacts->getContact(
5865        $contactID,
5866        array(
5867            'withCustomFields' => false,
5868            'withValues'       => true,
5869        )
5870    );
5871
5872    // throwaway obj apart from totals
5873    // later could optimise, but better to optimise 1 level up and not even use this func
5874    if ( isset( $contactWithVals['total_value'] ) ) {
5875        return $contactWithVals['total_value'];
5876    }
5877    return 0;
5878}
5879
5880/**
5881 * Adds up value of quotes for a customer...
5882 *
5883 * Evolved for dal3.0
5884 * Left in place + translated, but FAR better to just use 'withValues' => true on a getContact call directly.
5885 *
5886 * @param string $contact_id Contact ID.
5887 * @param array  $customer_quotes Customer quotes.
5888 * @return int Total.
5889 */
5890function zeroBS_customerQuotesValue( $contact_id = '', $customer_quotes = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
5891
5892    global $zbs;
5893
5894    $contact_with_vals = $zbs->DAL->contacts->getContact( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5895        $contact_id,
5896        array(
5897            'withCustomFields' => false,
5898            'withValues'       => true,
5899        )
5900    );
5901
5902    // throwaway obj apart from totals
5903    // later could optimise, but better to optimise 1 level up and not even use this func
5904    if ( isset( $contact_with_vals['quotes_total'] ) ) {
5905        return $contact_with_vals['quotes_total'];
5906    }
5907
5908    return 0;
5909}
5910
5911/**
5912 * Adds up value of invoices for a customer...
5913 *
5914 * Evolved for dal3.0
5915 * Left in place + translated, but FAR better to just use 'withValues' => true on a getContact call directly.
5916 *
5917 * @param string $contact_id Contact ID.
5918 * @param array  $customer_invoices Customer invoices.
5919 * @return int Total.
5920 */
5921function zeroBS_customerInvoicesValue( $contact_id = '', $customer_invoices = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
5922
5923    global $zbs;
5924
5925    $contact_with_vals = $zbs->DAL->contacts->getContact( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5926        $contact_id,
5927        array(
5928            'withCustomFields' => false,
5929            'withValues'       => true,
5930        )
5931    );
5932
5933    // throwaway obj apart from totals
5934    // later could optimise, but better to optimise 1 level up and not even use this func
5935    if ( isset( $contact_with_vals['invoices_total'] ) ) {
5936        return $contact_with_vals['invoices_total'];
5937    }
5938
5939    return 0;
5940}
5941
5942/**
5943 * Adds up value of transactions for a customer...
5944 *
5945 * Evolved for dal3.0
5946 * Left in place + translated, but FAR better to just use 'withValues' => true on a getContact call directly.
5947 * THIS STAYS THE SAME FOR DB2 until trans MOVED OVER #DB2ROUND2
5948 *
5949 * @param string $contact_id Contact ID.
5950 * @param array  $customer_transactions Customer transactions.
5951 * @return int Total.
5952 */
5953function zeroBS_customerTransactionsValue( $contact_id = '', $customer_transactions = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
5954
5955    global $zbs;
5956
5957    $contact_with_vals = $zbs->DAL->contacts->getContact( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
5958        $contact_id,
5959        array(
5960            'withCustomFields' => false,
5961            'withValues'       => true,
5962        )
5963    );
5964
5965    // throwaway obj apart from totals
5966    // later could optimise, but better to optimise 1 level up and not even use this func
5967    if ( isset( $contact_with_vals['transactions_total'] ) ) {
5968        return $contact_with_vals['transactions_total'];
5969    }
5970
5971    return 0;
5972}
5973
5974/*
5975======================================================
5976    / Value Calculator / helpers
5977====================================================== */
5978
5979// ===============================================================================
5980// ========  Security Logs (used for Quote + Trans hashlink access) ==============
5981
5982    // this is fired on all req (expects a "fini" followup fire of next func to mark "success")
5983    // (defaults to failed req.)
5984function zeroBSCRM_security_logRequest( $reqType = 'unknown', $reqHash = '', $reqID = -1 ) {
5985
5986    // don't log requests for admins, who by nature, can see all
5987    // needs to match zeroBSCRM_security_finiRequest precheck
5988    if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
5989        return false;
5990    }
5991
5992    global $wpdb, $ZBSCRM_t;
5993
5994    // if user logged in, also log id
5995    $userID       = -1;
5996    $current_user = wp_get_current_user();
5997    if ( isset( $current_user->ID ) ) {
5998        $userID = (int) $current_user->ID;
5999    }
6000    $userIP = zeroBSCRM_getRealIpAddr();
6001
6002    // validate these a bit
6003    $validTypes = array( 'quoteeasy', 'inveasy' );
6004    if ( ! in_array( $reqType, $validTypes ) ) {
6005        $reqType = 'na';
6006    }
6007    $reqHash = sanitize_text_field( $reqHash );
6008    if ( strlen( $reqHash ) > 128 ) {
6009        $reqHash = '';
6010    }
6011    $reqID = (int) $reqID;
6012
6013    if ( $wpdb->insert(
6014        $ZBSCRM_t['security_log'],
6015        array(
6016
6017            // 'zbs_site' => zeroBSCRM_installSite(),
6018            // 'zbs_team' => zeroBSCRM_installTeam(),
6019            'zbs_owner'         => -1, // zeroBSCRM_currentUserID(),
6020
6021            'zbssl_reqtype'     => $reqType,
6022            'zbssl_ip'          => $userIP,
6023            'zbssl_reqhash'     => $reqHash,
6024
6025            'zbssl_reqid'       => $reqID,
6026            'zbssl_loggedin_id' => $userID,
6027            'zbssl_reqstatus'   => -1, // guilty until proven...
6028            'zbssl_reqtime'     => time(),
6029        ),
6030        array(
6031            '%d',
6032
6033            '%s',
6034            '%s',
6035            '%s',
6036
6037            '%d',
6038            '%d',
6039            '%d',
6040            '%d',
6041        )
6042    ) ) {
6043
6044        // success
6045        return $wpdb->insert_id;
6046
6047    }
6048
6049    return false;
6050}
6051
6052    // after security validated,
6053function zeroBSCRM_security_finiRequest( $requestID = -1 ) {
6054
6055    // don't log requests for admins, who by nature, can see all
6056    // needs to match zeroBSCRM_security_logRequest precheck
6057    if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
6058        return false;
6059    }
6060
6061    // basic check
6062    $requestID = (int) $requestID;
6063
6064    if ( $requestID > 0 ) {
6065
6066        global $wpdb, $ZBSCRM_t;
6067
6068        // for now just brutal update, not even comparing IP
6069        if ( $wpdb->update(
6070            $ZBSCRM_t['security_log'],
6071            array(
6072                'zbssl_reqstatus' => 1,
6073            ),
6074            array( // where
6075                'ID' => $requestID,
6076            ),
6077            array(
6078                '%d',
6079            ),
6080            array(
6081                '%d',
6082            )
6083        ) !== false ) {
6084
6085            // return id
6086            return $requestID;
6087
6088        }
6089    }
6090
6091    return false;
6092}
6093
6094    // checks if blocked
6095function zeroBSCRM_security_blockRequest( $reqType = 'unknown' ) {
6096
6097    // don't log requests for admins, who by nature, can see all
6098    // needs to match zeroBSCRM_security_logRequest etc. above
6099    if ( zeroBSCRM_isZBSAdminOrAdmin() ) {
6100        return false;
6101    }
6102
6103    global $zbs, $wpdb, $ZBSCRM_t;
6104
6105    // see if more than X (5?) failed request accessed by this ip within last Y (48h?)
6106    $userIP      = zeroBSCRM_getRealIpAddr();
6107    $sinceTime   = time() - 172800; // 48h = 172800
6108    $maxFails    = 5;
6109    $query       = $wpdb->prepare( 'SELECT COUNT(ID) FROM ' . $ZBSCRM_t['security_log'] . ' WHERE zbssl_ip = %s AND zbssl_reqstatus <> %d AND zbssl_reqtime > %d', array( $userIP, 1, $sinceTime ) );
6110    $countFailed = (int) $wpdb->get_var( $query );
6111
6112    // less than ..
6113    if ( $countFailed < $maxFails ) {
6114        return false;
6115    }
6116
6117    return true;
6118}
6119
6120    // removes all security logs older than setting (72h at addition)
6121    // this is run DAILY by a cron job in ZeroBSCRM.CRON.php
6122function zeroBSCRM_clearSecurityLogs() {
6123
6124    global $zbs, $wpdb, $ZBSCRM_t;
6125
6126    // older than
6127    $deleteOlderThanTime = time() - 259200; // 72h = 259200
6128
6129    // delete
6130    $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $ZBSCRM_t['security_log'] . ' WHERE zbssl_reqtime < %d', $deleteOlderThanTime ) );
6131}
6132
6133function zeroBSCRM_hashes_GetHashForObj( $objID = -1, $objTypeID = -1 ) {
6134
6135    if ( $objID > 0 && $objTypeID > 0 ) {
6136
6137        global $zbs;
6138        $hash = $zbs->DAL->meta( $objTypeID, $objID, 'zbshash', '' );
6139
6140        // Return with PREFIX (makes it interpretable later on as this is shared between invID + invHash (for example) at endpoint /invoices/*hashorid)
6141        if ( ! empty( $hash ) ) {
6142            return 'zh-' . $hash;
6143        }
6144    }
6145
6146    return false;
6147}
6148
6149// NOTE: This is now GENERIC, for quotes/invs whatever has meta :) (DAL3+ pass objTypeID)
6150// function is this a hash of an INVOICE. Could be refined when DB2.0
6151// function for checking if a hash is valid
6152// ... THIS WAS refactored for v3.0, now uses hash cols on objs :)
6153function zeroBSCRM_hashes_GetObjFromHash( $hash = '', $pay = -1, $objTypeID = -1 ) {
6154
6155    // def
6156    $ret = array(
6157        'success' => false,
6158        'data'    => array(),
6159    );
6160
6161    // SANITIZE
6162    $hash = sanitize_text_field( $hash ); // sanitize it here
6163
6164    // if prefix still present, chunk off
6165    if ( str_starts_with( $hash, 'zh-' ) ) {
6166        $hash = substr( $hash, 3 );
6167    }
6168
6169    // get if poss
6170    if ( ! empty( $hash ) && $objTypeID > 0 ) {
6171
6172        global $zbs;
6173
6174        switch ( $objTypeID ) {
6175
6176            case ZBS_TYPE_INVOICE:
6177                // retrieve, if any
6178                $invoice = $zbs->DAL->invoices->getInvoice(
6179                    -1,
6180                    array(
6181                        'hash'         => $hash,
6182                        'withAssigned' => true,
6183                    )
6184                );
6185
6186                // got inv?
6187                if ( is_array( $invoice ) && isset( $invoice['id'] ) ) {
6188
6189                    $contactID = -1;
6190                    // return the customer information that the invoice will need (i.e. Stripe customerID) same function will be used
6191                    // in invoice checkout process (invoice pro) when being paid for using a HASH URL.
6192
6193                    if ( $pay > 0 ) {
6194
6195                        // paying so need the customerID from settings otherwise just viewing so dont need to expose data
6196                        // WH: I've added this for future ease:
6197                        if ( is_array( $invoice ) && isset( $invoice['contact'] ) && is_array( $invoice['contact'] ) && count( $invoice['contact'] ) > 0 ) {
6198                            $contactID = $invoice['contact'][0]['id'];
6199                        }
6200                        // $companyID = -1;  if (is_array($invoice) && isset($invoice['company']) && is_array($invoice['company']) && count($invoice['company']) > 0) $companyID = $invoice['company'][0]['id'];
6201
6202                    }
6203                    $ret['success'] = true;
6204                    $ret['data']    = array(
6205                        'ID'  => $invoice['id'],
6206                        'cID' => $contactID,
6207                    );
6208
6209                }
6210
6211                break;
6212            case ZBS_TYPE_QUOTE:
6213                // retrieve, if any
6214                $quote = $zbs->DAL->quotes->getQuote(
6215                    -1,
6216                    array(
6217                        'hash'         => $hash,
6218                        'withAssigned' => true,
6219                    )
6220                );
6221
6222                // got quote?
6223                if ( is_array( $quote ) && isset( $quote['id'] ) ) {
6224
6225                    $ret['success'] = true;
6226                    $ret['data']    = array(
6227                        'ID'  => $quote['id'],
6228                        'cID' => -1, // not req for quotes?
6229                    );
6230
6231                }
6232
6233                break;
6234
6235        } // / switch
6236
6237    } // / if hash + objtypeid
6238
6239    return $ret;
6240}
6241
6242// ======== / Security Logs (used for Quote + Trans hashlink access) =============
6243// ===============================================================================
6244
6245// ===============================================================================
6246// =======================  Tax Table Helpers ====================================
6247
6248// takes a subtotal and a (potential csv) of ID's of taxtable lines
6249// returns a Â£0 net value of the tax to be applied
6250function zeroBSCRM_taxRates_getTaxValue( $subtotal = 0.0, $taxRateIDCSV = '' ) {
6251
6252    $tax = 0.0;
6253
6254    // retrieve tax rate(s)
6255    if ( ! empty( $taxRateIDCSV ) ) {
6256
6257        $taxRateTable = zeroBSCRM_taxRates_getTaxTableArr( true );
6258
6259        // get (multiple) id's
6260        $taxRatesToApply = array();
6261        if ( strpos( $taxRateIDCSV, ',' ) ) {
6262
6263            $taxRateIDs = explode( ',', $taxRateIDCSV );
6264            if ( ! is_array( $taxRateIDs ) ) {
6265                $taxRatesToApply = array();
6266            } else {
6267                $taxRatesToApply = $taxRateIDs;
6268            }
6269        } else {
6270            $taxRatesToApply[] = (int) $taxRateIDCSV;
6271        }
6272
6273        if ( is_array( $taxRatesToApply ) ) {
6274            foreach ( $taxRatesToApply as $taxRateID ) {
6275
6276                $rateID = (int) $taxRateID;
6277                if ( isset( $taxRateTable[ $rateID ] ) ) {
6278
6279                    // get rate
6280                    $rate = 0.0;
6281                    if ( isset( $taxRateTable[ $rateID ]['rate'] ) ) {
6282                        $rate = (float) $taxRateTable[ $rateID ]['rate'];
6283                    }
6284
6285                    // calc + add
6286                    $tax += round( $subtotal * ( $rate / 100 ), 2 );
6287
6288                } // else not set?
6289
6290            } // / foreach tax rate to apply
6291        }
6292
6293        return $tax;
6294
6295    }
6296
6297    return 0.0;
6298}
6299
6300// gets single tax rate by id
6301function zeroBSCRM_taxRates_getTaxRate( $taxRateID = '' ) {
6302
6303    // retrieve tax rate(s)
6304    if ( ! empty( $taxRateID ) ) {
6305
6306        $taxRateID = (int) $taxRateID;
6307
6308        global $ZBSCRM_t, $wpdb;
6309
6310        // for v3.0, brutal direct sql
6311        $query = 'SELECT * FROM ' . $ZBSCRM_t['tax'] . ' WHERE ID = %d ORDER BY ID ASC';
6312        try {
6313
6314            #} Prep & run query
6315            $queryObj     = $wpdb->prepare( $query, array( $taxRateID ) );
6316            $potentialRes = $wpdb->get_row( $queryObj, OBJECT );
6317
6318        } catch ( Exception $e ) {
6319
6320        }
6321
6322        #} Interpret results (Result Set - multi-row)
6323        if ( isset( $potentialRes ) && isset( $potentialRes->ID ) ) {
6324
6325            return zeroBSCRM_taxRates_tidy_taxRate( $potentialRes );
6326
6327        }
6328    }
6329
6330    return array();
6331}
6332
6333// retrieve tax table as array
6334function zeroBSCRM_taxRates_getTaxTableArr( $indexByID = false ) {
6335
6336    /*
6337    // demo/dummy data
6338    return array(
6339
6340            //these will be populated based on the array
6341            1  => array(
6342                'id'    => 1,
6343                'tax'   => 20,
6344                'name'  => 'VAT'
6345            ),
6346
6347            2 => array(
6348                'id'    => 2,
6349                'tax'   => 19,
6350                'name'  => 'GST'
6351            )
6352
6353        ); */
6354
6355    global $ZBSCRM_t, $wpdb;
6356
6357    // for v3.0, brutal direct sql
6358    $query             = 'SELECT * FROM ' . $ZBSCRM_t['tax'] . ' ORDER BY ID ASC';
6359    $potentialTaxRates = $wpdb->get_results( $query, OBJECT );
6360
6361    #} Interpret results (Result Set - multi-row)
6362    if ( isset( $potentialTaxRates ) && is_array( $potentialTaxRates ) && count( $potentialTaxRates ) > 0 ) {
6363
6364        $res = array();
6365
6366        #} Has results, tidy + return
6367        foreach ( $potentialTaxRates as $resDataLine ) {
6368
6369            if ( $indexByID ) {
6370
6371                $lineID         = (int) $resDataLine->ID;
6372                $res[ $lineID ] = zeroBSCRM_taxRates_tidy_taxRate( $resDataLine );
6373
6374            } else {
6375
6376                $res[] = zeroBSCRM_taxRates_tidy_taxRate( $resDataLine );
6377
6378            }
6379        }
6380
6381        return $res;
6382    }
6383
6384    return array();
6385}
6386
6387/**
6388 * Generate a lookup key for tax rate duplicate detection
6389 *
6390 * @param string $name Tax rate name.
6391 * @param float  $rate Tax rate percentage.
6392 * @return string Normalized lookup key.
6393 */
6394function jpcrm_tax_rates_generate_lookup_key( $name, $rate ) {
6395    return strtolower( trim( $name ) ) . '_' . number_format( (float) $rate, 4, '.', '' );
6396}
6397
6398    /**
6399     * adds or updates a taxrate object
6400     *
6401     * @param array $args Associative array of arguments
6402     *              id (not req.), owner (not req.) data -> key/val
6403     *
6404     * @return int line ID
6405     */
6406function zeroBSCRM_taxRates_addUpdateTaxRate( $args = array() ) {
6407
6408    global $ZBSCRM_t, $wpdb;
6409
6410    #} ============ LOAD ARGS =============
6411    $defaultArgs = array(
6412
6413        'id'    => -1,
6414        'owner' => -1,
6415
6416        // fields (directly)
6417        'data'  => array(
6418
6419            'name'    => '',
6420            'rate'    => 0.0,
6421            'created' => -1, // override date? :(
6422
6423        ),
6424
6425    );
6426    foreach ( $defaultArgs as $argK => $argV ) {
6427        $$argK = $argV;
6428        if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
6429            if ( is_array( $args[ $argK ] ) ) {
6430                $newData = $$argK;
6431                if ( ! is_array( $newData ) ) {
6432                    $newData = array();
6433                }
6434                foreach ( $args[ $argK ] as $subK => $subV ) {
6435                    $newData[ $subK ] = $subV;
6436                }
6437                $$argK = $newData;
6438            } else {
6439                $$argK = $args[ $argK ];
6440            }
6441        }
6442    }
6443    #} =========== / LOAD ARGS ============
6444
6445    #} ========== CHECK FIELDS ============
6446
6447    $id = (int) $id;
6448
6449    // if owner = -1, add current
6450    if ( $owner === -1 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
6451        $owner = zeroBSCRM_user();
6452    }
6453
6454    // check if exists already (where no id passed)
6455    if ( $id < 1 && ! empty( $data['name'] ) && isset( $data['rate'] ) ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
6456
6457        // Get all tax rates from cache or database
6458        $all_tax_rates = wp_cache_get( 'all_tax_rates', 'zbscrm_tax_rates' );
6459
6460        if ( false === $all_tax_rates ) {
6461            // Cache miss - load all tax rates
6462            // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
6463            $table_name = $ZBSCRM_t['tax'];
6464            $sql        = "SELECT ID, zbsc_tax_name, zbsc_rate
6465                    FROM `{$table_name}`
6466                    ORDER BY ID DESC";
6467
6468            /* phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared -- Safe: table name is internal mapping, not user input */
6469            $results = $wpdb->get_results( $sql, ARRAY_A );
6470
6471            // Build lookup array for fast searching
6472            $all_tax_rates = array();
6473            if ( $results ) {
6474                foreach ( $results as $rate ) {
6475                    $lookup_key                   = jpcrm_tax_rates_generate_lookup_key( $rate['zbsc_tax_name'], $rate['zbsc_rate'] );
6476                    $all_tax_rates[ $lookup_key ] = (int) $rate['ID'];
6477                }
6478            }
6479
6480            // Cache for shorter time due to frequent updates
6481            wp_cache_set( 'all_tax_rates', $all_tax_rates, 'zbscrm_tax_rates', 5 * MINUTE_IN_SECONDS );
6482        }
6483
6484        // Check if this combination exists
6485        $lookup_key       = jpcrm_tax_rates_generate_lookup_key( $data['name'], $data['rate'] ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
6486        $existing_rate_id = isset( $all_tax_rates[ $lookup_key ] ) ? $all_tax_rates[ $lookup_key ] : 0;
6487
6488        if ( $existing_rate_id > 0 ) {
6489            // Return error for duplicate tax rate
6490            return new WP_Error( 'duplicate_tax_rate', __( 'A tax rate with this name and rate already exists.', 'zero-bs-crm' ) );
6491        }
6492    }
6493
6494    #} ========= / CHECK FIELDS ===========
6495
6496    $dataArr = array(
6497
6498        // ownership
6499        // no need to update these (as of yet) - can't move teams etc.
6500        // 'zbs_site' => zeroBSCRM_installSite(),
6501        // 'zbs_team' => zeroBSCRM_installTeam(),
6502        'zbs_owner'        => $owner,
6503
6504        // fields
6505        'zbsc_tax_name'    => $data['name'],
6506        'zbsc_rate'        => $data['rate'],
6507        'zbsc_lastupdated' => time(),
6508    );
6509
6510    $dataTypes = array( // field data types
6511        '%d',
6512
6513        '%s',
6514        '%s',
6515        '%d',
6516    );
6517
6518    if ( isset( $data['created'] ) && ! empty( $data['created'] ) && $data['created'] !== -1 ) {
6519        $dataArr['zbsc_created'] = $data['created'];
6520        $dataTypes[]             = '%d';
6521    }
6522
6523    if ( isset( $id ) && ! empty( $id ) && $id > 0 ) {
6524
6525            #} Check if obj exists (here) - for now just brutal update (will error when doesn't exist)
6526
6527            #} Attempt update
6528        if ( $wpdb->update(
6529            $ZBSCRM_t['tax'],
6530            $dataArr,
6531            array( // where
6532                'ID' => $id,
6533            ),
6534            $dataTypes,
6535            array( // where data types
6536                '%d',
6537            )
6538        ) !== false ) {
6539
6540                // Successfully updated - clear cache and return id
6541                wp_cache_delete( 'all_tax_rates', 'zbscrm_tax_rates' );
6542                return $id;
6543
6544        } else {
6545
6546            // FAILED update
6547            return false;
6548
6549        }
6550    } else {
6551
6552        // set created if not set
6553        if ( ! isset( $dataArr['zbsc_created'] ) ) {
6554            $dataArr['zbsc_created'] = time();
6555            $dataTypes[]             = '%d';
6556        }
6557
6558        // add team etc
6559        $dataArr['zbs_site'] = zeroBSCRM_site();
6560        $dataTypes[]         = '%d';
6561        $dataArr['zbs_team'] = zeroBSCRM_team();
6562        $dataTypes[]         = '%d';
6563
6564        #} No ID - must be an INSERT
6565        if ( $wpdb->insert(
6566            $ZBSCRM_t['tax'],
6567            $dataArr,
6568            $dataTypes
6569        ) > 0 ) {
6570
6571                #} Successfully inserted, lets return new ID
6572                $newID = $wpdb->insert_id;
6573
6574                // Clear cache after successful insert
6575                wp_cache_delete( 'all_tax_rates', 'zbscrm_tax_rates' );
6576
6577                return $newID;
6578
6579        } else {
6580
6581            #} Failed to Insert
6582            return false;
6583
6584        }
6585    }
6586
6587        return false;
6588}
6589
6590    /**
6591     * deletes a Taxrate object
6592     *
6593     * @param array $args Associative array of arguments
6594     *              id
6595     *
6596     * @return int success;
6597     */
6598function zeroBSCRM_taxRates_deleteTaxRate( $args = array() ) {
6599
6600    global $ZBSCRM_t, $wpdb;
6601
6602    #} ============ LOAD ARGS =============
6603    $defaultArgs = array(
6604
6605        'id' => -1,
6606
6607    );
6608    foreach ( $defaultArgs as $argK => $argV ) {
6609        $$argK = $argV;
6610        if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
6611            if ( is_array( $args[ $argK ] ) ) {
6612                $newData = $$argK;
6613                if ( ! is_array( $newData ) ) {
6614                    $newData = array();
6615                }
6616                foreach ( $args[ $argK ] as $subK => $subV ) {
6617                    $newData[ $subK ] = $subV;
6618                }
6619                $$argK = $newData;
6620            } else {
6621                $$argK = $args[ $argK ];
6622            }
6623        }
6624    }
6625    #} =========== / LOAD ARGS ============
6626
6627    #} Check ID & Delete :)
6628    $id = (int) $id;
6629    if ( ! empty( $id ) && $id > 0 ) {
6630        return zeroBSCRM_db2_deleteGeneric( $id, 'tax' );
6631    }
6632
6633    return false;
6634}
6635
6636    /**
6637     * tidy's the object from wp db into clean array
6638     *
6639     * @param array $obj (DB obj)
6640     *
6641     * @return array (clean obj)
6642     */
6643function zeroBSCRM_taxRates_tidy_taxRate( $obj = false ) {
6644
6645    $res = false;
6646
6647    if ( isset( $obj->ID ) ) {
6648        $res          = array();
6649        $res['id']    = $obj->ID;
6650        $res['owner'] = $obj->zbs_owner;
6651
6652        $res['name'] = $obj->zbsc_tax_name;
6653        $res['rate'] = $obj->zbsc_rate;
6654
6655        // to maintain old obj more easily, here we refine created into datestamp
6656        $res['created']    = zeroBSCRM_locale_utsToDatetime( $obj->zbsc_created );
6657        $res['createduts'] = $obj->zbsc_created; // this is the UTS (int14)
6658
6659        $res['lastupdated'] = $obj->zbsc_lastupdated;
6660
6661    }
6662
6663        return $res;
6664}
6665
6666// ======================= / Tax Table Helpers ===================================
6667// ===============================================================================
6668
6669// ===============================================================================
6670// =======================  File Upload Related Funcs ============================
6671
6672    // retrieve all files for a (customer)whatever
6673function zeroBSCRM_files_getFiles( $fileType = '', $objID = -1 ) {
6674
6675    global $zbs;
6676
6677    $filesArrayKey = zeroBSCRM_files_key( $fileType );
6678
6679    if ( ! empty( $filesArrayKey ) && $objID > 0 ) {
6680
6681        // DAL2+
6682        // bit gross hard-typed, could be genericified as all is using is >DAL()->getMeta
6683        switch ( $fileType ) {
6684
6685            case 'customer':
6686            case 'contact':
6687                return $zbs->DAL->contacts->getContactMeta( $objID, 'files' );
6688                break;
6689
6690            case 'quotes':
6691            case 'quote':
6692                return $zbs->DAL->quotes->getQuoteMeta( $objID, 'files' );
6693                break;
6694
6695            case 'invoices':
6696            case 'invoice':
6697                return $zbs->DAL->invoices->getInvoiceMeta( $objID, 'files' );
6698                break;
6699
6700            case 'companies':
6701            case 'company':
6702                return $zbs->DAL->companies->getCompanyMeta( $objID, 'files' );
6703                break;
6704
6705            // no default
6706
6707        }
6708    }
6709
6710    return array();
6711}
6712
6713// updates files array for a (whatever)
6714function zeroBSCRM_files_updateFiles( $fileType = '', $objID = -1, $filesArray = -1 ) {
6715
6716    global $zbs;
6717
6718    $filesArrayKey = zeroBSCRM_files_key( $fileType );
6719
6720    if ( ! empty( $filesArrayKey ) && $objID > 0 ) {
6721
6722        // DAL2+
6723        // bit gross hard-typed, could be genericified as all is using is >DAL()->getMeta
6724        switch ( $fileType ) {
6725
6726            case 'customer':
6727            case 'contact':
6728                $zbs->DAL->updateMeta( ZBS_TYPE_CONTACT, $objID, 'files', $filesArray );
6729                break;
6730
6731            case 'quotes':
6732            case 'quote':
6733                $zbs->DAL->updateMeta( ZBS_TYPE_QUOTE, $objID, 'files', $filesArray );
6734                break;
6735
6736            case 'invoices':
6737            case 'invoice':
6738                $zbs->DAL->updateMeta( ZBS_TYPE_INVOICE, $objID, 'files', $filesArray );
6739                break;
6740
6741            case 'companies':
6742            case 'company':
6743                $zbs->DAL->updateMeta( ZBS_TYPE_COMPANY, $objID, 'files', $filesArray );
6744                break;
6745
6746            // no default
6747
6748        }
6749
6750        return $filesArray;
6751
6752    }
6753
6754    return false;
6755}
6756
6757// gets meta key for file type arr
6758function zeroBSCRM_files_key( $fileType = '' ) {
6759
6760    switch ( $fileType ) {
6761
6762        case 'customer':
6763        case 'contact':
6764            return 'zbs_customer_files';
6765
6766            break;
6767        case 'quotes':
6768        case 'quote':
6769            return 'zbs_customer_quotes';
6770
6771            break;
6772        case 'invoices':
6773        case 'invoice':
6774            return 'zbs_customer_invoices';
6775
6776            break;
6777
6778        case 'companies':
6779        case 'company':
6780            return 'zbs_company_files';
6781
6782            break;
6783
6784    }
6785
6786    return '';
6787}
6788
6789// ======================= / File Upload Related Funcs ===========================
6790// ===============================================================================
6791
6792// ===============================================================================
6793// ===========   TEMPHASH (remains same for DAL2->3) =============================
6794
6795/**
6796 * checks validity of a temporary hash object
6797 *
6798 * @return int success;
6799 */
6800function zeroBSCRM_checkValidTempHash( $objid = -1, $type = '', $hash = '' ) {
6801
6802    // get a valid hash
6803    $hash = zeroBSCRM_getTempHash( -1, $type, $hash, 1 );
6804
6805    // check id
6806    if ( isset( $hash ) && is_array( $hash ) && isset( $hash['objid'] ) ) {
6807        if ( $objid == $hash['objid'] ) {
6808            return true;
6809        }
6810    }
6811
6812    return false;
6813}
6814
6815/**
6816 * retrieves a temporary hash object
6817 *
6818 * @return int success;
6819 */
6820function zeroBSCRM_getTempHash( $id = -1, $type = '', $hash = '', $status = -99 ) {
6821
6822    $id = (int) $id;
6823    if ( ! empty( $id ) && $id > 0 ) {
6824
6825        global $ZBSCRM_t, $wpdb;
6826
6827        $additionalWHERE = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
6828        $queryVars       = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
6829
6830        $queryVars[] = $id; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
6831        $whereStr    = 'ID = %d'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
6832
6833        if ( ! empty( $type ) ) {
6834
6835            $queryVars[]     = $type;
6836            $additionalWHERE = 'AND zbstemphash_objtype = %s ';
6837
6838        } // else will be from ANY type
6839
6840        if ( $status != -99 ) {
6841
6842            $queryVars[]     = $status;
6843            $additionalWHERE = 'AND zbstemphash_status = %d ';
6844
6845        }
6846
6847        /* -- prep started, see: #OWNERSHIP */
6848
6849        $sql = 'SELECT * FROM ' . $ZBSCRM_t['temphash'] . ' WHERE ' . $whereStr . ' ' . $additionalWHERE . 'ORDER BY ID ASC LIMIT 0,1';
6850
6851        $potentialReponse = $wpdb->get_row( $wpdb->prepare( $sql, $queryVars ), OBJECT );
6852
6853        if ( isset( $potentialReponse ) && isset( $potentialReponse->ID ) ) {
6854
6855            #} Retrieved :) fill + return
6856
6857            // tidy
6858            $res = zeroBS_tidy_temphash( $potentialReponse );
6859
6860            return $res;
6861        }
6862    }
6863
6864    return false;
6865}
6866
6867/**
6868 * adds or updates a temporary hash object
6869 *
6870 * @return int success;
6871 */
6872function zeroBSCRM_addUpdateTempHash( $id = -1, $objstatus = -1, $objtype = '', $objid = -1, $objhash = '', $returnHashArr = false ) {
6873
6874    // globals
6875    global $ZBSCRM_t, $wpdb;
6876
6877    // got id?
6878    $id = (int) $id;
6879    if ( ! empty( $id ) && $id > 0 ) {
6880
6881        // check exists?
6882
6883        // for now just brutal update.
6884        if ( $wpdb->update(
6885            $ZBSCRM_t['temphash'],
6886            array(
6887                // 'zbs_site' => zeroBSCRM_installSite(),
6888                // 'zbs_team' => zeroBSCRM_installTeam(),
6889                // 'zbs_owner' => zeroBSCRM_currentUserID(),
6890
6891                'zbstemphash_status'      => (int) $objstatus,
6892                'zbstemphash_objtype'     => $objtype,
6893                'zbstemphash_objid'       => (int) $objid,
6894                'zbstemphash_objhash'     => $objhash,
6895
6896                // 'zbsmaillink_created' => time(),
6897                'zbstemphash_lastupdated' => time(),
6898            ),
6899            array( // where
6900                'ID' => $id,
6901            ),
6902            array(
6903                '%d',
6904                '%s',
6905                '%d',
6906                '%s',
6907                '%d',
6908            ),
6909            array(
6910                '%d',
6911            )
6912        ) !== false ) {
6913
6914                    // if "return hash"
6915            if ( $returnHashArr ) {
6916                return array(
6917                    'id'   => $id,
6918                    'hash' => $objhash,
6919                );
6920            }
6921
6922                    // return id
6923                    return $id;
6924
6925        }
6926    } else {
6927
6928        // insert
6929
6930        // create hash if not created :)
6931        if ( empty( $objhash ) ) {
6932            $objhash = zeroBSCRM_GenerateTempHash();
6933        }
6934
6935        // go
6936        if ( $wpdb->insert(
6937            $ZBSCRM_t['temphash'],
6938            array(
6939                // 'zbs_site' => zeroBSCRM_installSite(),
6940                // 'zbs_team' => zeroBSCRM_installTeam(),
6941                // 'zbs_owner' => zeroBSCRM_currentUserID(),
6942
6943                'zbstemphash_status'      => (int) $objstatus,
6944                'zbstemphash_objtype'     => $objtype,
6945                'zbstemphash_objid'       => (int) $objid,
6946                'zbstemphash_objhash'     => $objhash,
6947
6948                'zbstemphash_created'     => time(),
6949                'zbstemphash_lastupdated' => time(),
6950            ),
6951            array(
6952                // '%d',  // site
6953                // '%d',  // team
6954                // '%d',  // owner
6955
6956                '%d',
6957                '%s',
6958                '%d',
6959                '%s',
6960                '%d',
6961                '%d',
6962            )
6963        ) > 0 ) {
6964
6965                // inserted, let's move on
6966                $newID = $wpdb->insert_id;
6967
6968                // if "return hash"
6969            if ( $returnHashArr ) {
6970                return array(
6971                    'id'   => $id,
6972                    'hash' => $objhash,
6973                );
6974            }
6975
6976                return $newID;
6977        }
6978    }
6979
6980    return false;
6981}
6982/**
6983 * deletes a temporary hash object
6984 *
6985 * @param array $args Associative array of arguments
6986 *              id
6987 *
6988 * @return int success;
6989 */
6990function zeroBSCRM_deleteTempHash( $args = array() ) {
6991
6992    // Load Args
6993    $defaultArgs = array(
6994
6995        'id' => -1,
6996
6997    );
6998    foreach ( $defaultArgs as $argK => $argV ) {
6999        $$argK = $argV;
7000        if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
7001            $$argK = $args[ $argK ];
7002        }
7003    }
7004
7005    // globals
7006    global $ZBSCRM_t, $wpdb;
7007
7008    $id = (int) $id;
7009    if ( ! empty( $id ) && $id > 0 ) {
7010        return zeroBSCRM_db2_deleteGeneric( $id, 'temphash' );
7011    }
7012
7013    return false;
7014}
7015
7016function zeroBS_tidy_temphash( $obj = false ) {
7017
7018    $res = false;
7019
7020    if ( isset( $obj->ID ) ) {
7021        $res                = array();
7022        $res['id']          = $obj->ID;
7023        $res['created']     = $obj->zbstemphash_created;
7024        $res['lastupdated'] = $obj->zbstemphash_lastupdated;
7025
7026        $res['status']  = $obj->zbstemphash_status;
7027        $res['objtype'] = $obj->zbstemphash_objtype;
7028        $res['objid']   = $obj->zbstemphash_objid;
7029        $res['objhash'] = $obj->zbstemphash_objhash;
7030    }
7031
7032    return $res;
7033}
7034// generates generic HASH (used for links etc.)
7035function zeroBSCRM_GenerateTempHash( $str = -1, $length = 20 ) {
7036
7037    #} Brutal hash generator, for now
7038    if ( ! empty( $str ) ) {
7039
7040        #} Semi-nonsense, not "secure"
7041        // $newMD5 = md5($postID.time().'fj30948hjfaosindf');
7042
7043        $newMD5 = wp_generate_password( 64, false );
7044
7045        return substr( $newMD5, 0, $length - 1 );
7046
7047    }
7048
7049    return '';
7050}
7051
7052// =========== / TEMPHASH   ======================================================
7053// ===============================================================================
7054
7055/*
7056======================================================
7057    General/WP helpers
7058====================================================== */
7059
7060    // in effect this is: get owner (WP USER)'s email
7061    // currently only used on Automations extension
7062    // use jpcrm_get_obj_owner_wordpress_email() in future...
7063function zeroBS_getAssigneeEmail( $cID = -1 ) {
7064    return jpcrm_get_obj_owner_wordpress_email( $cID, ZBS_TYPE_CONTACT );
7065}
7066
7067    // returns an obj owner's email as set against their WordPress account
7068function jpcrm_get_obj_owner_wordpress_email( $objID, $objTypeID ) {
7069
7070    global $zbs;
7071    if ( $objID > 0 && $zbs->DAL->isValidObjTypeID( $objTypeID ) ) {
7072
7073        $ownerID = zeroBS_getOwner( $objID, false, $objTypeID );
7074
7075        if ( $ownerID > 0 ) {
7076            return get_the_author_meta( 'user_email', $ownerID );
7077        }
7078    }
7079
7080    return false;
7081}
7082
7083    // in effect this is: get owner (WP USER)'s mobile
7084    // use zeroBS_getObjOwnerWPMobile in future... (renamed, bad naming)
7085function zeroBS_getAssigneeMobile( $wpUID = -1 ) {
7086    return zeroBS_getObjOwnerWPMobile( $wpUID );
7087}
7088
7089    // returns owner of obj's mobile (from WP USER)
7090function zeroBS_getObjOwnerWPMobile( $objID = -1, $objType = 'zerobs_customer' ) {
7091
7092    if ( $objID > 0 ) {
7093
7094        $ownerID = zeroBS_getOwner( $objID, false, $objType );
7095
7096        if ( $ownerID > 0 ) {
7097            return zeroBS_getWPUsersMobile( $ownerID );
7098        }
7099    }
7100
7101    return false;
7102}
7103
7104    // returns an obj owner's mobile number as per their wp account
7105function zeroBS_getWPUsersMobile( $uID = -1 ) {
7106    if ( $uID !== -1 ) {
7107        if ( ! empty( $uID ) ) {
7108            $mobile_number = get_user_meta( 'mobile_number', $uID );
7109            $mobile_number = apply_filters( 'zbs_filter_mobile', $mobile_number );
7110            return $mobile_number;
7111        }
7112        return false;
7113    }
7114}
7115
7116    /*
7117    * Gets formatted display name for user (tries to retrieve fname lname)
7118    */
7119function jpcrm_wp_user_name( $wordpress_user_id = -1 ) {
7120
7121    $user_info = get_userdata( $wordpress_user_id );
7122    if ( ! $user_info ) {
7123        return false;
7124    }
7125
7126    // start with display name
7127    $user_name = $user_info->display_name;
7128
7129    // else try and use fname lname
7130    if ( empty( $user_name ) ) {
7131        $user_name = $user_info->user_firstname;
7132        if ( ! empty( $user_info->user_lastname ) ) {
7133
7134            if ( ! empty( $user_name ) ) {
7135                $user_name .= ' ';
7136            }
7137
7138            $user_name .= $user_info->user_lastname;
7139
7140        }
7141    }
7142
7143    // else fall back to nice name
7144    if ( empty( $user_name ) ) {
7145        $user_name = $user_info->user_nicename;
7146    }
7147
7148    // else email?
7149    if ( empty( $user_name ) ) {
7150        $user_name = $user_info->user_email;
7151    }
7152
7153    return $user_name;
7154}
7155
7156    // ======= Statuses wrappers - bit antiquated  now...
7157
7158        // outdated wrapper
7159function zeroBS_getTransactionsStatuses() {
7160    return zeroBSCRM_getTransactionsStatuses();
7161}
7162
7163function zeroBSCRM_getCustomerStatuses( $asArray = false ) {
7164
7165    global $zbs;
7166
7167    $setting = $zbs->DAL->setting( 'customisedfields', false );
7168
7169    $zbsStatusStr = '';
7170
7171    #} stored here: $settings['customisedfields']
7172    if ( is_array( $setting ) && isset( $setting['customers']['status'] ) && is_array( $setting['customers']['status'] ) ) {
7173        $zbsStatusStr = $setting['customers']['status'][1];
7174    }
7175    if ( empty( $zbsStatusStr ) ) {
7176        #} Defaults:
7177        global $zbsCustomerFields;
7178        if ( is_array( $zbsCustomerFields ) ) {
7179            $zbsStatusStr = implode( ',', $zbsCustomerFields['status'][3] );
7180        }
7181    }
7182
7183    if ( $asArray ) {
7184
7185        if ( str_contains( '#' . $zbsStatusStr, ',' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
7186
7187            $arr = explode( ',', $zbsStatusStr );
7188            $ret = array();
7189            foreach ( $arr as $x ) {
7190                $z = trim( $x );
7191                if ( ! empty( $z ) ) {
7192                    $ret[] = $z;
7193                }
7194            }
7195
7196            return $ret;
7197
7198        }
7199    }
7200
7201    return $zbsStatusStr;
7202}
7203
7204/**
7205 * Retrieve valid transaction statuses
7206 *
7207 * @param bool $return_array Return an array instead of a CSV.
7208 */
7209function zeroBSCRM_getTransactionsStatuses( $return_array = false ) {
7210    global $zbs;
7211
7212    $setting = $zbs->DAL->setting( 'customisedfields', false ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
7213
7214    $zbs_status_str = '';
7215
7216    if ( is_array( $setting ) && isset( $setting['transactions']['status'] ) && is_array( $setting['transactions']['status'] ) ) {
7217        $zbs_status_str = $setting['transactions']['status'][1];
7218    }
7219    if ( empty( $zbs_status_str ) ) {
7220        global $zbsTransactionFields; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
7221        if ( is_array( $zbsTransactionFields ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
7222            $zbs_status_str = implode( ',', $zbsTransactionFields['status'][3] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
7223        }
7224    }
7225
7226    if ( $return_array ) {
7227
7228        if ( str_contains( $zbs_status_str, ',' ) ) {
7229            return explode( ',', $zbs_status_str );
7230        } else {
7231            return array();
7232        }
7233    }
7234
7235    return $zbs_status_str;
7236}
7237
7238/**
7239 * Retrieve an array of valid invoice statuses
7240 */
7241function zeroBSCRM_getInvoicesStatuses() {
7242    // for DAL3+ these are hard typed, probably need to sit in the obj:
7243    return array(
7244        'Draft',
7245        'Unpaid',
7246        'Paid',
7247        'Overdue',
7248        'Deleted',
7249    );
7250}
7251
7252function zeroBSCRM_getCompanyStatusesCSV() {
7253
7254    global $zbs;
7255
7256    $setting = $zbs->DAL->setting( 'customisedfields', false );
7257
7258    $zbsStatusStr = '';
7259
7260    #} stored here: $settings['customisedfields']
7261    if ( is_array( $setting ) && isset( $setting['companies']['status'] ) && is_array( $setting['companies']['status'] ) ) {
7262        $zbsStatusStr = $setting['companies']['status'][1];
7263    }
7264    if ( empty( $zbsStatusStr ) ) {
7265        #} Defaults:
7266        global $zbsCompanyFields;
7267        if ( is_array( $zbsCompanyFields ) ) {
7268            $zbsStatusStr = implode( ',', $zbsCompanyFields['status'][3] );
7269        }
7270    }
7271
7272    return $zbsStatusStr;
7273}
7274
7275/**
7276 * Retrieve an array of valid company statuses
7277 */
7278function zeroBSCRM_getCompanyStatuses() {
7279    $statuses_str = zeroBSCRM_getCompanyStatusesCSV();
7280
7281    if ( str_contains( $statuses_str, ',' ) ) {
7282        return explode( ',', $statuses_str );
7283    } else {
7284        return array();
7285    }
7286}
7287
7288        // ======= / Statuses wrappers - bit antiquated  now...
7289
7290    // DELETES ALL rows from any table, based on ID
7291    // no limits! be careful.
7292function zeroBSCRM_db2_deleteGeneric( $id = -1, $tableKey = '' ) {
7293
7294    // req
7295    global $ZBSCRM_t, $wpdb;
7296
7297    // lazy id check
7298    $id = (int) $id;
7299    if ( $id > 0 && ! empty( $tableKey ) && array_key_exists( $tableKey, $ZBSCRM_t ) ) {
7300
7301        return $wpdb->delete(
7302            $ZBSCRM_t[ $tableKey ],
7303            array( // where
7304                'ID' => $id,
7305            ),
7306            array(
7307                '%d',
7308            )
7309        );
7310
7311    }
7312
7313    return false;
7314}
7315
7316    // this has a js equivilent in global.js: zeroBSCRMJS_telURLFromNo
7317function zeroBSCRM_clickToCallPrefix() {
7318
7319    $click2CallType = zeroBSCRM_getSetting( 'clicktocalltype' );
7320
7321    if ( $click2CallType === 1 ) {
7322        return 'tel:';
7323    }
7324    if ( $click2CallType === 2 ) {
7325        return 'callto:';
7326    }
7327    if ( $click2CallType === 3 ) {
7328        return 'sip:';
7329    }
7330}
7331
7332function zeroBS_getCurrentUserUsername() {
7333
7334    // https://codex.wordpress.org/Function_Reference/wp_get_current_user
7335
7336    $current_user = wp_get_current_user();
7337    if ( ! ( $current_user instanceof WP_User ) ) {
7338        return;
7339    }
7340    return $current_user->user_login;
7341}
7342
7343    #} ZBS users page - returns list of WP user IDs, which have a ZBS role and includes name / email, etc
7344function zeroBSCRM_crm_users_list() {
7345    // from Permissions
7346    /*
7347    remove_role('zerobs_admin');
7348    remove_role('zerobs_customermgr');
7349    remove_role('zerobs_quotemgr');
7350    remove_role('zerobs_invoicemgr');
7351    remove_role('zerobs_transactionmgr');
7352    remove_role('zerobs_customer');
7353    remove_role('zerobs_mailmgr');
7354
7355    */
7356    // NOT zerbs_customer - this is people who have purchased (i.e. WooCommerce folk)
7357    $role      = array( 'zerobs_customermgr', 'zerobs_admin', 'administrator', 'zerobs_quotemgr', 'zerobs_invoicemgr', 'zerobs_transactionmgr', 'zerobs_mailmgr' );
7358    $crm_users = get_users(
7359        array(
7360            'role__in' => $role,
7361            'orderby'  => 'ID',
7362        )
7363    );
7364
7365    // this will return what WP holds (and can interpret on the outside.)
7366    return $crm_users;
7367}
7368
7369    // returns a system setting for ignore ownership
7370    // ... ownership ignored, unless the setting is on + not admin
7371function zeroBSCRM_DAL2_ignoreOwnership( $objType = 1 ) {
7372
7373    global $zbs;
7374
7375    // FOR NOW EVERYONE CAN SEE EVERYTHING
7376    // Later add - strict ownership? isn't this a platform UPSELL?
7377    // if ($zbs->settings->get('perusercustomers') && !current_user_can('administrator')) return false;
7378
7379    return true;
7380}
7381
7382function zeroBSCRM_DEPRECATEDMSG( $msg = '' ) {
7383
7384    echo '<div class="zbs info msg">' . $msg . '</div>';
7385    error_log( strip_tags( $msg ) );
7386}
7387
7388    /**
7389     * This takes a passed object type (old or new) and returns the new type.
7390     *
7391     * @param string|int - an object type in old or new format, e.g.:
7392     *   old: 'zerobs_customer'
7393     *   new: 1, ZBS_TYPE_CONTACT
7394     *
7395     * @return int|bool false - the object type ID if it exists, false if not
7396     */
7397function jpcrm_upconvert_obj_type( $obj_type = -1 ) {
7398    global $zbs;
7399
7400    if ( $zbs->DAL->isValidObjTypeID( $obj_type ) ) {
7401        // already a valid new obj?
7402        return (int) $obj_type;
7403    } else {
7404        // upconvert old type into new
7405        return $zbs->DAL->objTypeID( $obj_type );
7406    }
7407}
7408
7409    /**
7410     * Backward compat - `zbsLink` got renamed to `jpcrm_esc_link` in 5.5
7411     **/
7412function zbsLink( $key = '', $id = -1, $type = 'zerobs_customer', $prefixOnly = false, $taxonomy = false ) {
7413
7414    return jpcrm_esc_link( $key, $id, $type, $prefixOnly, $taxonomy );
7415}
7416
7417    /**
7418     * Core Link building function
7419     * Produces escaped raw URLs for links within wp-admin based CRM areas
7420     *
7421        Examples:
7422        echo '<a href="'.jpcrm_esc_link('edit',-1,'contact',false,false).'">New Contact</a>';
7423        echo '<a href="'.jpcrm_esc_link('edit',$id,'contact',false,false).'">Edit Contact</a>';
7424
7425     * Notes:
7426     * - accepts new (contact,ZBS_TYPE_CONTACT) or old (zerobs_customer) references (but use NEW going forward)
7427     * - previously called `zbsLink`
7428     **/
7429function jpcrm_esc_link( $key = '', $id = -1, $type = 'zerobs_customer', $prefixOnly = false, $taxonomy = false ) {
7430
7431    global $zbs;
7432
7433    // infer objTypeID (turns contact|zerobs_contact -> ZBS_TYPE_CONTACT)
7434    $objTypeID = jpcrm_upconvert_obj_type( $type );
7435
7436    // switch through potentials
7437    switch ( $key ) {
7438
7439        case 'list':
7440            $url = admin_url( 'admin.php?page=' . $zbs->slugs['dash'] );
7441
7442            // switch based on type.
7443            switch ( $objTypeID ) {
7444
7445                case ZBS_TYPE_CONTACT:
7446                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['managecontacts'] );
7447                    break;
7448                case ZBS_TYPE_COMPANY:
7449                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['managecompanies'] );
7450                    break;
7451                case ZBS_TYPE_QUOTE:
7452                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['managequotes'] );
7453                    break;
7454                case ZBS_TYPE_INVOICE:
7455                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['manageinvoices'] );
7456                    break;
7457                case ZBS_TYPE_TRANSACTION:
7458                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['managetransactions'] );
7459                    break;
7460                case ZBS_TYPE_FORM:
7461                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['manageformscrm'] );
7462                    break;
7463                case ZBS_TYPE_TASK:
7464                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['manage-tasks'] );
7465                    break;
7466                case ZBS_TYPE_SEGMENT:
7467                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['segments'] );
7468                    break;
7469                case ZBS_TYPE_QUOTETEMPLATE:
7470                    $url = admin_url( 'admin.php?page=' . $zbs->slugs['quote-templates'] );
7471                    break;
7472
7473            }
7474
7475            // rather than return admin.php?page=list, send to dash if not these ^
7476            return esc_url_raw( $url );
7477
7478            break;
7479
7480        case 'view':
7481            // view page (theoretically returns for all obj types, even tho contact + company only ones using view pages atm)
7482            if ( $objTypeID > 0 ) {
7483
7484                if ( $id > 0 ) {
7485
7486                    // view with actual ID
7487                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=view&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' . $id ) );
7488
7489                } elseif ( $prefixOnly ) {
7490
7491                    // prefix only
7492                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=view&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' ) );
7493
7494                }
7495            } // / got objType
7496            break;
7497
7498        case 'edit':
7499            // edit page (returns for all obj types)
7500            if ( $objTypeID > 0 ) {
7501
7502                if ( $id > 0 ) {
7503
7504                    // view with actual ID
7505                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=edit&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' . $id ) );
7506
7507                } elseif ( $prefixOnly ) {
7508
7509                    // prefix only
7510                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=edit&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' ) );
7511
7512                }
7513            } // / got objType
7514            break;
7515        case 'create':
7516            // create page (returns for all obj types)
7517            if ( $objTypeID > 0 ) {
7518
7519                return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=edit&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) ) );
7520
7521            } // / got objType
7522
7523            // mail campaigns specific catch
7524            if ( $type == 'mailcampaign' || $type == 'mailsequence' ) {
7525                global $zeroBSCRM_MailCampaignsslugs;
7526                if ( isset( $zeroBSCRM_MailCampaignsslugs ) ) {
7527                    return esc_url_raw( admin_url( 'admin.php?page=' . $zeroBSCRM_MailCampaignsslugs['editcamp'] ) );
7528                }
7529            }
7530
7531            break;
7532
7533        case 'delete':
7534            // delete page
7535            if ( $objTypeID > 0 ) {
7536
7537                if ( $id > 0 ) {
7538
7539                    // view with actual ID
7540                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=delete&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' . $id ) );
7541
7542                } elseif ( $prefixOnly ) {
7543
7544                    // prefix only
7545                    return esc_url_raw( admin_url( 'admin.php?page=zbs-add-edit&action=delete&zbstype=' . $zbs->DAL->objTypeKey( $objTypeID ) . '&zbsid=' ) );
7546
7547                }
7548            } // / got objType
7549            break;
7550        case 'tags':
7551            // Tag manager page (returns for all obj types)
7552            if ( $objTypeID > 0 ) {
7553
7554                return esc_url_raw( admin_url( 'admin.php?page=' . $zbs->slugs['tagmanager'] . '&tagtype=' . $zbs->DAL->objTypeKey( $objTypeID ) ) );
7555
7556            } // / got objType
7557
7558            break;
7559
7560        case 'listtagged':
7561            // List view -> tagged (returns for all obj types)
7562            if ( $objTypeID > 0 ) {
7563
7564                // exception: event tags
7565                if ( $objTypeID == ZBS_TYPE_TASK ) {
7566
7567                    return esc_url_raw( admin_url( 'admin.php?page=' . $zbs->slugs['manage-tasks-list'] . ( is_string( $taxonomy ) ? '&zbs_tag=' . $taxonomy : '' ) ) );
7568
7569                }
7570
7571                return esc_url_raw( admin_url( 'admin.php?page=' . $zbs->DAL->listViewSlugFromObjID( $objTypeID ) . ( is_string( $taxonomy ) ? '&zbs_tag=' . $taxonomy : '' ) ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
7572
7573            } // / got objType
7574            break;
7575
7576        case 'email':
7577            switch ( $objTypeID ) {
7578
7579                case ZBS_TYPE_CONTACT:
7580                    if ( $id > 0 ) {
7581
7582                        // email with actual ID
7583                        return esc_url_raw( zeroBSCRM_getAdminURL( $zbs->slugs['emails'] ) . '&zbsprefill=' . $id );
7584
7585                    } elseif ( $prefixOnly ) {
7586
7587                        // page only
7588                        return esc_url_raw( zeroBSCRM_getAdminURL( $zbs->slugs['emails'] ) . '&zbsprefill=' );
7589
7590                    }
7591
7592                    break;
7593
7594            }
7595
7596            break;
7597
7598    }
7599
7600    // if $key isn't in switch, assume it's a slug :)
7601    return esc_url_raw( admin_url( 'admin.php?page=' . $key ) );
7602
7603    // none? DASH then!
7604    // return esc_url_raw( admin_url('admin.php?page=zerobscrm-dash') );
7605}
7606
7607#} This is run by main init :) (Installs Quote Templates etc.)
7608function zeroBSCRM_installDefaultContent() {
7609
7610    global $zbs;
7611
7612    #} Quote Builder, defaults
7613    $quoteBuilderDefaultsInstalled = zeroBSCRM_getSetting( 'quotes_default_templates' );
7614
7615    if ( ! is_array( $quoteBuilderDefaultsInstalled ) ) {
7616
7617        #} Need installing!
7618        $installedQuoteTemplates = array();
7619
7620        #} Load content
7621        $quoteBuilderDefaultTemplates = array();
7622
7623        #} Web Design: Example
7624        $templatedHTML = file_get_contents( ZEROBSCRM_PATH . 'html/quotes/quote-template-web-design.html' );
7625        if ( ! empty( $templatedHTML ) ) {
7626            $quoteBuilderDefaultTemplates['webdesignexample'] = array(
7627                'title' => __( 'Web Design: Example', 'zero-bs-crm' ),
7628                'html'  => $templatedHTML,
7629                'value' => 500.00,
7630            );
7631        }
7632
7633        #} Install..
7634        if ( count( $quoteBuilderDefaultTemplates ) > 0 ) {
7635            foreach ( $quoteBuilderDefaultTemplates as $template ) {
7636
7637                // Insert via DAL3
7638                $newTemplateID = $zbs->DAL->quotetemplates->addUpdateQuotetemplate(
7639                    array(
7640                        // fields (directly)
7641                        'data'      => array(
7642
7643                            'title'       => $template['title'],
7644                            'value'       => $template['value'],
7645                            'date_str'    => '',
7646                            'date'        => '',
7647                            'content'     => $template['html'],
7648                            'notes'       => '',
7649                            'currency'    => '',
7650                            'created'     => time(),
7651                            'lastupdated' => time(),
7652
7653                        ),
7654
7655                        'extraMeta' => array( 'zbsdefault' => 1 ),
7656                    )
7657                );
7658
7659                if ( $newTemplateID > 0 ) {
7660                    $installedQuoteTemplates[] = $newTemplateID;
7661                }
7662            }
7663        }
7664
7665        #} Log installed
7666        $zbs->settings->update( 'quotes_default_templates', $installedQuoteTemplates );
7667
7668    }
7669}
7670
7671/*
7672======================================================
7673    / General/WP helpers
7674====================================================== */