PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 3.1.1 All 140 releases
defender-security / src / controller / class-login-access.php

class-login-access.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at src/controller/class-login-access.php

432 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The advanced tools class.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use WP_Defender\Event;
11 use WP_Filesystem_Base;
12 use Calotes\Component\Response;
13 use WP_Defender\Integrations\MaxMind_Geolocation;
14 use WP_Defender\Controller\Captcha as Captcha_Controller;
15 use WP_Defender\Controller\Mask_Login as Mask_Login_Controller;
16 use WP_Defender\Controller\Password_Reset as Password_Reset_Controller;
17 use WP_Defender\Controller\Strong_Password as Strong_Password_Controller;
18 use WP_Defender\Controller\Session_Protection as Session_Protection_Controller;
19 use WP_Defender\Controller\Password_Protection as Password_Protection_Controller;
20 use WP_Defender\Model\Setting\Session_Protection as Model_Session_Protection;
21
22 /**
23 * Since advanced tools will have many submodules, this just using for render.
24 *
25 * Class Login_Access
26 */
27 class Login_Access extends Event {
28 /**
29 * Ajax action for page/post search.
30 */
31 private const SEARCH_POSTS_AJAX_ACTION = 'wpdef_hide_login_search_posts';
32
33 /**
34 * The slug identifier for this controller.
35 *
36 * @var string
37 */
38 protected $slug = 'wdf-advanced-tools';
39
40 /**
41 * Initializes the model and service, registers routes.
42 */
43 public function __construct() {
44 $this->register_page(
45 $this->get_title(),
46 $this->slug,
47 array( $this, 'main_view' ),
48 $this->parent_slug
49 );
50 $this->register_routes();
51
52 // Additional hooks.
53 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ), 11 );
54 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
55 add_action( 'wp_ajax_' . self::SEARCH_POSTS_AJAX_ACTION, array( $this, 'ajax_search_posts' ) );
56 }
57
58 /**
59 * Adds a page-specific body class to the admin area.
60 *
61 * @param string $classes Existing body classes.
62 *
63 * @return string
64 */
65 public function admin_body_class( string $classes ): string {
66 if ( $this->is_page_active() ) {
67 return trim( $classes . ' wdf-login-access-react-page' );
68 }
69
70 return $classes;
71 }
72
73 /**
74 * Return the title of the page.
75 *
76 * @return string The title of the page.
77 */
78 public function get_title(): string {
79 return esc_html__( 'Login & Access', 'defender-security' );
80 }
81
82 /**
83 * Render the view page.
84 *
85 * @return void
86 */
87 public function main_view(): void {
88 $this->render( 'main' );
89 }
90
91 /**
92 * Save settings.
93 *
94 * @return Response
95 * @defender_route
96 */
97 public function save_settings(): Response {
98 return new Response(
99 true,
100 array(
101 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ),
102 'auto_close' => true,
103 )
104 );
105 }
106
107 /**
108 * Converts the current object to an array representation.
109 *
110 * @return array The array representation of the object.
111 */
112 public function to_array(): array {
113 return array();
114 }
115
116 /**
117 * Enqueues scripts and styles for this page.
118 * Only enqueues assets if the page is active.
119 */
120 public function enqueue_assets() {
121 if ( ! $this->is_page_active() ) {
122 return;
123 }
124
125 $handle = 'defender-ui-login-access';
126 wp_enqueue_media();
127
128 wp_enqueue_script(
129 $handle,
130 WP_DEFENDER_BASE_URL . 'assets/js/login-access-ui.js',
131 array( 'def-vue', 'def-manifest', 'def-core-ui', 'defender', 'wp-i18n' ),
132 DEFENDER_VERSION,
133 true
134 );
135 wp_set_script_translations( $handle, 'wpdef' );
136
137 wp_localize_script(
138 $handle,
139 'defenderUIData',
140 array_merge(
141 $this->get_shared_data(),
142 $this->data_frontend()
143 )
144 );
145
146 wp_enqueue_style(
147 $handle,
148 WP_DEFENDER_BASE_URL . 'assets/css/showcase.css',
149 array(),
150 DEFENDER_VERSION
151 );
152
153 $this->enqueue_main_assets();
154 }
155
156 /**
157 * Removes settings for all submodules.
158 */
159 public function remove_settings(): void {
160 ( new \WP_Defender\Model\Setting\Mask_Login() )->delete();
161 ( new \WP_Defender\Model\Setting\Security_Headers() )->delete();
162 ( new \WP_Defender\Model\Setting\Password_Protection() )->delete();
163 ( new \WP_Defender\Model\Setting\Password_Reset() )->delete();
164 ( new \WP_Defender\Model\Setting\Captcha() )->delete();
165 ( new \WP_Defender\Model\Setting\Strong_Password() )->delete();
166 ( new Model_Session_Protection() )->delete();
167 }
168
169 /**
170 * Delete all the data.
171 */
172 public function remove_data(): void {
173 wd_di()->get( Mask_Login_Controller::class )->remove_data();
174 // Remove data of all Password features.
175 wd_di()->get( Password_Protection_Controller::class )->remove_data();
176 wd_di()->get( Password_Reset_Controller::class )->remove_data();
177 wd_di()->get( Strong_Password_Controller::class )->remove_data();
178 wd_di()->get( Session_Protection_Controller::class )->remove_data();
179 wd_di()->get( Captcha_Controller::class )->remove_data();
180
181 global $wp_filesystem;
182 // Initialize the WP filesystem, no more using 'file-put-contents' function.
183 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
184 require_once ABSPATH . '/wp-admin/includes/file.php';
185 WP_Filesystem();
186 }
187
188 $service_geo = wd_di()->get( MaxMind_Geolocation::class );
189 $maxmind_dir = $service_geo->get_db_base_path();
190 $wp_filesystem->delete( $maxmind_dir, true );
191 $arr_deleted_files = array(
192 \WP_Defender\Behavior\Scan\Malware_Scan::MALWARE_LOG,
193 \WP_Defender\Controller\Firewall::FIREWALL_LOG,
194 wd_internal_log(),
195 \WP_Defender\Controller\Scan::SCAN_LOG,
196 \WP_Defender\Component\Password_Protection::PASSWORD_LOG,
197 \WP_Defender\Component\IP\Antibot_Global_Firewall::LOG_FILE_NAME,
198 \WP_Defender\Component\Security_Tweak::LOG_FILE_NAME,
199 // Outdated logs.
200 'defender.log',
201 );
202
203 foreach ( $arr_deleted_files as $deleted_file ) {
204 $wp_filesystem->delete( $deleted_file );
205 }
206
207 $this->handle_log_file_deletion();
208 }
209
210 /**
211 * Handle log file deletion.
212 *
213 * @since 4.7.2
214 * @return void
215 */
216 public function handle_log_file_deletion(): void {
217 if ( is_multisite() ) {
218 global $wpdb;
219
220 $offset = 0;
221 $limit = 100;
222 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
223 $wpdb->prepare(
224 "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d",
225 $offset,
226 $limit
227 ),
228 ARRAY_A
229 );
230 while ( is_array( $blogs ) && array() !== $blogs ) {
231 foreach ( $blogs as $blog ) {
232 switch_to_blog( $blog['blog_id'] );
233
234 $this->delete_log_files();
235
236 restore_current_blog();
237 }
238 $offset += $limit;
239 $blogs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
240 $wpdb->prepare(
241 "SELECT blog_id FROM {$wpdb->blogs} LIMIT %d, %d",
242 $offset,
243 $limit
244 ),
245 ARRAY_A
246 );
247 }
248 } else {
249 $this->delete_log_files();
250 }
251 }
252
253 /**
254 * Delete log files.
255 *
256 * @since 4.7.2
257 * @return void
258 */
259 private function delete_log_files(): void {
260 global $wp_filesystem;
261
262 // Initialize the WP filesystem, no more using 'file-put-contents' function.
263 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
264 require_once ABSPATH . '/wp-admin/includes/file.php';
265 WP_Filesystem();
266 }
267
268 $upload_dir = wp_upload_dir();
269 $upload_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'wp-defender';
270
271 if ( is_dir( $upload_path ) ) {
272 $files = glob( $upload_path . '/*.log' );
273
274 foreach ( $files as $file ) {
275 if ( $wp_filesystem->is_file( $file ) ) {
276 $wp_filesystem->delete( $file );
277 }
278 }
279 }
280 }
281
282 /**
283 * Provides data for the frontend.
284 *
285 * @return array An array of data for the frontend.
286 */
287 public function data_frontend(): array {
288
289 return array(
290 'sessionProtection' => wd_di()->get( Session_Protection_Controller::class )->data_frontend(),
291 'twoFactorAuth' => wd_di()->get( Two_Factor::class )->data_frontend(),
292 'pwnedPasswords' => wd_di()->get( Password_Protection_Controller::class )->data_frontend(),
293 'strongPassword' => wd_di()->get( Strong_Password_Controller::class )->data_frontend(),
294 'passwordReset' => wd_di()->get( Password_Reset_Controller::class )->data_frontend(),
295 'maskLogin' => wd_di()->get( Mask_Login_Controller::class )->data_frontend(),
296 'botProtection' => wd_di()->get( Captcha_Controller::class )->data_frontend(),
297 'loginAccessAjax' => array(
298 'searchPostsAction' => self::SEARCH_POSTS_AJAX_ACTION,
299 'searchPostsNonce' => wp_create_nonce( self::SEARCH_POSTS_AJAX_ACTION ),
300 ),
301 'loginAccessCopy' => array(
302 'hideLoginUrl' => array(
303 'title' => __( 'Hide Login URL', 'defender-security' ),
304 'intro' => __(
305 'Change the default WordPress login URL to make it harder for bots to find, while keeping access simple for your users.',
306 'defender-security'
307 ),
308 'slugTitle' => __( 'New login URL slug', 'defender-security' ),
309 'slugTooltip' => __(
310 'Set a unique URL slug to replace your default login endpoints.',
311 'defender-security'
312 ),
313 'slugPlaceholder' => __( 'Custom URL', 'defender-security' ),
314 'slugExample' => __( "E.g. 'my-secret-login'.", 'defender-security' ),
315 'slugAvoid' => __(
316 'Avoid using: wp-admin, wp-login.php, wp-login, login, dashboard, admin.',
317 'defender-security'
318 ),
319 'slugConflictStrong' => __( 'URL already exist.', 'defender-security' ),
320 'slugConflictRest' => __( 'Please enter a different slug.', 'defender-security' ),
321 'trafficTitle' => __( 'Redirect traffic', 'defender-security' ),
322 'trafficTooltip' => __(
323 'Choose where visitors should be redirected when they try to access the default WordPress login URLs.',
324 'defender-security'
325 ),
326 'options' => array(
327 'off' => __( 'Off', 'defender-security' ),
328 'page' => __( 'Page', 'defender-security' ),
329 'url' => __( 'Custom URL', 'defender-security' ),
330 ),
331 'redirectToUrl' => __( 'Redirect to URL', 'defender-security' ),
332 'redirectUrlLabel' => __( 'Redirection URL', 'defender-security' ),
333 'redirectToPageId' => __( 'Redirect to page/post ID', 'defender-security' ),
334 'redirectUrlHelp' => __(
335 'Visitors accessing default login URLs will be redirected to the custom URL defined above. E.g 404-error',
336 'defender-security'
337 ),
338 'pageSearchLabel' => __( 'Type post / Page title', 'defender-security' ),
339 'pageSearchEmpty' => __( 'No matching pages or posts found.', 'defender-security' ),
340 'offDescription' => __(
341 'Redirect visitors and bots attempting to access default WordPress login URLs to an alternative URL, preventing 404 errors.',
342 'defender-security'
343 ),
344 'ariaClearPage' => __( 'Clear selected page', 'defender-security' ),
345 'ariaSelectPage' => __( 'Select page', 'defender-security' ),
346 ),
347 ),
348 );
349 }
350
351 /**
352 * Ajax endpoint for searching posts/pages by title.
353 *
354 * @return void
355 */
356 public function ajax_search_posts(): void {
357 check_ajax_referer( self::SEARCH_POSTS_AJAX_ACTION );
358
359 if ( ! current_user_can( 'manage_options' ) ) {
360 wp_send_json_error(
361 array(
362 'message' => esc_html__( 'Permission denied.', 'defender-security' ),
363 ),
364 403
365 );
366 }
367
368 $per_page = isset( $_POST['per_page'] ) ? absint( wp_unslash( $_POST['per_page'] ) ) : 10;
369 $search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
370 $per_page = max( 1, min( 50, $per_page ) );
371
372 add_filter( 'posts_where', array( $this, 'posts_where_title' ), 10, 2 );
373 $post_query = new \WP_Query(
374 array(
375 'post_type' => array( 'page', 'post' ),
376 'posts_per_page' => $per_page,
377 'search_by_post_title' => $search,
378 'post_status' => 'publish',
379 'orderby' => 'title',
380 'order' => 'ASC',
381 )
382 );
383 remove_filter( 'posts_where', array( $this, 'posts_where_title' ), 10 );
384
385 $data = array();
386 foreach ( $post_query->posts as $post ) {
387 $data[] = array(
388 'id' => $post->ID,
389 'name' => $post->post_title,
390 'url' => get_permalink( $post->ID ),
391 );
392 }
393
394 wp_send_json_success( $data );
395 }
396
397 /**
398 * Filter posts query by title.
399 *
400 * @param string $where Existing where clause.
401 * @param \WP_Query $wp_query Query object.
402 *
403 * @return string
404 */
405 public function posts_where_title( string $where, \WP_Query $wp_query ): string {
406 global $wpdb;
407
408 $search_term = $wp_query->get( 'search_by_post_title' );
409 if ( is_string( $search_term ) && '' !== trim( $search_term ) ) {
410 $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( $wpdb->esc_like( $search_term ) ) . '%\'';
411 }
412
413 return $where;
414 }
415
416 /**
417 * Imports data into the model.
418 *
419 * @param array $data Data to be imported into the model.
420 */
421 public function import_data( array $data ) {}
422
423 /**
424 * Exports strings.
425 *
426 * @return array An array of strings.
427 */
428 public function export_strings(): array {
429 return array();
430 }
431 }
432