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