File "cerber-admin.php"

Full Path: /home/concvitk/public_html/wp-content/plugins/wp-cerber/admin/cerber-admin.php
File size: 44.9 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/*
	Copyright (C) 2015-22 CERBER TECH INC., https://cerber.tech
	Copyright (C) 2015-22 Markov Gregory, https://wpcerber.com

    Licenced under the GNU GPL.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

/*

*========================================================================*
|                                                                        |
|	       ATTENTION!  Do not change or edit this file!                  |
|                                                                        |
*========================================================================*

*/

const UIS_LOADER_HTML = '<div class="uis_loader_wrapper"><div class="uis_page_loader"></div></div>';

add_action( 'admin_init', function () {

    CRB_Globals::$assets_url  = cerber_plugin_dir_url() . 'assets/';
	CRB_Globals::$ajax_loader = CRB_Globals::$assets_url . 'ajax-loader.gif';

	if ( cerber_is_wp_ajax() ) {
		return;
	}

	cerber_phpinfo();
	cerber_admin_init();
	cerber_export();
	cerber_import();
	cerber_delete_alert();

	// @since 8.8.2.3 Workaround: if scheduled (cron) tasks are not executed on the website
	$last = get_site_transient( 'cerber_daily_1' );
	if ( ! $last
	     || ! is_array( $last )
	     || $last[0] < ( time() - DAY_IN_SECONDS ) ) {
		cerber_bg_task_add( 'cerber_do_hourly_2' );
		cerber_bg_task_add( 'cerber_daily_run' );
	}

} );

function cerber_assets_dir() {
	return cerber_plugin_dir() . '/assets';
}

// Scan dashboard ===========================================================

function cerber_scanner_show_dashboard( $msg = '', $status = 0 ) {

	$loader = ( $status ) ? UIS_LOADER_HTML : '';
	$stats = cerber_get_stats_html();
	$stats = array_shift( $stats );
	$note = ( $status ) ? '' : __( 'It seems this website has never been scanned. To start scanning click the button below.', 'wp-cerber' );

	?>
    <div id="crb-scan-display">
        <div id="crb-the-table">
            <div class="crb-scan-info scan-tile">
                <table>
                    <tr>
                        <td><?php _e( 'Started', 'wp-cerber' ); ?></td>
                        <td id="crb-started" data-init="-">-</td>
                    </tr>
                    <tr>
                        <td><?php _e( 'Finished', 'wp-cerber' ); ?></td>
                        <td id="crb-finished" data-init="-">-</td>
                    </tr>
                    <tr>
                        <td><?php _e( 'Duration', 'wp-cerber' ); ?></td>
                        <td id="crb-duration" data-init="-">-</td>
                    </tr>
                    <tr>
                        <td><?php _e( 'Performance', 'wp-cerber' ); ?></td>
                        <td id="crb-performance" data-init="-">-</td>
                    </tr>
                    <tr>
                        <td>Mode</td>
                        <td id="crb-smode" data-init="-">-</td>
                    </tr>
                </table>
            </div>
            <div id="crb-scan-filter" class="crb-scan-info scan-tile">
                <?php echo $stats; ?>
            </div>
            <div class="scan-tile">
                <div><p><span id="crb-scanned-files" data-init="0">0</span> / <span id="crb-total-files"
                                                                                    data-init="0">0</span>
                    </p>
                    <p><?php echo __( 'Scanned', 'wp-cerber' ) . ' / ' . __( 'Files to scan', 'wp-cerber' ); ?></p>
                </div>
            </div>

            <div class="scan-tile">
                <div><p><span id="crb-critical" data-init="0">0</span> / <span id="crb-warning" data-init="0">0</span>
                    </p>
                    <p><?php _e( 'Critical issues', 'wp-cerber' ); ?> / <?php _e( 'Issues total', 'wp-cerber' ); ?></p>
                </div>
            </div>

        </div>

        <div id="crb-scan-progress">
            <div>
                <div id="the-scan-bar"></div>
            </div>
        </div>

        <div id="crb-scan-message"><?php echo $msg; ?></div>

    </div>
    <div id="crb-scan-details">

        <?php

        if ( $note ) {
	        echo '<div id="crb-scan-note">' . $note . '</div>';
        }

        ?>

        <table class="crb-table" id="crb-browse-files">
			<?php
			$rows = array();
			$rows[] = '<tr class="crb-scan-container" id="crb-wordpress" style=""><td colspan="6">WordPress</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-muplugins" style=""><td colspan="6">Must use plugins</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-dropins" style=""><td colspan="6">Drop-ins</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-plugins" style=""><td colspan="6">Plugins</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-themes" style=""><td colspan="6">Themes</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-uploads" style=""><td colspan="6">Uploads folder</td></tr>';
			$rows[] = '<tr class="crb-scan-container" id="crb-unattended" style=""><td colspan="6">Unattended files</td></tr>';
			echo implode( "\n", $rows );
			?>
        </table>
        <?php echo $loader; ?>
    </div>

	<?php

	cerber_ref_upload_form();
}

function cerber_show_scanner() {
	// http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html

	$msg = '';
	$status = 0;

	if ( $scan = cerber_get_scan() ) {
		if ( ! $scan['finished'] ) {
			if ( $scan['cloud']
			     && cerber_is_cloud_enabled()
			     && $scan['started'] > ( time() - 900 )
			) {
				$msg = __( 'Currently a scheduled scan in progress. Please wait until it is finished.', 'wp-cerber' );
				$status = 1;
			}
			else {
				$msg = sprintf( __( 'Previous scan started %s has not been completed. Continue scanning?', 'wp-cerber' ), cerber_date( $scan['started'], false ) );
				$status = 2;
			}
		}
		else {
			$status = 3;
		}
	}

	$start_quick = '<input data-control="start_scan" data-mode="quick" type="button" value="' . __( 'Start Quick Scan', 'wp-cerber' ) . '" class="button button-primary">';
	$start_full = '<input data-control="start_scan" data-mode="full" type="button" value="' . __( 'Start Full Scan', 'wp-cerber' ) . '" class="button button-primary">';
	$stop = '<input id="crb-stop-scan" style="display: none;" data-control="stop_scan" type="button" value="' . __( 'Stop Scanning', 'wp-cerber' ) . '" class="button button-primary">';
	$continue = '<input id="crb-continue-scan" data-control="continue_scan" type="button" value="' . __( 'Continue Scanning', 'wp-cerber' ) . '" class="button button-primary">';
	$controls = '';

	switch ( $status ) {
		case 0:
		case 3:
			$controls = $start_quick . $start_full;
			break;
		case 1:
			$controls = '';
			break;
		case 2:
			$controls = $start_quick . $start_full . $continue;
			break;
	}

	$controls .= $stop;

	$class = ( $status ) ? '' : 'crb-scanner-has-data';

	echo '<div id="crb-scanner" class="' . $class . '">';

	cerber_scanner_show_dashboard( $msg, $status );

	$d = '';
	if ( nexus_is_valid_request() && ! nexus_is_granted( 'submit' ) ) {
		$d = 'disabled="disabled"';
	}

	?>
    <div id="crb-scan-area">
        <form>
            <table id="crb-scan-controls">
                <tr>
                    <td id="crb-file-controls">
                        <input data-control="delete_file" type="button"
                               class="button button-secondary"
							<?php echo $d; ?>
                               value="<?php _e( 'Delete', 'wp-cerber' ); ?>"/>
                        <input data-control="ignore_add_file" type="button" class="button button-secondary"
							<?php echo $d; ?>
                               value="<?php _e( 'Ignore', 'wp-cerber' ); ?>"/>
                    </td>
                    <td>
						<?php echo $controls; ?>
                    </td>
                    <!-- <td><a href="#" data-control="full-paths">Show full paths</a></td> -->
                    <td><a href="#" class="dashicons dashicons-list-view" data-control="full-paths"
                           title="Toggle full/relative paths"></a></td>
                </tr>
            </table>
        </form>
    </div>

	<?php

	echo '</div>';
}

function cerber_ref_upload_form() {
	?>
    <div id="crb-ref-upload-dialog" style="display: none;">
        <p><?php _e( 'We have not found any integrity data to verify', 'wp-cerber' ); ?> <span
                    id="ref-section-name"></span>.</p>
        <p><?php _e( "You have to upload a ZIP archive from which you've installed it. This enables the security scanner to verify the integrity of the code and detect malware.", 'wp-cerber' ); ?></p>
        <p><?php echo sprintf( __( 'Maximum upload file size: %s.' ), esc_html( size_format( wp_max_upload_size() ) ) ); ?></p>
        <form enctype="multipart/form-data">
            <input type="file" name="refile" id="refile" required="required" accept=".zip">
            <input type="submit" name="submit" value="<?php _e( 'Upload file', 'wp-cerber' ); ?>"
                   class="button button-primary">
            <ul style="list-style: none;">
                <li style="display:none;" class="crb-status-msg">Uploading the file, please wait&#8230;</li>
                <li style="display:none;" class="crb-status-msg">Processing the file, please wait&#8230;</li>
            </ul>
        </form>
    </div>

	<?php
}

add_action( 'wp_ajax_cerber_scan_control', 'cerber_manual_scan' );
function cerber_manual_scan() {

	cerber_check_ajax_permissions();

	ob_start(); // Collecting possible junk warnings and notices cause we need clean JSON to be sent

	$scanner = array();
	$console_log = array();
	$scan_do = '';

	if ( cerber_is_http_post() && $scan_do = crb_get_post_fields( 'cerber_scan_do' ) ) {
		$scan_do = preg_replace( '/[^a-z_\-\d]/i', '', $scan_do );
		$mode = ( $mode = crb_get_post_fields( 'cerber_scan_mode' ) ) ? preg_replace( '/[^a-z_\-\d]/i', '', $mode ) : 'quick';

		$scanner = cerber_scanner( $scan_do, $mode );

	}
	else {
		$console_log[] = 'Unknown HTTP request';
	}

	$next_do = ( ! empty( $scanner['cerber_scan_do'] ) ) ? $scanner['cerber_scan_do'] : 'stop';

	$console_log = array_merge( $console_log, cerber_db_get_errors() );

	$console_log[] = 'PHP MEMORY ' . @ini_get( 'memory_limit' );

	$ret = array(
		'console_log'    => $console_log,
		'cerber_scan_do' => $next_do,
		'cerber_scanner' => $scanner,
		//'scan'           => cerber_get_scan(), // debug only
	);

	if ( $scan_do != 'continue_scan' ) {
		$ret['strings'] = cerber_get_strings();
	}

	ob_end_clean();

	echo json_encode( $ret );

	crb_admin_stop_ajax();
}

/**
 * File viewer, server side AJAX
 *
 */
add_action( 'wp_ajax_cerber_view_file', function () {

	cerber_check_ajax_permissions();

	$get = crb_get_query_params();

	if ( ! $file_name = $get['file'] ) {
		crb_admin_stop_ajax( 'Error: Filename not specified. Please run a new malware scan.' );
	}

	if ( ! @file_exists( $file_name ) ) {
		crb_admin_stop_ajax( 'Error: The requested file doesn\'t exist or has been deleted: ' . htmlspecialchars( $file_name ). '. Please run a new malware scan.' );

		return;
	}

	if ( ! @is_file( $file_name ) ) {
		crb_admin_stop_ajax( 'Error: The requested file is not an ordinary file: ' . htmlspecialchars( $file_name ) );

		return;
	}

	$file_size = filesize( $file_name );

	if ( $file_size > 8000000 ) {
		crb_admin_stop_ajax( 'Error: The requested is too large to display: ' . crb_size_format( $file_size ) );

		return;
	}

	if ( $file_size <= 0 ) {
		crb_admin_stop_ajax( 'The requested file is empty.' );

		return;
	}

	$scan_id = absint( $get['scan_id'] );

	$the_file = cerber_db_get_row( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_name = "' . $file_name . '"' );

	if ( ! $the_file ) {
		crb_admin_stop_ajax( __( 'File access error. Possibly scan results are outdated. Please run Quick or Full Scan.', 'wp-cerber' ) );

		return;
	}

	if ( ! $source = file_get_contents( $file_name ) ) {
		crb_admin_stop_ajax( 'Error: Unable to load file.' );

		return;
	}

	$source = htmlspecialchars( $source, ENT_SUBSTITUTE );

	if ( ! $source ) {
		$source = 'Unable to display the contents of the file. This file contains non-printable characters.';
	}

	if ( cerber_detect_exec_extension( $file_name )
	     || cerber_check_extension( $file_name, array( 'js', 'css', 'inc' ) )
	     || cerber_is_htaccess( $file_name )
	) {
		$paint = true;
	}
	else {
		$paint = false;
	}

	$overlay = '';
	if ( $paint ) {
		$overlay = '<div id="crb-overlay">Loading, please wait...</div>';
	}

	//$sh_url   = plugin_dir_url( __FILE__ ) . 'assets/sh/';
	$sh_url = CRB_Globals::$assets_url . 'sh/';
	$sheight = absint( $get['sheight'] ) - 100; // highlighter is un-responsible, so we need tell him the real height

	?>
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script type="text/javascript" src="<?php echo $sh_url ?>scripts/shCore.js"></script>
        <script type="text/javascript" src="<?php echo $sh_url; ?>scripts/shBrushPhp.js"></script>
        <link href="<?php echo $sh_url; ?>styles/shCore.css" rel="stylesheet" type="text/css"/>
        <link href="<?php echo $sh_url; ?>styles/shThemeDefault.css" rel="stylesheet" type="text/css"/>
        <style>
            body {
                overflow: hidden;
                font-family: 'Roboto', sans-serif;
                font-size: 14px;
            }

            #crb-overlay {
                display: flex;
                justify-content: center;
                align-items: center;
                text-align: center;
                background-color: #fff;
                position: fixed;
                width: 100%;
                height: 100%
                z-index: 2;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
            }

            #crb-issue {
                border-left: 3px solid crimson;
                background-color: #eee;
                padding: 1em;
                overflow: auto;
            }

            #crb-file-content {
            <?php
            if (!$paint) {
                echo '
                max-height: '.$sheight .'px;
                overflow: auto;
                padding: 15px;
                ';
            }
            else {
                echo 'overflow: hidden;';
            }
            ?>
            }

            .syntaxhighlighter {
                max-height: <?php echo $sheight; ?>px;
            }

            .syntaxhighlighter code {
                font-family: Menlo, Consolas, Monaco, monospace !important;
                font-size: 13px !important;
            }

            .syntaxhighlighter .gutter .line {
                border-right: 3px solid #c7c7c7 !important;
            }

        </style>
    </head>

    <body>

	<?php

	echo $overlay;

	echo '<pre id="crb-file-content" class="brush: php; toolbar: false;">' . $source . '</pre>';

	if ( $the_file ) {
		echo '<div id="crb-issue">Issue: ' . cerber_get_issue_label( $the_file['scan_status'] ) . '</div>';
	}

	if ( $paint ) :
		?>

        <script type="text/javascript">

            SyntaxHighlighter.defaults["highlight"];
            SyntaxHighlighter.all();

            function crb_waitUntilRender() {
                let overlay = document.getElementById("crb-overlay").style.visibility = "hidden";
            }

            let intervalID = setInterval(crb_waitUntilRender, 200);

        </script>

	<?php

	endif;

	?>

    </body>
    </html>

	<?php

	crb_admin_stop_ajax();

} );

/**
 * Upload a reference ZIP archive for a theme or a plugin
 *
 */
add_action( 'wp_ajax_cerber_ref_upload', function () {

	cerber_check_ajax_permissions();

	//ob_start(); // Collecting possible junk warnings and notices cause we need clean JSON to be sent

	$error = '';

	$folder = cerber_get_tmp_file_folder();
	if ( crb_is_wp_error( $folder ) ) {
		cerber_end_ajax( array( 'error' => $folder->get_error_message() ) );
	}

	if ( isset( $_FILES['refile'] ) ) {

		// Step 1, saving file

		if ( ! is_uploaded_file( $_FILES['refile']['tmp_name'] ) ) {
			$error = 'Unable to read uploaded file';
		}

		if ( ! cerber_check_extension( $_FILES['refile']['name'], array( 'zip' ) ) ) {
			$error = __( 'This type of file is not supported. Please upload a ZIP archive.', 'wp-cerber' );
		}

		if ( cerber_detect_exec_extension( $_FILES['refile']['name'] ) ) {
			$error = __( 'Executable files are not supported. Please upload a ZIP archive.', 'wp-cerber' );
		}

		if ( false !== strpos( $_FILES['refile']['name'], '/' ) ) {
			$error = 'Incorrect filename';
		}

		if ( $error ) {
			cerber_end_ajax( array( 'error' => $error ) );
		}

		if ( false === @move_uploaded_file( $_FILES['refile']['tmp_name'], $folder . $_FILES['refile']['name'] ) ) {
			cerber_end_ajax( array( 'error' => 'Unable to copy file to ' . $folder ) );
		}

	}
	else {

		// Step 2, creating hash

		$result = cerber_need_for_hash();
		if ( crb_is_wp_error( $result ) ) {
			cerber_end_ajax( array( 'error' => $result->get_error_message() ) );
		}
	}

	cerber_end_ajax();

} );

/**
 * Deleting files, server side AJAX
 *
 */
add_action( 'wp_ajax_cerber_scan_bulk_files', function () {

	cerber_check_ajax_permissions();

	$post = crb_get_post_fields();

	if ( empty( $post['files'] ) || empty( $post['scan_id'] ) ) {
		crb_admin_stop_ajax( 'Error!' );

		return;
	}

	$scan_id = absint( $post['scan_id'] );

	if ( ! cerber_get_scan( $scan_id ) ) {
		crb_admin_stop_ajax( 'Error!' );

		return;
	}

	$operation = $post['scan_file_operation'];

	if ( ( ! $ignore = cerber_get_set( 'ignore-list' ) ) || ! is_array( $ignore ) ) {
		$ignore = array();
	}

	global $crb_list;
	$crb_list = array();
	$i = 0;
	$errors = array();
	$time = time();
	$user_id = get_current_user_id();

	foreach ( $post['files'] as $file_name ) {

		if ( ! is_file( $file_name ) ) {
			continue;
		}

		$the_file = cerber_db_get_row( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_name = "' . $file_name . '"', MYSQL_FETCH_OBJECT );
		if ( ! $the_file || ! is_file( $the_file->file_name ) ) {
			$errors[] = 'Unknown file: ' . $file_name;
			continue;
		}

		switch ( $operation ) {
			case 'delete_file':
				$result = cerber_quarantine_file( $file_name, $scan_id );
				break;
			case 'ignore_add_file':
				$ignore[ $the_file->file_name_hash ] = array(
					$the_file->file_name,
					@hash_file( 'sha256', $the_file->file_name ),
					$user_id,
					$time,
				);
				$result = true;
				break;
		}

		if ( crb_is_wp_error( $result ) ) {
			$errors[] = $result->get_error_message();
		}
        elseif ( ! $result ) {
			$errors[] = 'Unknown error 55';
		}
		else {
			$i ++;
			$crb_list[] = $file_name;
		}

	}

	if ( $operation == 'ignore_add_file' ) {
		// Update the last scan results to keep it up to date and avoid user confusing
		if ( $scan = cerber_get_scan() ) {

		    crb_file_filter( $scan['issues'], function ( $file_name ) {
				global $crb_list;
				if ( in_array( $file_name, $crb_list ) ) {
					return false;
				}

				return true;
			} );

			cerber_update_scan( $scan );
		}
		if ( ! cerber_update_set( 'ignore-list', $ignore ) ) {
			$errors [] = 'Unable to update the ignore list';
		}
	}

	crb_scan_debug( $errors );

	cerber_end_ajax( array( 'errors' => $errors, 'number' => $i, 'processed' => $crb_list ) );

} );

/**
 * Finalizes current AJAX request and sends data to the client
 *
 * @param $data array
 */
function cerber_end_ajax( $data = array() ) {

	if ( ! $data ) {
		$data = array();
	}

	$data['cerber_db_errors'] = cerber_db_get_errors();

	if ( ! $data['cerber_db_errors'] ) {
		$data['OK'] = 'OK!';
	}

	echo json_encode( $data );

	if ( ! nexus_is_valid_request() ) {
		wp_die();
	}
}

function crb_admin_stop_ajax( $msg = '' ) {
	if ( $msg ) {
		echo $msg;
	}
	if ( ! nexus_is_valid_request() ) {
		exit;
	}
}

function cerber_show_quarantine() {

	$folder = cerber_get_the_folder( true );
	if ( crb_is_wp_error( $folder ) ) {
		echo $folder->get_error_message();

		return;
	}

	$no_files = '<p>' . __( 'There are no files in the quarantine at the moment.', 'wp-cerber' ) . '</p>';
	$per_page = crb_admin_get_per_page();
	$first = ( cerber_get_pn() - 1 ) * $per_page;
	$last = $first + $per_page;
	$list = array();

	$filter_scan = crb_get_query_params( 'scan', '\d+' );

	list ( $list, $count, $scan_list ) = cerber_quarantine_get_files( $first, $last, $filter_scan );

	_crb_qr_total_sync( $count );

	if ( ! $list ) {
		if ( ! $filter_scan ) {
			echo $no_files;
		}
		else {
			echo __( 'No files match the specified filter.', 'wp-cerber' ) . ' <a href="' . cerber_admin_link( 'scan_quarantine' ) . '">' . __( 'Click here to see the full list of files', 'wp-cerber' ) . '</a>.';
		}

		return;
	}

	$rows = array();
	$ofs = get_option( 'gmt_offset' ) * 3600;
	$confirm = ' onclick="return confirm(\'' . __( 'Are you sure?', 'wp-cerber' ) . '\');"';

	foreach ( $list as $file ) {
		$p = array(
			'cerber_admin_do' => 'scan_tegrity',
			'crb_scan_id'     => $file['scan_id'],
			'crb_file_id'     => $file['qfile']
		);

		$p['crb_scan_adm'] = 'delete';
		$delete = '<a ' . $confirm . ' href="' . cerber_admin_link_add( $p ) . '">' . __( 'Delete permanently', 'wp-cerber' ) . '</a>';

		$p['crb_scan_adm'] = 'restore';
		$restore = ( ! $file['can'] ) ? '' : ' | <a ' . $confirm . ' href="' . cerber_admin_link_add( $p ) . '">' . __( 'Restore', 'wp-cerber' ) . '</a>';

		$moved = strtotime( $file['date'] ) - $ofs;
		$will = cerber_auto_date( $file['scan_id'] + DAY_IN_SECONDS * crb_get_settings( 'scan_qcleanup' ) );

		$file_name = str_replace( DIRECTORY_SEPARATOR, '<wbr>' . DIRECTORY_SEPARATOR, $file['source'] );

		$rows[] = array(
			'<span title="' . cerber_date( $file['scan_id'] ) . '">' . cerber_auto_date( $file['scan_id'] ) . '</span>',
			'<span title="' . cerber_date( $moved ) . '">' . cerber_auto_date( $moved ) . '</span>',
			$will,
			$file['size'],
			$file_name,
			'<span style="white-space: pre;">' . $delete . $restore . '</span>'
		);
	}

	$heading = array(
		__( 'Scanned', 'wp-cerber' ),
		__( 'Quarantined', 'wp-cerber' ),
		__( 'Automatic deletion', 'wp-cerber' ),
		__( 'Size', 'wp-cerber' ),
		__( 'File', 'wp-cerber' ),
		__( 'Action', 'wp-cerber' ),
	);

	$table = cerber_make_table( $rows, $heading, 'crb-quarantine' );

	$table .= cerber_page_navi( $count, $per_page );

	$filter = '';
	if ( count( $scan_list ) > 1 ) {
		krsort( $scan_list );
		$list = array( 0 => __( 'All scans', 'wp-cerber' ) );
		foreach ( $scan_list as $s ) {
			$list[ $s ] = cerber_date( $s, false );
		}
		$filter = '<div style="text-align: right; margin-bottom: 1em;"><form style="width: auto;" action="">' . cerber_select( 'scan', $list, $filter_scan ) . ' <input value="Filter" class="button" type="submit"><input name="page" value="cerber-integrity" type="hidden"><input name="tab" value="scan_quarantine" type="hidden"></form></div>';
	}

	echo $filter . $table;
}

function cerber_quarantine_do( $what, $scan_id, $qfile ) {
	$scan_id = absint( $scan_id );
	if ( ! $scan_id ) {
		cerber_admin_notice( 'Error: Wrong scan parameters.' );

		return;
	}
	//$dir = cerber_get_the_folder() . 'quarantine' . DIRECTORY_SEPARATOR . $scan_id;
	$dir = cerber_get_the_folder( true );
	if ( crb_is_wp_error( $dir ) ) {
		cerber_admin_notice( $dir->get_error_message() );

		return;
	}

	$dir .= 'quarantine' . DIRECTORY_SEPARATOR . $scan_id;

	$file = $dir . DIRECTORY_SEPARATOR . $qfile;
	if ( ! @is_file( $file ) || is_link( $file ) ) {
		cerber_admin_notice( 'Error: No file to process' );

		return;
	}

	$rst = $dir . '/.restore';
	if ( ! file_exists( $rst ) || ! $handle = @fopen( $rst, 'r' ) ) {
		cerber_admin_notice( 'Error: A restore registry file is corrupt or missing.' );

		return;
	}

	$data = null;
	while ( ( $line = fgets( $handle ) ) !== false ) {
		if ( $p = crb_parse_qline( $dir, $line ) ) {
			if ( $p['qfile'] == $qfile ) {
				$data = $p;
				break;
			}
		}
	}

	if ( ! $data ) {
		cerber_admin_notice( 'Error: No information about this file. Unable to proceed.' );

		return;
	}

	$err = null;
	$msg = null;
	switch ( $what ) {
		case 'delete':
			if ( unlink( $file ) ) {
				$msg = __( 'The file has been deleted permanently.', 'wp-cerber' );
				crb_qr_total_update( -1 );
			}
			else {
				$err = 'Unable to delete the file: ' . $file;
			}
			break;
		case 'restore':
			if ( $data['can'] ) {
				$target_dir = dirname( $data['source'] );
				if ( ! file_exists( $target_dir ) && ! mkdir( $target_dir, 0755, true ) ) {
					$err = 'Unable to create the folder <b>' . $target_dir . '</b>. Check permissions of parent folders.';
				}
				if ( ! $err ) {
					if ( @rename( $file, $data['source'] ) ) {
						$msg = __( 'The file has been restored to its original location.', 'wp-cerber' );
						crb_qr_total_update( -1 );
					}
					else {
						$err = 'A file error occurred while restoring the file. Check permissions of folders.';
					}
				}
			}
			else {
				$err = 'This file cannot be restored and needs to be manually copied. <p>See instructions in this file: ' . $rst . '</p>';
			}
			break;
	}
	if ( $err ) {
		cerber_admin_notice( __( 'ERROR:', 'wp-cerber' ) . ' ' . $err );
	}
	if ( $msg ) {
		cerber_admin_message( $msg );
	}
}

function cerber_show_ignore() {

	// For translators
	__( 'Apply', 'wp-cerber' );
	__( 'Remove from the list', 'wp-cerber' );
	__( 'User Insights', 'wp-cerber' );
	__( 'Traffic Insights', 'wp-cerber' );
	__( 'Activity Insights', 'wp-cerber' );

	$no_files = __( 'The list is empty.', 'wp-cerber' );
	$per_page = crb_admin_get_per_page();
	$first = ( cerber_get_pn() - 1 ) * $per_page;

	if ( ! $list = cerber_get_set( 'ignore-list' ) ) {
		echo '<p>' . $no_files . '</p>';

		return;
	}

	$count = count( $list );
	$list = array_slice( $list, $first, $per_page );

	$rows = array();
	$confirm = ' onclick="return confirm(\'' . __( 'Are you sure?', 'wp-cerber' ) . '\');"';

	foreach ( $list as $key => $file ) {

		$delete = '<a ' . $confirm . ' href="' . cerber_admin_link_add( array(
				'cerber_admin_do' => 'scan_tegrity',
				'crb_scan_adm'    => 'remove_ignore',
				'crb_file_id'     => $key
			) ) . '">' . __( 'Remove from the list', 'wp-cerber' ) . '</a>';

		$rows[] = array(
			cerber_date( $file[3] ),
			cerber_date( cerber_get_date( $file[0] ) ),
			crb_size_format( cerber_get_size( $file[0] ) ),
			$file[0],
			'<span style="white-space: pre;">' . $delete . '</span>'
		);
	}

	$heading = array(
		__( 'Added', 'wp-cerber' ),
		__( 'Modified', 'wp-cerber' ),
		__( 'Size', 'wp-cerber' ),
		__( 'File', 'wp-cerber' ),
		__( 'Action', 'wp-cerber' ),
	);

	$table = cerber_make_table( $rows, $heading );

	$table .= cerber_page_navi( $count, $per_page );

	echo $table;
}

function crb_remove_ignore( $id ) {
	if ( ! $list = cerber_get_set( 'ignore-list' ) ) {
		return false;
	}
	if ( ! isset( $list[ $id ] ) ) {
		return false;
	}

	unset( $list[ $id ] );

	return cerber_update_set( 'ignore-list', $list );

}

// Scan analytics ===========================================================

function cerber_scan_insights() {

	if ( ! $scan_id = crb_scan_last_full() ) {
		echo 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.';

		return;
	}

	if ( $ext = crb_get_query_params( 'fext' ) ) {
		cerber_show_files( $ext, $scan_id );

		return;
	}

	?>

    <div id="crb_scan_insights">
        <div class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="1">
        </div>
        <div class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="2">
        </div>
        <div id="crb_ins_ext_list" class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="3">
        </div>
    </div>

	<?php

}

/**
 * @param string $ext
 * @param int $scan_id
 */
function cerber_show_files( $ext, $scan_id ) {

	$ext = cerber_real_escape( $ext );
	$per_page = 25;
	$limit = cerber_get_sql_limit( $per_page );
	$files = cerber_db_get_results( 'SELECT SQL_CALC_FOUND_ROWS file_name, file_mtime, file_size FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_ext = "' . $ext . '" ' . $limit );
	$total = cerber_db_get_var( 'SELECT FOUND_ROWS()' );

	if ( ! $files ) {
		echo '<p>No files found. Please run the Full Scan.</p>';

		return;
	}

	$title = ( $ext == '.' ) ? __( 'Files without extension', 'wp-cerber' ) : 'Files with the "' . $ext . '" extension';
	echo '<div style="display: table-cell"><h3>' . $title . '</h3></div><div style="display: table-cell; vertical-align: middle;">  &nbsp; <a href="' . cerber_admin_link( 'scan_insights' ) . '#crb_ins_ext_list" class="page-title-action">' . __( 'Back to list', 'wp-cerber' ) . '</a></div>';

	echo crb_make_file_table( $files ) . cerber_page_navi( $total, $per_page );
}

/**
 * @param string $type
 *
 * @return string[]
 *
 * @since 8.6.4
 */
function cerber_generate_insights( $type ) {

	if ( ! $scan_id = crb_scan_last_full() ) {
		return array( 'html' => __( 'No data for generating reports. Please run the Full Scan. After the scan is completed, the reports will be generated.', 'wp-cerber' ) );
	}

	$key = 'scan_insights_' . $type;

	// Cache
	if ( $report = cerber_get_set( $key, $scan_id, false ) ) {
		return array( 'html' => $report );
	}

	switch ( $type ) {
		case 1:
			$report = crb_scan_insights_brief( $scan_id );
			break;
		case 2:
			$report = crb_scan_insights_lrgst( $scan_id );
			break;
		case 3:
			$report = crb_scan_insights_exts( $scan_id );
			break;
	}

	// Cache
	if ( $report ) {
		cerber_update_set( $key, $report, $scan_id, false, time() + 24 * 3600 );
	}
	else {
		$report = 'ERROR: Unknown report';
	}

	$response = array( 'html' => $report, 'error' => '' );

	/*if ( $_REQUEST['request'] < 2 ) {
		$response['continue'] = 1;
	}*/

	return $response;
}

function crb_scan_insights_brief( $scan_id ) {

	$scan_id = absint( $scan_id );

	$table = cerber_get_db_prefix() . CERBER_SCAN_TABLE;

	$result = '<h3>' . __( 'Brief summary', 'wp-cerber' ) . '</h3>';

	$list = array(
		array( 'WordPress root folder (ABSPATH) ', ABSPATH ),
		array( 'WordPress uploads folder', cerber_get_upload_dir() ),
		array( 'WordPress content folder', dirname( cerber_get_plugins_dir() ) ),
		array( 'WordPress plugins folder', cerber_get_plugins_dir() ),
		array( 'WordPress themes folder', cerber_get_themes_dir() ),
		array( 'WordPress must-use plugin folder (WPMU_PLUGIN_DIR) ', WPMU_PLUGIN_DIR ),
		array( 'PHP folder for uploading files', ini_get( 'upload_tmp_dir' ) ),
		array( 'Server folder for temporary files', sys_get_temp_dir() ),
		array( 'Server folder for users\' session data', session_save_path() ),
	);

	/*
	It's not scanned by the scanner
	$cerber_folder = cerber_get_my_folder();
	if ( ! crb_is_wp_error( $cerber_folder ) ) {
		$list[] = array( 'WP Cerber\'s folder', $cerber_folder );
	}*/

	$folders = array();
	foreach ( $list as $item ) {
		if ( ! $item[1] || ! @file_exists( $item[1] ) ) {
			continue;
		}
		$item[2] = 0;
		$item[3] = 0;
		$item[1] = rtrim( $item[1], DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
		if ( $files = cerber_db_get_col( 'SELECT file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' AND file_name LIKE "' . $item[1] . '%"' ) ) {
			$item[2] = count( $files );
			if ( $sum = array_sum( $files ) ) {
				$item[3] = crb_size_format( $sum );
			}
		}
		$folders[] = $item;
	}

	$sql = 'SELECT file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' AND file_name NOT LIKE "' . ABSPATH . '%"';
	$files = cerber_db_get_col( $sql );
	if ( $files ) {
		if ( $sum = array_sum( $files ) ) {
			$sum = crb_size_format( $sum );
		}
		$folders[] = array( 'Above the WordPress installation folder', '', count( $files ), $sum );
	}

	$column = array_column( $folders, 2 );
	array_multisort( $column, SORT_DESC, $folders );

	return $result . cerber_make_table( $folders, array(
			__( 'Folder', 'wp-cerber' ),
			__( 'Path', 'wp-cerber' ),
			__( 'Files', 'wp-cerber' ),
			__( 'Space Occupied', 'wp-cerber' )
		), '', 'crb_align_right crb_align_left_2', 'crb-monospace' );

}

function crb_scan_insights_exts( $scan_id ) {

	$scan_id = absint( $scan_id );

	$table = cerber_get_db_prefix() . CERBER_SCAN_TABLE;
	$list = array();

	$offset = 0;
	$total = 0;
	$done = false;
	//cerber_exec_timer( 15 );

	while ( true ) {

		if ( ! $files = cerber_db_get_results( 'SELECT file_name, file_name_hash, file_mtime, file_size, file_ext FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' LIMIT ' . CRB_SQL_CHUNK . ' OFFSET ' . $offset ) ) {
			$done = true;
			break;
		}

		$offset += CRB_SQL_CHUNK;

		foreach ( $files as $file ) {
			if ( ! $ext = crb_get_extension( $file['file_name'] ) ) {
				$ext = '.';
			}


			if ( empty( $file['file_ext'] ) ) {
				cerber_db_query( 'UPDATE ' . $table . ' SET file_ext = "' . $ext . '" WHERE scan_id = ' . $scan_id . ' AND file_name_hash = "' . $file['file_name_hash'] . '"' );
			}

			if ( ! isset( $list[ $ext ] ) ) {
				$list[ $ext ] = array( 0, 0, array(), array() );
			}

			$list[ $ext ][0] ++;
			$list[ $ext ][1] += $file['file_size'];

			$list[ $ext ][2][] = $file['file_size'];
			$list[ $ext ][3][] = $file['file_mtime'];

			$total ++;
		}

	}

	if ( ! $done && ( count( $files ) < CRB_SQL_CHUNK ) ) {
		$done = true;
	}

	if ( ! $done ) {
		return array( 'continue' => 1 );
	}

	if ( ! $list ) {
		return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.';
	}

	$base = cerber_admin_link( 'scan_insights' );
	$none = __( 'No extension', 'wp-cerber' );
	$result = array();

	foreach ( $list as $ext => $data ) {
		$text = ( $ext == '.' ) ? $none : $ext;
		$result[] = array(
			'<a href="' . $base . esc_attr( '&fext=' . $ext ) . '">' . $text . '</a>',
			$data[0],
			crb_size_format( $data[1] ),
			crb_size_format( min( $data[2] ) ),
			crb_size_format( max( $data[2] ) ),
			crb_size_format( round( $data[1] / $data[0], 0 ) ),
			cerber_date( min( $data[3] ) ),
			cerber_date( max( $data[3] ) ),
		);
	}

	// Sorting by a column in a multidimensional array

	$column = array_column( $result, 1 );
	array_multisort( $column, SORT_DESC, $result );

	// Create table

	//$report = '<h3>The file system report is based on the last full scan.</h3>';
	$report = '<h3>' . __( 'File extensions statistics', 'wp-cerber' ) . '</h3>';

	$report .= cerber_make_table( $result, array(
		__( 'Extension', 'wp-cerber' ),
		__( 'Files', 'wp-cerber' ),
		__( 'Space Occupied', 'wp-cerber' ),
		__( 'Smallest', 'wp-cerber' ),
		__( 'Largest', 'wp-cerber' ),
		__( 'Average Size', 'wp-cerber' ),
		__( 'Oldest', 'wp-cerber' ),
		__( 'Newest', 'wp-cerber' ),
	), 'crb-ext-statistics', 'crb_align_right', 'crb-monospace crb-anchor-decorated' );

	return $report;
}

function crb_scan_insights_lrgst( $scan_id ) {

	$scan_id = absint( $scan_id );

	$table = cerber_get_db_prefix() . CERBER_SCAN_TABLE;

	if ( ! $files = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' ORDER BY file_size DESC LIMIT 10' ) ) {
		return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.';
	}

	$title = '<h3>' . __( 'Top 10 largest files', 'wp-cerber' ) . '</h3>';

	return $title . crb_make_file_table( $files );
}

function crb_scan_insights_duplicates( $scan_id ) {

	$scan_id = absint( $scan_id );

	// SQL Doesn't work
	/* ( ! $files = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size, file_hash FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' GROUP BY file_hash HAVING COUNT(*) > 1 LIMIT 100' ) ) {
		return '';
	}*/

	if ( ! $files = cerber_db_get_col( 'SELECT file_hash FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_size !=0 AND file_hash !="" ' ) ) {
		return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.';
	}

	$dup = array_unique( array_diff_assoc( $files, array_unique( $files ) ) );

	rsort( $dup );

	$list = array();
	foreach ( $dup as $hash ) {
		if ( $fl = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_hash = "' . $hash . '"' ) ) {
			$list = array_merge( $list, $fl );
		}
	}

	$result = '<h3>Duplicate files</h3>';

	return $result . crb_make_file_table( $list );

}

function crb_scan_last_full() {

	$scans = cerber_db_get_col( 'SELECT DISTINCT scan_id FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_mode = 1 ORDER BY scan_id DESC LIMIT 1' );

	if ( $scans ) {
		$scan_id = $scans[0];
		$scan = cerber_get_scan( $scan_id );
		if ( $scan['finished']
		     || ( $scan['aborted'] && $scan['next_step'] > 5 ) ) { // Step 5 is enough for analysis
			return $scan_id;
		}
	}

	return false;
}

/**
 * @param int $user_id
 * @param string $tab
 * @param bool $cache_only
 *
 * @return string
 */
function crb_generate_user_insights( $user_id, $tab, $cache_only = false ) {

	if ( $tab != 'activity' ) {
		$tab = 'traffic';
	}

	$result = '';
	$links = array();

	if ( $user = get_userdata( $user_id ) ) {
		$labels = cerber_get_labels();
		$list = array();

		if ( $data = crb_q_cache_get( 'SELECT COUNT(activity) as cnt, activity FROM ' . CERBER_LOG_TABLE . ' WHERE user_id = ' . $user_id . ' GROUP BY activity', CERBER_LOG_TABLE, $cache_only ) ) {
			foreach ( $data as $item ) {
				$list[ $item[1] ] = 1;
				$links[] = array( array( 'filter_activity' => $item[1], 'filter_user' => $user_id ), crb_array_get( $labels, $item[1], 'Unknown' ) . ' - ' . $item[0] );
			}
		}

		if ( $tab == 'activity' ) {
			if ( $data = crb_q_cache_get( 'SELECT COUNT(activity) as cnt, activity FROM ' . CERBER_LOG_TABLE . ' WHERE user_login = "' . $user->user_login . '" OR user_login = "' . $user->user_email . '" GROUP BY activity', CERBER_LOG_TABLE, $cache_only ) ) {
				foreach ( $data as $item ) {
					if ( isset( $list[ $item[1] ] ) ) {
						continue;
					}
					$links[] = array( array( 'filter_activity' => $item[1], 'filter_login' => $user->user_login . '|' . $user->user_email ), crb_array_get( $labels, $item[1], 'Unknown' ) . ' - ' . $item[0] );
				}
			}
		}

		if ( $links ) {
			array_unshift( $links, array( array( 'filter_user' => $user_id ), __( 'All' ) ) );
			$result = '<div id="crb-quick-insights">' . crb_make_nav_links( $links, $tab ) . '</div>';
		}

	}

	if ( ! $result ) {
		if ( $cache_only ) {
			return '';
		}

		$result = __( 'No activity has been logged yet.', 'wp-cerber' );
	}

	return $result;
}

// Miscellaneous admin routines ===========================================================

/**
 * Detects file extension
 *
 * @param string $file_name
 *
 * @return string
 */
function crb_get_extension( $file_name ) {

	$name = basename( $file_name );

	if ( $name == '.htaccess' ) {
		return '';
	}

	if ( false === ( $last_dot = strrpos( $name, '.' ) ) ) {
		return '';
	}

	/*$first_dot = strpos( $name, '.' );

	if ( $last_dot !== $first_dot
	     && cerber_detect_exec_extension( $name ) ) {
		$ext = substr( $name, $first_dot + 1 );
	}
	else {
		$ext = substr( $name, $last_dot + 1 );
	}*/

	$ext = substr( $name, $last_dot + 1 );

	if ( ! $ext ) {
		$ext = '';
	}

	return $ext;
}

function crb_make_file_table( $files ) {

	$result = array();
	foreach ( $files as $file ) {
		$result[] = array(
			$file['file_name'],
			crb_size_format( $file['file_size'] ),
			cerber_date( $file['file_mtime'] ),
		);
	}

	return cerber_make_table( $result, array(
		__( 'File Name', 'wp-cerber' ),
		__( 'Size', 'wp-cerber' ),
		__( 'Modified', 'wp-cerber' ),
	), '', 'crb_align_right', 'crb-monospace crb-anchor-decorated' );

}

/**
 * Generates an HTML table
 *
 * @param $heading
 * @param $rows
 * @param string $id
 * @param $class
 * @param string $body_class
 * @param string $head_class
 * @param bool $show_footer
 *
 * @return string
 * @since 8.6.4
 *
 */
function cerber_make_table( $rows, $heading = array(), $id = '', $class = '', $body_class = '', $head_class = '', $show_footer = true ) {

	$tr = array();
	foreach ( $rows as $row ) {
		$tr[] = '<td>' . implode( '</td><td>', $row ) . '</td>';
	}

	$tfoot = '';
	$thead = '';

	if ( $heading ) {
		$titles = '<tr><th>' . implode( '</th><th>', $heading ) . '</th></tr>';
		$thead = '<thead class="' . $head_class . '">' . $titles . '</thead>';

		if ( $show_footer ) {
			$tfoot = '<tfoot class="' . $head_class . '">' . $titles . '</tfoot>';
		}
	}

	$id = ( $id ) ? 'id="' . $id . '"' : '';

	return '<table ' . $id . ' class="widefat crb-table cerber-margin ' . $class . '">' . $thead . $tfoot . '<tbody class="' . $body_class . '"><tr>' . implode( '</tr><tr>', $tr ) . '</tr></tbody></table>';

}

function cerber_get_chmod( $file ) {
	return substr( sprintf( '%o', @fileperms( $file ) ), - 4 );
}

function cerber_get_size( $file ) {
	$size = @filesize( $file );

	return ( is_numeric( $size ) ) ? $size : 0;
}

function cerber_get_date( $file ) {
	$mtime = @filemtime( $file );

	return ( is_numeric( $mtime ) ) ? $mtime : 0;
}

function crb_show_admin_announcement( $text = '', $big = true ) {
	$class = ( $big ) ? 'crb-cerber-logo-big' : 'crb-cerber-logo-small';

	echo '<div class="cerber-msg crb-announcement ' . $class . '"><table><tr><td id="crb-ann-logo"></td>' .
	     '<td id="crb-ann-text"><div>' . $text . '</div></td></tr></table></div>';
}

/**
 * Returns GET (query string) params of the currently displayed page
 *
 * @return array
 */
function crb_get_referrer_params() {

	if ( cerber_is_wp_ajax() ) {
		$referrer = array();
		if ( $ref_url = crb_get_request_field( 'referrer_page_url' ) ) {
			if ( $temp = parse_url( $ref_url, PHP_URL_QUERY ) ) {
				parse_str( $temp, $referrer );
			}
		}
	}
	else {
		$referrer = crb_get_query_params();
	}

	if ( ! is_array( $referrer ) ) {
		$referrer = array();
	}

	array_walk_recursive( $referrer, function ( &$item ) {
		$item = preg_replace( '/[^\w\-.:*|@]/i', '', $item ); // Some sanitization
	} );

	return $referrer;
}

add_filter( 'manage_application-passwords-user_columns', function ( $columns ) {
	end( $columns );
	$key = key( $columns );
	$last = array_pop( $columns );

	return array_merge( $columns,
		array(
			'app_cerber_log_ok'   => __( 'Authorized', 'wp-cerber' ),
			'app_cerber_log_fail' => __( 'Authorization Failed', 'wp-cerber' ),
			$key                  => $last
		) );
} );

add_action( 'manage_application-passwords-user_custom_column', function ( $column_name, $item ) {
	global $user_id;
	if ( $column_name == 'app_cerber_log_ok' && ! empty( $item['last_ip'] ) ) {
		$link = cerber_activity_link( array( 4 ) ) . '&amp;filter_user=' . $user_id;
		echo '<a href="' . $link . '">View Log</a>';
	}
	if ( $column_name == 'app_cerber_log_fail' ) {
		$user = get_user_by( 'ID', $user_id );
		$link = cerber_activity_link( array( 8 ) ) . '&amp;filter_login=' . $user->user_email . '|' . $user->user_login;
		echo '<a href="' . $link . '">View Log</a>';
	}

}, 10, 2 );

/**
 * @param string $fname
 * @param string $ct Content-Type @since 8.6.9
 */
function crb_file_headers( $fname, $ct = 'text/csv' ) {
	$fname = rawurlencode( $fname ); // encode non-ASCII symbols
	@ob_clean(); // This trick is crucial for some servers/environments (e.g. some IIS)
	header( "Content-Type: " . $ct );
	header( "Content-Disposition: attachment; filename*=UTF-8''{$fname}" );
}