Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.33% covered (danger)
2.33%
22 / 946
1.18% covered (danger)
1.18%
1 / 85
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZeroBSCRM
2.33% covered (danger)
2.33%
22 / 944
1.18% covered (danger)
1.18%
1 / 85
107411.06
0.00% covered (danger)
0.00%
0 / 1
 instance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 __wakeup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 verify_minimum_requirements
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 verify_extension_minimum_requirements
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 add_wp_admin_notice
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 wp_admin_notices
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 define_constants
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 debugMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 is_database_installed
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_database_server_info
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
42
 isDAL2
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isDAL3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 extensionCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setupUrlsSlugsEtc
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 1
56
 includes
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 1
30
 init_hooks
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 filterExtensions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 loadBaseExternalSources
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadExtraExternalSources
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
132
 filterExternalSources
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildOutObjectModels
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 add_action_links
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_row_meta
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 post_init_plugins_loaded
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 preInit
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
56
 postSettingsIncludes
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
110
 init
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
870
 postInit
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 post_wp_loaded
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 admin_init
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 pre_deactivation_check_exts_deactivated
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 uninstall
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
240
 install
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 wizardInitCheck
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
110
 include_updater
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 jpcrm_register_modules
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 jpcrm_sniff_features
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 log_errors
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 write_log
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 define
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 is_request
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
56
 is_active_theme
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 frontend_includes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_path
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkSettingsSetup
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
3.19
 checkBackendAccess
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 ajax_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_domain
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 globalise_vars
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
420
 zbsvar
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
30
 setPageTitle
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
600
 userScreenOptions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 global_screen_options
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 user
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 license
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 has_license_key
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 hasEntrepreneurBundleMin
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 hasFreelancerBundleMin
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getSubscriptionLabel
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
132
 getMenu
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 admin_menu
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 applyMenu
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 initialise_learn_menu
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 has_min_php_version
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 autoload_from_directory
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 load_admin_ajax
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 run_migrations
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 woocommerce_is_active
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
 mailpoet_is_active
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 load_usage_tracking
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 load_listener
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 load_oauth_handler
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 load_encryption
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 load_package_installer
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 ensure_package_installed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 pdf_engine
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 get_templating
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_fonts
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getErrorCode
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 get_page_messages_transient_key
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_retrieve_page_messages
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 new_record_edit_redirect
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 catch_preheader_interrupts
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Jetpack CRM Core
4 *
5 * @author  Woody Hayday, Mike Stott
6 * @package automattic/jetpack-crm
7 * @since   2.27
8 */
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 ); // Exit if accessed directly.
12}
13
14/**
15 * Main ZeroBSCRM Class.
16 *
17 * @class ZeroBSCRM
18 * @version 2.27
19 */
20final class ZeroBSCRM {
21
22    /**
23     * Jetpack CRM version.
24     *
25     * @var string
26     */
27    const VERSION = '6.7.2';
28
29    /**
30     * Jetpack CRM version (used in various extensions as of January 2025).
31     *
32     * @deprecated
33     * @var string
34     */
35    public $version = '';
36
37    /**
38     * WordPress version tested with.
39     *
40     * @var string
41     */
42    public $wp_tested = '6.9';
43
44    /**
45     * WordPress update API version.
46     *
47     * @var string
48     */
49    public $api_ver = '1.0';
50
51    /**
52     * Jetpack CRM update API version.
53     *
54     * @var string
55     */
56    public $update_api_version = '1.0';
57
58    /**
59     * ZeroBSCRM DB version.
60     *
61     * @var string
62     */
63    public $db_version = '3.0';
64
65    /**
66     * Database details.
67     *
68     * @var array
69     */
70    public $database_server_info = array();
71
72    /**
73     * ZeroBSCRM DAL version.
74     *
75     * @var string
76     */
77    public $dal_version = '3.0';
78
79    /**
80     * ZeroBSCRM Extension Compatible versions
81     * Stores which extensions are viable to use with newly-migrated v3.0
82     *
83     * @var string
84     */
85    public $compat_versions = array(
86
87        // v3.0 Migration needed a 'minimum version' for any extensions which might not work with v3.0 but were active premigration
88        // 15th Nov - as numbers it does not like the 1.4.1 type format so added as strings.
89        'v3extminimums' => array(
90
91            'advancedsegments'   => '1.3',
92            'apiconnector'       => '1.6',
93            'automations'        => '1.4.1',
94            'aweber'             => '1.2',
95            'awesomesupport'     => '2.5',
96            'batchtag'           => '2.3',
97            'passwordmanager'    => '1.4.1',
98            'clientportalpro'    => '1.7',
99            'contactform'        => '2.5',
100            'convertkit'         => '2.5',
101            'csvpro'             => '2.0',
102            'envato'             => '2.4.2',
103            'exitbee'            => '1.1',
104            'funnels'            => '1.2',
105            'googlecontact'      => '2.6',
106            'gravity'            => '2.6',
107            'groove'             => '2.6',
108            'invpro'             => '2.6',
109            'livestorm'          => '1.1',
110            'mailcamp'           => '2.0.4',
111            'mailchimp'          => '2.6',
112            'membermouse'        => '1.5',
113            'optinmonster'       => '1.1',
114            'paypal'             => '2.6.1',
115            'registrationmagic'  => '1.1',
116            'salesdash'          => '2.6',
117            'stripe'             => '2.6.2',
118            'systememail'        => '1.1',
119            'twilio'             => '1.5',
120            'woosync'            => '2.9',
121            'wordpressutilities' => '1.2',
122            'worldpay'           => '2.4',
123
124        ),
125
126    );
127
128    /**
129     * ZeroBSCRM DAL .
130     *
131     * @var object (DAL Class init) ;)
132     */
133    public $DAL = false;
134
135    /**
136     * Dependency checker
137     *
138     * @var object JPCRM_DependencyChecker init
139     */
140    public $dependency_checker = false;
141
142    /**
143     * Feature sniffer
144     *
145     * @var object JPCRM_FeatureSniffer init
146     */
147    public $feature_sniffer = false;
148
149    /**
150     * WordPress User Integrations
151     *
152     * @var object Wordpress_User_Integration class
153     */
154    public $wordpress_user_integration = false;
155
156    /**
157     * Zapier integration
158     *
159     * @var ?
160     */
161    public $zapier = false;
162
163    /**
164     * Fonts
165     *
166     * @var object JPCRM_Fonts init
167     */
168    public $fonts = false;
169
170    /**
171     * DB1 compatability support
172     *
173     * @var Bool - if true, basically $obj['meta'] is a clone of $obj itself (To be disabled once safely in DAL2 + updated extensions)
174     */
175    public $db1CompatabilitySupport = false;
176
177    /**
178     * DB2 compatability support
179     *
180     * @var Bool - if true, basically $obj['meta'] is a clone of $obj itself (To be disabled once safely in DAL3 + updated extensions)
181     * This variant accounts for stray objs in quotes, trans, invs, etc.
182     */
183    public $db2CompatabilitySupport = false;
184
185    /**
186     * ZeroBSCRM DB Version Switch.
187     *
188     * @var string
189     */
190    public $DBVER = 1;
191
192    /**
193     * The single instance of the class.
194     *
195     * @var ZeroBSCRM
196     * @since 2.1
197     */
198    protected static $_instance = null;
199
200    /**
201     * JP CRM Page Loaded (KEY - used for screenoptions) (equivilent of pagenow)
202     *
203     * @var string
204     */
205    public $pageKey = 'root';
206
207    /**
208     * WordPress Admin notice stack
209     *
210     * @var array
211     */
212    public $admin_notices = array();
213
214    /**
215     * Hide admin_notices for specified pages
216     *
217     * @var array
218     */
219    public $hide_admin_pages = array(
220
221        // hidden due to #gh-1442
222        'manage-tasks',
223        'zerobscrm-csvimporterlite-app',
224
225    );
226
227    /**
228     * Template path, this is where we look in the theme directory for templates
229     *
230     * @var string
231     */
232    public $template_path = 'jetpack-crm';
233
234    /**
235     * Extensions instances
236     *
237     * @var ?array
238     */
239    public $extensions = null;
240
241    /**
242     * External Sources
243     *
244     * @var ?array
245     */
246    public $external_sources = null;
247
248    /**
249     * Listview filters
250     *
251     * @var array Jetpack CRM External Sources
252     */
253    public $listview_filters = array();
254
255    /**
256     * Settings Object
257     *
258     * @var ?WHWPConfigLib
259     */
260
261    public $settings = null;
262
263    /**
264     * Internal Automator Block
265     *
266     * @var Bool - if true, IA will not fire anything :)
267     */
268    public $internalAutomatorBlock = false;
269
270    /**
271     * Metaboxes Object
272     *
273     * @var ?array
274     */
275
276    public $metaboxes = null;
277
278    /**
279     * Menus Object
280     *
281     * @var ?array
282     * This ultimately adds any WP menus that need injecting
283     */
284    private $menu = null;
285
286    /**
287     * Learn Menu Object
288     *
289     * @var ?\Automattic\JetpackCRM\Learn_Menu
290     */
291    public $learn_menu = null;
292
293    /**
294     * URLS Array
295     *
296     * @var array
297     */
298    public $urls;
299
300    /**
301     * Slugs Array
302     *
303     * @var array
304     */
305    public $slugs;
306
307    /**
308     * Transient Array
309     *
310     * @var array
311     */
312    public $transients;
313
314    /**
315     * Houses all module classes
316     * e.g. $zbs->modules->woosync
317     */
318    public $modules = null;
319
320    /**
321     * Package installer (Automattic\JetpackCRM\Package_Installer)
322     */
323    public $package_installer = null;
324
325    /**
326     * OAuth handler
327     */
328    public $oauth = null;
329
330    /**
331     * Endpoint Listener
332     */
333    public $listener = null;
334
335    /**
336     * Encryption tooling
337     */
338    public $encryption = null;
339
340    /**
341     * Included Array (means we cannot 'reinclude' stripe etc.)
342     */
343    public $included = array(
344
345        'stripe' => false,
346
347    );
348
349    /**
350     * Usage Tracking
351     *
352     * @var object Usage tracking class
353     */
354    public $tracking = false;
355
356    /**
357     * Page Messages Array
358     * Experimental: stores msgs such as "Contact Updated"
359     *
360     * @var array
361     */
362    public $pageMessages;
363
364    /**
365     * Templating: placeholders
366     *
367     * @var object Placeholder Class
368     */
369    public $templating_placeholders = false;
370
371    /**
372     * Acceptable mime types Array
373     *
374     * @var array
375     */
376    public $acceptable_mime_types;
377
378    /**
379     * Acceptable fields to be included in the Total Value of contacts and companies
380     *
381     * @var array
382     */
383    public $acceptable_total_value_fields = array(
384        'transactions' => 'Transactions',
385        'invoices'     => 'Invoices',
386    );
387
388    /**
389     * Acceptable html array
390     *
391     * @var array
392     * Was previously: $zeroBSCRM_allowedHTML
393     */
394    public $acceptable_html = array(
395        'h1'         => array(
396            'class' => array(),
397            'style' => array(),
398            'id'    => array(),
399        ),
400        'h2'         => array(
401            'class' => array(),
402            'style' => array(),
403            'id'    => array(),
404        ),
405        'h3'         => array(
406            'class' => array(),
407            'style' => array(),
408            'id'    => array(),
409        ),
410        'h4'         => array(
411            'class' => array(),
412            'style' => array(),
413            'id'    => array(),
414        ),
415        'h5'         => array(
416            'class' => array(),
417            'style' => array(),
418            'id'    => array(),
419        ),
420        'h6'         => array(
421            'class' => array(),
422            'style' => array(),
423            'id'    => array(),
424        ),
425        'a'          => array(
426            'href'   => array(),
427            'title'  => array(),
428            'target' => array(),
429            'class'  => array(),
430        ),
431        'b'          => array(),
432        'br'         => array(),
433        'em'         => array(),
434        'strong'     => array(),
435        'ul'         => array(),
436        'ol'         => array(),
437        'li'         => array(),
438        'p'          => array(
439            'style' => true,
440        ),
441        'div'        => array(
442            'class' => array(),
443            'style' => array(),
444            'id'    => array(),
445        ),
446        'span'       => array(
447            'class' => array(),
448            'style' => array(),
449            'id'    => array(),
450        ),
451        'img'        => array(
452            'class' => array(),
453            'style' => array(),
454            'src'   => array(),
455        ),
456        'i'          => array(
457            'class' => array(),
458        ),
459        'table'      => array(
460            'tr'    => array(
461                'th'    => array(
462                    'label' => array(),
463                ),
464                'class' => array(),
465                'label' => array(),
466                'th'    => array(),
467            ),
468            'style' => array(),
469            'label' => array(),
470        ),
471        'td'         => array(),
472        'tr'         => array(),
473        'blockquote' => array(),
474        'del'        => array(),
475        'hr'         => array(),
476    );
477
478    /**
479     * Acceptable (restricted) html array
480     *
481     * @var array
482     * (e.g. for use in contact logs)
483     */
484    public $acceptable_restricted_html = array(
485        'a'          => array(
486            'href'   => array(),
487            'title'  => array(),
488            'id'     => array(),
489            'target' => array(),
490        ),
491        'br'         => array(),
492        'em'         => array(),
493        'strong'     => array(),
494        'blockquote' => array(),
495    );
496
497    /**
498     * Error Codes Array
499     * Experimental: loads + stores error codes, (only when needed/requested)
500     *
501     * @var error code arr
502     */
503    private $errorCodes;
504
505    /**
506     * Main ZeroBSCRM Instance.
507     *
508     * Ensures only one instance of ZeroBSCRM is loaded or can be loaded.
509     *
510     * @since 2.27
511     * @static
512     * @return ZeroBSCRM - Main instance.
513     */
514    public static function instance() {
515        if ( self::$_instance === null ) {
516            self::$_instance = new self();
517        }
518        return self::$_instance;
519    }
520
521    /**
522     * Unserializing instances of this class is forbidden.
523     *
524     * @since 2.1
525     */
526    public function __wakeup() {
527        zerobscrm_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'zero-bs-crm' ), '2.1' );
528    }
529
530    /**
531     * Auto-load in-accessible properties on demand - What is this wizadrey?
532     *
533     * @param mixed $key Key name.
534     * @return mixed
535     */
536    /*
537    See: http://php.net/manual/en/language.oop5.overloading.php#object.get
538    public function __get( $key ) {
539        if ( in_array( $key, array( 'payment_gateways', 'shipping', 'mailer', 'checkout' ), true ) ) {
540            return $this->$key();
541        }
542    }
543
544    */
545
546    /**
547     * Jetpack CRM Constructor.
548     */
549    public function __construct() {
550        // @phan-suppress-next-line PhanDeprecatedProperty - Define old property for backward compatibility.
551        $this->version = $this::VERSION;
552
553        // Simple global definitions without loading any core files...
554        // required for verify_minimum_requirements()
555
556        // define constants & globals
557        $this->define_constants();
558
559        // Verify we have minimum requirements (e.g. DAL3.0 and extension versions up to date)
560        if ( $this->verify_minimum_requirements() ) {
561
562            $this->debugMode();
563
564            // } Load includes
565            $this->includes();
566
567            // urls, slugs, (post inc.)
568            $this->setupUrlsSlugsEtc();
569
570            // } Initialisation
571            $this->init_hooks();
572
573            // } Post Init hook
574            do_action( 'zerobscrm_loaded' );
575
576        } else {
577            // used by some extensions to determine if current page is an admin page
578            require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.AdminPages.Checks.php';
579
580            // extensions use the dependency checker functions
581            require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-dependency-checker.php';
582            $this->dependency_checker = new JPCRM_DependencyChecker();
583        }
584
585        // display any wp admin notices in the stack
586        // needs to be outside of any above functionality as used for exposing failed verify_minimum_requirements()
587        add_action( 'admin_notices', array( $this, 'wp_admin_notices' ) );
588    }
589
590    /**
591     * Verify we have minimum requirements (e.g. DAL3.0)
592     */
593    private function verify_minimum_requirements() {
594
595        // fresh installs get a pass so that migrations can run
596        // ... as soon as DB is installed, this'll be skipped
597
598        // gather database server info
599        $this->get_database_server_info();
600
601        if ( ! $this->is_database_installed() ) {
602
603            return true;
604
605        }
606
607        // v5.0+ JPCRM requires DAL3+
608        if ( ! $this->isDAL3() ) {
609
610            // we need urls
611            $this->setupUrlsSlugsEtc();
612
613            // build message
614            $message_html = '<p>' . sprintf( esc_html__( 'This version of CRM (%1$s) requires an upgraded database (3.0). Your database is using an older version than this (%2$s). To use CRM you will need to install version 4 of CRM and run the database upgrade.', 'zero-bs-crm' ), $this::VERSION, $this->dal_version ) . '</p>'; // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
615
616            ##WLREMOVE
617            $message_html  = '<p>' . sprintf( esc_html__( 'This version of Jetpack CRM (%1$s) requires an upgraded database (3.0). Your database is using an older version than this (%2$s). To use Jetpack CRM you will need to install version 4 of Jetpack CRM and run the database upgrade.', 'zero-bs-crm' ), $this::VERSION, $this->dal_version ) . '</p>'; // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
618            $message_html .= '<p><a href="' . esc_url( $this->urls['kb-pre-v5-migration-todo'] ) . '" target="_blank" class="button">' . __( 'Read the guide on migrating', 'zero-bs-crm' ) . '</a></p>';
619            ##/WLREMOVE
620
621            $this->add_wp_admin_notice(
622                '',
623                array(
624                    'class' => 'warning',
625                    'html'  => $message_html,
626                )
627            );
628
629            return false;
630
631        } elseif ( ! function_exists( 'openssl_get_cipher_methods' ) ) {
632
633            // build message
634            $message_html  = '<p>' . sprintf( __( 'Jetpack CRM uses the OpenSSL extension for PHP to properly protect sensitive data. Most PHP environments have this installed by default, but it seems yours does not; we recommend contacting your host for further help.', 'zero-bs-crm' ), $this::VERSION, $this->dal_version ) . '</p>';
635            $message_html .= '<p><a href="' . esc_url( 'https://www.php.net/manual/en/book.openssl.php' ) . '" target="_blank" class="button">' . __( 'PHP docs on OpenSSL', 'zero-bs-crm' ) . '</a></p>';
636
637            $this->add_wp_admin_notice(
638                '',
639                array(
640                    'class' => 'warning',
641                    'html'  => $message_html,
642                )
643            );
644            return false;
645        }
646
647        return true;
648    }
649
650    /**
651     * Verify our extensions meet minimum requirements (e.g. DAL3.0)
652     * Where extensions are found which do not meet requirements, these are deactivated and notices posted
653     * Note: This has to fire lower down the stack than `verify_minimum_requirements()`
654     *       because it relies on extension linkages
655     */
656    private function verify_extension_minimum_requirements() {
657
658        // v5.0+ JPCRM/DAL3 requires these extension versions
659        $installed_extensions = zeroBSCRM_installedProExt();
660        if ( is_array( $installed_extensions ) ) {
661
662            foreach ( $installed_extensions as $extension_name => $extension_info ) {
663
664                // get minimum version okay with v3
665                $minimum_version = 99.99;
666                if ( isset( $this->compat_versions['v3extminimums'][ $extension_info['key'] ] ) ) {
667                    $minimum_version = $this->compat_versions['v3extminimums'][ $extension_info['key'] ];
668                }
669
670                // do we have an active outdated version?
671                if ( $extension_info['active'] == 1 && $minimum_version > 0 && ! ( version_compare( $extension_info['ver'], $minimum_version ) >= 0 ) ) {
672
673                    // deactivate
674                    jpcrm_extensions_deactivate_by_key( $extension_info['key'] );
675
676                    // show warning notice
677                    $message_html = '<p>' . sprintf( __( 'Your CRM extension %1$s (v%2$s) is not compatible with this version of CRM. You will need to run a database upgrade to use this extension. For now this extension has been deactivated.', 'zero-bs-crm' ), $extension_name, $extension_info['ver'] ) . '</p>';
678
679                    ##WLREMOVE
680                    $message_html  = '<p>' . sprintf( __( 'Your Jetpack CRM extension %1$s (v%2$s) is not compatible with this version of Jetpack CRM. You will need to run a database upgrade to be able to use this extension. For now this extension has been deactivated.', 'zero-bs-crm' ), $extension_name, $extension_info['ver'] ) . '</p>';
681                    $message_html .= '<p><a href="' . esc_url( $this->urls['kb-pre-v5-migration-todo'] ) . '" target="_blank" class="button">' . __( 'Read the guide on migrating', 'zero-bs-crm' ) . '<a></p>';
682                    ##/WLREMOVE
683
684                    $this->add_wp_admin_notice(
685                        '',
686                        array(
687                            'class' => 'warning',
688                            'html'  => $message_html,
689                        )
690                    );
691
692                }
693            }
694        }
695
696        return false;
697    }
698
699    /**
700     * Add admin notice to the stack
701     */
702    private function add_wp_admin_notice( $page, $notice ) {
703
704        // validate existing
705        if ( ! is_array( $this->admin_notices ) ) {
706            $this->admin_notices = array();
707        }
708
709        // add to stack if new page
710        if ( ! isset( $this->admin_notices[ $page ] ) ) {
711            $this->admin_notices[ $page ] = array();
712        }
713
714        // add notice to stack
715        $this->admin_notices[ $page ][] = $notice;
716    }
717
718    /**
719     * Output any admin notices in the stack
720     */
721    public function wp_admin_notices() {
722
723        global $pagenow;
724
725        if ( is_array( $this->admin_notices ) ) {
726
727            foreach ( $this->admin_notices as $page => $notices ) {
728
729                // matching page or all pages (empty)
730                if ( $pagenow == $page || empty( $page ) ) {
731
732                    foreach ( $notices as $notice ) {
733                        wp_admin_notice(
734                            wp_kses(
735                                $notice['html'],
736                                array(
737                                    'a' => array(
738                                        'href'   => array(),
739                                        'target' => array(),
740                                        'class'  => array(),
741                                    ),
742                                    'p' => array(),
743                                )
744                            ),
745                            array(
746                                'type'           => esc_attr( $notice['class'] ),
747                                'dismissible'    => true,
748                                'paragraph_wrap' => false,
749                            )
750                        );
751                    }
752                }
753            }
754        }
755    }
756
757    /**
758     *   Maintain a list of Jetpack CRM extension slugs here.
759     *      (This was an MS initiative for updates/licensing, WH removed 27/11/18, doing via Keys = rebrandr friendly)
760     */
761    /*
762    public $zeroBSCRM_extensionSlugs = array(
763
764        'ZeroBSCRM_BulkTagger.php',
765
766
767    );
768     */
769
770    /**
771     * Define ZeroBSCRM Constants.
772     */
773    private function define_constants() {
774
775        // Main paths etc.
776        $this->define( 'ZBS_ABSPATH', dirname( ZBS_ROOTFILE ) . '/' );
777        $this->define( 'ZEROBSCRM_PATH', plugin_dir_path( ZBS_ROOTFILE ) );
778        $this->define( 'ZEROBSCRM_URL', plugin_dir_url( ZBS_ROOTFILE ) );
779
780        // Template paths
781        $this->define( 'ZEROBSCRM_TEMPLATEPATH', ZEROBSCRM_PATH . 'templates/' );
782        $this->define( 'ZEROBSCRM_TEMPLATEURL', ZEROBSCRM_URL . 'templates/' );
783
784        // include/module paths
785        $this->define( 'ZEROBSCRM_INCLUDE_PATH', ZEROBSCRM_PATH . 'includes/' );
786        $this->define( 'JPCRM_MODULES_PATH', ZEROBSCRM_PATH . 'modules/' );
787
788        // } define that the CORE has been loaded - for backwards compatibility with other extensions
789        $this->define( 'ZBSCRMCORELOADED', true );
790
791        // } Menu types
792        $this->define( 'ZBS_MENU_FULL', 1 );
793        $this->define( 'ZBS_MENU_SLIM', 2 );
794        $this->define( 'ZBS_MENU_CRMONLY', 3 );
795
796        // } Debug
797        $this->define( 'ZBS_CRM_DEBUG', true );
798    }
799
800    private function debugMode() {
801
802        if ( defined( 'ZBS_CRM_DEBUG' ) ) {
803            /*
804            ini_set('display_errors', 1);
805            ini_set('display_startup_errors', 1);
806            error_reporting(E_ALL);
807            */
808        }
809    }
810
811    // shorthand for lack of presence of any DB presence
812    public function is_database_installed() {
813
814        global $ZBSCRM_t, $wpdb;
815
816        // we need db
817        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Database.php';
818
819        // check
820        $tables = $wpdb->get_results( "SHOW TABLES LIKE '" . $ZBSCRM_t['contacts'] . "'" );
821
822        return ( count( $tables ) > 0 );
823    }
824
825    /**
826     * Retrieves MySQL/MariaDB/Percona database server info
827     */
828    public function get_database_server_info() {
829        if ( empty( $this->database_server_info ) ) {
830
831            // Adapted from proposed SQLite integration for core
832            // https://github.com/WordPress/sqlite-database-integration/blob/4a687709bb16a569a7d1ecabfcce433c0e471de8/health-check.php
833            if ( defined( 'DB_ENGINE' ) && DB_ENGINE === 'sqlite' ) {
834                $db_engine       = DB_ENGINE;
835                $db_engine_label = 'SQLite';
836                $raw_version     = class_exists( 'SQLite3' ) ? SQLite3::version()['versionString'] : null;
837                $version         = $raw_version;
838            } else {
839                global $wpdb;
840                $raw_version = $wpdb->get_var( 'SELECT VERSION()' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
841                $version     = preg_replace( '/[^0-9.].*/', '', $raw_version );
842                if ( stripos( $raw_version, 'mariadb' ) !== false ) {
843                    $db_engine       = 'mariadb';
844                    $db_engine_label = 'MariaDB';
845                } else {
846                    $db_engine       = 'mysql';
847                    $db_engine_label = 'MySQL';
848                }
849            }
850
851            $database_server_info       = array(
852                'raw_version'     => $raw_version,
853                'version'         => $version,
854                'db_engine'       => $db_engine,
855                'db_engine_label' => $db_engine_label,
856            );
857            $this->database_server_info = $database_server_info;
858        }
859
860        return $this->database_server_info;
861    }
862
863    // } Use this for shorthand checking new DAL additions
864    // this says "is At least DAL2"
865    public function isDAL2() {
866
867        // is DAL > 1.0
868        return ( version_compare( $this->dal_version, '1.0' ) > 0 );
869    }
870
871    // } Use this for shorthand checking new DAL additions
872    // this says "is At least DAL3"
873    public function isDAL3() {
874
875        // is DAL > 1.0
876        return ( version_compare( $this->dal_version, '2.53' ) > 0 );
877    }
878
879    // } Use this to output the number of plugins with "Jetpack CRM" in the name
880    public function extensionCount( $activatedOnly = false ) {
881
882        /*
883        Following func: zeroBSCRM_extensionsInstalledCount
884        ... will get all active rebrandr extensions,
885        ... and all active/inactive branded extensions
886
887        ... and returns a count here */
888        return zeroBSCRM_extensionsInstalledCount( $activatedOnly );
889    }
890
891    private function setupUrlsSlugsEtc() {
892
893        // array check
894        if ( ! is_array( $this->urls ) ) {
895            $this->urls = array();
896        }
897        if ( ! is_array( $this->slugs ) ) {
898            $this->slugs = array();
899        }
900        if ( ! is_array( $this->transients ) ) {
901            $this->transients = array();
902        }
903        if ( ! is_array( $this->acceptable_mime_types ) ) {
904            $this->acceptable_mime_types = array();
905        }
906        if ( ! is_array( $this->pageMessages ) ) {
907            $this->pageMessages = array();
908        }
909
910        // Urls
911        $this->urls['home']              = 'https://jetpackcrm.com';
912        $this->urls['kb']                = 'https://kb.jetpackcrm.com';
913        $this->urls['support']           = 'https://kb.jetpackcrm.com/crm-support/';
914        $this->urls['feedback']          = 'https://kb.jetpackcrm.com/crm-support/';
915        $this->urls['pricing']           = 'https://jetpackcrm.com/pricing/';
916        $this->urls['usagetrackinginfo'] = 'https://jetpackcrm.com/usage-tracking/';
917        $this->urls['support-forum']     = 'https://wordpress.org/support/plugin/zero-bs-crm';
918
919        $this->urls['docs']              = 'https://kb.jetpackcrm.com/';
920        $this->urls['productsdatatools'] = 'https://jetpackcrm.com/data-tools/';
921        $this->urls['extimgrepo']        = 'https://jetpackcrm.com/_plugin_dependent_assets/_i/';
922        $this->urls['rateuswporg']       = 'https://wordpress.org/support/view/plugin-reviews/zero-bs-crm?filter=5#new-post';
923        $this->urls['extdlreporoot']     = 'https://jetpack-crm-cdn.s3.amazonaws.com/';
924        $this->urls['extdlrepo']         = $this->urls['extdlreporoot'] . 'ext/';
925        $this->urls['extdlfonts']        = $this->urls['extdlreporoot'] . 'fonts/';
926        $this->urls['extdlpackages']     = $this->urls['extdlreporoot'] . 'packages/';
927        $this->urls['apidocs']           = 'https://automattic.github.io/jetpack-crm-api-docs/';
928        $this->urls['oauthdocs']         = 'https://kb.jetpackcrm.com/knowledge-base/using-gmail-with-jetpack-crm-mail-delivery-system/#setting-up-gmail-oauth-connection-and-mail-delivery-method';
929        $this->urls['woosync']           = 'https://jetpackcrm.com/woocommerce/';
930        $this->urls['woomanagingorders'] = 'https://woocommerce.com/document/managing-orders/#order-statuses';
931
932        // used for ext manager:
933        $this->urls['checkoutapi']       = 'https://jetpackcrm.com/wp-json/zbsextensions/v1/extensions/0';
934        $this->urls['howtoinstall']      = 'https://kb.jetpackcrm.com/knowledge-base/how-do-i-install-a-zero-bs-extension/';
935        $this->urls['apiconnectorsales'] = 'https://jetpackcrm.com/product/api-connector/';
936        $this->urls['autonumberhelp']    = 'https://kb.jetpackcrm.com/knowledge-base/custom-field-type-autonumber/';
937        $this->urls['akamode']           = 'https://jetpackcrm.com/feature/aka-mode/';
938        $this->urls['licensinginfo']     = 'https://kb.jetpackcrm.com/knowledge-base/yearly-subscriptions-refunds/';
939        $this->urls['easyaccessguide']   = 'https://kb.jetpackcrm.com/knowledge-base/easy-access-links-for-client-portal/';
940
941        // API v3.0 - licensing - 5/12/18
942        $this->urls['api']           = 'https://app.jetpackcrm.com/api/updates/updates';
943        $this->urls['apilocalcheck'] = 'https://app.jetpackcrm.com/api/updates/localcheck';
944        $this->urls['smm']           = 'https://app.jetpackcrm.com/api/welcome-wizard';
945        $this->urls['api-support']   = 'https://app.jetpackcrm.com/api/support';
946
947        // account
948        $this->urls['account']     = 'https://app.jetpackcrm.com/';
949        $this->urls['licensekeys'] = 'https://app.jetpackcrm.com/license-keys';
950
951        // } sales urls
952        $this->urls['products']          = 'https://jetpackcrm.com/extensions/';
953        $this->urls['extcsvimporterpro'] = 'https://jetpackcrm.com/product/csv-importer-pro/';
954        $this->urls['invpro']            = 'https://jetpackcrm.com/product/invoicing-pro/';
955        $this->urls['upgrade']           = 'https://jetpackcrm.com/checkout/?plan=entrepreneur&utm_source=plugin&utm_medium=plugin&utm_campaign=welcome_upgrade';
956        $this->urls['extcpp']            = 'https://jetpackcrm.com/product/client-portal-pro/';
957        $this->urls['extcal']            = 'https://jetpackcrm.com/product/calendar-pro/';
958        $this->urls['roadtov3']          = 'https://jetpackcrm.com/road-to-v3/';
959        $this->urls['advancedsegments']  = 'https://jetpackcrm.com/product/advanced-segments/';
960        $this->urls['bulktagger']        = 'https://jetpackcrm.com/product/bulk-tagger/';
961        $this->urls['salesdash']         = 'https://jetpackcrm.com/product/sales-dashboard/';
962        $this->urls['connect-multi-woo'] = 'https://jetpackcrm.com/feature/multiple-woocommerce-stores/';
963        $this->urls['woosync']           = 'https://jetpackcrm.com/woocommerce/';
964        $this->urls['mailpoet']          = 'https://jetpackcrm.com/feature/mailpoet-crm-sync/';
965
966        $this->urls['feedbackform'] = 'https://forms.gle/k94AdShUHZ3LWPvx8';
967
968        // social
969        $this->urls['twitter'] = 'https://twitter.com/jetpackcrm';
970
971        // assets
972        $this->urls['crm-logo'] = plugins_url( 'i/jpcrm-logo-stacked-black.png', ZBS_ROOTFILE );
973
974        // kb
975        $this->urls['kbdevmode']                = 'https://kb.jetpackcrm.com/knowledge-base/developer-mode/';
976        $this->urls['kbquoteplaceholders']      = 'https://kb.jetpackcrm.com/knowledge-base/placeholders-in-emails-quote-templates-invoices-etc/#quote-template-placeholders';
977        $this->urls['kblicensefaq']             = 'https://kb.jetpackcrm.com/knowledge-base/license-keys-faq/';
978        $this->urls['kbcronlimitations']        = 'https://kb.jetpackcrm.com/knowledge-base/wordpress-cron-limitations/';
979        $this->urls['kbfirstcontact']           = 'https://kb.jetpackcrm.com/knowledge-base/adding-your-first-customer/';
980        $this->urls['kbactivatecoreext']        = 'https://kb.jetpackcrm.com/knowledge-base/how-to-activate-deactivate-core-modules/';
981        $this->urls['kbinvoicebuilder']         = 'https://kb.jetpackcrm.com/knowledge-base/how-to-use-the-invoice-builder/';
982        $this->urls['kbteam']                   = 'https://kb.jetpackcrm.com/knowledge-base/setting-up-your-team/';
983        $this->urls['kbupdateext']              = 'https://kb.jetpackcrm.com/knowledge-base/how-do-i-update-an-extension/';
984        $this->urls['kbclientportal']           = 'https://kb.jetpackcrm.com/knowledge-base/how-does-the-client-portal-work/';
985        $this->urls['kbtemplatefiles']          = 'https://kb.jetpackcrm.com/knowledge-base/templating-how-to-change-templates-pdfs-portal-emails/';
986        $this->urls['kbeasyaccess']             = 'https://kb.jetpackcrm.com/knowledge-base/easy-access-links-for-client-portal/';
987        $this->urls['kbdisablewelcome']         = 'https://kb.jetpackcrm.com/knowledge-base/automatically-create-wordpress-users-but-not-send-them-a-welcome-email/';
988        $this->urls['kbapi']                    = 'https://kb.jetpackcrm.com/knowledge-base/using-the-api-connector/';
989        $this->urls['kbshowwpmenus']            = 'https://kb.jetpackcrm.com/knowledge-base/how-to-get-wordpress-menu-items-back/';
990        $this->urls['kbsmtpsetup']              = 'https://kb.jetpackcrm.com/knowledge-base/mail-delivery-method-setup-smtp/';
991        $this->urls['kbcrmdashboard']           = 'https://kb.jetpackcrm.com/knowledge-base/zero-bs-crm-dashboard/';
992        $this->urls['kbrevoverview']            = 'https://kb.jetpackcrm.com/knowledge-base/revenue-overview-chart/';
993        $this->urls['kbcsvformat']              = 'https://kb.jetpackcrm.com/knowledge-base/what-should-my-csv-be-formatted-like/';
994        $this->urls['kbcat_cal']                = 'https://kb.jetpackcrm.com/article-categories/calendar/';
995        $this->urls['kbsegment_issues']         = 'https://kb.jetpackcrm.com/knowledge-base/segment-issues-and-errors/';
996        $this->urls['kb-woosync-home']          = 'https://kb.jetpackcrm.com/knowledge-base/using-the-woocommerce-sync-hub/';
997        $this->urls['kb-pre-v5-migration-todo'] = 'https://kb.jetpackcrm.com/knowledge-base/upgrading-to-jetpack-crm-v5-0/';
998        $this->urls['kb-mailpoet']              = 'https://kb.jetpackcrm.com/knowledge-base/mailpoet-crm-sync/';
999        $this->urls['kb-automations']           = 'https://kb.jetpackcrm.com/knowledge-base/automations/';
1000        $this->urls['kb-contact-fields']        = 'https://kb.jetpackcrm.com/knowledge-base/contact-field-list/';
1001        $this->urls['kb-pdf-custom-fonts']      = 'https://kb.jetpackcrm.com/knowledge-base/using-custom-fonts-in-crm-pdfs-e-g-invoice-templates/';
1002
1003        // coming soon
1004        $this->urls['soon'] = 'https://jetpackcrm.com/coming-soon/';
1005
1006        // v4 rebrand announcement
1007        $this->urls['v4announce'] = 'https://jetpackcrm.com/rebrand-announcement';
1008        $this->urls['v5announce'] = 'https://jetpackcrm.com/announcing-jetpack-crm-v5-woocommerce-crm/';
1009
1010        // } Usage Tracking
1011        $this->urls['usage']     = 'https://app.jetpackcrm.com/api/usage';
1012        $this->urls['usageinfo'] = 'https://jetpackcrm.com/usage-tracking';
1013
1014        // YouTubes!
1015        $this->urls['youtube_channel']          = 'https://www.youtube.com/channel/UCyT-wMU7Gp6r1wN6W5YMAiQ';
1016        $this->urls['youtube_intro_playlist']   = 'https://www.youtube.com/watch?v=tCC25uTFDTs&list=PLO9bxAENhBHhnc53Eq3OGBKLMSj0leAel';
1017        $this->urls['youtube_intro_to_crm']     = 'https://www.youtube.com/watch?v=tCC25uTFDTs';
1018        $this->urls['youtube_intro_to_tags']    = 'https://www.youtube.com/watch?v=KwGh-Br_exc';
1019        $this->urls['youtube_intro_to_forms']   = 'https://www.youtube.com/watch?v=mBPjV1KUb-w';
1020        $this->urls['youtube_intro_to_modules'] = 'https://www.youtube.com/watch?v=j9RsXPcgeIo';
1021        // $this->urls['youtube_intro_to_woosync']   = 'https://www.youtube.com/watch?v=4G-FtmMhy-s';
1022
1023        // Page slugs
1024        $this->slugs['home'] = 'zerobscrm-settings';
1025
1026        ##WLREMOVE
1027        $this->slugs['home'] = 'zerobscrm-plugin';
1028        ##/WLREMOVE
1029        $this->slugs['dash']         = 'zerobscrm-dash';
1030        $this->slugs['settings']     = 'zerobscrm-plugin-settings';
1031        $this->slugs['logout']       = 'zerobscrm-logout';
1032        $this->slugs['datatools']    = 'zerobscrm-datatools';
1033        $this->slugs['welcome']      = 'zerobscrm-welcome';
1034        $this->slugs['crmresources'] = 'jpcrm-resources';
1035        $this->slugs['support']      = 'jpcrm-support';
1036        $this->slugs['extensions']   = 'zerobscrm-extensions';
1037        $this->slugs['modules']      = 'zerobscrm-modules';
1038        $this->slugs['export']       = 'zerobscrm-export';
1039        $this->slugs['systemstatus'] = 'zerobscrm-systemstatus';
1040        $this->slugs['sync']         = 'zerobscrm-sync';
1041
1042        // CSV importer Lite
1043        $this->slugs['csvlite'] = 'zerobscrm-csvimporterlite-app';
1044
1045        // } FOR NOW wl needs these:
1046        $this->slugs['bulktagger'] = 'zerobscrm-batch-tagger';
1047        $this->slugs['salesdash']  = 'sales-dash';
1048        $this->slugs['stripesync'] = 'zerobscrm-stripesync-app';
1049        $this->slugs['woosync']    = 'woo-sync-hub'; // previously 'woo-importer';
1050        $this->slugs['paypalsync'] = 'zerobscrm-paypal-app';
1051        $this->slugs['mailpoet']   = 'crm-mail-poet-hub'; // note can't use `*mailpoet*` as the plugin inteferes with styles
1052
1053        // } OTHER UI PAGES WHICH WEREN'T IN SLUG - MS CLASS ADDITION
1054        // } WH: Not sure which we're using here, think first set cleaner:
1055        // NOTE: DAL3 + these are referenced in DAL2.php so be aware :)
1056        // (This helps for generically linking back to list obj etc.)
1057        // USE zbsLink!
1058        $this->slugs['managecontacts']         = 'manage-customers';
1059        $this->slugs['managequotes']           = 'manage-quotes';
1060        $this->slugs['manageinvoices']         = 'manage-invoices';
1061        $this->slugs['managetransactions']     = 'manage-transactions';
1062        $this->slugs['managecompanies']        = 'manage-companies';
1063        $this->slugs['manageformscrm']         = 'manage-forms';
1064        $this->slugs['segments']               = 'manage-segments';
1065        $this->slugs['quote-templates']        = 'manage-quote-templates';
1066        $this->slugs['manage-tasks']           = 'manage-tasks';
1067        $this->slugs['manage-tasks-completed'] = 'manage-tasks-completed';
1068        $this->slugs['manage-tasks-list']      = 'manage-tasks-list';
1069        $this->slugs['managecontactsprev']     = 'manage-customers-crm';
1070        $this->slugs['managequotesprev']       = 'manage-quotes-crm';
1071        $this->slugs['managetransactionsprev'] = 'manage-transactions-crm';
1072        $this->slugs['manageinvoicesprev']     = 'manage-invoices-crm';
1073        $this->slugs['managecompaniesprev']    = 'manage-companies-crm';
1074        $this->slugs['manageformscrmprev']     = 'manage-forms-crm';
1075
1076        // } NEW UI - ADD or EDIT, SEND EMAIL, NOTIFICATIONS
1077        $this->slugs['addedit']  = 'zbs-add-edit';
1078        $this->slugs['sendmail'] = 'zerobscrm-send-email';
1079
1080        $this->slugs['emails'] = 'zerobscrm-emails';
1081
1082        $this->slugs['notifications'] = 'zerobscrm-notifications';
1083
1084        // } TEAM - Manage the CRM team permissions
1085        $this->slugs['team'] = 'zerobscrm-team';
1086
1087        // } Export tools
1088        $this->slugs['export-tools'] = 'zbs-export-tools';
1089
1090        // } Your Profile (for Calendar Sync and Personalised Stuff (like your own task history))
1091        $this->slugs['your-profile'] = 'your-crm-profile';
1092        $this->slugs['reminders']    = 'zbs-reminders';
1093
1094        // } Adds a USER (i.e. puts our menu on user-new.php through ?page =)
1095        $this->slugs['zbs-new-user']  = 'zbs-add-user';
1096        $this->slugs['zbs-edit-user'] = 'zbs-edit-user'; // WH Added this, not sure what you're using for
1097
1098        // } Install helper
1099        $this->slugs['zerobscrm-install-helper'] = 'zerobscrm-install-helper';
1100
1101        // emails
1102        $this->slugs['email-templates'] = 'zbs-email-templates';
1103
1104        // tag manager
1105        $this->slugs['tagmanager'] = 'tag-manager';
1106
1107        // no access
1108        $this->slugs['zbs-noaccess'] = 'zbs-noaccess';
1109
1110        // } File Editor
1111        $this->slugs['editfile']   = 'zerobscrm-edit-file';
1112        $this->slugs['addnewfile'] = 'zerobscrm-add-file';
1113
1114        // } Extensions Deactivated error
1115        $this->slugs['extensions-active'] = 'zbs-extensions-active';
1116
1117        // Activates a module and redirects to its 'hub' slug.
1118        $this->slugs['module-activate-redirect'] = 'jpcrm-module-activate-redirect';
1119
1120        // Transients
1121        // These are transients which CRM owns which can be set via jpcrm_set_jpcrm_transient() etc.
1122
1123        // Licensing prompts
1124        $this->transients['jpcrm-license-modal'] = false;
1125
1126        // Mime types just use this func () - needs rethinking - includes/ZeroBSCRM.FileUploads.php
1127        if ( function_exists( 'zeroBSCRM_returnMimeTypes' ) ) {
1128            $this->acceptable_mime_types = zeroBSCRM_returnMimeTypes();
1129        }
1130    }
1131
1132    /**
1133     * Include required core files used in admin and on the frontend. Note. In the main
1134     * file it was included everything on front end too. Can move the relevant ones
1135     * to if ( $this->is_request( 'admin' ) ) { } once initial tests complete.
1136     */
1137    public function includes() {
1138
1139        // Admin messages (for any promos etc)
1140        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.PluginAdminNotices.php';
1141
1142        // ====================================================================
1143        // ==================== General Perf Testing ==========================
1144        if ( defined( 'ZBSPERFTEST' ) ) {
1145            zeroBSCRM_performanceTest_startTimer( 'includes' );
1146        }
1147        // =================== / General Perf Testing =========================
1148        // ====================================================================
1149
1150        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.GeneralFuncs.php';
1151        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.DateTime.php';
1152        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.AdminPages.Checks.php';
1153        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.ScriptsStyles.php';
1154
1155        // } Settings
1156        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Config.Init.php';
1157        require_once ZEROBSCRM_INCLUDE_PATH . 'wh.config.lib.php';
1158
1159        // } WP REST API SUPPORT (better performant AJAX)
1160        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.REST.php';
1161
1162        // } General store of Error Codes
1163        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.ErrorCodes.php';
1164
1165        // Language modifiers (e.g. Company -> Organisation)
1166        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-language.php';
1167
1168        // Segment conditions
1169        require_once ZEROBSCRM_INCLUDE_PATH . 'class-segment-condition.php';
1170        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-segment-conditions.php';
1171
1172        // Generic CRM exceptions
1173        require_once ZEROBSCRM_INCLUDE_PATH . 'class-crm-exception.php';
1174
1175        // WordPress user integrations
1176        require_once ZEROBSCRM_INCLUDE_PATH . 'class-wordpress-user-integration.php';
1177
1178        // Endpoint Listener
1179        require_once ZEROBSCRM_INCLUDE_PATH . 'class-endpoint-listener.php';
1180
1181        // OAuth Handler
1182        require_once ZEROBSCRM_INCLUDE_PATH . 'class-oauth-handler.php';
1183
1184        // } DATA
1185
1186        // DAL3
1187        // Here we include:
1188        // - DAL 3 (base class)
1189        // - DAL 3 Objects
1190        // - DAL3.Helpers.php (our helper funcs)
1191
1192        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.php';
1193
1194        // 3.0 DAL objs:
1195        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.ObjectLayer.php';
1196        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Contacts.php';
1197        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Companies.php';
1198        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Segments.php';
1199        require_once ZEROBSCRM_INCLUDE_PATH . 'class-segment-condition-exception.php';
1200        require_once ZEROBSCRM_INCLUDE_PATH . 'class-missing-settings-exception.php';
1201        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Quotes.php';
1202        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.QuoteTemplates.php';
1203        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Invoices.php';
1204        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Transactions.php';
1205        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Forms.php';
1206        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Events.php';
1207        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.EventReminders.php';
1208        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.Logs.php';
1209        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Obj.LineItems.php';
1210        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Export.php';
1211
1212        // helper funcs
1213        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Helpers.php';
1214
1215        // drop-in-replacement for previous global fields (uses models from objs now.)
1216        // NOTE: Rather than initially hard-typed, this now needs to WAIT until DAL3 initialised
1217        // ... so field Globals available LATER in build queue in DAL3+
1218        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL3.Fields.php';
1219
1220        // } Metaboxes v3.0
1221
1222        // Root classes
1223        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBox.php';
1224        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Logs.php';
1225        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Tags.php';
1226        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.ExternalSources.php';
1227
1228        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Contacts.php';
1229        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Companies.php';
1230        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.TagManager.php';
1231
1232        // } 3.0 + ALL are in our metaboxes
1233        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Quotes.php';
1234        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.QuoteTemplates.php';
1235        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Invoices.php';
1236        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Ownership.php';
1237        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Tasks.php';
1238        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Transactions.php';
1239        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes3.Forms.php';
1240
1241        // NO CPTs! YAY!
1242
1243        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.ExternalSources.php';
1244        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DataIOValidation.php';
1245        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Database.php';
1246
1247        // } Split out DAL2:
1248        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DAL2.Mail.php';
1249
1250        // } Admin Pages
1251        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.AdminStyling.php';
1252        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.AdminPages.php';
1253        require_once ZEROBSCRM_PATH . 'admin/tags/tag-manager.page.php';
1254        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.FormatHelpers.php';
1255
1256        // } Dashboard Boxes - WH Q why do we also need to define VARS for these, require once only requires once, right?
1257        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.DashboardBoxes.php';
1258
1259        // } The kitchen sink
1260        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Migrations.php';
1261        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.Localisation.php';
1262        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-localisation.php';
1263        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.Extensions.php';
1264        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Actions.php';
1265        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.Menus.WP.php';
1266        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.Menus.Top.php';
1267        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.License.php';
1268        require_once ZEROBSCRM_INCLUDE_PATH . 'class-learn-menu.php';
1269
1270        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Permissions.php';
1271        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.ScreenOptions.php';
1272        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Inventory.php';
1273        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-rewrite-rules.php';
1274        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-mail-templating.php';
1275        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-templating.php';
1276        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-templating-placeholders.php';
1277        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MailTracking.php';
1278        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.InternalAutomator.php';
1279        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.CRON.php';
1280        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Social.php';
1281
1282        // } Secondary
1283        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.AJAX.php';
1284        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.WYSIWYGButtons.php';
1285        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.CustomerFilters.php';
1286        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.InternalAutomatorRecipes.php';
1287        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.FileUploads.php';
1288        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Forms.php';
1289        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.InvoiceBuilder.php';
1290        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.QuoteBuilder.php';
1291
1292        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.SystemChecks.php';
1293        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.IntegrationFuncs.php';
1294
1295        // Temporarily removed until MC2 catches up + finishes Mail Delivery:
1296        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Mail.php';
1297
1298        // } OBJ List Class (ZeroBSCRM.List.php) & List render funcs (ZeroBSCRM.List.Views.php) & List Column data
1299        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.List.php';
1300        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.List.Views.php';
1301        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.List.Columns.php';
1302
1303        // } OBJ Edit & Delete Classes (ZeroBSCRM.Edit.php)
1304        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Edit.php';
1305        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Delete.php';
1306        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.TagManager.php';
1307
1308        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Core.Page.Controller.php';
1309        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Edit.Segment.php';
1310
1311        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.List.Tasks.php';
1312
1313        // } Semantic UI Helper + columns list
1314        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.SemanticUIHelpers.php';
1315
1316        // } Put Plugin update message (notifications into the transient /wp-admin/plugins.php) page.. that way the nag message is not needed at the top of pages (and will always show, not need to be dismissed)
1317        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.PluginUpdates.php';
1318
1319        // } FROM PLUGIN HUNT THEME - LOT OF USEFUL CODE IN HERE.
1320        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.NotifyMe.php';
1321
1322        // load dependency checker (since 4.5.0)
1323        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-dependency-checker.php';
1324
1325        // load feature sniffer (since 4.5.0)
1326        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-feature-sniffer.php';
1327
1328        if ( defined( 'WP_CLI' ) && WP_CLI ) {
1329            // if we need CLI stuff
1330        }
1331
1332        // ====================================================================
1333        // ==================== General Perf Testing ==========================
1334        if ( defined( 'ZBSPERFTEST' ) ) {
1335            zeroBSCRM_performanceTest_closeGlobalTest( 'includes' );
1336        }
1337        // =================== / General Perf Testing =========================
1338        // ====================================================================
1339    }
1340
1341    /**
1342     * Hook into actions and filters.
1343     *
1344     * @since 2.3
1345     */
1346    private function init_hooks() {
1347
1348        // Pre-init Hook
1349        do_action( 'before_zerobscrm_init' );
1350
1351        // our 'pre-init', this is the last step before init
1352        // ... and loads settings :)
1353        // add_action('admin_init', array($this, 'preInit'), 1);
1354        add_action( 'init', array( $this, 'preInit' ), 1 );
1355
1356        // our formal init
1357        add_action( 'init', array( $this, 'init' ), 10 );
1358
1359        // post init (init 99)
1360        add_action( 'init', array( $this, 'postInit' ), 99 );
1361
1362        // Admin init - should condition this per page..
1363        add_action( 'admin_init', array( $this, 'admin_init' ) );
1364
1365        // Extension links
1366        add_filter( 'plugin_action_links_' . plugin_basename( ZBS_ROOTFILE ), array( $this, 'add_action_links' ) );
1367
1368        // Row meta
1369        add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 );
1370
1371        // Install/uninstall - use uninstall.php here
1372        register_deactivation_hook( ZBS_ROOTFILE, array( $this, 'uninstall' ) );
1373
1374        // CRM top menu
1375        add_action( 'wp_after_admin_bar_render', 'zeroBSCRM_admin_top_menu', 10 );
1376
1377        // Learn menu
1378        // Note the priority here. This causes the "learn" block to present after the top menu
1379        add_action( 'wp_after_admin_bar_render', array( $this, 'initialise_learn_menu' ), 11 );
1380
1381        // Run late-stack events, e.g. post-loaded migrations, exports
1382        // note: this fires AFTER all advanced segments loaded, reliably
1383        add_action( 'wp_loaded', array( $this, 'post_wp_loaded' ) );
1384    }
1385
1386    public function filterExtensions( $extensions_array = false ) {
1387
1388        $extensions_array = apply_filters( 'zbs_extensions_array', $extensions_array );
1389
1390        // remove dupes - even this doesn't seem to remove the dupes!
1391        return array_unique( $extensions_array );
1392    }
1393
1394    // load initial external sources
1395    private function loadBaseExternalSources() {
1396
1397        // simply loads our initial set from array, for now.
1398        $this->external_sources = zeroBS_baseExternalSources();
1399    }
1400
1401    // load any extra hooked-in external sources
1402    private function loadExtraExternalSources() {
1403
1404        // load initials if not loaded/borked
1405        if ( ! is_array( $this->external_sources ) || count( $this->external_sources ) < 1 ) {
1406
1407            // reload initial
1408            $this->loadBaseExternalSources();
1409
1410        }
1411
1412        // should be guaranteed that this->external_sources is an array now, but if for god-knows what reason, it's not, error.
1413        if ( ! is_array( $this->external_sources ) ) {
1414
1415            // error out? (hard error not useful as err500's peeps)
1416            // ... rude text? (no translation, this way if someone EVER sees, they'll hopefully tell us)
1417            echo 'CRM ERROR #399: No external sources!<br>';
1418
1419            // should NEVER happen:
1420            $this->external_sources = array();
1421
1422        }
1423
1424        // NOW we apply any filters to a blank array, then merge that with our HARD typed array to insure we never LOOSE originals
1425        $newExtSources = $this->filterExternalSources( array() );
1426        // ^^ this is kind of miss-use of filters, but it'll work well here.
1427
1428        // if anything to add, manually parse here (for complete control)
1429        if ( is_array( $newExtSources ) && count( $newExtSources ) > 0 ) {
1430            foreach ( $newExtSources as $extKey => $extDeets ) {
1431
1432                // will look like this:
1433                // $external_sources['woo'] = array('WooCommerce', 'ico' => 'fa-shopping-cart');
1434
1435                // override/add to main (if checks out):
1436                if ( is_string( $extKey ) && ! empty( $extKey ) && is_array( $extDeets ) && count( $extDeets ) > 0 ) {
1437
1438                    // seems in right format
1439                    $this->external_sources[ $extKey ] = $extDeets;
1440
1441                }
1442            } // / if any new to add
1443        }
1444
1445        // at this point $this->external_sources should be fully inline with apply_filter's added,
1446        // but will NEVER lack the original 'pack'
1447        // and 100% will be an array.
1448    }
1449
1450    // simply applies filters to anny passed array
1451    // NOTE: From 2.97.7 this is only taken as a 'second' layer, as per loadExtraExternalSources() above.
1452    // ... so it can stay super simple.
1453    public function filterExternalSources( $approved_sources = false ) {
1454
1455        return apply_filters( 'zbs_approved_sources', $approved_sources );
1456    }
1457
1458    // } Build-out Object Models
1459    public function buildOutObjectModels() {
1460
1461        // ====================================================================
1462        // ==================== General Perf Testing ==========================
1463        if ( defined( 'ZBSPERFTEST' ) ) {
1464            zeroBSCRM_performanceTest_startTimer( 'customfields' );
1465        }
1466        // =================== / General Perf Testing =========================
1467        // ====================================================================
1468
1469        // } Unpack Custom Fields + Apply sorts
1470        zeroBSCRM_unpackCustomFields();
1471        zeroBSCRM_unpackCustomisationsToFields();
1472        if ( 1 == 1 ) { // } switch off for perf?
1473            zeroBSCRM_applyFieldSorts();
1474        }
1475
1476        // } Unpacks any settings logged against listview setups
1477        zeroBSCRM_unpackListViewSettings();
1478
1479        // ====================================================================
1480        // ==================== General Perf Testing ==========================
1481        if ( defined( 'ZBSPERFTEST' ) ) {
1482            zeroBSCRM_performanceTest_closeGlobalTest( 'customfields' );
1483        }
1484        // =================== / General Perf Testing =========================
1485        // ====================================================================
1486    }
1487
1488    public function add_action_links( $links ) {
1489        global $zbs;
1490
1491        $mylinks = array(
1492            '<a href="' . zeroBSCRM_getAdminURL( $zbs->slugs['settings'] ) . '">' . __( 'Settings', 'zero-bs-crm' ) . '</a>',
1493            '<a href="' . zeroBSCRM_getAdminURL( $zbs->slugs['extensions'] ) . '">' . __( 'Extensions', 'zero-bs-crm' ) . '</a>',
1494        );
1495        return array_merge( $mylinks, $links );
1496    }
1497
1498    /**
1499     * Show row meta on the plugin screen for Jetpack CRM plugin.
1500     *
1501     * @param mixed $links_array Plugin Row Meta.
1502     * @param mixed $plugin  Plugin Base Name.
1503     *
1504     * @return array
1505     */
1506    public static function plugin_row_meta( $links_array, $plugin ) {
1507        if ( ! str_contains( $plugin, plugin_basename( ZBS_ROOTFILE ) ) ) {
1508            return $links_array;
1509        }
1510
1511        global $zbs;
1512        $row_meta = array(
1513            'docs' => '<a href="' . esc_url( $zbs->urls['docs'] ) . '" aria-label="' . esc_attr__( 'Jetpack CRM knowledgebase', 'zero-bs-crm' ) . '" target="_blank">' . esc_html__( 'Docs', 'zero-bs-crm' ) . '</a>',
1514        );
1515
1516        ##WLREMOVE
1517        $license_key_array = zeroBSCRM_getSetting( 'license_key' );
1518        if ( is_array( $license_key_array ) && ! empty( $license_key_array['key'] ) ) {
1519            $row_meta['account'] = '<a href="' . esc_url( $zbs->urls['account'] ) . '" aria-label="' . esc_attr__( 'Your account', 'zero-bs-crm' ) . '" target="_blank">' . esc_html__( 'Your account', 'zero-bs-crm' ) . '</a>';
1520        }
1521        ##/WLREMOVE
1522
1523        return array_merge( $links_array, $row_meta );
1524    }
1525
1526    public function post_init_plugins_loaded() {
1527
1528        // } renamed to postSettingsIncludes and moved into that flow, (made more logical sense)
1529
1530        // Veriy extension requirements
1531        $this->verify_extension_minimum_requirements();
1532
1533        // } Forms - only initiate if installed :)
1534        if ( zeroBSCRM_isExtensionInstalled( 'forms' ) ) {
1535            zeroBSCRM_forms_includeEndpoint();
1536        }
1537
1538        // ====================================================================
1539        // ==================== General Perf Testing ==========================
1540        if ( defined( 'ZBSPERFTEST' ) ) {
1541            zeroBSCRM_performanceTest_closeGlobalTest( 'postsettingsincludes' );
1542        }
1543        // =================== / General Perf Testing =========================
1544        // ====================================================================
1545    }
1546
1547    public function preInit() {
1548
1549        // ====================================================================
1550        // ==================== General Perf Testing ==========================
1551        if ( defined( 'ZBSPERFTEST' ) ) {
1552            zeroBSCRM_performanceTest_startTimer( 'preinit' );
1553        }
1554        // =================== / General Perf Testing =========================
1555        // ====================================================================
1556
1557        global $zeroBSCRM_Conf_Setup, $zbscrmApprovedExternalSources;
1558
1559        // } Init DAL (DAL2, now always enabled)
1560        $this->DAL = new zbsDAL();
1561
1562        // } ASAP after DAL is initialised, need to run this, which DEFINES all DAL3.Obj.Models into old-style $globalFieldVars
1563        // } #FIELDLOADING'
1564        zeroBSCRM_fields_initialise();
1565
1566        // } Setup Config (centralises version numbers temp)
1567        global $zeroBSCRM_Conf_Setup;
1568        $zeroBSCRM_Conf_Setup['conf_pluginver']   = $this::VERSION; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1569        $zeroBSCRM_Conf_Setup['conf_plugindbver'] = $this->db_version;
1570
1571        // Not needed yet :) do_action( 'before_zerobscrm_settings_init' );
1572
1573        // } Init settings + sources
1574        $this->settings = new WHWPConfigLib( $zeroBSCRM_Conf_Setup );
1575
1576        // register any modules with core
1577        $this->jpcrm_register_modules();
1578
1579        // external sources, load, then initially filter
1580        $this->loadBaseExternalSources();
1581        $this->loadExtraExternalSources();
1582
1583        // This just sets up metaboxes (empty array for now) - see zeroBSCRM_add_meta_box in Edit.php
1584        if ( ! is_array( $this->metaboxes ) ) {
1585            $this->metaboxes = array();
1586        }
1587
1588        // } This houses includes which need to fire post settings model load
1589        // NOTE: BECAUSE this has some things which add_action to init
1590        // ... this MUST fire on init with a priority of 1, so that these still "have effect"
1591        $this->postSettingsIncludes();
1592
1593        // TEMP (ext update for 2.5 notice):
1594        if ( defined( 'ZBSTEMPLEGACYNOTICE' ) ) {
1595            zeroBS_temp_ext_legacy_notice();
1596        }
1597
1598        // Legacy support for pre v2.50 settings in extensions
1599        zeroBSCRM_legacySupport();
1600
1601        // load dependency checker for any modules/extensions
1602        $this->dependency_checker = new JPCRM_DependencyChecker();
1603
1604        // load feature sniffer to alert user to available integrations
1605        ##WLREMOVE
1606        $this->feature_sniffer = new JPCRM_FeatureSniffer();
1607        $this->jpcrm_sniff_features();
1608        ##/WLREMOVE
1609
1610        // load WordPress User integrations
1611        $this->wordpress_user_integration = new Automattic\JetpackCRM\Wordpress_User_Integration();
1612
1613        // fire an action
1614        do_action( 'after_zerobscrm_settings_preinit' );
1615
1616        // load included modules
1617        // this needs to be fired early so modules can hook into third-party plugin actions/filters
1618        do_action( 'jpcrm_load_modules' );
1619
1620        // Where on frontend, load our endpoint listener and OAuth handler
1621        if ( $this->is_request( 'frontend' ) && ! $this->is_request( 'ajax' ) ) {
1622
1623            // load
1624            $this->load_oauth_handler();
1625            $this->load_listener();
1626
1627            // catch listener requests (if any)
1628            $this->listener->catch_listener_request();
1629
1630        }
1631
1632        // ====================================================================
1633        // ==================== General Perf Testing ==========================
1634        if ( defined( 'ZBSPERFTEST' ) ) {
1635            zeroBSCRM_performanceTest_closeGlobalTest( 'preinit' );
1636        }
1637        // =================== / General Perf Testing =========================
1638        // ====================================================================
1639    }
1640
1641    public function postSettingsIncludes() {
1642
1643        // ====================================================================
1644        // ==================== General Perf Testing ==========================
1645        if ( defined( 'ZBSPERFTEST' ) ) {
1646            zeroBSCRM_performanceTest_startTimer( 'postsettingsincludes' );
1647        }
1648        // =================== / General Perf Testing =========================
1649        // ====================================================================
1650
1651        // } extensions :D - here are files that don't need including if they're switched off...
1652        // } ^^ can probably include this via free extension manager class (longer term tidier?)
1653        // WH addition: this was firing PRE init (you weren't seeing because no PHP warnings...needs to fire after)
1654
1655        // } free extensions setup (needs to be post settings)
1656        zeroBSCRM_freeExtensionsInit();
1657
1658        // } CSV Importer LITE
1659        // } only run all this is no PRO installed :)
1660        if ( ! zeroBSCRM_isExtensionInstalled( 'csvpro' ) && zeroBSCRM_isExtensionInstalled( 'csvimporterlite' ) && ! defined( 'ZBSCRM_INC_CSVIMPORTERLITE' ) ) {
1661            require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.CSVImporter.php';
1662        }
1663
1664        // } API
1665        if ( zeroBSCRM_isExtensionInstalled( 'api' ) && ! defined( 'ZBSCRM_INC_API' ) ) {
1666            require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.API.php';
1667        }
1668
1669        // } If zbs admin: Tour
1670        if ( zeroBSCRM_isZBSAdminOrAdmin() && ! defined( 'ZBSCRM_INC_ONBOARD_ME' ) ) {
1671            require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.OnboardMe.php';
1672        }
1673
1674        // If usage tracking is active - include the tracking code.
1675        $this->load_usage_tracking();
1676
1677        if ( zeroBSCRM_isExtensionInstalled( 'jetpackforms' ) ) {
1678            // } Jetpack - can condition this include on detection of Jetpack - BUT the code in Jetpack.php only fires on actions so will be OK to just include
1679            require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.Jetpack.php';
1680        }
1681    }
1682
1683    // } Initialisation - enqueueing scripts/styles
1684    public function init() {
1685
1686        // ====================================================================
1687        // ==================== General Perf Testing ==========================
1688        if ( defined( 'ZBSPERFTEST' ) ) {
1689            zeroBSCRM_performanceTest_startTimer( 'init' );
1690        }
1691        // =================== / General Perf Testing =========================
1692        // ====================================================================
1693
1694        // this catches zbs_customers who may be accessing backend (shouldn't)
1695        $this->checkBackendAccess();
1696
1697        global $zeroBSCRM_Conf_Setup, $zeroBSCRM_extensionsInstalledList, $zbscrmApprovedExternalSources;
1698
1699        // unpack custom fieldsbuildOutObjectModels
1700        // #} #FIELDLOADING
1701        $this->buildOutObjectModels();
1702
1703        // } Unpacks any settings logged against listview setups
1704        zeroBSCRM_unpackListViewSettings();
1705
1706        // } Post settings hook - all meta views load in this hook :)
1707        // this has to fire for public + admin (things like mail campaigns rely on this for link tracking)
1708        do_action( 'after_zerobscrm_settings_init' );
1709
1710        // } this needs to be post settings
1711        $this->extensions = $this->filterExtensions( $zeroBSCRM_extensionsInstalledList );
1712
1713        // } Post extensions loaded hook
1714        do_action( 'after_zerobscrm_ext_init' );
1715
1716        // } Load the admin menu. Can consider in here the 'ORDER' of the menu
1717        // } As well where extensions put their settings too
1718        add_action( 'admin_menu', array( $this, 'admin_menu' ) );
1719
1720        $this->include_updater();
1721        // Admin unlock for ZBS users if WooCommerce installed
1722        zeroBSCRM_wooCommerceRemoveBlock();
1723        // Registers stuff that needs settings etc.
1724        $this->post_init_plugins_loaded();
1725
1726        // run migrations
1727        $this->run_migrations( 'init' );
1728
1729        // } Brutal override for feeding in json data to typeahead
1730        // WH: should these be removed now we're using REST?
1731        if ( isset( $_GET['zbscjson'] ) && is_user_logged_in() && zeroBSCRM_permsCustomers() ) {
1732            // This function outputs JSON-encoded contacts and exits.
1733            zeroBSCRM_cjson();
1734        } elseif ( isset( $_GET['zbscojson'] ) && is_user_logged_in() && zeroBSCRM_permsCustomers() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1735            // This function outputs JSON-encoded companies and exits.
1736            zeroBSCRM_cojson();
1737        }
1738
1739        // } Catch Dashboard + redir (if override mode)
1740        // } but not for wp admin (wptakeovermodeforall)
1741        if ( $this->settings->get( 'wptakeovermode' ) == 1 ) {
1742
1743            // Not if API or Client Portal...
1744            // ... moved them inside this func..
1745            zeroBSCRM_catchDashboard();
1746
1747        }
1748
1749        // } JUST before cpt, we do any install/uninstall of extensions, so that cpt's can adjust instantly:
1750        zeroBSCRM_extensions_init_install();
1751
1752        // } Here we do any 'default content' installs (quote templates) (In CPT <DAL3, In DAL3.Helpers DAL3+)
1753        zeroBSCRM_installDefaultContent();
1754
1755        // } Admin & Public req
1756        wp_enqueue_script( 'jquery' );
1757
1758        // } Post Init hook
1759        do_action( 'zerobscrm_post_init' );
1760
1761        // } Public & non wp-cli only
1762        if ( ! is_admin() && ! defined( 'WP_CLI' ) ) {
1763
1764            // } Catch front end loads :)
1765            if ( $this->settings->get( 'killfrontend' ) == 1 ) {
1766                global $pagenow;
1767
1768                if ( ! zeroBSCRM_isLoginPage()
1769                    && ! zeroBSCRM_isWelcomeWizPage()
1770                    && ! zeroBSCRM_isAPIRequest()
1771                    && ! defined( 'XMLRPC_REQUEST' )
1772                    && ! defined( 'REST_REQUEST' )
1773                    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1774                    && ! ( 'index.php' === $pagenow && ! empty( $_GET['rest_route'] ) )
1775                ) {
1776
1777                    zeroBSCRM_stopFrontEnd();
1778
1779                }
1780            }
1781        }
1782
1783        // } Finally, if it's an edit page for a (obj) which is hard owned by another, redir away
1784        // if admin, ignore :)
1785        if ( $this->settings->get( 'perusercustomers' ) && ! zeroBSCRM_isZBSAdminOrAdmin() ) {
1786
1787            // } Using ownership
1788            if ( ! $this->settings->get( 'usercangiveownership' ) ) {
1789
1790                // } is one of our dal3 edit pages
1791                if ( zeroBSCRM_is_zbs_edit_page() ) {
1792
1793                    // in this specific case we pre-call globalise_vars
1794                    // ... which later gets recalled if on an admin page (but it's safe to do so here too)
1795                    // this moves any _GET into $zbsPage
1796                    $this->globalise_vars();
1797
1798                    // this allows us to use these:
1799                    $obj_id       = $this->zbsvar( 'zbsid' ); // -1 or 123 ID
1800                    $obj_type_str = $this->zbsvar( 'zbstype' ); // -1 or 'contact'
1801
1802                    // if objtypestr is -1, assume contact (default)
1803                    if ( $obj_type_str === -1 ) {
1804                        $obj_type_id = ZBS_TYPE_CONTACT;
1805                    } else {
1806                        $obj_type_id = $this->DAL->objTypeID( $obj_type_str ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1807                    }
1808
1809                    // if is edit page + has obj id, (e.g. is not "new")
1810                    // then check ownership
1811                    if ( isset( $obj_id ) && $obj_id > 0 && $obj_type_id > 0 ) {
1812
1813                        $is_ownership_valid = $this->DAL->checkObjectOwner( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1814                            array(
1815
1816                                'objID'              => $obj_id,
1817                                'objTypeID'          => $obj_type_id,
1818                                'potentialOwnerID'   => get_current_user_id(),
1819                                'allowNoOwnerAccess' => true, // ?
1820
1821                            )
1822                        );
1823
1824                        // } If user ! has rights, redir
1825                        if ( ! $is_ownership_valid ) {
1826
1827                            // Redirect to our "no rights" page
1828                            // OLD WAY header("Location: edit.php?post_type=".$postType."&page=".$this->slugs['zbs-noaccess']."&id=".$postID);
1829                            header( 'Location: admin.php?page=' . $this->slugs['zbs-noaccess'] . '&zbsid=' . $obj_id . '&zbstype=' . $obj_type_str );
1830                            exit( 0 );
1831
1832                        } // / no rights.
1833
1834                    } // / obj ID
1835
1836                } // / is edit page
1837
1838            } // / is setting usercangiveownership
1839
1840        } // / !is admin
1841
1842        // ====================================================================
1843        // ==================== General Perf Testing ==========================
1844        if ( defined( 'ZBSPERFTEST' ) ) {
1845            zeroBSCRM_performanceTest_closeGlobalTest( 'init' );
1846        }
1847        // =================== / General Perf Testing =========================
1848        // ====================================================================
1849    }
1850
1851    public function postInit() {
1852
1853        // pre 2.97.7:
1854        // WH note: filterExternalSources() above didn't seem to be adding them all (stripesync e.g. was being added on init:10)
1855        // ... so this gets called a second time (should be harmless) at init:99 (here)
1856        // $this->external_sources = $this->filterExternalSources($this->external_sources);
1857
1858        // 2.97.7, switched to this, a more ROBUST check which only 'adds' and won't remove.
1859        $this->loadExtraExternalSources();
1860
1861        // this allows various extensions to add users AFTER external sources def loaded
1862        do_action( 'after_zerobscrm_extsources_init' );
1863
1864        // This action should replace after_zerobscrm_extsources_init when we refactor load order in this class.
1865        // initially used by advanced segments to add custom field segment condition classes after the class is declared in jpcrm-segment-conditions.php
1866        do_action( 'jpcrm_post_init' );
1867
1868        $default_listview_filters = array();
1869        $this->listview_filters   = apply_filters( 'jpcrm_listview_filters', $default_listview_filters );
1870
1871        // this allows us to do stuff (e.g. redirect based on a condition) prior to headers being sent
1872        $this->catch_preheader_interrupts();
1873    }
1874
1875    /**
1876     * Fires after CRM and WP fully loaded
1877     * (Late in stack)
1878     **/
1879    public function post_wp_loaded() {
1880
1881        // Run any migrations
1882        $this->run_migrations( 'wp_loaded' );
1883
1884        // do our late-fire actions
1885        do_action( 'jpcrm_post_wp_loaded' );
1886    }
1887
1888    /**
1889     * Fires on admin_init
1890     **/
1891    public function admin_init() {
1892
1893        // ====================================================================
1894        // ==================== General Perf Testing ==========================
1895        if ( defined( 'ZBSPERFTEST' ) ) {
1896            zeroBSCRM_performanceTest_startTimer( 'admin_init' );
1897        }
1898        // =================== / General Perf Testing =========================
1899        // ====================================================================
1900
1901        // Autoload AJAX files for any admin pages
1902        $this->load_admin_ajax();
1903
1904        // only load if we are a ZBS admin page? Will this break the world?!?
1905        if ( zeroBSCRM_isAdminPage() ) {
1906
1907            // catch wiz + redirect
1908            $this->wizardInitCheck();
1909
1910            // Let's ensure that our cronjobs are there
1911            if ( ! wp_next_scheduled( 'jpcrm_cron_watcher' ) ) {
1912                jpcrm_cron_watcher();
1913            }
1914
1915            // apply any filters req. to the exclude-from-settings arr
1916            global $zbsExtensionsExcludeFromSettings;
1917            $zbsExtensionsExcludeFromSettings = apply_filters( 'zbs_exclude_from_settings', $zbsExtensionsExcludeFromSettings );
1918
1919            // this moves any _GET into $zbsPage
1920            $this->globalise_vars();
1921
1922            // this sets page titles where it can ($this->setPageTitle();)
1923            add_filter( 'zbs_admin_title_modifier', array( $this, 'setPageTitle' ), 10, 2 );
1924
1925            // This is a pre-loader for edit page classes, allows us to save data before loading the page :)
1926            zeroBSCRM_prehtml_pages_admin_addedit();
1927
1928            // Again, necessary? do_action('before_zerobscrm_admin_init');
1929
1930            // All style registering moved into ZeroBSCRM.ScriptsStyles.php for v3.0, was getting messy
1931            zeroBSCRM_scriptStyles_initStyleRegister();
1932
1933            // JS Root obj (zbs_root)
1934            zeroBSCRM_scriptStyles_enqueueJSRoot();
1935
1936            // Check for stored messages in case we were redirected.
1937            $this->maybe_retrieve_page_messages();
1938
1939            // autohide admin_notices on pages we specify
1940            jpcrm_autohide_admin_notices_for_specific_pages();
1941        }
1942
1943        // ====================================================================
1944        // ==================== General Perf Testing ==========================
1945        if ( defined( 'ZBSPERFTEST' ) ) {
1946            zeroBSCRM_performanceTest_closeGlobalTest( 'admin_init' );
1947        }
1948        // =================== / General Perf Testing =========================
1949        // ====================================================================
1950
1951        // ====================================================================
1952        // ==================== General Perf Testing ==========================
1953        if ( defined( 'ZBSPERFTEST' ) ) {
1954            zeroBSCRM_performanceTest_startTimer( 'after-zerobscrm-admin-init' );
1955        }
1956        // =================== / General Perf Testing =========================
1957        // ====================================================================
1958
1959        // Action hook
1960        do_action( 'after-zerobscrm-admin-init' );
1961
1962        // ====================================================================
1963        // ==================== General Perf Testing ==========================
1964        if ( defined( 'ZBSPERFTEST' ) ) {
1965            zeroBSCRM_performanceTest_closeGlobalTest( 'after-zerobscrm-admin-init' );
1966        }
1967        // =================== / General Perf Testing =========================
1968        // ====================================================================
1969    }
1970
1971    // this checks whether any extensions are active which might bring down an install to 500 error
1972    // backstop in case extensions used which don't deactivate for whatver reason (being doubly sure in core)
1973    // as part of UX work also make sure all extensions are classified and only load when core hook triggers
1974    // some users were still hitting 500 errors so worth having this in place to protect us / help retain / PICNIC
1975    // wh renamed: check_active_zbs_extensions -> pre_deactivation_check_exts_deactivated
1976    function pre_deactivation_check_exts_deactivated() {
1977            global $zbs;
1978            // } from telemetry however what if someone has extensions installed this should show up
1979            // } this is the full count (not whether they are active)
1980            $extensions_installed = zeroBSCRM_extensionsInstalledCount( true );
1981        if ( $extensions_installed > 0 ) {
1982            // tried to add an error above the plugins.php list BUT it did not seem to show
1983            // instead re-direct to one of our pages which tells them about making sure extensions are
1984            // deactivated before deactivating core
1985            wp_safe_redirect( admin_url( 'admin.php?page=' . $zbs->slugs['extensions-active'] ) );
1986            die( 0 ); // will killing it here stop deactivation?
1987
1988            // failsafe?
1989            return false;
1990        }
1991            return true;
1992    }
1993
1994    public function uninstall() {
1995        // Deactivate all the extensions
1996        zeroBSCRM_extensions_deactivateAll();
1997
1998        // Skip the deactivation feedback if it's a JSON/AJAX request or via WP-CLI
1999        if ( wp_doing_ajax() || wp_is_json_request() || ( defined( 'WP_CLI' ) && WP_CLI ) || wp_is_xml_request() ) {
2000            return;
2001        }
2002
2003        ##WLREMOVE
2004
2005        // Remove roles :)
2006        zeroBSCRM_clearUserRoles();
2007
2008        // Skip redirect if it's a bulk deactivation with more than one plugin.
2009        if (
2010            // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- This is safe.
2011            isset( $_POST['action'] )
2012            && $_POST['action'] === 'deactivate-selected'
2013            && isset( $_POST['checked'] )
2014            && count( $_POST['checked'] ) > 1
2015            // phpcs:enable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- This is safe.
2016        ) {
2017            return;
2018        }
2019
2020            $feedbackAlready = get_option( 'zbsfeedback' );
2021
2022            // if php notice, (e.g. php ver to low, skip this)
2023        if ( ! defined( 'ZBSDEACTIVATINGINPROG' ) && $feedbackAlready == false && ! defined( 'ZBSPHPVERDEACTIVATE' ) ) {
2024
2025            // } Show stuff + Deactivate
2026            // } Define is to stop an infinite loop :)
2027            // } (Won't get here the second time)
2028            define( 'ZBSDEACTIVATINGINPROG', true );
2029
2030            // } Before you go...
2031            if ( function_exists( 'file_get_contents' ) ) {
2032
2033                // } telemetry
2034                // V3.0 No more telemetry if (!zeroBSCRM_isWL()) zeroBSCRM_teleLogAct(3);
2035
2036                try {
2037
2038                    // } Also manually deactivate before exit
2039                    deactivate_plugins( plugin_basename( ZBS_ROOTFILE ) );
2040
2041                    // } require template
2042                    require_once ZEROBSCRM_PATH . 'admin/activation/before-you-go.php';
2043                    exit( 0 );
2044
2045                } catch ( Exception $e ) {
2046
2047                    // } Nada
2048
2049                }
2050            }
2051        }
2052            ##/WLREMOVE
2053
2054        // } //end of check if there are extensions active
2055    }
2056
2057    public function install() {
2058
2059        // dir build
2060        zeroBSCRM_privatisedDirCheck();
2061        zeroBSCRM_privatisedDirCheckWorks(); // the folder used to be '_wip', updated to 'tmp'
2062
2063        // Additional DB tables hook on activation (such as api keys table) - requires ZeroBSCRM.Database.php
2064        zeroBSCRM_database_check();
2065
2066        // roles
2067        zeroBSCRM_clearUserRoles();
2068
2069        // roles +
2070        zeroBSCRM_addUserRoles();
2071    }
2072
2073    // this func runs on admin_init and xxxx
2074    public function wizardInitCheck() {
2075
2076        // not authorized to run the wizard
2077        if ( ! current_user_can( 'manage_options' ) ) {
2078            return;
2079        }
2080
2081        $force_wizard = false;
2082
2083        // reset any wizard overrides if URL param is present
2084        if ( ! empty( $_GET['jpcrm_force_wizard'] ) ) {
2085            delete_option( 'jpcrm_skip_wizard' );
2086            delete_transient( 'jpcrm_defer_wizard' );
2087            $force_wizard = true;
2088        }
2089
2090        // set option if URL param
2091        if ( ! empty( $_GET['jpcrm_skip_wizard'] ) ) {
2092            update_option( 'jpcrm_skip_wizard', 1, false );
2093        }
2094
2095        // wizard was purposely skipped
2096        if ( get_option( 'jpcrm_skip_wizard' ) ) {
2097            return;
2098        }
2099
2100        // set transient if URL param
2101        if ( ! empty( $_GET['jpcrm_defer_wizard'] ) ) {
2102            set_transient( 'jpcrm_defer_wizard', 1, 30 );
2103        }
2104
2105        // Skip wizard temporarily if transient is set
2106        // this can be used for Jetpack CTA installs, for example
2107        if ( get_transient( 'jpcrm_defer_wizard' ) ) {
2108            return;
2109        }
2110
2111        // Bail if activating from network, or bulk
2112        if ( is_network_admin() ) { // WH removed this, if bulk, still do it :D || isset( $_GET['activate-multi'] ) ) {
2113            return;
2114        }
2115
2116        ##WLREMOVE
2117        // Bail if already completed wizard
2118        // $run_count increments each time the wizard is loaded
2119        // always run if forced
2120        $run_count = get_option( 'zbs_wizard_run', 0 );
2121        if ( $run_count <= 0 || $force_wizard ) {
2122            // require welcome wizard template
2123            require_once ZEROBSCRM_PATH . 'admin/activation/welcome-to-jpcrm.php';
2124            exit( 0 );
2125        }
2126        ##/WLREMOVE
2127    }
2128
2129    /**
2130     * Loads the Plugin Updater Class
2131     *
2132     * @since 2.97.x
2133     */
2134    public function include_updater() {
2135
2136        // } Initialise ZBS Updater Class
2137        global $zeroBSCRM_Updater;
2138        if ( ! isset( $zeroBSCRM_Updater ) ) {
2139            $zeroBSCRM_Updater = new zeroBSCRM_Plugin_Updater(
2140                $this->urls['api'],
2141                $this->update_api_version,
2142                ZBS_ROOTFILE,
2143                array(
2144                    'version' => $this::VERSION,
2145                    'license' => false, // license initiated to false..
2146                )
2147            );
2148        }
2149    }
2150
2151    /**
2152     *
2153     * Autoloads the modules (core extensions) included with core
2154     *
2155     * Ultimately we should probably include Jetpack Forms and maybe CSV Importer.
2156     *
2157     * Modularisation of this manner may allow easier drop-in integrations by third-party devs as well.
2158     */
2159    public function jpcrm_register_modules() {
2160
2161        // include modules class
2162        require_once ZEROBSCRM_INCLUDE_PATH . 'class-crm-modules.php';
2163
2164        // load it (which registers modules and creates $zbs->modules)
2165        $this->modules = new Automattic\JetpackCRM\CRM_Modules();
2166    }
2167
2168    /**
2169     *
2170     * Check to see if there's an integration our users could be using
2171     * At some point we should modularize this as well
2172     */
2173    public function jpcrm_sniff_features() {
2174        $this->feature_sniffer->sniff_for_plugin(
2175            array(
2176                'feature_slug'   => 'jetpackforms',
2177                'plugin_slug'    => 'jetpack/jetpack.php',
2178                'more_info_link' => 'https://kb.jetpackcrm.com/knowledge-base/jetpack-contact-forms/',
2179            )
2180        );
2181        do_action( 'jpcrm_sniff_features' );
2182        $this->feature_sniffer->show_feature_alerts();
2183    }
2184
2185    /**
2186     * Ensures fatal errors are logged so they can be picked up in the status report.
2187     *
2188     * @since 3.2.0
2189     */
2190    public function log_errors() {
2191        $error = error_get_last();
2192        if ( E_ERROR === $error['type'] ) {
2193            $this->write_log( $error['message'] . PHP_EOL );    // check this method.. should be OK
2194        }
2195    }
2196
2197    public function write_log( $log ) {
2198        if ( is_array( $log ) || is_object( $log ) ) {
2199            error_log( print_r( $log, true ) );
2200        } else {
2201            error_log( $log );
2202        }
2203    }
2204
2205    /**
2206     * Define constant if not already set.
2207     *
2208     * @param string      $name  Constant name.
2209     * @param string|bool $value Constant value.
2210     */
2211    private function define( $name, $value ) {
2212        if ( ! defined( $name ) ) {
2213            define( $name, $value );
2214        }
2215    }
2216
2217    /**
2218     * What type of request is this?
2219     * Note: 'frontend' returns true for ajax calls too.
2220     *
2221     * @param  string $type admin, ajax, cron or frontend.
2222     * @return bool
2223     */
2224    private function is_request( $type ) {
2225        switch ( $type ) {
2226            case 'admin':
2227                return is_admin();
2228            case 'ajax':
2229                return defined( 'DOING_AJAX' );
2230            case 'cron':
2231                return defined( 'DOING_CRON' );
2232            case 'frontend':
2233                return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
2234        }
2235    }
2236
2237    /**
2238     * Check the active theme.
2239     *
2240     * @since  2.6.9
2241     * @param  string $theme Theme slug to check.
2242     * @return bool
2243     */
2244    private function is_active_theme( $theme ) {
2245        return get_template() === $theme;
2246    }
2247
2248    /**
2249     * Include required frontend files.
2250     */
2251    public function frontend_includes() {
2252    }
2253
2254    /**
2255     * Get the plugin url.
2256     *
2257     * @return string
2258     */
2259    public function plugin_url() {
2260        return untrailingslashit( plugins_url( '/', ZBS_ROOTFILE ) );
2261    }
2262
2263    /**
2264     * Get the plugin path.
2265     *
2266     * @return string
2267     */
2268    public function plugin_path() {
2269        return untrailingslashit( plugin_dir_path( ZBS_ROOTFILE ) );
2270    }
2271
2272    /**
2273     * Check if Settings exists, load default if not
2274     *
2275     * @return string
2276     */
2277    public function checkSettingsSetup() {
2278        global $zeroBSCRM_Conf_Setup;
2279        if ( ! isset( $this->settings ) ) {
2280            $this->settings = null; // https://stackoverflow.com/questions/8900701/creating-default-object-from-empty-value-in-php
2281            $this->settings = new WHWPConfigLib( $zeroBSCRM_Conf_Setup );
2282        }
2283    }
2284
2285    /**
2286     * Check if user has capabilities to view backend :)
2287     *
2288     * @return string
2289     */
2290    public function checkBackendAccess() {
2291        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
2292            return;
2293        }
2294
2295        // if zbs_customer in admin area, kick em out :)
2296        if ( zeroBSCRM_isRole( 'zerobs_customer' ) ) {
2297
2298            if ( is_admin() ) {
2299                $redirect = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : home_url( '/' );
2300                if ( current_user_can( 'zerobs_customer' ) ) {
2301                    wp_redirect( $redirect );
2302                    exit( 0 );
2303                }
2304            }
2305
2306            // and remove wp bar from front end
2307            add_filter( 'show_admin_bar', '__return_false' );
2308        }
2309    }
2310
2311    /**
2312     * Get Ajax URL.
2313     *
2314     * @return string
2315     */
2316    public function ajax_url() {
2317        return admin_url( 'admin-ajax.php', 'relative' );
2318    }
2319
2320    /**
2321     * Get Site Domain
2322     *
2323     * @return string
2324     */
2325    public function get_domain() {
2326
2327        $urlparts = parse_url( home_url() );
2328
2329        if ( isset( $urlparts['host'] ) ) {
2330            return $urlparts['host'];
2331        }
2332
2333        return false;
2334    }
2335
2336    /**
2337     * Globalise ZBS Vars
2338     *
2339     * @return nout
2340     */
2341    public function globalise_vars() {
2342
2343        // here, where things are consistently used through a page
2344        // e.g. admin.php?page=zbs-add-edit&action=edit&zbsid=3
2345        // we globally set them to save time later :)
2346        global $zbsPage;
2347        $zbsPage = array();
2348
2349        // zbsid
2350        if ( isset( $_GET['zbsid'] ) && ! empty( $_GET['zbsid'] ) ) {
2351
2352            $zbsid = (int) sanitize_text_field( $_GET['zbsid'] );
2353
2354            // if $zbsid is set, make it a GLOBAL (save keep re-getting)
2355            // this is used by metaboxes, insights + hypothesis, titles below etc. DO NOT REMOVE
2356            if ( $zbsid > 0 ) {
2357                $zbsPage['zbsid'] = $zbsid;
2358            }
2359        }
2360
2361        // page
2362        if ( isset( $_GET['page'] ) && ! empty( $_GET['page'] ) ) {
2363
2364            $page = sanitize_text_field( $_GET['page'] );
2365
2366            // if $page is set, make it a GLOBAL (save keep re-getting)
2367            // this is used by metaboxes, insights + hypothesis, titles below etc. DO NOT REMOVE
2368            if ( ! empty( $page ) ) {
2369                $zbsPage['page'] = $page;
2370            }
2371        }
2372
2373        // action
2374        if ( isset( $_GET['action'] ) && ! empty( $_GET['action'] ) ) {
2375
2376            $action = sanitize_text_field( $_GET['action'] );
2377
2378            // if $action is set, make it a GLOBAL (save keep re-getting)
2379            // this is used by metaboxes, insights + hypothesis, titles below etc. DO NOT REMOVE
2380            if ( ! empty( $action ) ) {
2381                $zbsPage['action'] = $action;
2382            }
2383        }
2384
2385        // type
2386        if ( isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ) {
2387
2388            $type = sanitize_text_field( $_GET['type'] );
2389
2390            // if $type is set, make it a GLOBAL (save keep re-getting)
2391            // this is used by metaboxes, insights + hypothesis, titles below etc. DO NOT REMOVE
2392            if ( ! empty( $type ) ) {
2393                $zbsPage['type'] = $type;
2394            }
2395        }
2396
2397        // zbstype
2398        if ( isset( $_GET['zbstype'] ) && ! empty( $_GET['zbstype'] ) ) {
2399
2400            $zbstype = sanitize_text_field( $_GET['zbstype'] );
2401
2402            // if $zbstype is set, make it a GLOBAL (save keep re-getting)
2403            // this is used by metaboxes, insights + hypothesis, titles below etc. DO NOT REMOVE
2404            if ( ! empty( $zbstype ) ) {
2405                $zbsPage['zbstype'] = $zbstype;
2406            }
2407        }
2408
2409        // if action = 'edit' + no 'zbsid' = NEW EDIT (e.g. new contact)
2410        if ( isset( $zbsPage['action'] ) && $zbsPage['action'] == 'edit' && ( ! isset( $zbsPage['zbsid'] ) || $zbsPage['zbsid'] < 1 ) ) {
2411
2412            $zbsPage['new_edit'] = true;
2413
2414        }
2415    }
2416
2417    /**
2418     * Get Globalised ZBS Vars
2419     *
2420     * @return string|int|bool
2421     */
2422    public function zbsvar( $key = '' ) {
2423
2424        // globalise_vars returned
2425        global $zbsPage;
2426
2427        // zbsid
2428        if ( is_array( $zbsPage ) && ! empty( $key ) && isset( $zbsPage[ $key ] ) && ! empty( $zbsPage[ $key ] ) ) {
2429
2430            return $zbsPage[ $key ];
2431
2432        }
2433
2434        return -1;
2435    }
2436
2437    /**
2438     * Tries to set page title (where it can)
2439     *
2440     * @return nout
2441     */
2442    public function setPageTitle( $title = '', $adminTitle = '' ) {
2443
2444        // default
2445        $pageTitle = ( ( $adminTitle == '' ) ? __( 'Jetpack CRM', 'zero-bs-crm' ) : $adminTitle );
2446
2447        // we only need to do this for pages where we're using custom setups (not added via wp_add_menu whatever)
2448        if ( $this->zbsvar( 'page' ) != -1 ) {
2449
2450            switch ( $this->zbsvar( 'page' ) ) {
2451
2452                case 'zbs-add-edit':
2453                    // default
2454                    $pageTitle = __( 'View | Jetpack CRM' . $this->zbsvar( 'action' ), 'zero-bs-crm' );
2455
2456                    // default/no type passed
2457                    $objType = __( 'Contact', 'zero-bs-crm' );
2458
2459                    switch ( $this->zbsvar( 'zbstype' ) ) {
2460
2461                        case 'contact':
2462                            $objType = __( 'Contact', 'zero-bs-crm' );
2463                            break;
2464
2465                        case 'company':
2466                            $objType = jpcrm_label_company();
2467                            break;
2468
2469                        case 'segment':
2470                            $objType = __( 'Segment', 'zero-bs-crm' );
2471                            break;
2472
2473                        case 'quote':
2474                            $objType = __( 'Quote', 'zero-bs-crm' );
2475                            break;
2476
2477                        case 'invoice':
2478                            $objType = __( 'Invoice', 'zero-bs-crm' );
2479                            break;
2480
2481                        case 'transaction':
2482                            $objType = __( 'Transaction', 'zero-bs-crm' );
2483                            break;
2484
2485                        case 'event':
2486                            $objType = __( 'Task', 'zero-bs-crm' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2487                            break;
2488
2489                        case 'form':
2490                            $objType = __( 'Form', 'zero-bs-crm' );
2491                            break;
2492
2493                        case 'quotetemplate':
2494                            $objType = __( 'Quote Template', 'zero-bs-crm' );
2495                            break;
2496
2497                        case 'log':
2498                            $objType = __( 'Log', 'zero-bs-crm' );
2499                            break;
2500
2501                    }
2502
2503                    // just formatting:
2504                    if ( ! empty( $objType ) ) {
2505                        $objType = ' ' . $objType;
2506                    }
2507
2508                    switch ( $this->zbsvar( 'action' ) ) {
2509
2510                        case 'edit':
2511                            $pageTitle = __( 'Edit ' . $objType . ' | Jetpack CRM', 'zero-bs-crm' );
2512                            break;
2513
2514                        case 'delete':
2515                            $pageTitle = __( 'Delete ' . $objType . ' | Jetpack CRM', 'zero-bs-crm' );
2516                            break;
2517
2518                        case 'view':
2519                            $pageTitle = __( 'View ' . $objType . ' | Jetpack CRM', 'zero-bs-crm' );
2520                            break;
2521
2522                    }
2523
2524                    break;
2525
2526                case 'zerobscrm-emails':
2527                    // default
2528                    $pageTitle = __( 'Email Manager | Jetpack CRM', 'zero-bs-crm' );
2529
2530                    break;
2531
2532                case 'tag-manager':
2533                    $pageTitle = __( 'Tag Manager | Jetpack CRM', 'zero-bs-crm' );
2534                    break;
2535
2536                case 'zerobscrm-notifications':
2537                    $pageTitle = __( 'Notifications | Jetpack CRM', 'zero-bs-crm' );
2538                    break;
2539
2540                case 'zbs-email-templates':
2541                    $pageTitle = __( 'Email Templates | Jetpack CRM', 'zero-bs-crm' );
2542                    break;
2543
2544                case 'zbs-export-tools':
2545                    $pageTitle = __( 'Export Tools | Jetpack CRM', 'zero-bs-crm' );
2546                    break;
2547
2548                case 'zerobscrm-csvimporterlite-app':
2549                    $pageTitle = __( 'Import Tools | Jetpack CRM', 'zero-bs-crm' );
2550                    break;
2551
2552            }
2553        }
2554
2555        return $pageTitle;
2556    }
2557
2558    /**
2559     * Get Current User's screen options for current page
2560     * This requires add_filter on page to work :)
2561     * // actually just use a global for now :) - so just set global $zbs->pageKey on page :)
2562     * // 2.94.2+ can pass pagekey to get opts for page other than current (used for list view perpage)
2563     *
2564     * @return array() screen options
2565     */
2566    public function userScreenOptions( $pageKey = false ) {
2567
2568        // TO ADD LATER: (optionally) allow admins to create a 'forced' screen options set (e.g. metabox layout etc.)
2569        // ... forced or default :)
2570
2571        $currentUserID = get_current_user_id();
2572
2573        if ( ! $pageKey ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
2574
2575            // actually just use a global for now :) - so just set global $zbs->pageKey on page :)
2576            $pageKeyCheck = apply_filters( 'zbs_pagekey', $this->pageKey );
2577
2578        } else {
2579            $pageKeyCheck = $pageKey;
2580        }
2581
2582        if ( $currentUserID > 0 && ! empty( $pageKeyCheck ) ) {
2583
2584            // retrieve via dal
2585
2586            return $this->DAL->userSetting( $currentUserID, 'screenopts_' . $pageKeyCheck, false );
2587
2588        }
2589
2590        return array();
2591    }
2592
2593    /**
2594     * Get global screen option settings
2595     *
2596     * @param string $page_key Page key.
2597     */
2598    public function global_screen_options( $page_key = false ) {
2599
2600        if ( empty( $page_key ) ) {
2601            $page_key = apply_filters( 'zbs_pagekey', $this->pageKey ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
2602        }
2603
2604        if ( empty( $page_key ) ) {
2605            return array();
2606        }
2607
2608        $screen_options = $this->DAL->getSetting( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
2609            array(
2610                'key' => 'screenopts_' . $page_key,
2611            )
2612        );
2613        return $screen_options;
2614    }
2615
2616    /**
2617     * Shorthand for get_Current_user_id
2618     *
2619     * @return int user id
2620     */
2621    public function user() {
2622
2623        return get_current_user_id();
2624    }
2625
2626    /**
2627     * Shorthand for zeroBSCRM_getSetting('license_key')
2628     *
2629     * @return array() license settings (trimmed down)
2630     */
2631    public function license() {
2632
2633        // default
2634        $ret = array(
2635            'validity' => false,
2636            'access'   => 'none',
2637            'expires'  => -1,
2638        );
2639
2640        // retrieve
2641        $licenseKeyArr = zeroBSCRM_getSetting( 'license_key' );
2642
2643        // return only these (not key, for simple semi-security? lol. not even)
2644        if ( is_array( $licenseKeyArr ) ) {
2645            if ( isset( $licenseKeyArr['validity'] ) ) {
2646                $ret['validity'] = $licenseKeyArr['validity'];
2647            }
2648            if ( isset( $licenseKeyArr['access'] ) ) {
2649                $ret['access'] = $licenseKeyArr['access'];
2650            }
2651            if ( isset( $licenseKeyArr['expires'] ) ) {
2652                $ret['expires'] = $licenseKeyArr['expires'];
2653            }
2654        }
2655
2656        return $ret;
2657    }
2658
2659    /*
2660    * Do we have a license key?
2661    */
2662    public function has_license_key() {
2663
2664        $settings = $this->settings->get( 'license_key' );
2665
2666        // checks if exists and it's not empty
2667        if ( ! empty( $settings['key'] ) ) {
2668            return true;
2669        }
2670
2671        return false;
2672    }
2673
2674    /**
2675     * Returns true/false if has AT LEAST entrepreneur Bundle
2676     *  ... suspect this is an easy way to HACK out show promotional material. So rethink around that at some point.
2677     *
2678     * @return bool
2679     */
2680    public function hasEntrepreneurBundleMin() {
2681
2682        $license             = $this->license();
2683        $valid               = array( 'entrepreneur', 'reseller' );
2684        $license['validity'] = ( $license['validity'] === 'true' ? true : false );
2685        if ( $license['validity'] && ( in_array( $license['access'], $valid ) ) ) {
2686            return true;
2687        }
2688
2689        return false;
2690    }
2691
2692    /**
2693     * Returns true/false if has AT LEAST entrepreneur Bundle
2694     *  ... suspect this is an easy way to HACK out show promotional material. So rethink around that at some point.
2695     *
2696     * @return bool
2697     */
2698    public function hasFreelancerBundleMin() {
2699
2700        $license             = $this->license();
2701        $valid               = array( 'freelancer', 'entrepreneur', 'reseller' );
2702        $license['validity'] = ( $license['validity'] === 'true' ? true : false );
2703        if ( $license['validity'] && ( in_array( $license['access'], $valid ) ) ) {
2704            return true;
2705        }
2706
2707        return false;
2708    }
2709
2710    /**
2711     * Returns pretty label for subscription
2712     *  ... suspect this is an easy way to HACK out show promotional material. So rethink around that at some point.
2713     *
2714     * @return string
2715     */
2716    public function getSubscriptionLabel( $str = '' ) {
2717
2718        if ( empty( $str ) ) {
2719            // get from license
2720            $license = $this->license();
2721            if ( isset( $license['validity'] ) ) {
2722                $license['validity'] = ( $license['validity'] === 'true' ? true : false );
2723                if ( $license['validity'] && isset( $license['access'] ) && ! empty( $license['access'] ) ) {
2724                    $str = $license['access'];
2725                }
2726            }
2727        }
2728
2729        switch ( $str ) {
2730
2731            case 'freelancer':
2732                return 'Freelancer Bundle';
2733                break;
2734
2735            case 'entrepreneur':
2736                return 'Entrepreneur Bundle';
2737                break;
2738
2739            case 'reseller':
2740                return 'Branded Bundle';
2741                break;
2742
2743            // for all others, use this:
2744            default:
2745                return 'Extension: ' . ucwords( $str );
2746                break;
2747
2748        }
2749
2750        return false;
2751    }
2752
2753    // ======= Menu Management =========
2754
2755    /**
2756     * Return private $menu
2757     *
2758     * @since 3.0
2759     */
2760    public function getMenu() {
2761        return $this->menu; }
2762
2763    /**
2764     * Sets up admin menu
2765     *
2766     * @since 3.0
2767     */
2768    public function admin_menu() {
2769
2770        $this->applyMenu();
2771
2772        // hook for extensions to add menus :)
2773        do_action( 'zerobs_admin_menu' );
2774    }
2775
2776    /**
2777     * Applys admin menu
2778     * (v3.0 + this replaces zeroBSCRM_admin_menu)
2779     *
2780     * @since 3.0
2781     */
2782    private function applyMenu() {
2783
2784        // build init, if not there
2785        if ( ! isset( $this->menu ) && ! is_array( $this->menu ) ) {
2786            $this->menu = zeroBSCRM_menu_buildMenu();
2787        }
2788        // ready?
2789        if ( isset( $this->menu ) && is_array( $this->menu ) ) {
2790
2791            // Here we apply filters, this allows other ext etc. to modify menu items before we priotise + build
2792            $menu = apply_filters( 'zbs_menu_wpmenu', $this->menu );
2793
2794            // remove non-permissioned
2795            $menu = zeroBSCRM_menu_securityGuard( $menu );
2796
2797            // resort based on 'order'
2798            $menu = zeroBSCRM_menu_order( $menu );
2799
2800            // output (adds menus)
2801            zeroBSCRM_menu_applyWPMenu( $menu );
2802
2803        }
2804    }
2805
2806    /**
2807     * Learn Menu
2808     *
2809     * @since 3.0
2810     */
2811    public function initialise_learn_menu() {
2812
2813        $this->learn_menu = new Automattic\JetpackCRM\Learn_Menu();
2814
2815        // render the menu where possible
2816        $this->learn_menu->render_learn_menu();
2817    }
2818
2819    // ======= / Menu Management =========
2820
2821    /*
2822    * Returns bool, are we running on a PHP version $operator (e.g. '>=') to $version
2823    */
2824    public function has_min_php_version( $php_version ) {
2825
2826        return version_compare( PHP_VERSION, $php_version, '>=' );
2827    }
2828
2829    /**
2830     * Autoload files from a directory which match a regex filter
2831     */
2832    public function autoload_from_directory( string $directory, string $regex_filter ) {
2833
2834        $files = scandir( $directory );
2835
2836        if ( is_array( $files ) ) {
2837
2838            foreach ( $files as $file ) {
2839
2840                // find files `*.ajax.*`
2841                if ( preg_match( $regex_filter, $file ) ) {
2842
2843                    // load it
2844                    require_once $directory . '/' . $file;
2845
2846                }
2847            }
2848        }
2849    }
2850
2851                /**
2852                 * Autoload page AJAX
2853                 * (This should be fired on admin_init and later should be rethought to be true autoloading, not loading all AJAX partials as this does)
2854                 */
2855    private function load_admin_ajax() {
2856
2857        $admin_page_directories = jpcrm_get_directories( ZEROBSCRM_PATH . 'admin' );
2858
2859        if ( is_array( $admin_page_directories ) ) {
2860
2861            foreach ( $admin_page_directories as $directory ) {
2862
2863                $files = scandir( ZEROBSCRM_PATH . 'admin/' . $directory );
2864
2865                if ( is_array( $files ) ) {
2866
2867                    foreach ( $files as $file ) {
2868
2869                        // find files `*.ajax.*`
2870                        if ( strrpos( $file, '.ajax.' ) > 0 ) {
2871
2872                            // load it
2873                            require_once ZEROBSCRM_PATH . 'admin/' . $directory . '/' . $file;
2874
2875                        }
2876                    }
2877                }
2878            }
2879        }
2880    }
2881
2882                /**
2883                 * Runs migrations routine
2884                 */
2885    public function run_migrations( $run_at = 'init' ) {
2886
2887        // Run any migrations
2888        zeroBSCRM_migrations_run( $this->settings->get( 'migrations' ), $run_at );
2889    }
2890
2891                /**
2892                 * Centralised method for detecting presence of WooCommerce
2893                 */
2894    public function woocommerce_is_active() {
2895
2896        // check for Woo Presence
2897        if ( is_plugin_active( 'woocommerce/woocommerce.php' ) || ( isset( $GLOBALS['woocommerce'] ) && $GLOBALS['woocommerce'] ) ) {
2898
2899                            return true;
2900
2901        }
2902
2903        return false;
2904    }
2905
2906                /**
2907                 * Centralised method for detecting presence of MailPoet
2908                 */
2909    public function mailpoet_is_active() {
2910
2911        // check for mailpoet Presence
2912        if ( is_plugin_active( 'mailpoet/mailpoet.php' ) ) {
2913
2914                    return true;
2915
2916        }
2917
2918        return false;
2919    }
2920
2921                // ======= / Basic Library Management =========
2922
2923                // } If usage tracking is active - include the tracking code.
2924
2925                // =========== Resource Loading ===============
2926
2927                /**
2928                 * Loads usage tracking where user has accepted via settings
2929                 */
2930    public function load_usage_tracking() {
2931
2932        // load if not loaded, and permitted
2933        if ( $this->tracking === false ) {
2934
2935                            $tracking_active = zeroBSCRM_getSetting( 'shareessentials' );
2936            if ( $tracking_active ) {
2937                require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-usage-tracking.php';
2938                $this->tracking = new jpcrm_usage_tracking();
2939            }
2940        }
2941
2942        return $this->tracking;
2943    }
2944
2945                /**
2946                 * Loads Endpoint Listener library
2947                 */
2948    public function load_listener() {
2949
2950        if ( ! isset( $this->listener ) || $this->listener == null ) {
2951
2952                    $this->listener = new \Automattic\JetpackCRM\Endpoint_Listener();
2953
2954        }
2955
2956        return $this->listener;
2957    }
2958
2959                /**
2960                 * Loads OAuth handler library
2961                 */
2962    public function load_oauth_handler() {
2963
2964        if ( ! isset( $this->oauth ) || $this->oauth == null ) {
2965                    $this->oauth = new \Automattic\JetpackCRM\Oauth_Handler();
2966        }
2967
2968        return $this->oauth;
2969    }
2970
2971                /**
2972                 * Loads Encryption library
2973                 */
2974    public function load_encryption() {
2975
2976        require_once ZEROBSCRM_INCLUDE_PATH . 'class-encryption.php';
2977
2978        if ( ! isset( $this->encryption ) || $this->encryption == null ) {
2979            $this->encryption = new \Automattic\JetpackCRM\Encryption();
2980        }
2981
2982        return $this->encryption;
2983    }
2984
2985    /**
2986     * Loads Package Installer library
2987     */
2988    public function load_package_installer() {
2989
2990        if ( ! isset( $this->package_installer ) || $this->package_installer == null ) {
2991
2992            // Package installer
2993            require_once ZEROBSCRM_INCLUDE_PATH . 'class-package-installer.php';
2994
2995            $this->package_installer = new \Automattic\JetpackCRM\Package_Installer();
2996
2997        }
2998
2999        return $this->package_installer;
3000    }
3001
3002    /**
3003     * Package Installer pass through to simplify package dependency
3004     */
3005    public function ensure_package_installed( $package_key, $min_version = 0 ) {
3006
3007        $this->load_package_installer();
3008
3009        return $this->package_installer->ensure_package_is_installed( $package_key, $min_version );
3010    }
3011
3012    // ============ / Resource Loading ============
3013
3014    // =============  PDF engine ============
3015
3016    /**
3017     * Initialise Dompdf instance
3018     */
3019    public function pdf_engine() {
3020
3021        // PDF Install check:
3022        zeroBSCRM_extension_checkinstall_pdfinv();
3023
3024        // if we don't set options initially, weird issues happen (e.g. `installed-fonts.json` is overwritten at times):
3025        // https://github.com/dompdf/dompdf/issues/2990
3026        $options = new Dompdf\Options();
3027
3028        // this batch of option setting ensures we allow remote images (http/s)
3029        // ... but they're only allowed from same-site urls
3030        $options->set( 'isRemoteEnabled', true );
3031
3032        // We have to specify a temporary dir to download the remote images. Not all systems have a writable /tmp dir.
3033        $jpcrm_storage_path = wf_jpcrm_storage_dir_path();
3034        if ( $jpcrm_storage_path ) {
3035            $options->setTempDir( $jpcrm_storage_path . '/tmp' );
3036        }
3037
3038        // Commented: Not necessary. Using CDN sites (Jetpack Boost) for assets will fail because of the validation.
3039        // $options->addAllowedProtocol( 'http://', 'jpcrm_dompdf_assist_validate_remote_uri' );
3040        // $options->addAllowedProtocol( 'https://', 'jpcrm_dompdf_assist_validate_remote_uri' );
3041
3042        // use JPCRM storage dir for extra fonts
3043        $options->set( 'fontDir', jpcrm_storage_fonts_dir_path() );
3044
3045        // build PDF
3046        $dompdf = new Dompdf\Dompdf( $options );
3047
3048        // set some generic defaults, (can be overriden later)
3049        $dompdf->set_paper( 'A4', 'portrait' );
3050        $dompdf->setHttpContext(
3051            stream_context_create(
3052                array(
3053                    'ssl' => array(
3054                        'verify_peer'       => false,
3055                        'verify_peer_name'  => false,
3056                        'allow_self_signed' => true,
3057                    ),
3058                )
3059            )
3060        );
3061
3062        return $dompdf;
3063    }
3064
3065    // ============ / PDF engine ============
3066
3067    // =========== Templating Class ===============
3068    /**
3069     * Loads templating placeholder class into this->templating_placeholders
3070     * and returns it
3071     * (Can be loaded on-demand)
3072     */
3073    public function get_templating() {
3074
3075        // load if not loaded
3076        if ( $this->templating_placeholders === false ) {
3077
3078            $this->templating_placeholders = new jpcrm_templating_placeholders();
3079
3080        }
3081
3082        return $this->templating_placeholders;
3083    }
3084    /**
3085     * Loads fonts class into this->fonts
3086     * and returns it
3087     * (Can be loaded on-demand)
3088     */
3089    public function get_fonts() {
3090
3091        // load if not loaded
3092        require_once ZEROBSCRM_INCLUDE_PATH . 'jpcrm-fonts.php';
3093
3094        // load if not loaded
3095        if ( ! $this->fonts ) {
3096
3097            $this->fonts = new JPCRM_Fonts();
3098
3099        }
3100
3101        return $this->fonts;
3102    }
3103
3104    // ============ / Templating Class ============
3105
3106    // =========== Error Coding ===================
3107
3108    public function getErrorCode( $errorCodeKey = -1 ) {
3109
3110        if ( $errorCodeKey ) {
3111
3112            // load err codes if not loaded
3113            if ( ! isset( $this->errorCodes ) ) {
3114                $this->errorCodes = zeroBSCRM_errorCodes();
3115            }
3116
3117            // if set, return
3118            if ( isset( $this->errorCodes[ $errorCodeKey ] ) ) {
3119                return $this->errorCodes[ $errorCodeKey ];
3120            }
3121        }
3122
3123        return false;
3124    }
3125
3126    // =========== / Error Coding ===================
3127
3128    private function get_page_messages_transient_key( $obj_type, $inserted_id ) {
3129        return sprintf( 'pageMessages_%d_%d_%d', $this->DAL->objTypeID( $obj_type ), get_current_user_id(), $inserted_id );
3130    }
3131
3132    private function maybe_retrieve_page_messages() {
3133        if ( zeroBS_hasGETParamsWithValues(
3134            array( 'admin.php' ),
3135            array(
3136                'page'   => 'zbs-add-edit',
3137                'action' => 'edit',
3138            )
3139        )
3140            && zeroBS_hasGETParams( array( 'admin.php' ), array( 'zbstype', 'zbsid' ) ) ) {
3141            $transient_key = $this->get_page_messages_transient_key( $_GET['zbstype'], $_GET['zbsid'] );
3142            $page_messages = get_transient( $transient_key );
3143            if ( ! empty( $page_messages ) ) {
3144                $this->pageMessages = $page_messages;
3145                delete_transient( $transient_key );
3146            }
3147        }
3148    }
3149
3150    public function new_record_edit_redirect( $obj_type, $inserted_id ) {
3151        if ( ! empty( $this->pageMessages ) ) {
3152            $transient_key = $this->get_page_messages_transient_key( $obj_type, $inserted_id );
3153            set_transient( $transient_key, $this->pageMessages, MINUTE_IN_SECONDS );
3154        }
3155        wp_redirect( jpcrm_esc_link( 'edit', $inserted_id, $obj_type ) );
3156        exit( 0 );
3157    }
3158
3159    public function catch_preheader_interrupts() {
3160        // This intercepts the request, looking for $_GET['page'] == $zbs->slugs['module-activate-redirect'].
3161        // If it sucessfully finds it, activates the module in $_GET['jpcrm-module-name'] and redirects to its 'hub' slug.
3162        if ( isset( $_GET['page'] ) && $_GET['page'] == $this->slugs['module-activate-redirect'] ) {
3163            $this->modules->activate_module_and_redirect();
3164        }
3165    }
3166}