PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | class/class-mainwp-plugins-install-list-table.php +566 -457 5.0 → 6.2.1 View file →
@@ -6,11 +6,16 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +// Exit if accessed directly.
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
10 15 // Include class-wp-list-table.php.
11 16 if ( ! class_exists( '\WP_List_Table' ) ) {
12 - require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
17 + require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; // NOSONAR - WP compatible.
13 18 }
14 19
15 20 /**
16 21 * Class MainWP_Plugins_Install_List_Table
@@ -19,537 +24,641 @@
19 24 * @subpackage List_Table
20 25 * @since 3.1.0
21 26 * @access private
22 27 */
23 -class MainWP_Plugins_Install_List_Table extends \WP_List_Table {
28 +class MainWP_Plugins_Install_List_Table extends \WP_List_Table { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
24 29
25 - /**
26 - * Default direction.
27 - *
28 - * @var string $order
29 - */
30 - public $order = 'ASC';
30 + /**
31 + * Default direction.
32 + *
33 + * @var string $order
34 + */
35 + public $order = 'ASC';
31 36
32 - /**
33 - * Default order.
34 - *
35 - * @var int $orderby
36 - */
37 - public $orderby = null;
37 + /**
38 + * Default order.
39 + *
40 + * @var int $orderby
41 + */
42 + public $orderby = null;
38 43
39 - /**
40 - * Groups array.
41 - *
42 - * @var array $groups
43 - */
44 - public $groups = array();
44 + /**
45 + * Groups array.
46 + *
47 + * @var array $groups
48 + */
49 + public $groups = array();
45 50
46 - /**
47 - * Error messages.
48 - *
49 - * @var mixed $error
50 - */
51 - private $error;
51 + /**
52 + * Error messages.
53 + *
54 + * @var mixed $error
55 + */
56 + private $error;
52 57
53 - /**
54 - * Method ajax_user_can()
55 - *
56 - * Chck if the current user has the WP ability "install_plugins".
57 - *
58 - * @return boolean True|False.
59 - */
60 - public function ajax_user_can() {
61 - return current_user_can( 'install_plugins' );
62 - }
58 + /**
59 + * Method ajax_user_can()
60 + *
61 + * Chck if the current user has the WP ability "install_plugins".
62 + *
63 + * @return boolean True|False.
64 + */
65 + public function ajax_user_can() {
66 + return current_user_can( 'install_plugins' );
67 + }
63 68
64 - /**
65 - * Method prepair_items()
66 - *
67 - * @global array $tabs
68 - * @global string $tab
69 - * @global int $paged
70 - * @global string $type
71 - * @global string $term
72 - * @global string $wp_version
73 - */
74 - public function prepare_items() { // phpcs:ignore -- Current complexity is the only way to achieve desired results, pull request solutions appreciated.
75 - include ABSPATH . 'wp-admin/includes/plugin-install.php';
69 + /**
70 + * Method prepair_items()
71 + *
72 + * @global array $tabs
73 + * @global string $tab
74 + * @global int $paged
75 + * @global string $type
76 + * @global string $term
77 + * @global string $wp_version
78 + */
79 + public function prepare_items() { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
80 + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // NOSONAR - WP compatible.
76 81
77 - global $tab; // required.
82 + global $tab; // required.
78 83
79 - wp_reset_vars( array( 'tab' ) );
84 + wp_reset_vars( array( 'tab' ) );
80 85
81 - $paged = $this->get_pagenum();
86 + $paged = $this->get_pagenum();
82 87
83 - $per_page = 40;
88 + $per_page = 40;
84 89
85 - // These are the tabs which are shown on the page.
86 - $tabs = array();
90 + // These are the tabs which are shown on the page.
91 + $tabs = array();
87 92
88 - if ( 'search' === $tab ) {
89 - $tabs['search'] = esc_html__( 'Search Results', 'mainwp' );
90 - }
91 - $tabs['featured'] = _x( 'Featured', 'Plugin Installer' );
92 - $tabs['popular'] = _x( 'Popular', 'Plugin Installer' );
93 - $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer' );
94 - if ( 'beta' === $tab || false !== strpos( $GLOBALS['wp_version'], '-' ) ) {
95 - $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer' );
96 - }
97 - if ( current_user_can( 'upload_plugins' ) ) {
98 - // No longer a real tab. Here for filter compatibility.
99 - // Gets skipped in get_views().
100 - $tabs['upload'] = esc_html__( 'Upload Plugin', 'mainwp' );
101 - }
93 + if ( 'search' === $tab ) {
94 + $tabs['search'] = esc_html__( 'Search Results', 'mainwp' );
95 + }
96 + $tabs['featured'] = _x( 'Featured', 'Plugin Installer', 'mainwp' );
97 + $tabs['popular'] = _x( 'Popular', 'Plugin Installer', 'mainwp' );
98 + $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer', 'mainwp' );
99 + if ( 'beta' === $tab || false !== strpos( $GLOBALS['wp_version'], '-' ) ) {
100 + $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer', 'mainwp' );
101 + }
102 + if ( current_user_can( 'upload_plugins' ) ) {
103 + // No longer a real tab. Here for filter compatibility.
104 + // Gets skipped in get_views().
105 + $tabs['upload'] = esc_html__( 'Upload Plugin', 'mainwp' );
106 + }
102 107
103 - $nonmenu_tabs = array( 'plugin-information' );
104 - // Valid actions to perform which do not have a Menu item.
105 - // If a non-valid menu tab has been selected, And it's not a non-menu action.
106 - if ( empty( $tab ) || ( ! isset( $tabs[ $tab ] ) && ! in_array( $tab, (array) $nonmenu_tabs ) ) ) {
108 + $nonmenu_tabs = array( 'plugin-information' );
109 + // Valid actions to perform which do not have a Menu item.
110 + // If a non-valid menu tab has been selected, And it's not a non-menu action.
111 + if ( empty( $tab ) || ( ! isset( $tabs[ $tab ] ) && ! in_array( $tab, (array) $nonmenu_tabs ) ) ) {
107 112
108 - $tab = key( $tabs ); // phpcs:ignore -- required for custom bulk install plugins.
109 - }
113 + $tab = key( $tabs ); // phpcs:ignore -- required for custom bulk install plugins.
114 + }
110 115
111 - $args = array(
112 - 'page' => $paged,
113 - 'per_page' => $per_page,
114 - 'fields' => array(
115 - 'last_updated' => true,
116 - 'icons' => true,
117 - 'active_installs' => true,
118 - ),
119 - 'locale' => get_locale(),
120 - 'installed_plugins' => array(),
121 - );
116 + $args = array(
117 + 'page' => $paged,
118 + 'per_page' => $per_page,
119 + 'fields' => array(
120 + 'last_updated' => true,
121 + 'icons' => true,
122 + 'active_installs' => true,
123 + ),
124 + 'locale' => get_locale(),
125 + 'installed_plugins' => array(),
126 + );
122 127
123 - switch ( $tab ) {
124 - case 'search':
125 - $type = isset( $_REQUEST['type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) : 'term'; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
126 - $term = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
128 + switch ( $tab ) {
129 + case 'search':
130 + $type = isset( $_REQUEST['type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) : 'term'; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
131 + $term = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
127 132
128 - switch ( $type ) {
129 - case 'tag':
130 - $args['tag'] = sanitize_title_with_dashes( $term );
131 - break;
132 - case 'term':
133 - $args['search'] = $term;
134 - break;
135 - case 'author':
136 - $args['author'] = $term;
137 - break;
138 - }
133 + switch ( $type ) {
134 + case 'tag':
135 + $args['tag'] = sanitize_title_with_dashes( $term );
136 + break;
137 + case 'term':
138 + $args['search'] = $term;
139 + break;
140 + case 'author':
141 + $args['author'] = $term;
142 + break;
143 + default:
144 + break;
145 + }
139 146
140 - break;
147 + break;
141 148
142 - case 'featured':
143 - $args['fields']['group'] = true;
144 - $this->orderby = 'group';
145 - // no b r e a k!
146 - case 'popular':
147 - case 'new':
148 - case 'beta':
149 - case 'recommended':
150 - $args['browse'] = $tab;
151 - break;
149 + case 'featured':
150 + $args['fields']['group'] = true;
151 + $this->orderby = 'group';
152 + // no b r e a k!
153 + case 'popular':
154 + case 'new':
155 + case 'beta':
156 + case 'recommended':
157 + $args['browse'] = $tab;
158 + break;
152 159
153 - case 'favorites':
154 - $user = isset( $_GET['user'] ) ? sanitize_text_field( wp_unslash( $_GET['user'] ) ) : get_user_option( 'wporg_favorites' ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
155 - update_user_meta( get_current_user_id(), 'wporg_favorites', $user );
156 - if ( $user ) {
157 - $args['user'] = $user;
158 - } else {
159 - $args = false;
160 - }
160 + case 'favorites':
161 + $user = isset( $_GET['user'] ) ? sanitize_text_field( wp_unslash( $_GET['user'] ) ) : get_user_option( 'wporg_favorites' ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
162 + update_user_meta( get_current_user_id(), 'wporg_favorites', $user );
163 + if ( $user ) {
164 + $args['user'] = $user;
165 + } else {
166 + $args = false;
167 + }
161 168
162 - break;
169 + break;
163 170
164 - default:
165 - $args = false;
166 - break;
167 - }
171 + default:
172 + $args = false;
173 + break;
174 + }
168 175
169 - if ( ! $args ) {
170 - return;
171 - }
176 + if ( ! $args ) {
177 + return;
178 + }
172 179
173 - $api = plugins_api( 'query_plugins', $args );
180 + $api = plugins_api( 'query_plugins', $args );
174 181
175 - if ( is_wp_error( $api ) ) {
176 - $this->error = $api;
177 - return;
178 - }
182 + if ( is_wp_error( $api ) ) {
183 + $this->error = $api;
184 + return;
185 + }
179 186
180 - $this->items = $api->plugins;
187 + $this->items = $api->plugins;
181 188
182 - if ( $this->orderby ) {
183 - uasort( $this->items, array( $this, 'order_callback' ) );
184 - }
189 + if ( $this->orderby ) {
190 + uasort( $this->items, array( $this, 'order_callback' ) );
191 + }
185 192
186 - $this->set_pagination_args(
187 - array(
188 - 'total_items' => $api->info['results'],
189 - 'per_page' => $args['per_page'],
190 - )
191 - );
193 + $this->set_pagination_args(
194 + array(
195 + 'total_items' => $api->info['results'],
196 + 'per_page' => $args['per_page'],
197 + )
198 + );
192 199
193 - if ( isset( $api->info['groups'] ) ) {
194 - $this->groups = $api->info['groups'];
195 - }
196 - }
200 + if ( isset( $api->info['groups'] ) ) {
201 + $this->groups = $api->info['groups'];
202 + }
203 + }
197 204
198 - /**
199 - * Method no_items()
200 - *
201 - * Check for errors.
202 - */
203 - public function no_items() {
204 - if ( isset( $this->error ) ) {
205 - $message = $this->error->get_error_message() . '<p class="hide-if-no-js"><a href="#" class="button" onclick="document.location.reload(); return false;">' . esc_html__( 'Try again', 'mainwp' ) . '</a></p>';
206 - } else {
207 - $message = esc_html__( 'No plugins match your request.', 'mainwp' );
208 - }
209 - echo '<div class="ui message yellow">' . $message . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput
210 - }
205 + /**
206 + * Method no_items()
207 + *
208 + * Check for errors.
209 + */
210 + public function no_items() {
211 + echo '<div class="ui center aligned segment">';
212 + MainWP_UI::render_empty_page_placeholder( __( 'No plugins found.', 'mainwp' ), __( 'Please try a different search.', 'mainwp' ), '<em data-emoji=":mag:" class="big"></em>' );
213 + echo '</div>';
214 + }
211 215
212 - /**
213 - * Method dislpay()
214 - *
215 - * Override the parent display() so we can provide a different container.
216 - */
217 - public function display() {
218 - ?>
219 - <div id="mainwp-install-plugins-container" class="ui four cards">
220 - <?php $this->display_rows_or_placeholder(); ?>
221 - </div>
222 - <div class="ui hidden divider"></div>
223 - <div class="ui column grid">
224 - <div class="column right aligned">
225 - <div class="inline field">
226 - <?php $this->display_tablenav( 'bottom' ); ?>
227 - </div>
228 - </div>
229 - </div>
230 - <?php
231 - }
216 + /**
217 + * Method dislpay()
218 + *
219 + * Override the parent display() so we can provide a different container.
220 + */
221 + public function display() {
222 + $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
223 + ?>
224 + <div class="ui two column stackable grid">
225 + <div class="column left aligned middle aligned">
226 + <?php if ( ! empty( $search ) ) : ?>
227 + <h3 class="ui header"><?php
228 + /* translators: %s: Search term. */
229 + echo esc_html( sprintf( __( 'Search results for: %s', 'mainwp' ), $search ) ); ?></h3>
230 + <?php endif; ?>
231 + </div>
232 + <div class="column right aligned middle aligned">
233 + <div class="inline field">
234 + <?php $this->display_tablenav( 'top' ); ?>
235 + </div>
236 + </div>
237 + </div>
238 + <div class="ui hidden divider"></div>
239 + <div id="mainwp-install-plugins-container" class="ui three mainwp-cards cards">
240 + <?php $this->display_rows_or_placeholder(); ?>
241 + </div>
242 + <div class="ui hidden divider"></div>
243 + <div class="ui two column stackable grid">
244 + <div class="column left aligned middle aligned">
245 + <?php if ( ! empty( $search ) ) : ?>
246 + <?php
247 + /* translators: %s: Search term. */
248 + echo esc_html( sprintf( __( 'Search results for: %s', 'mainwp' ), $search ) ); ?>
249 + <?php endif; ?>
250 + </div>
251 + <div class="column right aligned middle aligned">
252 + <div class="inline field">
253 + <?php $this->display_tablenav( 'bottom' ); ?>
254 + </div>
255 + </div>
256 + </div>
257 + <?php
258 + }
232 259
233 - /**
234 - * Method display_tablenav()
235 - *
236 - * Displays the table Navigation.
237 - *
238 - * @param mixed $which Position information.
239 - *
240 - * @return mixed wp_referer_field();
241 - */
242 - protected function display_tablenav( $which ) {
260 + /**
261 + * Method display_tablenav()
262 + *
263 + * Displays the table Navigation.
264 + *
265 + * @param mixed $which Position information.
266 + *
267 + * @return mixed wp_referer_field();
268 + */
269 + protected function display_tablenav( $which ) {
243 270
244 - if ( 'featured' === $GLOBALS['tab'] ) {
245 - return;
246 - }
271 + if ( 'featured' === $GLOBALS['tab'] ) {
272 + return;
273 + }
247 274
248 - if ( 'top' === $which ) {
249 - wp_referer_field();
250 - $this->pagination( $which );
251 - } else {
252 - $this->pagination( $which );
253 - }
254 - }
275 + if ( 'top' === $which ) {
276 + wp_referer_field();
277 + $this->pagination( $which );
278 + } else {
279 + $this->pagination( $which );
280 + }
281 + }
255 282
256 - /**
257 - * Method pagination()
258 - *
259 - * Build the pagination menu.
260 - *
261 - * @param mixed $which Position information.
262 - *
263 - * @return mixed Pagination HTML
264 - */
265 - protected function pagination( $which ) {
266 - if ( empty( $this->_pagination_args ) ) {
267 - return;
268 - }
283 + /**
284 + * Method pagination()
285 + *
286 + * Build the pagination menu.
287 + *
288 + * @param mixed $which Position information.
289 + *
290 + * @return mixed Pagination HTML
291 + */
292 + protected function pagination( $which ) { //phpcs:ignore -- NOSONAR - complex.
293 + if ( empty( $this->_pagination_args ) ) {
294 + return;
295 + }
269 296
270 - $total_items = $this->_pagination_args['total_items'];
271 - $total_pages = (int) $this->_pagination_args['total_pages'];
297 + $total_items = $this->_pagination_args['total_items'];
298 + $total_pages = (int) $this->_pagination_args['total_pages'];
272 299
273 - $perpage_paging = '<span>' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
300 + /* translators: %s: Number of items. */
301 + $perpage_paging = '<span>' . sprintf( _n( '%s item', '%s items', $total_items, 'mainwp' ), number_format_i18n( $total_items ) ) . '</span>';
274 302
275 - $current = (int) $this->get_pagenum();
276 - $removable_query_args = wp_removable_query_args();
303 + $current = (int) $this->get_pagenum();
304 + $removable_query_args = wp_removable_query_args();
277 305
278 - $current_url = set_url_scheme( 'http://' . ( isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
306 + $current_url = set_url_scheme( 'http://' . ( isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '' ) ); // phpcs:ignore WordPress.Security.NonceVerification -- NOSONAR - compatible with dashboard host.
279 307
280 - $current_url = remove_query_arg( $removable_query_args, $current_url );
308 + $current_url = remove_query_arg( $removable_query_args, $current_url );
281 309
282 - $page_links = array();
310 + $page_links = array();
283 311
284 - $disable_first = false;
285 - $disable_last = false;
286 - $disable_prev = false;
287 - $disable_next = false;
312 + $disable_first = false;
313 + $disable_last = false;
314 + $disable_prev = false;
315 + $disable_next = false;
288 316
289 - if ( 1 === $current ) {
290 - $disable_first = true;
291 - $disable_prev = true;
292 - }
293 - if ( 2 === $current ) {
294 - $disable_first = true;
295 - }
296 - if ( $current === $total_pages ) {
297 - $disable_last = true;
298 - $disable_next = true;
299 - }
300 - if ( $current === $total_pages - 1 ) {
301 - $disable_last = true;
302 - }
317 + if ( 1 === $current ) {
318 + $disable_first = true;
319 + $disable_prev = true;
320 + }
321 + if ( 2 === $current ) {
322 + $disable_first = true;
323 + }
324 + if ( $current === $total_pages ) {
325 + $disable_last = true;
326 + $disable_next = true;
327 + }
328 + if ( $current === $total_pages - 1 ) {
329 + $disable_last = true;
330 + }
303 331
304 - if ( $disable_first ) {
305 - $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle double left icon"></i></a>';
306 - } else {
307 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'First page' ) . "' aria-hidden='true'>%s</a>", esc_url( remove_query_arg( 'paged', $current_url ) ), '<i class="angle double left icon"></i>' );
308 - }
332 + if ( $disable_first ) {
333 + $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle double left icon"></i></a>';
334 + } else {
335 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'First page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( remove_query_arg( 'paged', $current_url ) ), '<i class="angle double left icon"></i>' );
336 + }
309 337
310 - if ( $disable_prev ) {
311 - $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle left icon"></i></a>';
312 - } else {
313 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Previous page' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ), '<i class="angle left icon"></i>' );
314 - }
338 + if ( $disable_prev ) {
339 + $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle left icon"></i></a>';
340 + } else {
341 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Previous page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ), '<i class="angle left icon"></i>' );
342 + }
315 343
316 - if ( $current - 1 > 0 ) {
317 - $page_links[] = sprintf( "<a class='item' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current - 1, $current_url ) ), $current - 1 );
318 - }
344 + if ( $current - 1 > 0 ) {
345 + $page_links[] = sprintf( "<a class='item' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current - 1, $current_url ) ), $current - 1 );
346 + }
319 347
320 - $page_links[] = sprintf( "<a class='item active' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current, $current_url ) ), $current );
348 + $page_links[] = sprintf( "<a class='item active' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current, $current_url ) ), $current );
321 349
322 - if ( $current + 1 <= $total_pages ) {
323 - $page_links[] = sprintf( "<a class='item' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current + 1, $current_url ) ), $current + 1 );
324 - }
350 + if ( $current + 1 <= $total_pages ) {
351 + $page_links[] = sprintf( "<a class='item' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current + 1, $current_url ) ), $current + 1 );
352 + }
325 353
326 - if ( $disable_next ) {
327 - $page_links[] = '<span class="item disabled " aria-hidden="true"><i class="right angle icon"></i></span>';
328 - } else {
329 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Next page', 'mainwp' ) . "'>%s</a>", esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ), '<i class="angle right icon"></i>' );
330 - }
354 + if ( $disable_next ) {
355 + $page_links[] = '<span class="item disabled " aria-hidden="true"><i class="right angle icon"></i></span>';
356 + } else {
357 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Next page', 'mainwp' ) . "'>%s</a>", esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ), '<i class="angle right icon"></i>' );
358 + }
331 359
332 - if ( $disable_last ) {
333 - $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="right angle double icon"></i></a>';
334 - } else {
335 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Last page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ), '<i class="right angle double icon"></i>' );
336 - }
360 + if ( $disable_last ) {
361 + $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="right angle double icon"></i></a>';
362 + } else {
363 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Last page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ), '<i class="right angle double icon"></i>' );
364 + }
337 365
338 - if ( $total_pages > 1 ) {
339 - $perpage_paging = $perpage_paging . "&nbsp;&nbsp;<div class='ui pagination menu'>" . join( "\n", $page_links ) . '</div>';
340 - } else {
341 - $perpage_paging = $perpage_paging;
342 - }
366 + if ( $total_pages > 1 ) {
367 + $perpage_paging = $perpage_paging . "&nbsp;&nbsp;<div class='ui mini pagination menu'>" . join( "\n", $page_links ) . '</div>';
368 + }
343 369
344 - ob_start();
370 + ob_start();
345 371
346 - echo $perpage_paging; // phpcs:ignore WordPress.Security.EscapeOutput
372 + echo $perpage_paging; // phpcs:ignore WordPress.Security.EscapeOutput
347 373
348 - $output = ob_get_clean();
374 + $output = ob_get_clean();
349 375
350 - $this->_pagination = $output;
376 + $this->_pagination = $output;
351 377
352 - echo $this->_pagination; // phpcs:ignore WordPress.Security.EscapeOutput
353 - }
378 + echo $this->_pagination; // phpcs:ignore WordPress.Security.EscapeOutput
379 + }
354 380
355 - /**
356 - * Method get_columns()
357 - *
358 - * Get the collumns to display.
359 - *
360 - * @return array List of collumns to display.
361 - */
362 - public function get_columns() {
363 - return array();
364 - }
381 + /**
382 + * Method get_columns()
383 + *
384 + * Get the collumns to display.
385 + *
386 + * @return array List of collumns to display.
387 + */
388 + public function get_columns() {
389 + return array();
390 + }
365 391
366 - /**
367 - * Method order_callback()
368 - *
369 - * @param object $plugin_a Plugin A.
370 - * @param object $plugin_b Plugin B.
371 - *
372 - * @return int 0|1 Default 0.
373 - */
374 - private function order_callback( $plugin_a, $plugin_b ) {
375 - $orderby = $this->orderby;
376 - if ( ! isset( $plugin_a->$orderby, $plugin_b->$orderby ) ) {
377 - return 0;
378 - }
392 + /**
393 + * Method order_callback()
394 + *
395 + * @param object $plugin_a Plugin A.
396 + * @param object $plugin_b Plugin B.
397 + *
398 + * @return int 0|1 Default 0.
399 + */
400 + private function order_callback( $plugin_a, $plugin_b ) {
401 + $orderby = $this->orderby;
402 + if ( ! isset( $plugin_a->$orderby, $plugin_b->$orderby ) ) {
403 + return 0;
404 + }
379 405
380 - $a = $plugin_a->$orderby;
381 - $b = $plugin_b->$orderby;
406 + $a = $plugin_a->$orderby;
407 + $b = $plugin_b->$orderby;
382 408
383 - if ( $a === $b ) {
384 - return 0;
385 - }
409 + if ( $a === $b ) {
410 + return 0;
411 + }
386 412
387 - if ( 'DESC' === $this->order ) {
388 - return ( $a < $b ) ? 1 : -1;
389 - } else {
390 - return ( $a < $b ) ? -1 : 1;
391 - }
392 - }
413 + if ( 'DESC' === $this->order ) {
414 + return ( $a < $b ) ? 1 : -1;
415 + } else {
416 + return ( $a < $b ) ? -1 : 1;
417 + }
418 + }
393 419
394 - /**
395 - * Method display_rows()
396 - *
397 - * Build Plugin Cards.
398 - *
399 - * @return mixed Plugin cards.
400 - */
401 - public function display_rows() {
402 - $plugins_allowedtags = array(
403 - 'a' => array(
404 - 'href' => array(),
405 - 'title' => array(),
406 - 'target' => array(),
407 - ),
408 - 'abbr' => array( 'title' => array() ),
409 - 'acronym' => array( 'title' => array() ),
410 - 'code' => array(),
411 - 'pre' => array(),
412 - 'em' => array(),
413 - 'strong' => array(),
414 - 'ul' => array(),
415 - 'ol' => array(),
416 - 'li' => array(),
417 - 'p' => array(),
418 - 'br' => array(),
419 - );
420 + /**
421 + * Check if a search term matches mainwp or mainwp child (case-insensitive).
422 + *
423 + * @param string $term Search term.
424 + * @return bool True if matches.
425 + */
426 + private function is_mainwp_search( $term ) {
427 + $normalized = strtolower( trim( $term ) );
428 + return (bool) preg_match( '/^mainwp(\s*-?\s*child)?$/i', $normalized );
429 + }
420 430
421 - $plugins_group_titles = array(
422 - 'Performance' => _x( 'Performance', 'Plugin installer group title' ),
423 - 'Social' => _x( 'Social', 'Plugin installer group title' ),
424 - 'Tools' => _x( 'Tools', 'Plugin installer group title' ),
425 - );
431 + /**
432 + * Render hardcoded MainWP Child plugin card.
433 + *
434 + * @return void
435 + */
436 + private function render_mainwp_child_card() {
437 + $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=mainwp-child&name=' . rawurlencode( 'MainWP Child' ) );
438 + $icon_url = MAINWP_PLUGIN_URL . 'assets/images/mainwp-icon.svg';
439 + $plugin_name = esc_attr__( 'MainWP Child &#8211; Securely Connects to the MainWP Dashboard to Manage Multiple Sites', 'mainwp' );
440 + ?>
441 + <div class="card plugin-card-mainwp-child" plugin-slug="mainwp-child" plugin-name="MainWP Child">
442 + <?php do_action( 'mainwp_install_plugin_card_top' ); ?>
443 + <div class="content">
444 + <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_url( $icon_url ); ?>" alt="<?php esc_attr_e( 'MainWP Child', 'mainwp' ); ?>" /></a>
445 + <div class="header">
446 + <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php esc_html_e( 'MainWP Child', 'mainwp' ); ?></a>
447 + </div>
448 + <div class="meta"><cite><?php
449 + /* translators: %s: Plugin author name. */
450 + printf( esc_html__( 'By %s', 'mainwp' ), '<a href="' . esc_url( 'https://profiles.wordpress.org/mainwp/' ) . '">mainwp</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput ?></cite></div>
451 + <div class="description">
452 + <p><?php esc_html_e( 'MainWP Child connects your WordPress sites to this Dashboard.', 'mainwp' ); ?></p>
453 + <p><?php esc_html_e( 'Already managing a site here? Select Install Plugin, choose your sites in the right panel, and click Complete Installation.', 'mainwp' ); ?></p>
454 + <p><?php esc_html_e( 'Setting up a new site? Download the plugin, then:', 'mainwp' ); ?></p>
455 + <div class="ui list">
456 + <div class="item"><p>1. <?php esc_html_e( 'Log in to your WordPress site', 'mainwp' ); ?></p></div>
457 + <div class="item"><p>2. <?php esc_html_e( 'Go to Plugins → Add New → Upload Plugin', 'mainwp' ); ?></p></div>
458 + <div class="item"><p>3. <?php esc_html_e( 'Upload and activate the downloaded file', 'mainwp' ); ?></p></div>
459 + <div class="item"><p>4. <?php esc_html_e( 'Return here to add the site to your Dashboard', 'mainwp' ); ?></p></div>
460 + </div>
461 + </div>
462 + </div>
463 + <div class="extra content">
464 + <div class="left aligned middle aligned column">
465 + <a href="<?php echo esc_url( 'https://downloads.wordpress.org/plugin/mainwp-child.latest-stable.zip' ); ?>" class="ui mini green basic fluid button"><i class="download icon"></i><?php esc_html_e( 'Download', 'mainwp' ); ?></a>
466 + </div>
467 + </div>
468 + <div class="extra content">
469 + <div class="ui two column grid">
470 + <div class="left aligned middle aligned column">
471 + <a href="<?php echo esc_url( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php esc_html_e( 'Plugin Details', 'mainwp' ); ?></a>
472 + </div>
473 + <div class="right aligned middle aligned column">
474 + <div class="ui radio checkbox">
475 + <input name="install-plugin" type="radio" id="install-plugin-mainwp-child" plugin-name="<?php echo esc_attr( $plugin_name ); ?>" plugin-version="">
476 + <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
477 + </div>
478 + </div>
479 + </div>
480 + </div>
481 + </div>
482 + <?php
483 + }
426 484
427 - $group = null;
485 + /**
486 + * Method display_rows()
487 + *
488 + * Build Plugin Cards.
489 + *
490 + * @return mixed Plugin cards.
491 + */
492 + public function display_rows() { //phpcs:ignore -- NOSONAR - complex.
493 + $search_term = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
428 494
429 - foreach ( (array) $this->items as $plugin ) {
430 - if ( is_object( $plugin ) ) {
431 - $plugin = (array) $plugin;
432 - }
495 + if ( ! empty( $search_term ) && $this->is_mainwp_search( $search_term ) ) {
496 + $this->render_mainwp_child_card();
497 + $this->items = array_filter(
498 + $this->items,
499 + function ( $plugin ) {
500 + $slug = is_array( $plugin ) ? ( $plugin['slug'] ?? '' ) : ( $plugin->slug ?? '' );
501 + return 'mainwp-child' !== $slug;
502 + }
503 + );
504 + }
433 505
434 - // Display the group heading if there is one.
435 - if ( isset( $plugin['group'] ) && $plugin['group'] != $group ) { //phpcs:ignore -- to valid.
436 - if ( isset( $this->groups[ $plugin['group'] ] ) ) {
437 - $group_name = $this->groups[ $plugin['group'] ];
438 - if ( isset( $plugins_group_titles[ $group_name ] ) ) {
439 - $group_name = $plugins_group_titles[ $group_name ];
440 - }
441 - } else {
442 - $group_name = $plugin['group'];
443 - }
506 + $plugins_allowedtags = array(
507 + 'a' => array(
508 + 'href' => array(),
509 + 'title' => array(),
510 + 'target' => array(),
511 + ),
512 + 'abbr' => array( 'title' => array() ),
513 + 'acronym' => array( 'title' => array() ),
514 + 'code' => array(),
515 + 'pre' => array(),
516 + 'em' => array(),
517 + 'strong' => array(),
518 + 'ul' => array(),
519 + 'ol' => array(),
520 + 'li' => array(),
521 + 'p' => array(),
522 + 'br' => array(),
523 + );
444 524
445 - // Starting a new group, close off the divs of the last one.
446 - if ( ! empty( $group ) ) {
447 - echo '</div></div>';
448 - }
525 + $plugins_group_titles = array(
526 + 'Performance' => _x( 'Performance', 'Plugin installer group title', 'mainwp' ),
527 + 'Social' => _x( 'Social', 'Plugin installer group title', 'mainwp' ),
528 + 'Tools' => _x( 'Tools', 'Plugin installer group title', 'mainwp' ),
529 + );
449 530
450 - echo '<div class="plugin-group"><h3>' . esc_html( $group_name ) . '</h3>';
451 - // Needs an extra wrapping div for nth-child selectors to work.
452 - echo '<div class="plugin-items">';
531 + $group = null;
453 532
454 - $group = $plugin['group'];
455 - }
456 - $title = wp_kses( $plugin['name'], $plugins_allowedtags );
533 + foreach ( (array) $this->items as $plugin ) {
534 + if ( is_object( $plugin ) ) {
535 + $plugin = (array) $plugin;
536 + }
457 537
458 - // Remove any HTML from the description.
459 - $description = wp_strip_all_tags( $plugin['short_description'] );
460 - $version = wp_kses( $plugin['version'], $plugins_allowedtags );
538 + // Display the group heading if there is one.
539 + if ( isset( $plugin['group'] ) && $plugin['group'] != $group ) { //phpcs:ignore -- to valid.
540 + if ( isset( $this->groups[ $plugin['group'] ] ) ) {
541 + $group_name = $this->groups[ $plugin['group'] ];
542 + if ( isset( $plugins_group_titles[ $group_name ] ) ) {
543 + $group_name = $plugins_group_titles[ $group_name ];
544 + }
545 + } else {
546 + $group_name = $plugin['group'];
547 + }
461 548
462 - $name = wp_strip_all_tags( $title . ' ' . $version );
549 + // Starting a new group, close off the divs of the last one.
550 + if ( ! empty( $group ) ) {
551 + echo '</div></div>';
552 + }
463 553
464 - $author = wp_kses( $plugin['author'], $plugins_allowedtags );
465 - if ( ! empty( $author ) ) {
466 - $author = ' <cite>' . sprintf( esc_html__( 'By %s', 'mainwp' ), $author ) . '</cite>';
467 - }
554 + echo '<div class="plugin-group"><h3>' . esc_html( $group_name ) . '</h3>';
555 + // Needs an extra wrapping div for nth-child selectors to work.
556 + echo '<div class="plugin-items">';
468 557
469 - $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] . '&url=' . ( isset( $plugin['PluginURI'] ) ? rawurlencode( $plugin['PluginURI'] ) : '' ) . '&name=' . rawurlencode( $plugin['name'] ) );
558 + $group = $plugin['group'];
559 + }
560 + $title = wp_kses( $plugin['name'], $plugins_allowedtags );
470 561
471 - if ( ! empty( $plugin['icons']['svg'] ) ) {
472 - $plugin_icon_url = $plugin['icons']['svg'];
473 - } elseif ( ! empty( $plugin['icons']['2x'] ) ) {
474 - $plugin_icon_url = $plugin['icons']['2x'];
475 - } elseif ( ! empty( $plugin['icons']['1x'] ) ) {
476 - $plugin_icon_url = $plugin['icons']['1x'];
477 - } else {
478 - $plugin_icon_url = $plugin['icons']['default'];
479 - }
562 + // Remove any HTML from the description.
563 + $description = wp_strip_all_tags( $plugin['short_description'] );
564 + $version = wp_kses( $plugin['version'], $plugins_allowedtags );
480 565
481 - $last_updated_timestamp = strtotime( $plugin['last_updated'] );
482 - ?>
566 + $author = wp_kses( $plugin['author'], $plugins_allowedtags );
567 + if ( ! empty( $author ) ) {
568 + /* translators: %s: Plugin author name. */
569 + $author = ' <cite>' . sprintf( esc_html__( 'By %s', 'mainwp' ), $author ) . '</cite>';
570 + }
483 571
484 - <div class="card plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-slug="<?php echo esc_attr( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $plugin['name'] ); ?>">
485 - <?php
486 - /**
487 - * Action: mainwp_install_plugin_card_top
488 - *
489 - * Fires at the plugin card at top on the Install Plugins page.
490 - *
491 - * @since 4.1
492 - */
493 - do_action( 'mainwp_install_plugin_card_top' );
494 - ?>
495 - <div class="content">
496 - <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_attr( $plugin_icon_url ); ?>" /></a>
497 - <div class="header">
498 - <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php echo $title; // phpcs:ignore WordPress.Security.EscapeOutput ?></a>
499 - </div>
500 - <div class="meta">
501 - <?php echo $author; // phpcs:ignore WordPress.Security.EscapeOutput ?>
502 - </div>
503 - <div class="description">
504 - <?php echo esc_html( wp_strip_all_tags( $description ) ); ?>
505 - </div>
506 - </div>
507 - <div class="extra content">
508 - <div class="ui stacking grid">
509 - <div class="four wide left aligned column">
510 - <?php
511 - wp_star_rating(
512 - array(
513 - 'rating' => $plugin['rating'],
514 - 'type' => 'percent',
515 - 'number' => $plugin['num_ratings'],
516 - )
517 - );
518 - ?>
519 - </div>
520 - <div class="twelve wide right aligned column"><span class="ui small text"><?php esc_html_e( 'Updated: ', 'mainwp' ); ?><?php printf( esc_html__( '%s ago', 'mainwp' ), human_time_diff( $last_updated_timestamp ) ); // phpcs:ignore WordPress.Security.EscapeOutput ?></span></div>
521 - </div>
522 - </div>
523 - <div class="extra content">
524 - <a href="<?php echo esc_attr( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php echo esc_html( 'Plugin Details' ); ?></a>
525 - <div class="ui radio checkbox right floated">
526 - <input name="install-plugin" type="radio" id="install-plugin-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $title ); ?>" plugin-version="<?php echo esc_attr( $version ); ?>">
527 - <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
528 - </div>
529 - </div>
530 - <?php
531 - /**
532 - * Action: mainwp_install_plugin_card_bottom
533 - *
534 - * Fires at the plugin card at bottom on the Install Plugins page.
535 - *
536 - * @since 4.1
537 - */
538 - do_action( 'mainwp_install_plugin_card_bottom', $plugin );
539 - ?>
540 - </div>
541 - <?php
542 - }
543 - ?>
544 - <script type="text/javascript">
545 - jQuery( document ).ready( function() {
546 - jQuery( '.card .ui.star.rating' ).rating();
547 - } );
548 - </script>
549 - <?php
550 - // Close off the group divs of the last one.
551 - if ( ! empty( $group ) ) {
552 - echo '</div></div>';
553 - }
554 - }
572 + $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] . '&url=' . ( isset( $plugin['PluginURI'] ) ? rawurlencode( $plugin['PluginURI'] ) : '' ) . '&name=' . rawurlencode( $plugin['name'] ) );
573 +
574 + if ( ! empty( $plugin['icons']['svg'] ) ) {
575 + $plugin_icon_url = $plugin['icons']['svg'];
576 + } elseif ( ! empty( $plugin['icons']['2x'] ) ) {
577 + $plugin_icon_url = $plugin['icons']['2x'];
578 + } elseif ( ! empty( $plugin['icons']['1x'] ) ) {
579 + $plugin_icon_url = $plugin['icons']['1x'];
580 + } else {
581 + $plugin_icon_url = $plugin['icons']['default'];
582 + }
583 +
584 + $last_updated_timestamp = strtotime( $plugin['last_updated'] );
585 + ?>
586 +
587 + <div class="card plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-slug="<?php echo esc_attr( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $plugin['name'] ); ?>">
588 + <?php
589 + /**
590 + * Action: mainwp_install_plugin_card_top
591 + *
592 + * Fires at the plugin card at top on the Install Plugins page.
593 + *
594 + * @since 4.1
595 + */
596 + do_action( 'mainwp_install_plugin_card_top' );
597 + ?>
598 + <div class="content">
599 + <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_attr( $plugin_icon_url ); ?>" alt="<?php echo esc_attr( $plugin['name'] ); ?>" /></a>
600 + <div class="header">
601 + <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php echo $title; // phpcs:ignore WordPress.Security.EscapeOutput ?></a>
602 + </div>
603 + <div class="meta"><?php echo $author; // phpcs:ignore WordPress.Security.EscapeOutput ?></div>
604 + <div class="description">
605 + <?php echo esc_html( wp_strip_all_tags( $description ) ); ?>
606 + </div>
607 + </div>
608 + <div class="extra content">
609 + <div class="ui stacking grid">
610 + <div class="four wide left aligned column">
611 + <?php
612 + wp_star_rating(
613 + array(
614 + 'rating' => $plugin['rating'],
615 + 'type' => 'percent',
616 + 'number' => $plugin['num_ratings'],
617 + )
618 + );
619 + ?>
620 + </div>
621 + <div class="twelve wide right aligned column"><span class="ui small text"><?php
622 + /* translators: %s: Human-readable time difference. */
623 + printf( esc_html__( 'Updated %s ago', 'mainwp' ), human_time_diff( $last_updated_timestamp ) ); // phpcs:ignore WordPress.Security.EscapeOutput ?></span></div>
624 + </div>
625 + </div>
626 + <div class="extra content">
627 + <div class="ui two column grid">
628 + <div class="left aligned middle aligned column">
629 + <a href="<?php echo esc_attr( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php echo esc_html__( 'Plugin Details', 'mainwp' ); ?></a>
630 + </div>
631 + <div class="right aligned middle aligned column">
632 + <div class="ui radio checkbox">
633 + <input name="install-plugin" type="radio" id="install-plugin-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $title ); ?>" plugin-version="<?php echo esc_attr( $version ); ?>">
634 + <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
635 + </div>
636 + </div>
637 + </div>
638 + </div>
639 + <?php
640 + /**
641 + * Action: mainwp_install_plugin_card_bottom
642 + *
643 + * Fires at the plugin card at bottom on the Install Plugins page.
644 + *
645 + * @since 4.1
646 + */
647 + do_action( 'mainwp_install_plugin_card_bottom', $plugin );
648 + ?>
649 + </div>
650 + <?php
651 + }
652 + ?>
653 + <script type="text/javascript">
654 + jQuery( document ).ready( function() {
655 + jQuery( '.card .ui.star.rating' ).rating();
656 + } );
657 + </script>
658 + <?php
659 + // Close off the group divs of the last one.
660 + if ( ! empty( $group ) ) {
661 + echo '</div></div>';
662 + }
663 + }
555 664 }