2019-05-07 18:42:42 +00:00
< ? php
/**
2019-08-12 21:52:09 +00:00
* PageController
2019-05-07 18:42:42 +00:00
*/
2019-07-31 18:16:56 +00:00
namespace Automattic\WooCommerce\Admin ;
2020-10-13 01:40:53 +00:00
use Automattic\WooCommerce\Admin\Loader ;
2019-07-31 18:16:56 +00:00
defined ( 'ABSPATH' ) || exit ;
2019-05-07 18:42:42 +00:00
/**
2019-08-12 21:52:09 +00:00
* PageController
2019-05-07 18:42:42 +00:00
*/
2019-08-12 21:52:09 +00:00
class PageController {
2019-05-07 18:42:42 +00:00
// JS-powered page root.
2019-05-07 19:48:34 +00:00
const PAGE_ROOT = 'wc-admin' ;
2019-05-07 18:42:42 +00:00
/**
* Singleton instance of self .
*
2019-08-12 21:52:09 +00:00
* @ var PageController
2019-05-07 18:42:42 +00:00
*/
private static $instance = false ;
2019-05-07 22:08:18 +00:00
/**
* Current page ID ( or false if not registered with this controller ) .
*
* @ var string
*/
private $current_page = null ;
2019-05-07 18:42:42 +00:00
/**
* Registered pages
* Contains information ( breadcrumbs , menu info ) about JS powered pages and classic WooCommerce pages .
*
* @ var array
*/
private $pages = array ();
/**
* We want a single instance of this class so we can accurately track registered menus and pages .
*/
public static function get_instance () {
if ( ! self :: $instance ) {
self :: $instance = new self ();
}
return self :: $instance ;
}
2019-05-08 14:41:35 +00:00
/**
* Connect an existing page to wc - admin .
*
* @ param array $options {
* Array describing the page .
*
* @ type string id Id to reference the page .
* @ type string | array title Page title . Used in menus and breadcrumbs .
* @ type string | null parent Parent ID . Null for new top level page .
2019-05-10 15:47:48 +00:00
* @ type string path Path for this page . E . g . admin . php ? page = wc - settings & tab = checkout
2019-05-08 14:41:35 +00:00
* @ type string capability Capability needed to access the page .
* @ type string icon Icon . Dashicons helper class , base64 - encoded SVG , or 'none' .
* @ type int position Menu item position .
2019-05-10 21:07:23 +00:00
* @ type boolean js_page If this is a JS - powered page .
2019-05-08 14:41:35 +00:00
* }
*/
public function connect_page ( $options ) {
if ( ! is_array ( $options [ 'title' ] ) ) {
$options [ 'title' ] = array ( $options [ 'title' ] );
}
2019-05-10 21:07:23 +00:00
/**
* Filter the options when connecting or registering a page .
*
* Use the `js_page` option to determine if registering .
*
* @ param array $options {
* Array describing the page .
*
* @ type string id Id to reference the page .
* @ type string | array title Page title . Used in menus and breadcrumbs .
* @ type string | null parent Parent ID . Null for new top level page .
2019-05-13 15:55:51 +00:00
* @ type string screen_id The screen ID that represents the connected page . ( Not required for registering ) .
2019-05-10 21:07:23 +00:00
* @ type string path Path for this page . E . g . admin . php ? page = wc - settings & tab = checkout
* @ type string capability Capability needed to access the page .
* @ type string icon Icon . Dashicons helper class , base64 - encoded SVG , or 'none' .
* @ type int position Menu item position .
* @ type boolean js_page If this is a JS - powered page .
* }
*/
2019-12-05 23:06:11 +00:00
$options = apply_filters ( 'woocommerce_navigation_connect_page_options' , $options );
2019-05-10 21:07:23 +00:00
// @todo check for null ID, or collision.
2019-05-08 14:41:35 +00:00
$this -> pages [ $options [ 'id' ] ] = $options ;
}
2019-05-07 22:08:18 +00:00
/**
* Determine the current page ID , if it was registered with this controller .
*/
public function determine_current_page () {
2019-05-08 14:41:35 +00:00
$current_url = '' ;
$current_screen_id = $this -> get_current_screen_id ();
2019-05-07 22:08:18 +00:00
if ( isset ( $_SERVER [ 'REQUEST_URI' ] ) ) {
$current_url = esc_url_raw ( wp_unslash ( $_SERVER [ 'REQUEST_URI' ] ) );
}
2019-06-13 19:58:21 +00:00
$current_query = wp_parse_url ( $current_url , PHP_URL_QUERY );
parse_str ( $current_query , $current_pieces );
$current_path = empty ( $current_pieces [ 'page' ] ) ? '' : $current_pieces [ 'page' ];
$current_path .= empty ( $current_pieces [ 'path' ] ) ? '' : '&path=' . $current_pieces [ 'path' ];
2019-05-07 22:08:18 +00:00
foreach ( $this -> pages as $page ) {
2019-05-08 14:41:35 +00:00
if ( isset ( $page [ 'js_page' ] ) && $page [ 'js_page' ] ) {
// Check registered admin pages.
if (
2019-06-13 19:58:21 +00:00
$page [ 'path' ] === $current_path
2019-05-08 14:41:35 +00:00
) {
$this -> current_page = $page ;
return ;
}
} else {
// Check connected admin pages.
if (
isset ( $page [ 'screen_id' ] ) &&
$page [ 'screen_id' ] === $current_screen_id
) {
$this -> current_page = $page ;
return ;
}
2019-05-07 22:08:18 +00:00
}
}
$this -> current_page = false ;
}
2019-05-08 14:41:35 +00:00
2019-05-07 22:08:18 +00:00
/**
2019-05-08 14:41:35 +00:00
* Get breadcrumbs for WooCommerce Admin Page navigation .
2019-05-07 22:08:18 +00:00
*
2019-05-08 14:41:35 +00:00
* @ return array Navigation pieces ( breadcrumbs ) .
*/
public function get_breadcrumbs () {
$current_page = $this -> get_current_page ();
// Bail if this isn't a page registered with this controller.
if ( false === $current_page ) {
2019-05-10 21:07:23 +00:00
// Filter documentation below.
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_get_breadcrumbs' , array ( '' ), $current_page );
2019-05-08 14:41:35 +00:00
}
if ( 1 === count ( $current_page [ 'title' ] ) ) {
$breadcrumbs = $current_page [ 'title' ];
} else {
// If this page has multiple title pieces, only link the first one.
$breadcrumbs = array_merge (
array (
2019-05-10 15:47:48 +00:00
array ( $current_page [ 'path' ], reset ( $current_page [ 'title' ] ) ),
2019-05-08 14:41:35 +00:00
),
array_slice ( $current_page [ 'title' ], 1 )
);
}
if ( isset ( $current_page [ 'parent' ] ) ) {
$parent_id = $current_page [ 'parent' ];
while ( $parent_id ) {
if ( isset ( $this -> pages [ $parent_id ] ) ) {
$parent = $this -> pages [ $parent_id ];
2020-06-05 06:26:32 +00:00
if ( 0 === strpos ( $parent [ 'path' ], self :: PAGE_ROOT ) ) {
2020-06-12 21:55:43 +00:00
$parent [ 'path' ] = 'admin.php?page=' . $parent [ 'path' ];
2020-06-05 06:26:32 +00:00
}
2019-05-10 15:47:48 +00:00
array_unshift ( $breadcrumbs , array ( $parent [ 'path' ], reset ( $parent [ 'title' ] ) ) );
2019-05-08 14:41:35 +00:00
$parent_id = isset ( $parent [ 'parent' ] ) ? $parent [ 'parent' ] : false ;
} else {
$parent_id = false ;
}
}
}
2020-04-30 21:56:54 +00:00
$woocommerce_breadcrumb = array ( 'admin.php?page=' . self :: PAGE_ROOT , __ ( 'WooCommerce' , 'woocommerce-admin' ) );
2020-03-23 22:57:16 +00:00
array_unshift ( $breadcrumbs , $woocommerce_breadcrumb );
2019-05-10 21:07:23 +00:00
/**
* The navigation breadcrumbs for the current page .
*
* @ param array $breadcrumbs Navigation pieces ( breadcrumbs ) .
* @ param array | boolean $current_page The connected page data or false if not identified .
*/
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_get_breadcrumbs' , $breadcrumbs , $current_page );
2019-05-08 14:41:35 +00:00
}
/**
* Get the current page .
*
* @ return array | boolean Current page or false if not registered with this controller .
2019-05-07 22:08:18 +00:00
*/
public function get_current_page () {
2019-07-19 18:35:33 +00:00
// If 'current_screen' hasn't fired yet, the current page calculation
// will fail which causes `false` to be returned for all subsquent calls.
if ( ! did_action ( 'current_screen' ) ) {
2019-08-01 14:19:56 +00:00
_doing_it_wrong ( __FUNCTION__ , esc_html__ ( 'Current page retrieval should be called on or after the `current_screen` hook.' , 'woocommerce-admin' ), '0.16.0' );
2019-07-19 18:35:33 +00:00
}
2019-05-07 22:08:18 +00:00
if ( is_null ( $this -> current_page ) ) {
$this -> determine_current_page ();
}
return $this -> current_page ;
}
2019-05-08 14:41:35 +00:00
/**
* Returns the current screen ID .
2019-05-10 21:07:23 +00:00
*
2019-05-08 14:41:35 +00:00
* This is slightly different from WP ' s get_current_screen , in that it attaches an action ,
* so certain pages like 'add new' pages can have different breadcrumbs or handling .
* It also catches some more unique dynamic pages like taxonomy / attribute management .
*
2019-05-10 21:07:23 +00:00
* Format :
* - { $current_screen -> action } - { $current_screen -> action } - tab - section
* - { $current_screen -> action } - { $current_screen -> action } - tab
* - { $current_screen -> action } - { $current_screen -> action } if no tab is present
* - { $current_screen -> action } if no action or tab is present
2019-05-08 14:41:35 +00:00
*
* @ return string Current screen ID .
*/
public function get_current_screen_id () {
$current_screen = get_current_screen ();
if ( ! $current_screen ) {
2019-05-10 21:07:23 +00:00
// Filter documentation below.
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_current_screen_id' , false , $current_screen );
2019-05-08 14:41:35 +00:00
}
$screen_pieces = array ( $current_screen -> id );
if ( $current_screen -> action ) {
$screen_pieces [] = $current_screen -> action ;
}
if (
! empty ( $current_screen -> taxonomy ) &&
isset ( $current_screen -> post_type ) &&
'product' === $current_screen -> post_type
) {
// Editing a product attribute.
if ( 0 === strpos ( $current_screen -> taxonomy , 'pa_' ) ) {
2019-05-10 21:07:23 +00:00
$screen_pieces = array ( 'product_page_product_attribute-edit' );
2019-05-08 14:41:35 +00:00
}
// Editing a product taxonomy term.
if ( ! empty ( $_GET [ 'tag_ID' ] ) ) {
2019-05-10 21:07:23 +00:00
$screen_pieces = array ( $current_screen -> taxonomy );
2019-05-08 14:41:35 +00:00
}
}
// Pages with default tab values.
$pages_with_tabs = apply_filters (
2019-12-05 23:06:11 +00:00
'woocommerce_navigation_pages_with_tabs' ,
2019-05-08 14:41:35 +00:00
array (
'wc-reports' => 'orders' ,
'wc-settings' => 'general' ,
'wc-status' => 'status' ,
'wc-addons' => 'browse-extensions' ,
)
);
// Tabs that have sections as well.
2019-07-31 18:16:56 +00:00
$wc_emails = \WC_Emails :: instance ();
2019-05-14 17:36:31 +00:00
$wc_email_ids = array_map ( 'sanitize_title' , array_keys ( $wc_emails -> get_emails () ) );
2019-05-08 14:41:35 +00:00
$tabs_with_sections = apply_filters (
2019-12-05 23:06:11 +00:00
'woocommerce_navigation_page_tab_sections' ,
2019-05-08 14:41:35 +00:00
array (
'products' => array ( '' , 'inventory' , 'downloadable' ),
'shipping' => array ( '' , 'options' , 'classes' ),
'checkout' => array ( 'bacs' , 'cheque' , 'cod' , 'paypal' ),
2019-05-14 17:36:31 +00:00
'email' => $wc_email_ids ,
2019-05-08 14:41:35 +00:00
'advanced' => array (
'' ,
'keys' ,
'webhooks' ,
'legacy_api' ,
'woocommerce_com' ,
),
'browse-extensions' => array ( 'helper' ),
)
);
if ( ! empty ( $_GET [ 'page' ] ) ) {
if ( in_array ( $_GET [ 'page' ], array_keys ( $pages_with_tabs ) ) ) { // WPCS: sanitization ok.
if ( ! empty ( $_GET [ 'tab' ] ) ) {
$tab = wc_clean ( wp_unslash ( $_GET [ 'tab' ] ) );
} else {
$tab = $pages_with_tabs [ $_GET [ 'page' ] ]; // WPCS: sanitization ok.
}
$screen_pieces [] = $tab ;
if ( ! empty ( $_GET [ 'section' ] ) ) {
if (
isset ( $tabs_with_sections [ $tab ] ) &&
in_array ( $_GET [ 'section' ], array_keys ( $tabs_with_sections [ $tab ] ) ) // WPCS: sanitization ok.
) {
$screen_pieces [] = wc_clean ( wp_unslash ( $_GET [ 'section' ] ) );
}
}
// Editing a shipping zone.
if ( ( 'shipping' === $tab ) && isset ( $_GET [ 'zone_id' ] ) ) {
$screen_pieces [] = 'edit_zone' ;
}
}
}
2019-05-10 21:07:23 +00:00
/**
* The current screen id .
*
* Used for identifying pages to render the WooCommerce Admin header .
*
* @ param string | boolean $screen_id The screen id or false if not identified .
* @ param WP_Screen $current_screen The current WP_Screen .
*/
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_current_screen_id' , implode ( '-' , $screen_pieces ), $current_screen );
2019-05-08 14:41:35 +00:00
}
2019-05-07 18:42:42 +00:00
/**
* Returns the path from an ID .
*
* @ param string $id ID to get path for .
2019-05-07 19:48:34 +00:00
* @ return string Path for the given ID , or the ID on lookup miss .
2019-05-07 18:42:42 +00:00
*/
public function get_path_from_id ( $id ) {
if ( isset ( $this -> pages [ $id ] ) && isset ( $this -> pages [ $id ][ 'path' ] ) ) {
return $this -> pages [ $id ][ 'path' ];
}
2019-05-07 19:48:34 +00:00
return $id ;
2019-05-07 18:42:42 +00:00
}
2019-05-08 14:41:35 +00:00
/**
* Returns true if we are on a page connected to this controller .
*
* @ return boolean
*/
public function is_connected_page () {
$current_page = $this -> get_current_page ();
if ( false === $current_page ) {
2019-05-10 21:07:23 +00:00
$is_connected_page = false ;
} else {
$is_connected_page = isset ( $current_page [ 'js_page' ] ) ? ! $current_page [ 'js_page' ] : true ;
2019-05-08 14:41:35 +00:00
}
2019-07-17 15:07:57 +00:00
// Disable embed on the block editor.
2019-09-25 17:59:11 +00:00
$current_screen = did_action ( 'current_screen' ) ? get_current_screen () : false ;
2019-07-17 15:07:57 +00:00
if ( method_exists ( $current_screen , 'is_block_editor' ) && $current_screen -> is_block_editor () ) {
$is_connected_page = false ;
}
2019-05-10 21:07:23 +00:00
/**
* Whether or not the current page is an existing page connected to this controller .
*
* Used to determine if the WooCommerce Admin header should be rendered .
*
* @ param boolean $is_connected_page True if the current page is connected .
* @ param array | boolean $current_page The connected page data or false if not identified .
*/
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_is_connected_page' , $is_connected_page , $current_page );
2019-05-08 14:41:35 +00:00
}
2019-05-07 22:08:18 +00:00
/**
* Returns true if we are on a page registed with this controller .
*
* @ return boolean
*/
public function is_registered_page () {
$current_page = $this -> get_current_page ();
2019-05-08 14:41:35 +00:00
if ( false === $current_page ) {
2019-05-10 21:07:23 +00:00
$is_registered_page = false ;
} else {
$is_registered_page = isset ( $current_page [ 'js_page' ] ) && $current_page [ 'js_page' ];
2019-05-08 14:41:35 +00:00
}
2019-05-10 21:07:23 +00:00
/**
* Whether or not the current page was registered with this controller .
*
* Used to determine if this is a JS - powered WooCommerce Admin page .
*
* @ param boolean $is_registered_page True if the current page was registered with this controller .
* @ param array | boolean $current_page The registered page data or false if not identified .
*/
2019-12-05 23:06:11 +00:00
return apply_filters ( 'woocommerce_navigation_is_registered_page' , $is_registered_page , $current_page );
2019-05-07 22:08:18 +00:00
}
2019-05-07 18:42:42 +00:00
/**
* Adds a JS powered page to wc - admin .
*
* @ param array $options {
* Array describing the page .
*
* @ type string id Id to reference the page .
* @ type string title Page title . Used in menus and breadcrumbs .
* @ type string | null parent Parent ID . Null for new top level page .
* @ type string path Path for this page , full path in app context ; ex / analytics / report
* @ type string capability Capability needed to access the page .
* @ type string icon Icon . Dashicons helper class , base64 - encoded SVG , or 'none' .
* @ type int position Menu item position .
2020-10-16 01:31:14 +00:00
* @ type int order Navigation item order .
2019-05-07 18:42:42 +00:00
* }
*/
public function register_page ( $options ) {
$defaults = array (
'id' => null ,
'parent' => null ,
'title' => '' ,
2019-05-14 18:31:03 +00:00
'capability' => 'view_woocommerce_reports' ,
2019-05-07 18:42:42 +00:00
'path' => '' ,
'icon' => '' ,
'position' => null ,
2019-05-08 14:41:35 +00:00
'js_page' => true ,
2019-05-07 18:42:42 +00:00
);
2019-05-07 19:48:34 +00:00
$options = wp_parse_args ( $options , $defaults );
if ( 0 !== strpos ( $options [ 'path' ], self :: PAGE_ROOT ) ) {
2019-06-13 19:58:21 +00:00
$options [ 'path' ] = self :: PAGE_ROOT . '&path=' . $options [ 'path' ];
2019-05-07 19:48:34 +00:00
}
2019-05-07 18:42:42 +00:00
if ( is_null ( $options [ 'parent' ] ) ) {
add_menu_page (
$options [ 'title' ],
$options [ 'title' ],
$options [ 'capability' ],
$options [ 'path' ],
array ( __CLASS__ , 'page_wrapper' ),
$options [ 'icon' ],
$options [ 'position' ]
);
} else {
$parent_path = $this -> get_path_from_id ( $options [ 'parent' ] );
2019-05-10 21:07:23 +00:00
// @todo check for null path.
2019-05-07 18:42:42 +00:00
add_submenu_page (
$parent_path ,
$options [ 'title' ],
$options [ 'title' ],
$options [ 'capability' ],
$options [ 'path' ],
array ( __CLASS__ , 'page_wrapper' )
);
}
2019-05-08 14:41:35 +00:00
$this -> connect_page ( $options );
2019-05-07 18:42:42 +00:00
}
2020-10-15 20:28:03 +00:00
/**
2020-11-05 14:00:04 +00:00
* Get registered pages .
2020-10-15 20:28:03 +00:00
*
2020-11-05 14:00:04 +00:00
* @ return array
2020-10-15 20:28:03 +00:00
*/
2020-11-05 14:00:04 +00:00
public function get_pages () {
return $this -> pages ;
2020-10-15 20:28:03 +00:00
}
2019-05-07 18:42:42 +00:00
/**
* Set up a div for the app to render into .
*/
public static function page_wrapper () {
2020-04-27 09:30:32 +00:00
Loader :: page_wrapper ();
2019-05-07 18:42:42 +00:00
}
}