Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 220
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
Client_Portal
0.00% covered (danger)
0.00%
0 / 218
0.00% covered (danger)
0.00%
0 / 22
8742
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 add_endpoint_class_folder
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 sort_endpoints_by_menu_order
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 init_endpoints
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 portal_enqueue_scripts_and_styles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 portal_theme_support
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 locate_template
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 get_template
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 jpcrm_portal_update_details_from_post
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
462
 is_user_enabled
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 redirect_fix_portal_as_homepage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 add_all_rewrite_endpoints
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 get_portal_query_vars
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 is_portal_page
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 is_child_of_portal_page
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
156
 is_a_client_portal_endpoint
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
 client_portal_shortcode
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 portal_login_fail_redirect
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 get_endpoint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 access_is_via_hash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_obj_id_from_current_portal_page_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/*
3* Jetpack CRM
4* https://jetpackcrm.com
5*
6* Client Portal Module
7*
8*/
9namespace Automattic\JetpackCRM;
10
11defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
12
13require_once plugin_dir_path( __FILE__ ) . 'class-client-portal-endpoint.php';
14
15/**
16 *
17 * Client Portal Module class for Jetpack CRM.
18 * To add a new endpoint use one of the existing endpoints located inside the
19 * './endpoints' folder.
20 */
21class Client_Portal {
22    public $router    = null;
23    public $render    = null;
24    public $endpoints = null;
25
26    /**
27     * The class constructor initializes the attribbutes and calls an init function.
28     */
29    public function __construct() {
30        require_once plugin_dir_path( __FILE__ ) . 'class-client-portal-render-helper.php';
31        require_once plugin_dir_path( __FILE__ ) . 'class-client-portal-router.php';
32
33        $this->router = new Client_Portal_Router();
34        $this->render = new Client_Portal_Render_Helper( $this );
35
36        // Initializes it later. Priority 10
37        add_action( 'init', array( $this, 'init' ) );
38    }
39
40    /**
41     * Initializes the Client Portal Module.
42     * Mainly sets ups all needed hooks.
43     */
44    function init() {
45        // Adding the shortcode function for the Client Portal.
46        add_shortcode( 'jetpackcrm_clientportal', array( $this, 'client_portal_shortcode' ) );
47        add_shortcode( 'zerobscrm_clientportal', array( $this, 'client_portal_shortcode' ) );
48        // Basic theme support (here for now, probs needs option).
49        add_filter( 'body_class', array( $this, 'portal_theme_support' ) );
50        // Fixes a bug when the Client Portal is set to the homepage (more info: gh-15).
51        add_filter( 'redirect_canonical', array( $this, 'redirect_fix_portal_as_homepage' ), 10, 2 );
52        // Hook used by our custom rewrite rules.
53        add_filter( 'query_vars', array( $this, 'get_portal_query_vars' ), 0 );
54        // Styles needed by the Client Portal.
55        add_action( 'zbs_enqueue_scripts_and_styles', array( $this, 'portal_enqueue_scripts_and_styles' ) );
56        // Custom login redirect hook (this one is in our $router).
57        add_action( 'login_redirect', array( $this->router, 'redirect_contacts_upon_login' ), 10, 3 );
58        // Initializes all endpoints (including the ones from external plugins).
59        $this->init_endpoints();
60        // this catches failed logins, checks if from our page, then redirs
61        // From mr pippin https://pippinsplugins.com/redirect-to-custom-login-page-on-failed-login/
62        add_action( 'wp_login_failed', array( $this, 'portal_login_fail_redirect' ) );  // hook failed login
63    }
64
65    /**
66     *
67     */
68    public function add_endpoint_class_folder( $endpoint_folder_path ) {
69        $endpoint_directory = glob( $endpoint_folder_path . '/class*endpoint.php' );
70        foreach ( $endpoint_directory as $endpoint_file ) {
71            require_once $endpoint_file;
72            // Gets the filename without the ';php' suffix. e.g. 'class-single-invoice-endpoint'.
73            $base_filename = basename( $endpoint_file, '.php' );
74            // Turns the snake case filename into pascal case separated by '_'. e.g. 'Class_Single_Invoice_Endpoint'
75            $pascal_case_filename = str_replace( '-', '_', ucwords( $base_filename, '-' ) );
76            // Removes the 'Class' prefix and adds the hardcoded namespace. e.g. 'Automattic\JetpackCRM\SingleInvoiceEndpoint'
77            $endpoint_class = 'Automattic\JetpackCRM\\' . str_replace( 'Class_', '', $pascal_case_filename );
78            // Registers the endpoint
79            $this->endpoints = $endpoint_class::register_endpoint( $this->endpoints, $this );
80        }
81    }
82
83    public function sort_endpoints_by_menu_order() {
84        // Sort all endpoints by their order
85        usort(
86            $this->endpoints,
87            function ( $endpoint_a, $endpoint_b ) {
88                if ( $endpoint_a->menu_order == $endpoint_b->menu_order ) {
89                    return 0;
90                } else {
91                    return ( $endpoint_a->menu_order < $endpoint_b->menu_order ) ? -1 : 1;
92                }
93            }
94        );
95    }
96
97    /**
98     *  Initializes all the endpoints for the Client Portal
99     */
100    public function init_endpoints() {
101        // Since this is the init function, we should start with an empty array.
102        $this->endpoints = array();
103        // By default we load all classes in the endpoints folder.
104        $this->add_endpoint_class_folder( plugin_dir_path( __FILE__ ) . 'endpoints' );
105        // Allowing plugins to declare their endpoint classes.
106        do_action( 'jpcrm_client_portal_register_endpoint', $this );
107
108        do_action( 'jpcrm_client_portal_post_init_endpoints', $this );
109
110        $this->sort_endpoints_by_menu_order();
111        $this->add_all_rewrite_endpoints();
112    }
113
114    /**
115     * Sorts out the stylesheet includes.
116     */
117    function portal_enqueue_scripts_and_styles() {
118        global $zbs;
119
120        wp_enqueue_style( 'zbs-portal', plugins_url( '/css/jpcrm-public-portal' . wp_scripts_get_suffix() . '.css', __FILE__ ), array(), $zbs::VERSION );
121        wp_enqueue_style( 'zbs-fa', ZEROBSCRM_URL . 'build/lib/font-awesome/css/font-awesome.min.css', array(), $zbs::VERSION );
122
123        // This do_action call was left here for compatibility purposes (legacy).
124        do_action( 'zbs_enqueue_portal', 'zeroBS_portal_enqueue_stuff' );
125        // This new action should be used for newer implementations.
126        do_action( 'jpcrm_enqueue_client_portal_styles' );
127    }
128
129    /**
130     * Function used to offer css support for some themes.
131     */
132    function portal_theme_support( $classes = array() ) {
133        $theme_slug = get_stylesheet();
134
135        switch ( $theme_slug ) {
136            case 'twentyseventeen':
137                $classes[] = 'zbs-theme-support-2017';
138                break;
139            case 'twentynineteen':
140                $classes[] = 'zbs-theme-support-2019';
141                break;
142            case 'twentytwenty':
143                $classes[] = 'zbs-theme-support-2020';
144                break;
145            case 'twentytwentyone':
146                $classes[] = 'zbs-theme-support-2021';
147                break;
148            case 'twentytwentytwo':
149                $classes[] = 'zbs-theme-support-2022';
150                break;
151        }
152        return $classes;
153    }
154
155    /**
156     * Locate template.
157     *
158     * Locate the called template.
159     * Search Order:
160     * 1. /themes/theme/zerobscrm-plugin-templates/$template_name
161     * 2. /themes/theme/$template_name
162     * 3. /plugins/portal/v3/templates/$template_name.
163     *
164     * @since 1.2.7
165     *
166     * @param string $template_name Template to load.
167     * @param string $string $template_path Path to templates.
168     * @param string $default_path Default path to template files.
169     * @return string Path to the template file.
170     */
171    function locate_template( $template_name, $template_path = '', $default_path = '' ) {
172        // Set variable to search in zerobscrm-plugin-templates folder of theme.
173        if ( ! $template_path ) :
174            $template_path = 'zerobscrm-plugin-templates/';
175        endif;
176        // Set default plugin templates path.
177        if ( ! $default_path ) :
178            $default_path = ZEROBSCRM_PATH . 'modules/portal/templates/'; // Path to the template folder
179        endif;
180        // Search template file in theme folder.
181        $template = locate_template(
182            array(
183                $template_path . $template_name,
184                $template_name,
185            )
186        );
187        // Get plugins template file.
188        if ( ! $template ) :
189            $template = $default_path . $template_name;
190        endif;
191        return apply_filters( 'locate_template', $template, $template_name, $template_path, $default_path );
192    }
193
194    /**
195     * Get template.
196     *
197     * Search for the template and include the file.
198     *
199     * @since 1.2.7
200     *
201     * @see get_template()
202     *
203     * @param string $template_name Template to load.
204     * @param array  $args Args passed for the template file.
205     * @param string $string $template_path Path to templates.
206     * @param string $default_path Default path to template files.
207     */
208    function get_template( $template_name, $args = array(), $tempate_path = '', $default_path = '' ) {
209
210        if ( is_array( $args ) && isset( $args ) ) :
211            extract( $args );
212        endif;
213        $template_file = $this->locate_template( $template_name, $tempate_path, $default_path );
214        if ( ! file_exists( $template_file ) ) :
215            _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $template_file ) ), '1.0.0' );
216            return;
217        endif;
218        include_once $template_file;
219    }
220
221    // this handles contact detail updates via $_POST from the client portal
222    // this is a #backward-compatibility landmine; proceed with caution (see gh-1642)
223    function jpcrm_portal_update_details_from_post( $cID = -1 ) {
224
225        global $zbs, $zbsCustomerFields;
226
227        /**
228        * This gets fields hidden in Client Portal settings.
229        * Eventually we should expand this to preprocess and filter
230        * the following fields altogether if disabled:
231        *   - countries: zeroBSCRM_getSetting('countries')
232        *   - second addresses: zeroBSCRM_getSetting('secondaddress')
233        *   - all addresses: zeroBSCRM_getSetting('showaddress')
234        *   - not sure what this is: $zbs->settings->get('fieldhides')
235        */
236        $hidden_fields    = $zbs->settings->get( 'portal_hidefields' );
237        $hidden_fields    = ! empty( $hidden_fields ) ? explode( ',', $hidden_fields ) : array();
238        $read_only_fields = $zbs->settings->get( 'portal_readonlyfields' );
239        $read_only_fields = ! empty( $read_only_fields ) ? explode( ',', $read_only_fields ) : array();
240
241        // get existing contact data
242        $old_contact_data = $zbs->DAL->contacts->getContact( $cID );
243
244        // downgrade to old-style second address keys so that field names match the object generated by zeroBS_buildContactMeta()
245        $key_map = array(
246            'secaddr_addr1'    => 'secaddr1',
247            'secaddr_addr2'    => 'secaddr2',
248            'secaddr_city'     => 'seccity',
249            'secaddr_county'   => 'seccounty',
250            'secaddr_country'  => 'seccountry',
251            'secaddr_postcode' => 'secpostcode',
252        );
253        foreach ( $key_map as $newstyle_key => $oldstyle_key ) {
254            if ( isset( $old_contact_data[ $newstyle_key ] ) ) {
255                $old_contact_data[ $oldstyle_key ] = $old_contact_data[ $newstyle_key ];
256                unset( $old_contact_data[ $newstyle_key ] );
257            }
258        }
259
260        // create new (sanitised) contact data from $_POST
261        $new_contact_data = zeroBS_buildContactMeta( $_POST, $old_contact_data ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
262
263        // process fields
264        $fields_to_change = array();
265        foreach ( $new_contact_data as $key => $value ) {
266            // check for hidden or read only field groups
267            $is_hidden_or_readonly_field_group = false;
268            if ( isset( $zbsCustomerFields[ $key ] ) && isset( $zbsCustomerFields[ $key ]['area'] ) ) {
269                $area_key = ( $zbsCustomerFields[ $key ]['area'] == 'Main Address' ) ? 'jpcrm-main-address' : '';
270                $area_key = ( $zbsCustomerFields[ $key ]['area'] == 'Second Address' ) ? 'jpcrm-main-address' : $area_key;
271                if ( in_array( $area_key, $hidden_fields ) || in_array( $area_key, $read_only_fields ) ) {
272                    $is_hidden_or_readonly_field_group = true;
273                }
274            }
275
276            // if invalid or unauthorised field, keep old value
277            if ( ! isset( $zbsCustomerFields[ $key ] ) || in_array( $key, $hidden_fields ) || in_array( $key, $read_only_fields ) || $is_hidden_or_readonly_field_group ) {
278                $new_contact_data[ $key ] = $old_contact_data[ $key ];
279            }
280
281            // collect fields that changed
282            elseif ( $old_contact_data[ $key ] != $value ) {
283                $fields_to_change[] = $key;
284            }
285        }
286        // update contact if fields changed
287        if ( count( $fields_to_change ) > 0 ) {
288
289            $cID = $zbs->DAL->contacts->addUpdateContact(
290                array(
291                    'id'                   => $cID,
292                    'data'                 => $new_contact_data,
293                    'do_not_update_blanks' => false,
294                )
295            );
296
297            // update log if contact update was successful
298            if ( $cID ) {
299
300                // build long description string for log
301                $longDesc = '';
302                foreach ( $fields_to_change as $field ) {
303                    if ( ! empty( $longDesc ) ) {
304                        $longDesc .= '<br>';
305                    }
306                    $longDesc .= sprintf( '%s: <code>%s</code> â†’ <code>%s</code>', $field, $old_contact_data[ $field ], $new_contact_data[ $field ] );
307                }
308
309                zeroBS_addUpdateLog(
310                    $cID,
311                    -1,
312                    -1,
313                    array(
314                        'type'      => __( 'Details updated via Client Portal', 'zero-bs-crm' ),
315                        'shortdesc' => __( 'Contact changed some of their details via the Client Portal', 'zero-bs-crm' ),
316                        'longdesc'  => $longDesc,
317                    ),
318                    'zerobs_customer'
319                );
320
321                echo "<div class='zbs_alert'>" . esc_html__( 'Details updated.', 'zero-bs-crm' ) . '</div>';
322
323            } else {
324                echo "<div class='zbs-alert-danger'>" . esc_html__( 'Error updating details!', 'zero-bs-crm' ) . '</div>';
325            }
326        }
327
328        return $cID;
329    }
330
331    /**
332     * Checks if a user has "enabled" or "disabled" access.
333     *
334     * @return bool True if the user is enabled in the Client Portal.
335     */
336    function is_user_enabled() {
337        // cached?
338        if ( defined( 'ZBS_CURRENT_USER_DISABLED' ) ) {
339            return false;
340        }
341
342        global $wpdb;
343        $uid = get_current_user_id();
344        $cID = zeroBS_getCustomerIDFromWPID( $uid );
345
346        // these ones definitely work
347        $uinfo          = get_userdata( $uid );
348        $potentialEmail = '';
349        if ( isset( $uinfo->user_email ) ) {
350            $potentialEmail = $uinfo->user_email;
351        }
352        $cID = zeroBS_getCustomerIDWithEmail( $potentialEmail );
353
354        $disabled = zeroBSCRM_isCustomerPortalDisabled( $cID );
355
356        if ( ! $disabled ) {
357            return true;
358        }
359
360        // cache to avoid multi-check
361        define( 'ZBS_CURRENT_USER_DISABLED', true );
362        return false;
363    }
364
365    /**
366     * Fixes a bug when the Client Portal is set to the homepage.
367     */
368    function redirect_fix_portal_as_homepage( $redirect_url, $requested_url ) {
369        // When the Client Portal is set to the homepage we have to allow the slug
370        // to be used for the child pages. We have to do this because WordPress will
371        // redirect child pages to the root (e.g. '/clients/invoices' to '/invoices')
372        // when the Client Portal is set to the homepage. This will fix it.
373        if ( $this->is_a_client_portal_endpoint() ) {
374            return $requested_url;
375        }
376
377        return $redirect_url;
378    }
379
380    function add_all_rewrite_endpoints() {
381        foreach ( $this->endpoints as $endpoint ) {
382            if ( $endpoint->add_rewrite_endpoint ) {
383                $slug = $endpoint->slug;
384                // TODO: remove reliance on Client Portal Pro from Core
385                if ( function_exists( 'zeroBSCRM_clientPortalgetEndpoint' ) ) {
386                    $slug = zeroBSCRM_clientPortalgetEndpoint( $slug );
387                }
388                add_rewrite_endpoint( $slug, EP_ROOT | EP_PAGES );
389            }
390        }
391        jpcrm_client_portal_flush_rewrite_rules_if_needed();
392    }
393
394    /**
395     * Returns the query vars associated with the Client Portal.
396     *
397     * @return array The list of the query vars associated with the Client Portal.
398     */
399    function get_portal_query_vars( $vars ) {
400        foreach ( $this->endpoints as $endpoint ) {
401            if ( $endpoint->add_rewrite_endpoint ) {
402                $slug = $endpoint->slug;
403                // TODO: remove reliance on Client Portal Pro from Core
404                if ( function_exists( 'zeroBSCRM_clientPortalgetEndpoint' ) ) {
405                    $slug = zeroBSCRM_clientPortalgetEndpoint( $slug );
406                }
407                $vars[] = $slug;
408            }
409        }
410        return $vars;
411    }
412
413    /**
414     * Lets us check early on in the action stack to see if page is ours.
415     * Only works after 'wp' in action order (needs wp_query->query_var)
416     * Is also used by zeroBSCRM_isClientPortalPage in Admin Checks
417     * (which affects force redirect to dash, so be careful).
418     *
419     * @return bool Returns true if the current page is a portal page.
420     */
421    function is_portal_page() {
422        return ! is_admin() && $this->is_a_client_portal_endpoint();
423    }
424
425    /**
426     * Checks if is a child, or a child of a child, of the client portal main page.
427     *
428     * @return bool Returns true if is a child, or a child of a child, of the client portal main page.
429     */
430    function is_child_of_portal_page() {
431        global $post;
432
433        if ( ! is_admin() && function_exists( 'zeroBSCRM_getSetting' ) && zeroBSCRM_isExtensionInstalled( 'portal' ) ) {
434
435            $portalPage = (int) zeroBSCRM_getSetting( 'portalpage' );
436
437            if ( $portalPage > 0 && isset( $post ) && is_object( $post ) ) {
438
439                if ( is_page() && ( $post->post_parent == $portalPage ) ) {
440                        return true;
441                } else {
442
443                    // check 1 level deeper
444                    if ( $post->post_parent > 0 ) {
445
446                        $parentsParentID = (int) wp_get_post_parent_id( $post->post_parent );
447
448                        if ( $parentsParentID > 0 && ( $parentsParentID == $portalPage ) ) {
449                            return true;
450                        }
451                    }
452                    return false;
453                }
454            }
455        }
456        return false;
457    }
458
459    /**
460     * Only works after 'wp' in action order (needs $wp_query->post).
461     *
462     *  @return bool If current page loaded has an endpoint that matches ours returns true. False otherwise.
463     */
464    function is_a_client_portal_endpoint() {
465        global $wp_query;
466        // We get the post id (which will be the page id) + compare to our setting.
467        $portalPage = zeroBSCRM_getSetting( 'portalpage' );
468        if (
469            ! empty( $portalPage ) &&
470            $portalPage > 0 &&
471            isset( $wp_query->post ) &&
472            gettype( $wp_query->post ) == 'object' &&
473            isset( $wp_query->post->ID ) &&
474            $wp_query->post->ID == $portalPage
475        ) {
476            return true;
477        } else {
478            return $this->is_child_of_portal_page();
479        }
480    }
481
482    /**
483     * This is the shortcode function for the Client Portal.
484     */
485    function client_portal_shortcode() {
486        // This function is being called by a shortcode (add_shortcode) and should never return any output (e.g. echo).
487        // The implementation is old and removing all the output requires a lot of work. This is a quick workaround to fix it.
488        ob_start();
489        // this checks that we're on the front-end
490        // ... a necessary step, because the editor (wp) now runs the shortcode on loading (probs gutenberg)
491        // ... and because this should RETURN, instead it ECHO's directly
492        // ... it should not run on admin side, because that means is probs an edit page!
493        if ( ! is_admin() ) {
494            global $wp_query;
495
496            // Setting the default endpoint to be the dashboard.
497            // This could be customizable by the user if we want to.
498            $endpoints_slug_array_column = array_column( $this->endpoints, null, 'slug' );
499            // Let the default endpoint to be overriden by plugins.
500            $default_endpoint_slug = apply_filters( 'jpcrm_client_portal_default_endpoint_slug', 'dashboard', $this );
501            $endpoint              = $endpoints_slug_array_column[ $default_endpoint_slug ];
502            $portal_query_vars     = $this->get_portal_query_vars( $wp_query->query_vars );
503
504            foreach ( $portal_query_vars as $var_key => $var_value ) {
505                foreach ( $this->endpoints as $endpoint_search ) {
506                    if ( $endpoint_search->slug === $var_key ) {
507                        $endpoint              = $endpoint_search;
508                        $endpoint->param_value = $var_value;
509                        break 2; // Breaks this loop and the outer loop, hence 2.
510                    }
511                }
512            }
513
514            // allows one to tweak endpoint properties as needed before running endpoint actions
515            $endpoint->before_endpoint_actions();
516            $endpoint->perform_endpoint_action();
517        }
518
519        $result = ob_get_contents();
520        ob_end_clean();
521        return $result;
522    }
523
524    /**
525     * This catches failed logins, checks if from our page, then redirs
526     * From mr pippin https://pippinsplugins.com/redirect-to-custom-login-page-on-failed-login/
527     */
528    function portal_login_fail_redirect( $username ) {
529        $referrer = '';
530        if ( array_key_exists( 'HTTP_REFERER', $_SERVER ) ) {
531            $referrer = $_SERVER['HTTP_REFERER'];  // where did the post submission come from?
532        }
533
534        // if there's a valid referrer, and it's not the default log-in screen + it's got our post
535        if ( ! empty( $referrer ) && ! strstr( $referrer, 'wp-login' ) && ! strstr( $referrer, 'wp-admin' ) && isset( $_POST['fromzbslogin'] ) ) {
536                wp_redirect( zeroBS_portal_link( 'dash' ) . '?login=failed' );  // let's append some information (login=failed) to the URL for the theme to use
537                exit( 0 );
538        }
539    }
540
541    /**
542     * Gets client portal endpoint name for a given object type.
543     *
544     * @param int $obj_type_id  object type ID.
545     *
546     * @return string|bool endpoint name or false if endpoint is not supported
547     */
548    function get_endpoint( $obj_type_id ) {
549        return $this->router->get_endpoint( $obj_type_id );
550    }
551
552    /**
553     * Returns bool if current portal access is provided via easy-access hash
554     *
555     * @return   bool - true if current access is via hash
556     */
557    function access_is_via_hash( $obj_type_id ) {
558        return $this->router->access_is_via_hash( $obj_type_id );
559    }
560
561    /**
562     * Gets current object ID based on portal page URL.
563     *
564     * @param int $obj_type_id  object type ID.
565     *
566     * @return int|false Object ID or false if invalid object, bad permissions, or any other failure
567     */
568    function get_obj_id_from_current_portal_page_url( $obj_type_id ) {
569        return $this->router->get_obj_id_from_current_portal_page_url( $obj_type_id );
570    }
571}