| Current Path : /home/krobertfnz/www/wp-content/plugins/wp-defender/src/behavior/scan-item/ |
| Current File : /home/krobertfnz/www/wp-content/plugins/wp-defender/src/behavior/scan-item/vuln-result.php |
<?php
namespace WP_Defender\Behavior\Scan_Item;
use Calotes\Component\Behavior;
use WP_Defender\Component\Error_Code;
use WP_Defender\Model\Scan;
use WP_Defender\Model\Scan_Item;
use WP_Defender\Traits\Formats;
use WP_Defender\Traits\IO;
use WP_Defender\Traits\Plugin;
use WP_Defender\Traits\Theme;
use WP_Error;
class Vuln_Result extends Behavior {
use Formats, IO, Plugin, Theme;
/**
* @param array $bugs
*
* @return string
*/
protected function upgrade_possible( array $bugs ): string {
$upgrade = 'disabled';
foreach ( $bugs as $bug ) {
if ( ! empty( $bug['fixed_in'] ) ) {
$upgrade = 'enabled';
break;
}
}
return $upgrade;
}
/**
* @return array
*/
public function to_array(): array {
$data = $this->owner->raw_data;
if ( isset( $data['name'], $data['version'], $data['bugs'] ) ) {
if ( 'wp_core' === $data['type'] ) {
// Check if the current WP version is the latest.
$upgrade = ( new \WP_Defender\Component\Security_Tweaks\WP_Version() )->check() ? 'disabled' : 'enabled';
} elseif ( in_array( $data['type'], [ 'plugin', 'theme' ], true ) ) {
$upgrade = $this->upgrade_possible( $data['bugs'] );
} else {
$upgrade = 'disabled';
}
return [
'id' => $this->owner->id,
'type' => Scan_Item::TYPE_VULNERABILITY,
'file_name' => $data['name'],
'short_desc' => sprintf(
/* translators: %s: Version of WP core, plugin or theme. */
__( 'Vulnerability found in %s.', 'wpdef' ), $data['version']
),
'details' => isset( $data['new_structure'] )
? $this->get_details_as_array( $data )
: $this->get_detail_as_string( $data ),
// Need for all scan items for WP-CLI command. Full path = base slug for this item.
'full_path' => $data['slug'],
'new_structure' => isset( $data['new_structure'] ) ? 'yes' : 'no',
'upgrade' => $upgrade,
];
}
return [];
}
/**
* @return array
*/
public function ignore(): array {
$scan = Scan::get_last();
$scan->ignore_issue( $this->owner->id );
return [
'message' => __( 'The suspicious file has been successfully ignored.', 'wpdef' ),
];
}
/**
* @return array
*/
public function unignore(): array {
$scan = Scan::get_last();
$scan->unignore_issue( $this->owner->id );
return [
'message' => __( 'The suspicious file has been successfully restored.', 'wpdef' ),
];
}
/**
* @return array|bool|WP_Error
*/
public function resolve() {
$data = $this->owner->raw_data;
// Redirect for WordPress-type.
if ( 'wp_core' === $data['type'] ) {
return [ 'url' => network_admin_url( 'update-core.php' ) ];
} elseif ( 'plugin' === $data['type'] ) {
return $this->upgrade_plugin( $data['slug'] );
} elseif ( 'theme' === $data['type'] ) {
return $this->upgrade_theme( $data['base_slug'] );
}
// If type does not match.
return new WP_Error(
Error_Code::INVALID,
__( 'Please try again! We could not find the issue type.', 'wpdef' )
);
}
/**
* @param $slug
*
* @return array|bool|WP_Error
*/
private function upgrade_theme( $slug ) {
$skin = new Silent_Skin();
$upgrader = new \Theme_Upgrader( $skin );
$ret = $upgrader->upgrade( $slug );
if ( true === $ret ) {
$model = Scan::get_last();
$model->remove_issue( $this->owner->id );
return [ 'message' => __( 'This item has been resolved.', 'wpdef' ) ];
}
// This is WP error.
if ( is_wp_error( $ret ) ) {
return $ret;
}
// Sometimes it returns false because of it could not complete the update process.
return new WP_Error(
Error_Code::INVALID,
__( "We couldn't update your theme. Please try updating with another method.", 'wpdef' )
);
}
/**
* @param string $slug
*
* @return array
* @since 2.8.1 Change Upgrade plugin logic.
*/
private function upgrade_plugin( $slug ): array {
$skin = new Plugin_Skin();
$upgrader = new \Plugin_Upgrader( $skin );
$result = $upgrader->bulk_upgrade( [ $slug ] );
if ( is_wp_error( $skin->result ) ) {
return [
'type_notice' => 'error',
'message' => $skin->result->get_error_message(),
];
} elseif ( $skin->get_errors()->has_errors() ) {
return [
'type_notice' => 'error',
'message' => $skin->get_error_messages(),
];
} elseif ( is_array( $result ) && ! empty( $result[ $slug ] ) ) {
/*
* Plugin is already at the latest version.
*
* This may also be the return value if the `update_plugins` site transient is empty,
* e.g. when you update two plugins in quick succession before the transient repopulates.
*
* Preferably something can be done to ensure `update_plugins` isn't empty.
* For now, surface some sort of error here.
*/
if ( true === $result[ $slug ] ) {
return [
'type_notice' => 'error',
'message' => $upgrader->strings['up_to_date'],
];
}
$model = Scan::get_last();
$model->remove_issue( $this->owner->id );
return [ 'message' => __( 'This item has been resolved.', 'wpdef' ) ];
} elseif ( false === $result ) {
return [
'type_notice' => 'error',
'message' => __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'wpdef' ),
];
}
return [
'type_notice' => 'info',
'message' => __( 'There is no update available for this plugin.', 'wpdef' ),
];
}
/**
* @param string $path
*
* @return bool
*/
private function remove_vulnerability( string $path ): bool {
if ( is_dir( $path ) ) {
return $this->delete_dir( $path );
} else {
// Sometimes a plugin consists of one file.
return unlink( $path );
}
}
/**
* @param array $data
*
* @return array
*/
private function remove_plugin( array $data ): array {
$active = $this->is_active_plugin( $data['slug'] );
if ( $active ) {
return [
'type_notice' => 'error',
'message' => __( 'This plugin cannot be removed because it is active.', 'wpdef' ),
];
}
$abs_path = wp_normalize_path( WP_PLUGIN_DIR ) . DIRECTORY_SEPARATOR . $data['base_slug'];
if ( ! $this->remove_vulnerability( $abs_path ) ) {
return [
'type_notice' => 'error',
'message' => __( 'Defender does not have enough permission to remove this plugin.', 'wpdef' ),
];
}
$message = sprintf(
/* translators: %s: Plugin name. */
__( '%s plugin', 'wpdef' ),
'<b>' . $data['name'] . '</b>'
);
$this->log( $message . 'is deleted', 'scan.log' );
$model = Scan::get_last();
$model->remove_issue( $this->owner->id );
return [
'collect_type' => true,
'message' => $message,
];
}
/**
* @param array $data
*
* @return array
*/
private function remove_theme( array $data ): array {
$active = $this->is_active_theme( $data['base_slug'] );
if ( $active ) {
return [
'type_notice' => 'error',
'message' => __( 'This theme cannot be removed because it is active.', 'wpdef' ),
];
}
$abs_path = $this->get_path_of_themes_dir() . $data['base_slug'];
if ( ! $this->remove_vulnerability( $abs_path ) ) {
return [
'type_notice' => 'error',
'message' => __( 'Defender does not have enough permission to remove this theme.', 'wpdef' ),
];
}
$message = sprintf(
/* translators: %s: Plugin theme. */
__( '%s theme', 'wpdef' ),
'<b>' . $data['name'] . '</b>'
);
$this->log( $message . 'is deleted', 'scan.log' );
$model = Scan::get_last();
$model->remove_issue( $this->owner->id );
return [
'collect_type' => true,
'message' => $message,
];
}
/**
* @param array $bug
*
* @return string
*/
protected function get_vulnerability_body( array $bug ): string {
$text = '#' . $bug['title'] . PHP_EOL;
$text .= '-' . __( 'Vulnerability type:', 'wpdef' ) . ' ' . $bug['vuln_type'] . PHP_EOL;
if ( empty( $bug['fixed_in'] ) ) {
$text .= '-' . __( 'No Update Available', 'wpdef' ) . PHP_EOL;
} else {
$text .= '-' . __( 'This bug has been fixed in version:', 'wpdef' ) . ' ' . $bug['fixed_in'] . PHP_EOL;
}
return $text;
}
/**
* @param array $data
*
* @return string
*/
public function get_detail_as_string( array $data ): string {
$strings = [];
foreach ( $data['bugs'] as $bug ) {
$strings[] = $this->get_vulnerability_body( $bug );
}
return implode( PHP_EOL, $strings );
}
/**
* @param array $data
*
* @return array
*/
public function get_details_as_array( array $data ): array {
$arr = [];
foreach ( $data['bugs'] as $bug ) {
$text = $this->get_vulnerability_body( $bug );
$arr[] = [
'score' => $bug['cvss_score'],
'detail' => str_replace( PHP_EOL, '<br/>', $text ),
];
}
return $arr;
}
/**
* Delete inactive plugin or theme.
*
* @return array|WP_Error
*/
public function delete() {
$data = $this->owner->raw_data;
// WP core, plugin or theme.
if ( 'wp_core' === $data['type'] ) {
return [
'type_notice' => 'error',
'message' => __( 'WordPress core cannot be removed.', 'wpdef' ),
];
} elseif ( 'plugin' === $data['type'] ) {
return $this->remove_plugin( $data );
} elseif ( 'theme' === $data['type'] ) {
return $this->remove_theme( $data );
}
// Sometimes it returns false because of it could not complete the remove process.
return new WP_Error(
Error_Code::INVALID,
__( "We couldn't remove this item.", 'wpdef' )
);
}
}
if ( ! class_exists( \WP_Upgrader::class ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
}
if ( ! class_exists( \Theme_Upgrader::class ) ) {
require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
}
class Silent_Skin extends \Automatic_Upgrader_Skin {
public function footer() {
return;
}
public function header() {
return;
}
public function feedback( $data, ...$args ) {
return '';
}
}
class Plugin_Skin extends \WP_Ajax_Upgrader_Skin {
public function feedback( $data, ...$args ) {
return '';
}
}