PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Admin / PageController.php
woocommerce / src / Admin Last commit date
API 4 weeks ago BlockTemplates 2 years ago Composer 2 years ago DateTimeProvider 4 years ago Features 4 weeks ago Marketing 1 year ago Notes 4 weeks ago Overrides 2 months ago PluginsInstallLoggers 1 year ago PluginsProvider 4 years ago RemoteInboxNotifications 4 weeks ago RemoteSpecs 2 months ago Schedulers 1 year ago Settings 2 weeks ago DataSourcePoller.php 2 years ago DeprecatedClassFacade.php 1 year ago FeaturePlugin.php 4 years ago Loader.php 2 years ago PageController.php 4 weeks ago PluginsHelper.php 4 weeks ago PluginsInstaller.php 4 years ago ReportCSVEmail.php 1 year ago ReportCSVExporter.php 1 year ago ReportExporter.php 3 years ago ReportsSync.php 4 months ago WCAdminHelper.php 4 weeks ago
PageController.php
686 lines
1 <?php
2 /**
3 * PageController
4 */
5
6 namespace Automattic\WooCommerce\Admin;
7
8 use Automattic\WooCommerce\Internal\Admin\Loader;
9
10 use WC_Gateway_BACS;
11 use WC_Gateway_Cheque;
12 use WC_Gateway_COD;
13 use WC_Gateway_Paypal;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * PageController
19 */
20 class PageController {
21 /**
22 * App entry point.
23 */
24 const APP_ENTRY_POINT = 'wc-admin';
25
26 // JS-powered page root.
27 const PAGE_ROOT = 'wc-admin';
28
29 /**
30 * Singleton instance of self.
31 *
32 * @var PageController
33 */
34 private static $instance = false;
35
36 /**
37 * Current page ID (or false if not registered with this controller).
38 *
39 * @var string
40 */
41 private $current_page = null;
42
43 /**
44 * Registered pages
45 * Contains information (breadcrumbs, menu info) about JS powered pages and classic WooCommerce pages.
46 *
47 * @var array
48 */
49 private $pages = array();
50
51 /**
52 * We want a single instance of this class so we can accurately track registered menus and pages.
53 */
54 public static function get_instance() {
55 if ( ! self::$instance ) {
56 self::$instance = new self();
57 }
58
59 return self::$instance;
60 }
61
62 /**
63 * Constructor.
64 * Hooks added here should be removed in `wc_admin_initialize` via the feature plugin.
65 */
66 public function __construct() {
67 add_action( 'admin_menu', array( $this, 'register_page_handler' ) );
68 add_action( 'admin_menu', array( $this, 'register_store_details_page' ) );
69
70 // priority is 20 to run after https://github.com/woocommerce/woocommerce/blob/a55ae325306fc2179149ba9b97e66f32f84fdd9c/includes/admin/class-wc-admin-menus.php#L165.
71 add_action( 'admin_head', array( $this, 'remove_app_entry_page_menu_item' ), 20 );
72 // Using low priority to run before other hooks.
73 add_action( 'admin_init', array( $this, 'maybe_redirect_payment_tasks_to_settings' ), 1 );
74 }
75
76 /**
77 * Connect an existing page to wc-admin.
78 *
79 * @param array $options {
80 * Array describing the page.
81 *
82 * @type string id Id to reference the page.
83 * @type string|array title Page title. Used in menus and breadcrumbs.
84 * @type string|null parent Parent ID. Null for new top level page.
85 * @type string path Path for this page. E.g. admin.php?page=wc-settings&tab=checkout
86 * @type string capability Capability needed to access the page.
87 * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'.
88 * @type int position Menu item position.
89 * @type boolean js_page If this is a JS-powered page.
90 * }
91 */
92 public function connect_page( $options ) {
93 if ( ! is_array( $options['title'] ) ) {
94 $options['title'] = array( $options['title'] );
95 }
96
97 /**
98 * Filter the options when connecting or registering a page.
99 *
100 * Use the `js_page` option to determine if registering.
101 *
102 * @param array $options {
103 * Array describing the page.
104 *
105 * @type string id Id to reference the page.
106 * @type string|array title Page title. Used in menus and breadcrumbs.
107 * @type string|null parent Parent ID. Null for new top level page.
108 * @type string screen_id The screen ID that represents the connected page. (Not required for registering).
109 * @type string path Path for this page. E.g. admin.php?page=wc-settings&tab=checkout
110 * @type string capability Capability needed to access the page.
111 * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'.
112 * @type int position Menu item position.
113 * @type boolean js_page If this is a JS-powered page.
114 * }
115 */
116 $options = apply_filters( 'woocommerce_navigation_connect_page_options', $options );
117
118 // In the future, we should consider check for collision, but keep in mind that the current behavior is: the later call silently overwrites the earlier one.
119 $id = $options['id'] ?? null;
120
121 if ( is_string( $id ) && '' !== $id ) {
122 $this->pages[ $id ] = $options;
123 }
124 }
125
126 /**
127 * Determine the current page ID, if it was registered with this controller.
128 */
129 public function determine_current_page() {
130 $current_url = '';
131 $current_screen_id = $this->get_current_screen_id();
132
133 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
134 $current_url = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
135 }
136
137 $current_query = wp_parse_url( $current_url, PHP_URL_QUERY );
138 parse_str( (string) $current_query, $current_pieces );
139 $current_path = empty( $current_pieces['page'] ) ? '' : $current_pieces['page'];
140 $current_path .= empty( $current_pieces['path'] ) ? '' : '&path=' . $current_pieces['path'];
141
142 foreach ( $this->pages as $page ) {
143 if ( isset( $page['js_page'] ) && $page['js_page'] ) {
144 // Check registered admin pages.
145 if (
146 $page['path'] === $current_path
147 ) {
148 $this->current_page = $page;
149 return;
150 }
151 } else {
152 // Check connected admin pages.
153 if (
154 isset( $page['screen_id'] ) &&
155 $page['screen_id'] === $current_screen_id
156 ) {
157 $this->current_page = $page;
158 return;
159 }
160 }
161 }
162 $this->current_page = false;
163 }
164
165
166 /**
167 * Get breadcrumbs for WooCommerce Admin Page navigation.
168 *
169 * @return array Navigation pieces (breadcrumbs).
170 */
171 public function get_breadcrumbs() {
172 $current_page = $this->get_current_page();
173
174 // Bail if this isn't a page registered with this controller.
175 if ( false === $current_page ) {
176 // Filter documentation below.
177 return apply_filters( 'woocommerce_navigation_get_breadcrumbs', array( '' ), $current_page );
178 }
179
180 $page_title = ! empty( $current_page['page_title'] ) ? $current_page['page_title'] : $current_page['title'];
181 $page_title = (array) $page_title;
182 if ( 1 === count( $page_title ) ) {
183 $breadcrumbs = $page_title;
184 } else {
185 // If this page has multiple title pieces, only link the first one.
186 $breadcrumbs = array_merge(
187 array(
188 array( $current_page['path'], reset( $page_title ) ),
189 ),
190 array_slice( $page_title, 1 )
191 );
192 }
193
194 if ( isset( $current_page['parent'] ) ) {
195 $parent_id = $current_page['parent'];
196
197 while ( $parent_id ) {
198 if ( isset( $this->pages[ $parent_id ] ) ) {
199 $parent = $this->pages[ $parent_id ];
200
201 if ( 0 === strpos( $parent['path'], self::PAGE_ROOT ) ) {
202 $parent['path'] = 'admin.php?page=' . $parent['path'];
203 }
204
205 array_unshift( $breadcrumbs, array( $parent['path'], reset( $parent['title'] ) ) );
206 $parent_id = isset( $parent['parent'] ) ? $parent['parent'] : false;
207 } else {
208 $parent_id = false;
209 }
210 }
211 }
212
213 $woocommerce_breadcrumb = array( 'admin.php?page=' . self::PAGE_ROOT, __( 'WooCommerce', 'woocommerce' ) );
214
215 array_unshift( $breadcrumbs, $woocommerce_breadcrumb );
216
217 /**
218 * The navigation breadcrumbs for the current page.
219 *
220 * @param array $breadcrumbs Navigation pieces (breadcrumbs).
221 * @param array|boolean $current_page The connected page data or false if not identified.
222 */
223 return apply_filters( 'woocommerce_navigation_get_breadcrumbs', $breadcrumbs, $current_page );
224 }
225
226 /**
227 * Get the current page.
228 *
229 * @return array|boolean Current page or false if not registered with this controller.
230 */
231 public function get_current_page() {
232 // If 'current_screen' hasn't fired yet, the current page calculation
233 // will fail which causes `false` to be returned for all subsequent calls.
234 if ( ! did_action( 'current_screen' ) ) {
235 _doing_it_wrong( __FUNCTION__, esc_html__( 'Current page retrieval should be called on or after the `current_screen` hook.', 'woocommerce' ), '0.16.0' );
236 }
237
238 if ( is_null( $this->current_page ) ) {
239 $this->determine_current_page();
240 }
241
242 return $this->current_page;
243 }
244
245
246 /**
247 * Returns the current screen ID.
248 *
249 * This is slightly different from WP's get_current_screen, in that it attaches an action,
250 * so certain pages like 'add new' pages can have different breadcrumbs or handling.
251 * It also catches some more unique dynamic pages like taxonomy/attribute management.
252 *
253 * Format:
254 * - {$current_screen->action}-{$current_screen->action}-tab-section
255 * - {$current_screen->action}-{$current_screen->action}-tab
256 * - {$current_screen->action}-{$current_screen->action} if no tab is present
257 * - {$current_screen->action} if no action or tab is present
258 *
259 * @return string Current screen ID.
260 */
261 public function get_current_screen_id() {
262 // Return early if this is a REST API request.
263 if ( wp_is_serving_rest_request() ) {
264 /**
265 * Filter the current screen ID for REST API requests.
266 *
267 * @since 3.9.0
268 *
269 * @param string|boolean $screen_id The screen id or false if not identified.
270 * @param WP_Screen $current_screen The current WP_Screen.
271 */
272 return apply_filters( 'woocommerce_navigation_current_screen_id', false, null );
273 }
274
275 $current_screen = get_current_screen();
276 if ( ! $current_screen ) {
277 // Filter documentation below.
278 return apply_filters( 'woocommerce_navigation_current_screen_id', false, $current_screen );
279 }
280
281 $screen_pieces = array( $current_screen->id );
282
283 if ( $current_screen->action ) {
284 $screen_pieces[] = $current_screen->action;
285 }
286
287 if (
288 ! empty( $current_screen->taxonomy ) &&
289 isset( $current_screen->post_type ) &&
290 'product' === $current_screen->post_type
291 ) {
292 // Editing a product attribute.
293 if ( 0 === strpos( $current_screen->taxonomy, 'pa_' ) ) {
294 $screen_pieces = array( 'product_page_product_attribute-edit' );
295 }
296
297 // Editing a product taxonomy term.
298 if ( ! empty( $_GET['tag_ID'] ) ) {
299 $screen_pieces = array( $current_screen->taxonomy );
300 }
301 }
302
303 // Pages with default tab values.
304 $pages_with_tabs = apply_filters(
305 'woocommerce_navigation_pages_with_tabs',
306 array(
307 'wc-reports' => 'orders',
308 'wc-settings' => 'general',
309 'wc-status' => 'status',
310 'wc-addons' => 'browse-extensions',
311 )
312 );
313
314 // Tabs that have sections as well.
315 $wc_emails = \WC_Emails::instance();
316 $wc_email_ids = array_map( 'sanitize_title', array_keys( $wc_emails->get_emails() ) );
317
318 $tabs_with_sections = apply_filters(
319 'woocommerce_navigation_page_tab_sections',
320 array(
321 'products' => array( '', 'inventory', 'downloadable', 'download_urls', 'advanced' ),
322 'shipping' => array( '', 'options', 'classes', 'pickup_location' ),
323 'checkout' => array( WC_Gateway_BACS::ID, WC_Gateway_Cheque::ID, WC_Gateway_COD::ID, WC_Gateway_Paypal::ID ),
324 'email' => $wc_email_ids,
325 'advanced' => array(
326 '',
327 'keys',
328 'webhooks',
329 'woocommerce_com',
330 'features',
331 'blueprint',
332 ),
333 'browse-extensions' => array( 'helper' ),
334 )
335 );
336
337 if ( ! empty( $_GET['page'] ) ) {
338 $page = wc_clean( wp_unslash( $_GET['page'] ) );
339 if ( in_array( $page, array_keys( $pages_with_tabs ) ) ) {
340 if ( ! empty( $_GET['tab'] ) ) {
341 $tab = wc_clean( wp_unslash( $_GET['tab'] ) );
342 } else {
343 $tab = $pages_with_tabs[ $page ];
344 }
345
346 $screen_pieces[] = $tab;
347
348 if ( ! empty( $_GET['section'] ) ) {
349 $section = wc_clean( wp_unslash( $_GET['section'] ) );
350 if (
351 isset( $tabs_with_sections[ $tab ] ) &&
352 in_array( $section, array_values( $tabs_with_sections[ $tab ] ), true )
353 ) {
354 $screen_pieces[] = $section;
355 }
356 }
357
358 // Editing a shipping zone.
359 if ( ( 'shipping' === $tab ) && isset( $_GET['zone_id'] ) ) {
360 $screen_pieces[] = 'edit_zone';
361 }
362 }
363 }
364
365 /**
366 * The current screen id.
367 *
368 * Used for identifying pages to render the WooCommerce Admin header.
369 *
370 * @param string|boolean $screen_id The screen id or false if not identified.
371 * @param WP_Screen $current_screen The current WP_Screen.
372 */
373 return apply_filters( 'woocommerce_navigation_current_screen_id', implode( '-', $screen_pieces ), $current_screen );
374 }
375
376 /**
377 * Returns the path from an ID.
378 *
379 * @param string $id ID to get path for.
380 * @return string Path for the given ID, or the ID on lookup miss.
381 */
382 public function get_path_from_id( $id ) {
383 if ( isset( $this->pages[ $id ] ) && isset( $this->pages[ $id ]['path'] ) ) {
384 return $this->pages[ $id ]['path'];
385 }
386 return $id;
387 }
388
389 /**
390 * Returns true if we are on a page connected to this controller.
391 *
392 * @return boolean
393 */
394 public function is_connected_page() {
395 $current_page = $this->get_current_page();
396
397 if ( false === $current_page ) {
398 $is_connected_page = false;
399 } else {
400 $is_connected_page = isset( $current_page['js_page'] ) ? ! $current_page['js_page'] : true;
401 }
402
403 // Disable embed on the block editor.
404 $current_screen = did_action( 'current_screen' ) ? get_current_screen() : false;
405 if ( ! empty( $current_screen ) && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
406 $is_connected_page = false;
407 }
408
409 /**
410 * Whether or not the current page is an existing page connected to this controller.
411 *
412 * Used to determine if the WooCommerce Admin header should be rendered.
413 *
414 * @param boolean $is_connected_page True if the current page is connected.
415 * @param array|boolean $current_page The connected page data or false if not identified.
416 */
417 return apply_filters( 'woocommerce_navigation_is_connected_page', $is_connected_page, $current_page );
418 }
419
420 /**
421 * Returns true if we are on a page registered with this controller.
422 *
423 * @return boolean
424 */
425 public function is_registered_page() {
426 $current_page = $this->get_current_page();
427
428 if ( false === $current_page ) {
429 $is_registered_page = false;
430 } else {
431 $is_registered_page = isset( $current_page['js_page'] ) && $current_page['js_page'];
432 }
433
434 /**
435 * Whether or not the current page was registered with this controller.
436 *
437 * Used to determine if this is a JS-powered WooCommerce Admin page.
438 *
439 * @param boolean $is_registered_page True if the current page was registered with this controller.
440 * @param array|boolean $current_page The registered page data or false if not identified.
441 */
442 return apply_filters( 'woocommerce_navigation_is_registered_page', $is_registered_page, $current_page );
443 }
444
445 /**
446 * Adds a JS powered page to wc-admin.
447 *
448 * @param array $options {
449 * Array describing the page.
450 *
451 * @type string id Id to reference the page.
452 * @type string title Page title. Used in menus and breadcrumbs.
453 * @type string|null parent Parent ID. Null for new top level page.
454 * @type string path Path for this page, full path in app context; ex /analytics/report
455 * @type string capability Capability needed to access the page.
456 * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'.
457 * @type int position Menu item position.
458 * @type int order Navigation item order.
459 * }
460 */
461 public function register_page( $options ) {
462 $defaults = array(
463 'id' => null,
464 'parent' => null,
465 'title' => '',
466 'page_title' => '',
467 'capability' => 'view_woocommerce_reports',
468 'path' => '',
469 'icon' => '',
470 'position' => null,
471 'js_page' => true,
472 );
473
474 $options = wp_parse_args( $options, $defaults );
475
476 if ( 0 !== strpos( $options['path'], self::PAGE_ROOT ) ) {
477 $options['path'] = self::PAGE_ROOT . '&path=' . $options['path'];
478 }
479
480 if ( null !== $options['position'] ) {
481 $options['position'] = intval( round( $options['position'] ) );
482 }
483
484 if ( empty( $options['page_title'] ) ) {
485 $options['page_title'] = $options['title'];
486 }
487
488 if ( is_null( $options['parent'] ) ) {
489 add_menu_page(
490 $options['page_title'],
491 $options['title'],
492 $options['capability'],
493 $options['path'],
494 array( __CLASS__, 'page_wrapper' ),
495 $options['icon'],
496 $options['position']
497 );
498 } else {
499 $parent_path = $this->get_path_from_id( $options['parent'] );
500 // @todo check for null path.
501 add_submenu_page(
502 $parent_path,
503 $options['page_title'],
504 $options['title'],
505 $options['capability'],
506 $options['path'],
507 array( __CLASS__, 'page_wrapper' )
508 );
509 }
510
511 $this->connect_page( $options );
512 }
513
514 /**
515 * Get registered pages.
516 *
517 * @return array
518 */
519 public function get_pages() {
520 return $this->pages;
521 }
522
523 /**
524 * Set up a div for the app to render into.
525 */
526 public static function page_wrapper() {
527 Loader::page_wrapper();
528 }
529
530 /**
531 * Connects existing WooCommerce pages.
532 *
533 * @todo The entry point for the embed needs moved to this class as well.
534 */
535 public function register_page_handler() {
536 require_once WC_ADMIN_ABSPATH . 'includes/react-admin/connect-existing-pages.php';
537 }
538
539 /**
540 * Registers the store details (profiler) page.
541 */
542 public function register_store_details_page() {
543 wc_admin_register_page(
544 array(
545 'id' => 'setup-wizard',
546 'title' => __( 'Setup Wizard', 'woocommerce' ),
547 'parent' => '',
548 'path' => '/setup-wizard',
549 )
550 );
551 }
552
553 /**
554 * Remove the menu item for the app entry point page.
555 */
556 public function remove_app_entry_page_menu_item() {
557 global $submenu;
558 // User does not have capabilities to see the submenu.
559 if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) {
560 return;
561 }
562
563 $wc_admin_key = null;
564 foreach ( $submenu['woocommerce'] as $submenu_key => $submenu_item ) {
565 // Our app entry page menu item has no title.
566 if ( is_null( $submenu_item[0] ) && self::APP_ENTRY_POINT === $submenu_item[2] ) {
567 $wc_admin_key = $submenu_key;
568 break;
569 }
570 }
571
572 if ( ! $wc_admin_key ) {
573 return;
574 }
575
576 unset( $submenu['woocommerce'][ $wc_admin_key ] );
577 }
578
579 /**
580 * Returns true if we are on a JS powered admin page or
581 * a "classic" (non JS app) powered admin page (an embedded page).
582 */
583 public static function is_admin_or_embed_page() {
584 return self::is_admin_page() || self::is_embed_page();
585 }
586
587 /**
588 * Returns true if we are on a JS powered admin page.
589 */
590 public static function is_admin_page() {
591 // phpcs:disable WordPress.Security.NonceVerification
592 return isset( $_GET['page'] ) && 'wc-admin' === $_GET['page'];
593 // phpcs:enable WordPress.Security.NonceVerification
594 }
595
596 /**
597 * Returns true if we are on a settings page.
598 */
599 public static function is_settings_page() {
600 // phpcs:disable WordPress.Security.NonceVerification
601 return isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'];
602 // phpcs:enable WordPress.Security.NonceVerification
603 }
604
605 /**
606 * Returns true if we are on a "classic" (non JS app) powered admin page.
607 *
608 * TODO: See usage in `admin.php`. This needs refactored and implemented properly in core.
609 */
610 public static function is_embed_page() {
611 return wc_admin_is_connected_page();
612 }
613
614 /**
615 * Redirect payment tasks to the settings page.
616 *
617 * Redirects both 'payments' and 'woocommerce-payments' tasks to the Payments settings page,
618 * when it is safe to do so in terms of backwards compatibility.
619 */
620 public function maybe_redirect_payment_tasks_to_settings() {
621 // Bail if we are not in the WP admin or not on a WC admin page.
622 if ( ! is_admin() || ! self::is_admin_page() ) {
623 return;
624 }
625
626 // Bail if we are not requesting a page for a WooCommerce task.
627 // phpcs:ignore WordPress.Security.NonceVerification
628 if ( empty( $_GET['task'] ) ) {
629 return;
630 }
631
632 // Only sufficiently capable users should be redirected.
633 if ( ! current_user_can( 'manage_woocommerce' ) ) {
634 return;
635 }
636
637 // Get the current task ID.
638 // phpcs:ignore WordPress.Security.NonceVerification
639 $task_id = wc_clean( wp_unslash( $_GET['task'] ) );
640
641 // Bail if the task is not a payments task.
642 if ( ! in_array( $task_id, array( 'payments', 'woocommerce-payments' ), true ) ) {
643 return;
644 }
645
646 $redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&from=WCADMIN_PAYMENT_TASK' );
647
648 // The WooPayments task is always redirected to the settings page.
649 if ( 'woocommerce-payments' === $task_id ) {
650 wp_safe_redirect( $redirect_url );
651 exit;
652 }
653
654 // The generic payments task is only redirected if the request is a regular user request,
655 // not part of an onboarding flow or other special case.
656 $special_request_params = array(
657 // This is used by the legacy, Payments task-based suggestions onboarding flow.
658 // Nobody should be using this anymore, but just in case.
659 'connection-return',
660 // This is used by the legacy, Payments task-based suggestions onboarding flow.
661 // Nobody should be using this anymore, but just in case.
662 'id',
663 // Some params for gateway IDs, just in case.
664 'gateway_id',
665 'gateway-id',
666 // Sometimes the gateway is referred to as 'method'. Stay clear of it.
667 'method',
668 // If there is a success or error param, better not redirect.
669 'success',
670 'error',
671 // If the URL is nonced, better not redirect.
672 '_wpnonce',
673 );
674 foreach ( $special_request_params as $param ) {
675 // phpcs:ignore WordPress.Security.NonceVerification
676 if ( isset( $_GET[ $param ] ) ) {
677 return;
678 }
679 }
680
681 // If we reach this point, we can safely redirect to the settings page.
682 wp_safe_redirect( $redirect_url );
683 exit;
684 }
685 }
686