Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 425
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
zeroBSCRM_isLocal
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
zeroBSCRM_localDblCheck
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
30
zeroBSCRM_updates_pluginHasUpdate
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_Plugin_Updater
0.00% covered (danger)
0.00%
0 / 368
0.00% covered (danger)
0.00%
0 / 11
21170
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 check_update
0.00% covered (danger)
0.00%
0 / 135
0.00% covered (danger)
0.00%
0 / 1
2162
 get_info
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
462
 get_license_key
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isLicenseValid
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isCRMExtension
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
56
 getCRMExtension
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
90
 getCoreWLCRM
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
110
 api_request
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
342
 maybe_adjust_source_dir
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
702
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 */
6
7defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
8
9#} Note: This update check will only run in the presence of a license key, or Paid-for extension (separate to the free Core CRM Plugin on wp.org)
10#} Users of Paid extensions have separately accepted our general privacy policy which allows the
11#} passing of data to our update server/license check system. For core-crm-only users, this will never fire, and no private data will ever be sent or collected.
12
13class zeroBSCRM_Plugin_Updater {
14
15    // vars
16    private $api_url       = '';
17    private $api_ver       = '';
18    private $api_data      = array();
19    private $name          = '';
20    private $slug          = '';
21    private $version       = '';
22    private $wp_override   = false;
23    private $installedExts = false;
24
25    private $nag_every = 432000; // php5.5 doesn't like this: (5 * 24 * 3600); //60; //nag every 5 days..  5 * 24 * 3600
26
27    /*
28    ===============================================================================================
29    =======================================  init functions ==========================================
30    ===============================================================================================
31    */
32    /**
33     * Class constructor.
34     *
35     * @uses plugin_basename()
36     * @uses hook()
37     *
38     * @param string $_api_url     The URL pointing to the custom API endpoint.
39     * @param string $_api_ver     ??WH: Is not clear? (is passing 1.0 currently)
40     * @param string $_plugin_file Path to the plugin file.
41     * @param array  $_api_data    Optional data to send with API calls.
42     */
43    public function __construct( $_api_url, $_api_ver, $_plugin_file, $_api_data = null ) {
44
45        global $zbs_plugin_data;
46        $this->api_url = trailingslashit( $_api_url );
47        $this->api_ver = untrailingslashit( $_api_ver ); // wh: not used as of this check? 17/8/18
48
49        if ( is_array( $_api_data ) ) { // Prevent warning if it's null
50            $this->api_data    = $_api_data;
51            $this->version     = $_api_data['version'];
52            $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
53        }
54        // Set up hooks.
55        $this->init();
56    }
57
58    /**
59     * Set up WordPress filters to hook into WP's update process.
60     *   - pre_set_transient is the info on whether it needs updating
61     *   - plugins_api is when the 'view version [x.x] details' is clicked.
62     */
63    public function init() {
64
65        // actual check for updates
66        add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
67        // can also use this, but untested: add_filter( 'site_transient_update_plugins', array( $this, 'check_update' ) );
68
69        // Set our own get_info call
70        add_filter( 'plugins_api', array( $this, 'get_info' ), 10, 3 );
71
72        // this 'renames' the source
73        // fixes a WP bug where wp renames zero-bs-extension-password-manager to zero-bs-extension-password-manager-KGBdBz
74        // (removes trailing group)
75        // not req: add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 100, 4);
76        add_filter( 'upgrader_source_selection', array( $this, 'maybe_adjust_source_dir' ), 1, 4 );
77    }
78
79    /*
80    ===============================================================================================
81    =====================================  / init functions ==========================================
82    ===============================================================================================
83    */
84
85    /*
86    ===============================================================================================
87    ====================================  main call functions ========================================
88    ===============================================================================================
89    */
90
91    public function check_update( $transient ) {
92
93        // req
94        global $zbs;
95
96        // define
97        $zbs_plugins            = array();
98        $zbs_check_update_start = time();
99        $installedExts          = 0;
100
101        // retrieves all active/all WP plugins installed (not just ours)
102        // Further gets other configs of same...
103        $active_plugins          = get_option( 'active_plugins' );
104        $zbs_active_plugins      = zeroBSCRM_activePluginListSimple();
105        $zbs_all_plugins_and_ver = zeroBSCRM_allPluginListSimple();
106        $zbs_extensions_on_site  = zeroBSCRM_installedProExt();
107        $active_core_modules     = jpcrm_core_modules_installed();
108        $installedExts           = zeroBSCRM_extensionsInstalledCount();
109
110        // 7/1/19 - needs to do if key entered, regardless of if ext, because some peeps enter it before adding extensions
111        if ( $installedExts > 0 || $this->get_license_key() !== false ) {
112
113            // retrieve license key info (later overwritten if updating)
114            $key_info = $zbs->settings->get( 'license_key' );
115
116            // api request (will retrieve cached from this func, if just called same request)
117            $response = $this->api_request(
118                'all_info',
119                array(
120                    'slug'              => 'all',
121                    'zbs-extensions'    => $zbs_extensions_on_site,
122                    'active-extensions' => $active_plugins,
123                    'telemetry-active'  => $zbs_active_plugins,
124                    'telemetry-all'     => $zbs_all_plugins_and_ver,
125                    'core-modules'      => $active_core_modules,
126                )
127            ); // , 'license'=>$lk
128
129            // if got response
130            if ( ! is_wp_error( $response ) ) {
131
132                // check presence of license_key_valid
133                // ... this catches the faulty/empty/devmode ones
134                if ( isset( $response['license_key_valid'] ) && ( $response['license_key_valid'] == 'false' || $response['license_key_valid'] == 'empty' || $response['license_key_valid'] == '' || $response['license_key_valid'] == 'expired' ) ) {
135
136                    // is it dev mode?
137                    if ( isset( $response['devmode'] ) ) {
138
139                        // updates server thinks this user is in dev mode
140                        // ... so, no need to hassle. or do nout.
141
142                    } else {
143
144                        // invalid license key
145                        $key_info['validity'] = '';
146                        if ( isset( $key_info['validity'] ) ) {
147                            $key_info['validity'] = $response['license_key_valid'];
148                        }
149
150                        // if this is saying false, then send a notification IF we haven't nagged for 5 days
151                        if ( ! get_transient( 'zbs-nag-license-key-now' ) ) {
152
153                            // generate nag
154                            $link       = admin_url( 'admin.php?page=zerobscrm-plugin-settings&tab=license' );
155                            $parameters = __( 'Your License Key is incorrect.', 'zero-bs-crm' ) . ' <a href="' . $link . '">' . __( 'Please enter your license key here.', 'zero-bs-crm' ) . '</a>';
156                            $reference  = time();
157                            $cid        = get_current_user_id();
158
159                            #NAG-ME-PLZ
160                            zeroBSCRM_notifyme_insert_notification( $cid, -999, -1, 'license.update.needed', $parameters, $reference );
161                            set_transient( 'zbs-nag-license-key-now', 'nag', $this->nag_every );
162
163                        } // / if set nag
164
165                    } // / if not dev mode
166
167                    // Otherwise, this is a valid license key
168                    // so next check if it's a rebranded one
169                } elseif ( $response['license_key_valid'] == 'brand.template' ) {
170
171                    // set validity - no brand template
172                    $key_info['validity'] = '';
173                    if ( isset( $key_info['validity'] ) ) {
174                        $key_info['validity'] = 'brand.template';
175                    }
176
177                    // This gives specific unbranded nag, so that wl peeps can forward to us, then we can say
178                    // "you need to add a brand template?"
179
180                    // if this is saying false, then send a notification IF we haven't nagged for 5 days
181                    // (rebrandr template needed)
182                    if ( ! get_transient( 'zbs-nag-license-key-now' ) ) {
183
184                        // generate nag
185                        $link       = admin_url( 'admin.php?page=zerobscrm-plugin-settings&tab=license' );
186                        $parameters = __( 'Your License Key is not linked to a CRM Brand (Error #401)', 'zero-bs-crm' ) . ' <a href="' . $link . '">' . __( 'Please enter your license key here.', 'zero-bs-crm' ) . '</a>';
187                        $reference  = time();
188                        $cid        = get_current_user_id();
189
190                        #NAG-ME-PLZ
191                        zeroBSCRM_notifyme_insert_notification( $cid, -999, -1, 'license.update.needed', $parameters, $reference );
192                        set_transient( 'zbs-nag-license-key-now', 'nag', $this->nag_every );
193                    }
194
195                    // otherwise this is a legit key :)
196                } else {
197
198                    // set validity
199                    $key_info['validity'] = 'true';
200
201                    // if passed, copy access level + expires
202                    if ( isset( $response['access'] ) ) {
203                        $key_info['access'] = sanitize_text_field( $response['access'] );
204                    }
205                    if ( isset( $response['expires'] ) ) {
206                        $key_info['expires'] = (int) sanitize_text_field( $response['expires'] );
207                    }
208
209                    // if this was the first time this api been used, it'll also send this back:
210                    if ( isset( $response['claimed'] ) ) {
211
212                        // define this just the once, it'll show a msg on license page if on the page
213                        global $zbsLicenseClaimed;
214                        $zbsLicenseClaimed = true;
215
216                    }
217                }
218
219                // here we build a var containing the correct 'up to date versions'
220                $official_ver = $response['extensions'];
221
222                // ... and we start with presumption everything is up to date
223                $extensions_all_updated = true;
224
225                // cycle through each ext on site & check against list recieved via api_request
226                // this is safer + more longterm stable:
227                foreach ( $zbs_extensions_on_site as $name => $pluginDetails ) {
228
229                    // Here we also now have 'key' to check against, which will in future
230                    // ... allow rebrandr auto updates
231                    // ... so what we do is go through all extensions on site...
232                    // ... and compare with dl'd versions:
233                    if ( is_array( $official_ver ) ) {
234                        foreach ( $official_ver as $extUpdateInfo ) {
235
236                            // if the key is set & matches
237                            if ( isset( $extUpdateInfo['zbskey'] ) && $extUpdateInfo['zbskey'] == $pluginDetails['key'] ) {
238
239                                // this is our ext :) - check the version to see if newer than installed
240                                if ( version_compare( $pluginDetails['ver'], $extUpdateInfo['version'], '<' ) ) {
241
242                                    // ===========================
243                                    // Local Mods to dl obj - these are needed in get_info and all_info (here)
244
245                                    $newSlug = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
246                                    $e       = false;
247                                    if ( is_array( $pluginDetails ) && isset( $pluginDetails['path'] ) ) {
248                                        $e = explode( '/', $pluginDetails['path'] );
249                                    }
250                                    if ( is_array( $e ) ) {
251                                        $newSlug = $e[0];
252                                    }
253                                    if ( empty( $newSlug ) ) {
254                                        $newSlug = $extUpdateInfo['slug'];
255                                    }
256
257                                    // this isn't needed once dl link fixed server side :)
258                                    // actually, is a fairly legit fix for now
259                                    // ... MAKES SURE installs in same dir as current for that ext, even in rebranded
260                                    $modifiedDLLink = false;
261                                    if ( isset( $extUpdateInfo['download_link'] ) ) {
262                                        $modifiedDLLink = $extUpdateInfo['download_link'];
263                                        $modifiedDLLink = str_replace( '?', '/' . $newSlug . '.zip?', $modifiedDLLink );
264                                    }
265
266                                    // / Local Mods to dl obj - these are needed in get_info and all_info (here)
267                                    // ===========================
268
269                                    // Build 'update needed' obj ref
270
271                                    $obj              = new stdClass();
272                                    $obj->slug        = $newSlug;
273                                    $obj->plugin      = ( isset( $pluginDetails['path'] ) ? $pluginDetails['path'] : '' );
274                                    $obj->new_version = $extUpdateInfo['version'];
275                                    $obj->tested      = $extUpdateInfo['tested'];
276                                    $obj->package     = $modifiedDLLink;
277
278                                    // these errored on some rebrandr, so setting defaults
279                                    $obj->url = false;
280                                    // these errored on some rebrandr, so checking first to stop php notices
281                                    if ( isset( $extUpdateInfo['url'] ) ) {
282                                        $obj->url = $extUpdateInfo['url'];
283                                    }
284
285                                    if ( isset( $pluginDetails['path'] ) ) {
286                                        $transient->response[ $pluginDetails['path'] ] = $obj;
287                                    } else {
288                                        $transient->response[] = $obj;
289                                    }
290
291                                    // generate nag
292                                    // then we are adding the notification to "please update Jetpack CRM extension "X")
293                                    $parameters = sprintf( __( 'Please update %1$s from version %2$s to version %3$s.', 'zero-bs-crm' ), $pluginDetails['name'], $pluginDetails['ver'], $extUpdateInfo['version'] );
294                                    $ref        = $pluginDetails['ver'] . $extUpdateInfo['version'];
295
296                                    #NAG-ME-PLZ
297                                    $reference = $pluginDetails['key'] . str_replace( '.', '', $ref );
298                                    $cid       = get_current_user_id();
299                                    zeroBSCRM_notifyme_insert_notification( $cid, -999, -1, 'custom.extension.update.needed', $parameters, $reference );
300
301                                    // make note that something needs an update
302                                    $extensions_all_updated = false;
303
304                                } // / end if ver is newer
305
306                            } // / end if key matches
307
308                        } // / end cycle through $official_ver
309                    }
310                } // / end cycle through $zbs_extensions_on_site
311
312                // This checks that this plugin_update is being called on a rebranded plugin
313                // .. further, it then proceeds to check if the WL ver of CORE CRM needs updating
314                // .. as that's not hosted on wp.org, it's a rebrand/remix :)
315                if ( zeroBSCRM_isWL() ) {
316
317                    // get core ver
318                    $core = zeroBSCRM_installedWLCore();
319
320                    // is core wl?
321                    if ( isset( $core ) && is_array( $core ) && isset( $core['ver'] ) ) {
322
323                        // get ext update info for core
324                        $coreUpdateInfo = false;
325                        if ( is_array( $official_ver ) ) {
326                            foreach ( $official_ver as $extUpdateInfo ) {
327                                if ( isset( $extUpdateInfo['zbskey'] ) && $extUpdateInfo['zbskey'] == 'core' ) {
328                                    $coreUpdateInfo = $extUpdateInfo;
329                                    break;
330                                }
331                            }
332                        }
333
334                        // this checks if it's the core & if the ver is older than latest
335                        if ( $coreUpdateInfo && version_compare( $core['ver'], $coreUpdateInfo['version'], '<' ) ) {
336
337                            // ===========================
338                            // Local Mods to dl obj - these are needed in get_info and all_info (here)
339
340                            $newSlug = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
341                            $e       = explode( '/', $core['path'] );
342                            if ( is_array( $e ) ) {
343                                $newSlug = $e[0];
344                            }
345                            if ( empty( $newSlug ) ) {
346                                $newSlug = $coreUpdateInfo['slug'];
347                            }
348
349                            // this isn't needed once dl link fixed server side :)
350                            // actually, is a fairly legit fix for now
351                            // ... MAKES SURE installs in same dir as current for that ext, even in rebranded
352                            $modifiedDLLink = $coreUpdateInfo['download_link'];
353                            $modifiedDLLink = str_replace( '?', '/' . $newSlug . '.zip?', $modifiedDLLink );
354
355                            // / Local Mods to dl obj - these are needed in get_info and all_info (here)
356                            // ===========================
357
358                            // Build 'update needed' obj ref
359                            $obj              = new stdClass();
360                            $obj->slug        = $newSlug;
361                            $obj->plugin      = $core['path'];
362                            $obj->new_version = $coreUpdateInfo['version'];
363                            $obj->url         = $coreUpdateInfo['url'];
364                            $obj->tested      = $coreUpdateInfo['tested'];
365                            $obj->package     = $modifiedDLLink;
366
367                            $transient->response[ $core['path'] ] = $obj;
368
369                            // generate nag
370                            // then we are adding the notification to "please update Jetpack CRM extension "X")
371                            $parameters = sprintf( __( 'Please update %1$s from version %2$s to version %3$s.', 'zero-bs-crm' ), $core['name'], $core['ver'], $coreUpdateInfo['version'] );
372                            $ref        = $core['ver'] . $coreUpdateInfo['version'];
373
374                            #NAG-ME-PLZ
375                            $reference = $core['key'] . str_replace( '.', '', $ref );
376                            $cid       = get_current_user_id();
377                            zeroBSCRM_notifyme_insert_notification( $cid, -999, -1, 'core.update.needed', $parameters, $reference );
378
379                            // make note that something needs an update
380                            $extensions_all_updated = false;
381
382                        } // / core crm rebrand has update
383
384                    } // / has core rebrand installed
385
386                } // if is rebranded
387
388                // If not all the Jetpack CRM extensions are updated. Fire off the warning shot.
389                if ( ! $extensions_all_updated ) {
390
391                    // 1+ ext needs update
392
393                    // Recently nagged?
394                    if ( ! get_transient( 'zbs-nag-extension-update-now' ) ) {
395
396                        // generate nag
397                        $parameters = __( 'You are running extensions which are out of date and not supported. Please update to avoid any issues.', 'zero-bs-crm' );
398                        $reference  = time();
399                        $cid        = get_current_user_id();
400
401                        #NAG-ME-PLZ
402                        zeroBSCRM_notifyme_insert_notification( $cid, -999, -1, 'general.extension.update.needed', $parameters, $reference );
403                        set_transient( 'zbs-nag-extension-update-now', 'nag', $this->nag_every );
404
405                    }
406
407                    // set extensions_updated to false in license info setting
408                    $key_info['extensions_updated'] = false;
409
410                } else {
411
412                    // all exts are up to date
413
414                    // set extensions_updated to true in license info setting
415                    $key_info['extensions_updated'] = true;
416
417                }
418            } else {
419
420                // There was a WP Error
421            }
422
423            // Brutally, update the settings obj containing license info
424            // name is a legacy throwback to old sys
425            $zbs->settings->update( 'license_key', $key_info );
426
427        } // / if $installdExts
428
429        // return the trans :)
430        // (modified or not)
431        return $transient;
432    }
433
434    #} This fires when a user clicks "view version x.x.x information" next to an update in plugins.php or update-core.php
435    // ... showing the info in the modal popup
436    public function get_info( $obj, $action, $arg ) {
437
438        // is this an info req? & is slug set?
439        if ( $action === 'plugin_information' && isset( $arg->slug ) ) {
440
441            // Grab the extension, if it is one of ours :)
442            $possibleExtension = $this->getCRMExtension( $arg->slug );
443
444            // or is it core?
445            if ( ! is_array( $possibleExtension ) ) {
446                $possibleExtension = $this->getCoreWLCRM( $arg->slug );
447            }
448
449            // if this is an array, it's either one of our ext or the core (or either rebranded)
450            if ( is_array( $possibleExtension ) ) {
451
452                // we'll need this:
453                global $zbs;
454
455                // do api request to get the info from api.jetpackcrm.com
456                $res = $this->api_request(
457                    'ext_info',
458                    array(
459                        'slug' => $arg->slug,
460                        'key'  => $possibleExtension['key'],
461                    )
462                );
463
464                // is it a WP error?
465                if ( ! is_wp_error( $res ) ) {
466
467                    // ===========================
468                    // Local Mods to dl obj - these are needed in get_info (here) and all_info
469
470                    $newSlug        = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
471                    $modifiedDLLink = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
472
473                    $e = explode( '/', $possibleExtension['path'] );
474                    if ( is_array( $e ) ) {
475                        $newSlug = $e[0];
476                    }
477                    if ( empty( $newSlug ) ) {
478                        $newSlug = $res['slug'];
479                    }
480
481                    if ( isset( $res['download_link'] ) && $res['download_link'] !== 'false' && ! empty( $newSlug ) ) {
482
483                        // this isn't needed once dl link fixed server side :)
484                        // actually, is a fairly legit fix for now
485                        // ... MAKES SURE installs in same dir as current for that ext, even in rebranded
486                        $modifiedDLLink = $res['download_link'];
487                        $modifiedDLLink = str_replace( '?', '/' . $newSlug . '.zip?', $modifiedDLLink );
488
489                    }
490
491                    // / Local Mods to dl obj - these are needed in get_info (here) and all_info
492                    // ===========================
493
494                    // Build 'update needed' obj ref
495                    $obj              = new stdClass();
496                    $obj->slug        = $newSlug;
497                    $obj->plugin_name = $res['slug'];
498                    $obj->new_version = $res['version'];
499                    $obj->requires    = $res['requires'];
500
501                        // name
502                    if ( isset( $res['name'] ) ) {
503                        $obj->name = $res['name'];
504                    } elseif ( isset( $possibleExtension['name'] ) ) {
505                        $obj->name = $possibleExtension['name'];
506                    } else {
507                        $obj->name = '';
508                    }
509
510                        // tested to
511                    if ( isset( $res['tested'] ) ) {
512                        $obj->tested = $res['tested'];
513                    } else {                          // default to latest in core
514                            $obj->tested = $zbs->wp_tested;
515                    }
516
517                        // url
518                    if ( isset( $res['url'] ) ) {
519                        $obj->url = $res['url'];
520                    } else {                          // default to core
521                            $obj->url = $zbs->urls['home'];
522                    }
523
524                        // mod link
525                    if ( isset( $modifiedDLLink ) && ! empty( $modifiedDLLink ) ) {
526                        $obj->download_link = $modifiedDLLink;
527                        $obj->package       = $modifiedDLLink;
528                    }
529
530                        // html sections
531                    if ( isset( $res['sections'] ) ) {
532                        $obj->sections = $res['sections'];
533                    } else {                          // default
534                            $obj->sections = array();
535                    }
536
537                    // optional
538                    if ( isset( $res['banners'] ) ) {
539                        $obj->banners = $res['banners'];
540                    }
541                    if ( isset( $res['downloaded'] ) ) {
542                        $obj->downloaded = $res['downloaded'];
543                    }
544                    if ( isset( $res['last_updated'] ) ) {
545                        $obj->last_updated = $res['last_updated'];
546                    }
547                } // / end if ! wp error
548
549            } // / end if is one of ours
550
551        } // / end if slug set && is plugin_information
552
553        // return the singular update obj
554        return $obj;
555    }
556
557    /*
558    ===============================================================================================
559    =================================== / main call functions ========================================
560    ===============================================================================================
561    */
562
563    /*
564    ===============================================================================================
565    =====================================  helper functions ==========================================
566    ===============================================================================================
567    */
568
569    // Retrieves license key, (if is one), from settings obj
570    public function get_license_key() {
571
572        global $zbs;
573        $settings = $zbs->settings->get( 'license_key' );
574
575        // checks if exists and it's not empty
576        if ( ! empty( $settings['key'] ) ) {
577            return $settings['key'];
578        }
579
580        return false;
581    }
582
583    public function isLicenseValid() {
584
585        // taken wholesale out of adminPages license key page.
586
587            $licenseKeyArr = zeroBSCRM_getSetting( 'license_key' );
588            // simplify following:
589            $licenseValid = false;
590        if ( isset( $licenseKeyArr['validity'] ) ) {
591            $licenseValid = ( $licenseKeyArr['validity'] === 'true' );
592        }
593
594            return $licenseValid;
595    }
596
597    /**
598     * This is used to verify if is one of our extensions
599     * >> only hook in if the slug is one of our plugins!! :)
600     */
601    public function isCRMExtension( $slug = '' ) {
602
603        // req.
604        global $zbs;
605
606        // if slug passed
607        if ( ! empty( $slug ) ) {
608
609            // get latest list (or cache)
610            if ( ! is_array( $this->installedExts ) ) {
611                $this->installedExts = zeroBSCRM_installedProExt();
612            }
613
614            // cycle through em
615            foreach ( $this->installedExts as $extName => $extDeets ) {
616
617                // debug echo 'checking '.$extDeets['slug'].' against '.$slug.'<br>';
618
619                // is slug in this arr?
620                if ( is_array( $extDeets ) && isset( $extDeets['slug'] ) && $extDeets['slug'] == $slug ) {
621                    return true;
622                }
623            }
624        }
625
626        return false;
627    }
628
629    #} Retireve extension details array based on the plugin slug
630    public function getCRMExtension( $slug = '' ) {
631
632        global $zbs;
633
634        // if slug
635        if ( ! empty( $slug ) && $slug !== 'jetpack' && $slug !== 'zero-bs-crm' ) {
636
637            // get latest list (or cache)
638            if ( ! is_array( $this->installedExts ) ) {
639                $this->installedExts = zeroBSCRM_installedProExt();
640            }
641
642            // cycle through each, check if matches
643            foreach ( $this->installedExts as $extName => $extDeets ) {
644
645                if (
646                    // something else we changed, changed this. Use first part of path now, not slug.
647                    ( is_array( $extDeets ) && isset( $extDeets['path'] ) && str_starts_with( $extDeets['path'], $slug ) ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
648                    ) {
649
650                    // simple return.
651                    $x              = $extDeets;
652                    $x['shortname'] = $extName;
653                    return $x;
654                }
655            }
656        } // / if slug
657
658        // no luck
659        return false;
660    }
661
662    #} Retireve rebranded core crm plugin details array based on the plugin slug
663    public function getCoreWLCRM( $slug = '' ) {
664
665        // req.
666        global $zbs;
667
668        // is this install rebranded, and is there a slug?
669        if ( zeroBSCRM_isWL() && ! empty( $slug ) && $slug !== 'jetpack' ) {
670
671            // get core ver
672            $core = zeroBSCRM_installedWLCore();
673
674            // echo 'core:';print_r($core);
675
676            // is core wl?
677            if ( isset( $core ) && is_array( $core ) && isset( $core['ver'] ) ) {
678
679                    // is this it?
680                if (
681                    // something else we changed, changed this. Use first part of path now, not slug.
682                    ( is_array( $core ) && isset( $core['path'] ) && str_starts_with( $core['path'], $slug ) )
683                    ) {
684
685                    $x              = $core;
686                    $x['shortname'] = $core['name'];
687                    return $x;
688                }
689            } // / this is core & it has a ver
690
691        } // / is this install rebranded, and is there a slug?
692
693        return false;
694    }
695
696    // Retrieves the latest info for ZBS extension list, or singular (if get_info)
697    // 13/12/18 - this now caches, as is recalled for each plugin when getting all_info.
698    // cachcing = side-hack for v1.0
699    public function api_request( $action, $data ) {
700
701        // req.
702        global $zbs;
703
704        // discern multisite, WL
705        $multisite = false;
706        $wl        = false;
707        $multisite = is_multisite();
708        if ( $multisite == '' ) {
709            $multisite = false;
710        } if ( zeroBSCRM_isWL() ) {
711            $wl = true;
712        }
713
714        // build request data package
715        $api_params = array(
716            'zbs-action'    => $action,
717            'license'       => $this->get_license_key(),
718            'url'           => home_url(),
719            'method'        => 'POST',
720            'is_multi_site' => $multisite,
721            'is_wl'         => $wl,
722            'country'       => '',
723            'core_ver'      => $zbs::VERSION,
724        );
725
726        // sites
727        $sites          = array();
728        $sites['sites'] = jpcrm_multisite_get_site_list();
729
730        // combine sites list, our package, and any passed $data
731        $api_params = array_merge( $api_params, $sites );
732        $api_params = array_merge( $api_params, $data );
733
734        // got cache? (we don't cache ext_info)
735        global $zbsExtUpdateCache;
736        if ( $action !== 'ext_info' && isset( $zbsExtUpdateCache ) && is_array( $zbsExtUpdateCache ) ) {
737
738            return $zbsExtUpdateCache;
739
740        } else {
741
742            // note, if we have recurring failures in this license key retrieve, it's likely that their is an SSL issue         //
743            // https://wordpress.stackexchange.com/questions/167898/is-it-safe-to-use-sslverify-true-for-with-wp-remote-get-wp-remote-post
744            $licensingAttempts = $zbs->DAL->setting( 'licensingcount', 0 );
745
746            // check for setting
747            $hasHitError = $zbs->DAL->setting( 'licensingerror', false );
748            if ( is_array( $hasHitError ) ) {
749                $hasHitError = true;
750            }
751
752            // if 1 + no success, turn this off for third
753            $sslIgnore = false;
754            if ( $licensingAttempts > 0 && ! $this->isLicenseValid() && $hasHitError ) {
755                $sslIgnore = true;
756            }
757            if ( $sslIgnore ) {
758                add_filter( 'https_ssl_verify', '__return_false' );
759            }
760
761            // run the req.
762            $responseFull = wp_remote_post(
763                $this->api_url,
764                array(
765                    'method'      => 'POST',
766                    'timeout'     => 60,
767                    'redirection' => 5,
768                    'httpversion' => '1.0',
769                    'blocking'    => true,
770                    'headers'     => array(),
771                    'body'        => $api_params,
772                    'cookies'     => array(),
773                )
774            );
775
776            // remove filter if set
777            if ( $sslIgnore ) {
778                remove_filter( 'https_ssl_verify', '__return_false' );
779            }
780
781            // up count.
782            $zbs->DAL->updateSetting( 'licensingcount', ( $licensingAttempts + 1 ) );
783
784            // hmmm - was wary of only allowing 200, but probs makes more sense than this?
785            $unacceptableHTTPCodes = array( 500, 443 );
786            $httpCode              = wp_remote_retrieve_response_code( $responseFull );
787
788            // got wp err?
789            if ( ! is_wp_error( $responseFull ) && ! in_array( $httpCode, $unacceptableHTTPCodes ) ) {
790
791                // decode
792                $response = json_decode( $responseFull['body'], true );
793
794                // set cache
795                $zbsExtUpdateCache = $response;
796
797                // clear any err
798                $zbs->DAL->updateSetting( 'licensingerror', false );
799
800                // return
801                return $response;
802
803            } else {
804
805                // wp err
806                if ( is_wp_error( $responseFull ) ) {
807
808                    // log the err
809                    $zbs->DAL->updateSetting(
810                        'licensingerror',
811                        array(
812                            'time' => time(),
813                            'err'  => $responseFull->get_error_message(),
814                        )
815                    );
816
817                }
818
819                // http err
820                if ( in_array( $httpCode, $unacceptableHTTPCodes ) ) {
821
822                    // log the err
823                    $msg = '';
824                    if ( is_array( $responseFull ) && isset( $responseFull['message'] ) ) {
825                        $msg = $responseFull['message'];
826                    }
827                    $zbs->DAL->updateSetting(
828                        'licensingerror',
829                        array(
830                            'time' => time(),
831                            'err'  => 'Error:' . $httpCode . ' ' . $msg,
832                        )
833                    );
834
835                }
836            }
837        }
838
839        return false;
840    }
841
842        /*
843        This is a WH hack using
844        .... https://github.com/CherryFramework/cherry-plugin-wizard/blob/master/includes/class-cherry-plugin-wizard-plugin-upgrader.php#L71
845        ... as a base
846        ... which shoves itself in the middle of all plugin installs/upgrades
847        ... IF it finds that the plugin being updated is one of OURS, it'll intercede and change:
848        ... zero-bs-extension-password-manager-KGBdBz
849        ... to
850        ... zero-bs-extension-password-manager
851
852        Useful:
853            https://core.trac.wordpress.org/browser/tags/5.0/src/wp-admin/includes/class-plugin-upgrader.php#L21
854            https://core.trac.wordpress.org/browser/tags/5.0/src/wp-admin/includes/class-wp-upgrader.php#L502
855            https://developer.wordpress.org/reference/hooks/upgrader_process_complete/
856        */
857        /**
858         * Adjust the plugin directory name if necessary.
859         *
860         * The final destination directory of a plugin is based on the subdirectory name found in the
861         * (un)zipped source. In some cases - most notably GitHub repository plugin downloads -, this
862         * subdirectory name is not the same as the expected slug and the plugin will not be recognized
863         * as installed. This is fixed by adjusting the temporary unzipped source subdirectory name to
864         * the expected plugin slug.
865         *
866         * @since  1.0.0
867         * @param  string       $source        Path to upgrade/zip-file-name.tmp/subdirectory/.
868         * @param  string       $remote_source Path to upgrade/zip-file-name.tmp.
869         * @param  \WP_Upgrader $upgrader      Instance of the upgrader which installs the plugin.
870         * @return string $source
871         */
872    public function maybe_adjust_source_dir( $source, $remote_source, $upgrader, $extraArgs ) {
873
874            // is this one of our plugins that's being updated? (will be called by all)
875            $ours   = false;
876        $pluginPath = false;
877        if ( isset( $extraArgs ) && isset( $extraArgs['plugin'] ) && ! empty( $extraArgs['plugin'] ) ) {
878            $pluginPath = $extraArgs['plugin'];
879        }
880
881            // normal ext + rebranded ext test
882        if ( $pluginPath !== false ) {
883            $ourExts = zeroBSCRM_installedProExt();
884            if ( is_array( $ourExts ) ) {
885                foreach ( $ourExts as $x => $ext ) {
886                    if ( $ext['path'] == $pluginPath ) {
887                                        $ours = true;
888                    }
889                }
890            }
891        }
892
893            // wl(rebranded) core test
894        if ( $pluginPath !== false && zeroBSCRM_isWL() ) {
895
896            // core check
897            $core = zeroBSCRM_installedWLCore();
898
899            // if wl core is installed
900            if ( isset( $core ) && is_array( $core ) && isset( $core['ver'] ) ) {
901
902                if ( $core['path'] == $pluginPath ) {
903                    $ours = true;
904                }
905            }
906        }
907
908            // if not ours, just return $source for now
909            // otherwise use ORIGINAL plugin dir :)
910            // https://core.trac.wordpress.org/browser/tags/5.0/src/wp-admin/includes/class-wp-upgrader.php#L502
911        if ( ! $ours ) {
912            return $source;
913        }
914
915            // req.
916            global $wp_filesystem;
917        if ( ! is_object( $wp_filesystem ) ) {
918            return $source; }
919
920            // check from path
921            $from_path    = untrailingslashit( $source );
922            $desired_slug = isset( $extraArgs['plugin'] ) ? $extraArgs['plugin'] : false;
923        if ( ! empty( $desired_slug ) ) {
924            $e = explode( '/', $desired_slug );
925            if ( is_array( $e ) ) {
926                $desired_slug = $e[0];
927            }
928            if ( empty( $desired_slug ) ) {
929                return $source;
930            }
931        }
932        if ( ! $desired_slug ) {
933            return $source;
934        }
935
936            // holder dir
937            $to_path = untrailingslashit( $source );
938
939            // remove working dir
940            // ?? not req.
941
942            // attempt to build a 'proper' to_path
943        if ( ! empty( $to_path ) ) {
944            $to_path  = substr( $to_path, 0, strrpos( $to_path, '/' ) );
945            $to_path .= '/' . $desired_slug;
946        }
947
948            // if checks out...
949        if ( ! empty( $to_path ) && $to_path !== $from_path ) {
950
951            if ( true === $wp_filesystem->move( $from_path, $to_path ) ) {
952                return trailingslashit( $to_path );
953            } else {
954                return new WP_Error(
955                    'rename_failed',
956                    esc_html__( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'zero-bs-crm' ) . ' ' . esc_html__( 'Please contact the plugin provider and ask them to package their plugin according to the WordPress guidelines.', 'zero-bs-crm' ),
957                    array(
958                        'found'    => $to_path,
959                        'expected' => $desired_slug,
960                    )
961                );
962            }
963        } elseif ( empty( $to_path ) ) {
964            return new WP_Error(
965                'packaged_wrong',
966                esc_html__( 'The remote plugin package consists of more than one file, but the files are not packaged in a folder.', 'zero-bs-crm' ) . ' ' . esc_html__( 'Please contact the plugin provider and ask them to package their plugin according to the WordPress guidelines.', 'zero-bs-crm' ),
967                array(
968                    'found'    => $to_path,
969                    'expected' => $desired_slug,
970                )
971            );
972        }
973            return $source;
974    }
975
976    /*
977    ===============================================================================================
978    ==================================== / helper functions ==========================================
979    ===============================================================================================
980    */
981}
982
983// https://stackoverflow.com/questions/2053245/how-can-i-detect-if-the-user-is-on-localhost-in-php
984// fullCheck = true = also checks in with our sever (max 1 x 24h) to see if we have an override rule in place
985// defaults to full check as of 2.97.9
986// SELECT * FROM `zbs_app_users_licenses_requests` WHERE action = 'localcheck'
987function zeroBSCRM_isLocal( $fullCheck = true ) {
988    // quick caching
989    if ( jpcrm_is_devmode_override() ) {
990        return false;
991    }
992
993    // is local, unless override setting set within past 48h
994    $whitelist = array( '127.0.0.1', 'localhost', '::1' );
995    if ( in_array( zeroBSCRM_getRealIpAddr(), $whitelist, true ) ) {
996
997        if ( $fullCheck ) {
998
999            global $zbs;
1000
1001            // appears to be local
1002            // see if setting (if this is set, it's legit, and its the last timestamp it was 'checked')
1003            $key = $zbs->DAL->setting( 'localoverride', false );
1004            // debug echo 'settinglocal:'.$key.' vs '.(time()-172800).'!<br>';
1005
1006            // how often to recheck
1007            $ageOkay = time() - ( 7 * 86400 );
1008
1009            // if set, less than xxx ago
1010            if ( $key !== false && $key > $ageOkay ) {
1011                return false; // it appears not to be dev mode
1012            } else {
1013
1014                // is probably dev mode, if last check was more than xxx days ago, recheck
1015                $lastcheck = $zbs->DAL->setting( 'localoverridecheck', false );
1016                // debug echo 'lastcheck:'.$lastcheck.'!<br>';
1017
1018                // has last check
1019                if ( $lastcheck !== false && $lastcheck > $ageOkay ) {
1020                    // was checked less than a day ago, so is probs dev mode
1021                    return true;
1022                } else {
1023
1024                    // check + return
1025                    $check = zeroBSCRM_localDblCheck();
1026
1027                    // debug echo 'check:'.$check.'!<br>';
1028
1029                    return $check;
1030
1031                }
1032            }
1033        } else {
1034
1035            // non-full-check
1036            return true;
1037        }
1038    }
1039
1040    return false;
1041}
1042
1043/*
1044    This function connects to https://api.jetpackcrm.com/localcheck
1045    ... passing this site url
1046    ... it's to be used to test if we have an "override" logged for this siteurl
1047    ... (to say that it's NOT a dev server, at this site url.)
1048
1049    .. this should only ever be run once a day or so max, as calls API.
1050    .. in this case it's called by zeroBSCRM_isLocal itself, on param
1051
1052*/
1053function zeroBSCRM_localDblCheck() {
1054
1055        // quick caching
1056    if ( jpcrm_is_devmode_override() ) {
1057        return false;
1058    }
1059
1060        // req.
1061        global $zbs;
1062
1063        // build request data package
1064        $api_params = array(
1065            'siteurl' => home_url(),
1066            'method'  => 'POST',
1067        );
1068
1069        // run the req.
1070        $responseFull = wp_remote_post(
1071            $zbs->urls['apilocalcheck'],
1072            array(
1073                'method'      => 'POST',
1074                'timeout'     => 60,
1075                'redirection' => 5,
1076                'httpversion' => '1.0',
1077                'blocking'    => true,
1078                'headers'     => array(),
1079                'body'        => $api_params,
1080                'cookies'     => array(),
1081            )
1082        );
1083
1084        // log that it was checked
1085        $zbs->DAL->updateSetting( 'localoverridecheck', time() );
1086
1087        // got wp err?
1088    if ( ! is_wp_error( $responseFull ) ) {
1089
1090        // decode
1091        $response = json_decode( $responseFull['body'], true );
1092
1093        // infer
1094        if ( isset( $response['overridemode'] ) && $response['overridemode'] == 1 ) {
1095
1096            // debug echo home_url().' is override';
1097            // is legit, set here + Return false, this is to be override (not a local site)
1098            $zbs->DAL->updateSetting( 'localoverride', time() );
1099
1100            // we also set this runtime global to avoid us having to multicheck settings
1101            define( 'JPCRM_DEVOVERRIDE', true );
1102
1103            return false;
1104
1105        } else {
1106
1107            // debug echo home_url().' Not override';
1108            // nope. not override (return true, is a local site)
1109            return true;
1110
1111        }
1112    } else {
1113
1114        // log the err
1115        // $zbs->DAL->updateSetting('licensingerror',array('time'=>time(), 'err' => $responseFull->get_error_message()));
1116
1117    }
1118
1119        // nope. not override (return true, is a local site)
1120        return true;
1121}
1122
1123    // checks if a plugin has an update.
1124    // adapted from https://wordpress.stackexchange.com/questions/228468/plugin-update-warning
1125    // name = Jetpack CRM
1126    // textdom = zero-bs-crm (in lieu of slug)
1127function zeroBSCRM_updates_pluginHasUpdate( $name = '', $textDomain = '' ) {
1128
1129    if ( ! function_exists( 'get_plugin_updates' ) ) {
1130        require_once ABSPATH . 'wp-admin/includes/update.php';
1131        require_once ABSPATH . 'wp-admin/includes/plugin.php';
1132    }
1133
1134    $list = get_plugin_updates();
1135    $data = array();
1136
1137    foreach ( $list as $i => $item ) {
1138
1139        // debug echo 'item:<pre>'.print_r($item,1).'</pre><br>';
1140
1141        if ( ( ! empty( $name ) && strtolower( $name ) == strtolower( $item->Name ) )
1142            ||
1143            ( ! empty( $textDomain ) && strtolower( $textDomain ) == strtolower( $item->TextDomain ) )
1144            ) {
1145
1146            // simpler...
1147            return $list[ $i ];
1148        }
1149    }
1150
1151    /*
1152        not req.
1153    if( ! empty( $data ) ) {
1154
1155        return array(
1156            'name' => $data->Name,
1157            'version' => $data->Version,
1158            'new_version' => $data->update->new_version,
1159            'url' => $data->update->url,
1160            'package' => $data->update->package
1161        );
1162
1163    } */
1164
1165    return array();
1166}