701 lines
24 KiB
PHP
701 lines
24 KiB
PHP
<?php
|
|
|
|
|
|
class OCVMAutoUpdates {
|
|
|
|
use OCVMVulnerabilities;
|
|
|
|
public $updateAttempt = array(
|
|
'time' => '',
|
|
'failed' => array(),
|
|
'successful' => array(),
|
|
);
|
|
private $settings;
|
|
private $items;
|
|
|
|
const PLUGINS_PACKAGE_URL = 'https://downloads.wordpress.org/plugin/%s.%s.zip';
|
|
const THEMES_PACKAGE_URL = 'https://downloads.wordpress.org/theme/%s.%s.zip';
|
|
const WP_PACKAGE_URL = 'https://downloads.wordpress.org/release/%s/wordpress-%s.zip';
|
|
const WP_PACKAGE_NO_CONTENT_URL = 'https://downloads.wordpress.org/release/wordpress-%s-no-content.zip';
|
|
const ERROR_DESCRIPTION = 'Error description --> ';
|
|
const WP_UPGRADER_FILE_PATH = 'wp-admin/includes/class-wp-upgrader.php';
|
|
const WP_UPDATE_FILE_PATH = 'wp-admin/includes/update.php';
|
|
const WP_FILE_SYSTEM_PATH = 'wp-admin/includes/file.php';
|
|
const WP_MISC_PATH = 'wp-admin/includes/misc.php';
|
|
|
|
const LOG_DIVIDER = '#################################';
|
|
|
|
public function __construct() {
|
|
$this->settings = new OCVMSettings();
|
|
|
|
// ???
|
|
}
|
|
|
|
/**
|
|
* Get FQN of plugin by slug
|
|
* @param $slug string Plugin's PHP file name (without extension)
|
|
* @return string directory/plugin_file.php
|
|
*/
|
|
protected function find_plugin_for_slug( $slug ) {
|
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
$plugin_files = get_plugins( '/' . $slug );
|
|
if ( ! $plugin_files ) {
|
|
return '';
|
|
}
|
|
$plugin_files = array_keys( $plugin_files );
|
|
return $slug . '/' . reset( $plugin_files );
|
|
}
|
|
|
|
/**
|
|
* Prepare update attempt records
|
|
*/
|
|
public function prepareAttempt( $type, $slug, $new_ver, $itemFQN, $item_data ) {
|
|
$attempt = array(
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'dir' => $itemFQN,
|
|
'new_version' => $new_ver,
|
|
'time' => time(),
|
|
);
|
|
|
|
if ( ! empty( $item_data ) ) {
|
|
$attempt['name'] = $item_data['Name'];
|
|
$attempt['old_version'] = $item_data['Version'];
|
|
}
|
|
|
|
// get latest state from DB
|
|
$settings = $this->settings->get();
|
|
|
|
if ( 'wp' === $type ) {
|
|
$vuls = $settings['vulnerabilities'][ $type ]['vulnerabilities'];
|
|
} else {
|
|
$vuls = $settings['vulnerabilities'][ $type ][ $slug ]['vulnerabilities'];
|
|
}
|
|
|
|
foreach ( $vuls as $v ) {
|
|
// modified to manipulate the vuln_type string and url recieved from new API
|
|
$desc = 'wp_vul_' . strtolower( str_replace( array( ' ', '(', ')' ), array( '_', '', '' ), $v['vuln_type'] ) );
|
|
if ( $this->vulTranslation( $desc ) !== '' ) {
|
|
$attempt['vuls'][ $v['id'] ]['id'] = $this->vulTranslation( $desc );
|
|
|
|
} elseif ( isset( $attempt['vuls'][ $v['id'] ]['id'] ) || isset( $v['description'] ) ) {
|
|
$attempt['vuls'][ $v['id'] ]['id'] = $v['description'];
|
|
} else {
|
|
$attempt['vuls'][ $v['id'] ]['id'] = __( 'wp_vul_unknown', 'onecom-wp' );
|
|
}
|
|
$attempt['vuls'][ $v['id'] ]['url'] = $v['url'];
|
|
}
|
|
|
|
return $attempt;
|
|
}
|
|
|
|
/**
|
|
* Update item records after attempting their update
|
|
* @param string $type
|
|
* @param string $slug
|
|
* @param array $attempt
|
|
* @return void
|
|
*/
|
|
public function updateItemRecords( $type, $slug, $attempt = array(), $ver = '' ): void {
|
|
// seat belt
|
|
if ( empty( $type ) || empty( $slug ) ) {
|
|
error_log( "Failed to update records because either 'itemType' or 'itemSlug' or both are empty." );
|
|
return;
|
|
}
|
|
|
|
// get latest state from DB
|
|
$settings = $this->settings->get();
|
|
|
|
// Prepare consistent structure to make use of existing iterate/push functions
|
|
$log_item_data = array();
|
|
|
|
// remove the item from vulnerabilities list. in case of WP, clear the array
|
|
if ( 'wp' === $type ) {
|
|
// Before clearing vul, Add fixed item to VM log at one less level as compared to themes/plugins
|
|
$log_item_data[ $type ] = $settings['vulnerabilities'][ $type ];
|
|
$log_item_data[ $type ]['log_item_result'] = 'Auto-updated';
|
|
$log_item_data[ $type ]['log_latest_version'] = $ver;
|
|
|
|
// clear wp vulnerability
|
|
$settings['vulnerabilities'][ $type ] = array();
|
|
} else {
|
|
$failed_vm = array();
|
|
|
|
if ( isset( $attempt['failed'] ) && is_array( $attempt['failed'] ) && ! empty( $attempt['failed'] ) ) {
|
|
foreach ( $attempt['failed'] as $failed_array ) {
|
|
$slug = strtolower( $failed_array['slug'] );
|
|
if ( isset( $settings['vulnerabilities'][ $type ][ $slug ] ) ) {
|
|
$failed_vm[ $slug ] = $settings['vulnerabilities'][ $type ][ $slug ];
|
|
unset( $settings['vulnerabilities'][ $type ][ $slug ] );
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Add relevant information (type) inside vul item array itself
|
|
* Then, Fixed vul item array is ready to prepare in $log_item_data
|
|
*/
|
|
$settings['vulnerabilities'][ $type ][ $slug ]['item_type'] = $type;
|
|
$log_item_data[ $type ][ $slug ] = $settings['vulnerabilities'][ $type ][ $slug ];
|
|
$log_item_data[ $type ][ $slug ]['log_item_result'] = 'Auto-updated';
|
|
$log_item_data[ $type ][ $slug ]['log_latest_version'] = $ver;
|
|
|
|
// remove the item for which we have attempted an update
|
|
unset( $settings['vulnerabilities'][ $type ][ $slug ] );
|
|
$settings['vulnerabilities'][ $type ] = array_merge( $settings['vulnerabilities'][ $type ], $failed_vm );
|
|
}
|
|
|
|
// Fixed vulnerability item is Ready to Iterate (to extract all vuls from item) for VM log push
|
|
error_log( '======= Fixed vulnerability item is ready to iterate for log push =======' );
|
|
$history_log_obj = new OCVMHistoryLog();
|
|
$history_log_obj->iterateVulnerabilitiesForLog( $log_item_data );
|
|
|
|
// save the current attempt of item updates in database
|
|
if ( ! empty( $attempt ) ) {
|
|
|
|
if ( ! empty( $this->updateAttempt ) ) {
|
|
foreach ( $this->updateAttempt as $types => &$attempts ) {
|
|
if ( $types === 'time' ) {
|
|
continue;
|
|
}
|
|
// update attempt counts in transient
|
|
$this->add_update_attempts_count( $types, $attempts );
|
|
foreach ( $attempts as &$attempt ) {
|
|
// stored with attempts to prevent dupliate counts in the same attempt
|
|
$attempt['attempt_stored'] = true;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the failed attempts
|
|
if ( isset( $this->updateAttempt['failed'] ) ) {
|
|
unset( $this->updateAttempt['failed'] );
|
|
}
|
|
|
|
$settings['update_attempts'] = $this->updateAttempt;
|
|
}
|
|
|
|
$this->settings->update( $settings );
|
|
}
|
|
|
|
|
|
/** Update single plugin
|
|
* @param $type string Type of item (plugins)
|
|
* @param $slug string Filename of the item
|
|
* @param $ver string Desired version for update
|
|
* @return void
|
|
*/
|
|
public function updateTypePlugins( $type, $slug, $ver, $plugin_package ) {
|
|
|
|
// get all details of the plugin using slug
|
|
$pluginFQN = $this->find_plugin_for_slug( $slug );
|
|
if ( empty( $pluginFQN ) ) {
|
|
error_log( "Skipping update by rule...[Plugin '{$slug}' no longer exists]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug );
|
|
return;
|
|
}
|
|
|
|
if ( false === is_plugin_active( $pluginFQN ) ) {
|
|
error_log( "Skipping update by rule...[Plugin '{$slug}' is no longer active]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug );
|
|
return;
|
|
}
|
|
|
|
// load plugin functions to get plugin headers
|
|
if ( ! function_exists( 'get_plugin_data' ) ) {
|
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
}
|
|
|
|
// plugin headers by plugin dir
|
|
// e.g., /../../../../../test-plugin/test-plugin.php
|
|
$pluginData = get_plugin_data( trailingslashit( WP_PLUGIN_DIR ) . $pluginFQN );
|
|
// exit if fixed_version and current versions are same.
|
|
if ( (string) $ver === (string) $pluginData['Version'] ) {
|
|
error_log( "Skipping update by rule...[Plugin '{$slug}' is already at the 'fixed' version]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug, array(), $ver );
|
|
return;
|
|
}
|
|
|
|
// check if the plugin update with same versions was updated in last 24 hours to prevent repetitive attempts
|
|
if ( $this->validate_against_last_update( $slug, $type, $ver, $pluginData['Version'] ) ) {
|
|
return;
|
|
}
|
|
|
|
// record update attempt details
|
|
$attempt = $this->prepareAttempt( $type, $slug, $ver, $pluginFQN, $pluginData );
|
|
|
|
error_log( self::LOG_DIVIDER );
|
|
error_log( "##### starting update of {$slug}" );
|
|
error_log( self::LOG_DIVIDER );
|
|
|
|
$pluginObj = new stdClass();
|
|
$pluginObj->slug = $slug;
|
|
$pluginObj->plugin = $pluginFQN;
|
|
$pluginObj->new_version = $ver;
|
|
$pluginObj->package = ( $plugin_package !== '' ) ? $plugin_package : sprintf( self::PLUGINS_PACKAGE_URL, $slug, $ver );
|
|
|
|
$current = get_site_transient( 'update_plugins' );
|
|
if ( ! is_object( $current ) ) {
|
|
$current = new stdClass();
|
|
}
|
|
$current->response[ $pluginFQN ] = $pluginObj;
|
|
set_site_transient( 'update_plugins', $current );
|
|
|
|
require_once ABSPATH . self::WP_FILE_SYSTEM_PATH;
|
|
require_once ABSPATH . self::WP_UPGRADER_FILE_PATH;
|
|
require_once ABSPATH . self::WP_UPDATE_FILE_PATH;
|
|
require_once ABSPATH . self::WP_MISC_PATH;
|
|
wp_cache_flush();
|
|
$upgrader = new Plugin_Upgrader();
|
|
$result = $upgrader->upgrade( $pluginFQN );
|
|
|
|
// modified to handle the null result in case of failed plugin downloads may be due to the non-working url of plugin zip
|
|
if ( is_wp_error( $result ) || ! $result ) {
|
|
|
|
//TODO: handle failed updates - perhaps send an email and remove records from DB
|
|
|
|
// keep status of update
|
|
$this->updateAttempt['failed'][] = $attempt;
|
|
error_log( 'Error occurred during update of plugin --> ' . $pluginFQN );
|
|
// added for the case when update fails may be due to the non-working url of plugin zip
|
|
$err_description = ! is_null( $result ) ? json_encode( $result->get_error_message() ) : 'Plugin download failed for some reason.';
|
|
error_log( self::ERROR_DESCRIPTION . $err_description );
|
|
} else {
|
|
$pluginData = get_plugin_data( trailingslashit( WP_PLUGIN_DIR ) . $pluginFQN );
|
|
if ( (string) $ver <= (string) $pluginData['Version'] ) {
|
|
error_log( '######### Plugin version verified update was successful #######' );
|
|
// keep status of update
|
|
$this->updateAttempt['successful'][] = $attempt;
|
|
error_log( "Updated {$slug} successfully --> " . $pluginFQN );
|
|
} else {
|
|
$this->updateAttempt['failed'][] = $attempt;
|
|
error_log( '######### Plugin version verification check failed adding to failed attempts #########' );
|
|
}
|
|
}
|
|
// update records for this item if successfully updated
|
|
$this->updateItemRecords( $type, $slug, $this->updateAttempt, $ver );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Update single theme
|
|
* @param $type string Type of item (themes)
|
|
* @param $slug string Filename of the item
|
|
* @param $ver string Desired version for update
|
|
* @return void
|
|
*/
|
|
public function updateTypeThemes( $type, $slug, $ver, $theme_package ) {
|
|
|
|
// theme headers
|
|
$themeData = wp_get_theme( $slug );
|
|
|
|
// check if theme exists
|
|
if ( false === $themeData->exists() ) {
|
|
error_log( "Skipping update by rule...[Theme '{$slug}' no longer exists]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug );
|
|
return;
|
|
}
|
|
|
|
// check if theme is active
|
|
if ( get_site_option( 'template' ) !== $themeData->template ) {
|
|
error_log( "Skipping update by rule...[Theme '{$slug}' is no longer active]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug );
|
|
return;
|
|
}
|
|
|
|
// exit if fixed_version is equal to current versions are same.
|
|
if ( (string) $ver === (string) $themeData->get( 'Version' ) ) {
|
|
error_log( "Skipping update by rule...[Theme '{$slug}' is already at the 'fixed' version]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug, array(), $ver );
|
|
return;
|
|
}
|
|
|
|
// check if the theme update with same versions was updated in last 24 hours to prevent repetitive attempts
|
|
if ( $this->validate_against_last_update( $slug, $type, $ver, $themeData->get( 'Version' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
// record update attempt details
|
|
$attempt = $this->prepareAttempt( $type, $slug, $ver, $themeData->stylesheet, $themeData );
|
|
|
|
error_log( self::LOG_DIVIDER );
|
|
error_log( "##### starting update of {$slug}" );
|
|
error_log( self::LOG_DIVIDER );
|
|
|
|
$themeObj['theme'] = $themeData->template;
|
|
$themeObj['new_version'] = $ver;
|
|
$themeObj['package'] = ( $theme_package !== '' ) ? $theme_package : sprintf( self::THEMES_PACKAGE_URL, $slug, $ver );
|
|
|
|
$current = get_site_transient( 'update_themes' );
|
|
if ( $current !== false ) {
|
|
$current->response[ $themeData->template ] = $themeObj;
|
|
}
|
|
set_site_transient( 'update_themes', $current );
|
|
|
|
require_once ABSPATH . self::WP_FILE_SYSTEM_PATH;
|
|
require_once ABSPATH . self::WP_UPGRADER_FILE_PATH;
|
|
require_once ABSPATH . self::WP_UPDATE_FILE_PATH;
|
|
require_once ABSPATH . self::WP_MISC_PATH;
|
|
wp_cache_flush();
|
|
$upgrader = new Theme_Upgrader();
|
|
$result = $upgrader->upgrade( $themeData->template );
|
|
|
|
if ( is_wp_error( $result ) || ! $result ) {
|
|
|
|
//TODO: handle failed updates - perhaps send an email and remove records from DB
|
|
|
|
// keep status of update
|
|
$this->updateAttempt['failed'][] = $attempt;
|
|
|
|
error_log( 'Error occurred during update of theme --> ' . $themeData->template );
|
|
$err_description = ! is_null( $result ) ? json_encode( $result->get_error_message() ) : 'Theme download failed for some reason.';
|
|
error_log( self::ERROR_DESCRIPTION . $err_description );
|
|
} else {
|
|
// theme headers
|
|
$themeData = wp_get_theme( $slug );
|
|
if ( (string) $ver === (string) $themeData->get( 'Version' ) ) {
|
|
error_log( '######### Theme version verified update was successful #######' );
|
|
// keep status of update
|
|
$this->updateAttempt['successful'][] = $attempt;
|
|
|
|
error_log( "Updated {$slug} successfully --> " . $themeData->template );
|
|
} else {
|
|
$this->updateAttempt['failed'][] = $attempt;
|
|
error_log( '######### Theme version verification check failed adding to failed attempts #########' );
|
|
}
|
|
}
|
|
$this->updateItemRecords( $type, $slug, $this->updateAttempt, $ver );
|
|
}
|
|
|
|
|
|
/**
|
|
* Update single theme
|
|
* @param $type string Type of item (themes)
|
|
* @param $slug string Filename of the item
|
|
* @param $ver string Desired version for update
|
|
* @return void
|
|
*/
|
|
public function updateTypeWp( $type, $slug, $ver ) {
|
|
global $wpdb, $wp_version;
|
|
|
|
if ( version_compare( $wp_version, $ver ) >= 0 ) {
|
|
error_log( "Skipping update by rule...[WP core is already at a greater or equal version than provided version i.e., {$wp_version} >= {$ver}]" );
|
|
|
|
// (remove this item from database) update records for this item
|
|
$this->updateItemRecords( $type, $slug, array(), $wp_version );
|
|
return;
|
|
}
|
|
|
|
$item_data = array(
|
|
'Name' => 'WordPress core',
|
|
'Version' => $wp_version,
|
|
);
|
|
// record update attempt details
|
|
$attempt = $this->prepareAttempt( $type, $slug, $ver, '', $item_data );
|
|
|
|
error_log( 'attempt for WP ==> ' . json_encode( $attempt ) );
|
|
|
|
error_log( self::LOG_DIVIDER );
|
|
error_log( "##### starting update to WP core v{$ver}" );
|
|
error_log( self::LOG_DIVIDER );
|
|
|
|
$locale = apply_filters( 'core_version_check_locale', get_locale() );
|
|
|
|
$wp_obj = new stdClass();
|
|
$wp_obj->response = 'upgrade';
|
|
$wp_obj->download = sprintf( self::WP_PACKAGE_URL, $locale, $ver );
|
|
$wp_obj->locale = $locale;
|
|
$wp_obj->current = $wp_version;
|
|
$wp_obj->version = $wp_version;
|
|
$wp_obj->php_version = phpversion();
|
|
$wp_obj->mysql_version = preg_replace( '/[^0-9.].*/', '', $wpdb->db_version() );
|
|
$wp_obj->new_bundled = $wp_version;
|
|
|
|
$package = new stdClass();
|
|
$package->full = $wp_obj->download;
|
|
$package->no_content = sprintf( self::WP_PACKAGE_NO_CONTENT_URL, $ver );
|
|
$package->new_bundled = '';
|
|
$package->partial = '';
|
|
$package->rollback = '';
|
|
|
|
$wp_obj->packages = $package;
|
|
|
|
$current = get_site_transient( 'update_core' );
|
|
//create a generic empty class if $current is not an object
|
|
if ( ! is_object( $current ) ) {
|
|
$current = new stdClass();
|
|
}
|
|
$current->updates = array( $wp_obj );
|
|
set_site_transient( 'update_core', $current );
|
|
|
|
require_once ABSPATH . self::WP_FILE_SYSTEM_PATH;
|
|
require_once ABSPATH . self::WP_UPGRADER_FILE_PATH;
|
|
require_once ABSPATH . self::WP_UPDATE_FILE_PATH;
|
|
require_once ABSPATH . self::WP_MISC_PATH;
|
|
wp_cache_flush();
|
|
$upgrader = new Core_Upgrader();
|
|
$result = $upgrader->upgrade( $wp_obj, array( 'attempt_rollback' => true ) );
|
|
|
|
if ( is_wp_error( $result ) || ! $result ) {
|
|
|
|
//TODO: handle failed updates - perhaps send an email and remove records from DB
|
|
|
|
// keep status of update
|
|
$this->updateAttempt['failed'][] = $attempt;
|
|
|
|
error_log( "Error occurred during update of WP core --> {$ver}" );
|
|
error_log( self::ERROR_DESCRIPTION . json_encode( $result->get_error_message() ) );
|
|
} else {
|
|
|
|
// keep status of update
|
|
$this->updateAttempt['successful'][] = $attempt;
|
|
|
|
error_log( "Updated WP core v{$ver} successfully" );
|
|
}
|
|
// update records for this item if successfully updated
|
|
$this->updateItemRecords( $type, $slug, $this->updateAttempt, $ver );
|
|
}
|
|
|
|
/**
|
|
* Update single item
|
|
* @param $type string Type of item (plugins/themes/core)
|
|
* @param $slug string Filename of the item
|
|
* @param $ver string Desired version for update
|
|
* @return void
|
|
*/
|
|
public function updateSingle( $type, $slug, $ver, $package_url = '' ): void {
|
|
|
|
// call update method based on item type
|
|
if ( 'plugins' === $type || 'themes' === $type ) {
|
|
call_user_func(
|
|
array( $this, 'updateType' . ucfirst( $type ) ),
|
|
$type,
|
|
$slug,
|
|
$ver,
|
|
$package_url
|
|
);
|
|
} else {
|
|
call_user_func(
|
|
array( $this, 'updateType' . ucfirst( $type ) ),
|
|
$type,
|
|
$slug,
|
|
$ver
|
|
);
|
|
}
|
|
// alternate dynamic function call
|
|
//[$this, "updateType".ucfirst($type)]($type,$slug,$ver);
|
|
|
|
// select suitable WP's update method based on received params
|
|
// remove entry for this item from Settings in DB. So that its admin notice will go away.
|
|
// add slug in updateState array.
|
|
}
|
|
|
|
|
|
/**
|
|
* Auto update items
|
|
*/
|
|
public function updateItems() {
|
|
$items = $this->settings->get();
|
|
$this->items = $items['vulnerabilities'];
|
|
|
|
$this->updateAttempt['time'] = time();
|
|
foreach ( $this->items as $type => $vuls ) {
|
|
|
|
if ( 'wp' === $type ) {
|
|
$this->updateSingle( $type, $type, $vuls['fixed_in'] );
|
|
continue;
|
|
}
|
|
|
|
foreach ( $vuls as $slug => $item ) {
|
|
// skip if fix version not present
|
|
if ( empty( $item['fixed_in'] ) ) {
|
|
continue;
|
|
}
|
|
$get_latest_ver = $this->get_wp_latest_plugin_version( $slug, $type );
|
|
$patched_in_range = false;
|
|
foreach ( $item['vulnerabilities'] as $vuln ) {
|
|
if ( is_array( $vuln['patched_in_ranges'] ) && ! empty( $vuln['patched_in_ranges'] ) ) {
|
|
$patched_in_range = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( $type === 'plugins' && 'woocommerce' === $slug ) {
|
|
$this->updateSingle( $type, $slug, $item['fixed_in'] );
|
|
} elseif ( false !== $get_latest_ver && version_compare( $item['fixed_in'], $get_latest_ver['version'], '>' ) ) {
|
|
$update_transient = get_site_transient( 'update_plugins' );
|
|
$pluginFQN = $this->find_plugin_for_slug( $slug );
|
|
if ( $update_transient && ! empty( $pluginFQN ) && isset( $update_transient->response[ $pluginFQN ]->new_version ) ) {
|
|
$item['fixed_in'] = $update_transient->response[ $pluginFQN ]->new_version;
|
|
|
|
}
|
|
$this->updateSingle( $type, $slug, $item['fixed_in'] );
|
|
} elseif ( $patched_in_range ) {
|
|
$this->updateSingle( $type, $slug, $item['fixed_in'] );
|
|
} elseif ( $get_latest_ver !== false ) {
|
|
$this->updateSingle( $type, $slug, $get_latest_ver['version'], $get_latest_ver['download_link'] );
|
|
} else {
|
|
$this->updateSingle( $type, $slug, $item['fixed_in'] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// send email with param updateState array
|
|
}
|
|
|
|
/**
|
|
* Get the latest version of a WordPress plugin or theme.
|
|
*
|
|
* This function retrieves the latest version of a WordPress plugin or theme
|
|
* from the WordPress.org API based on the provided slug and type.
|
|
*
|
|
* @param string $slug The slug of the plugin or theme.
|
|
* @param string $type The type of the resource ('plugin' or 'theme').
|
|
*
|
|
* @return string|false The latest version of the resource as a string,
|
|
*
|
|
*/
|
|
public function get_wp_latest_plugin_version( $slug, $type ) {
|
|
// Ensure the slug is provided.
|
|
if ( empty( $slug ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Define the API endpoint based on the type.
|
|
$api_endpoint = ( $type === 'themes' )
|
|
? "https://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]={$slug}"
|
|
: "https://api.wordpress.org/plugins/info/1.0/{$slug}.json";
|
|
|
|
// Make an HTTP request to the API.
|
|
$response = wp_safe_remote_get( $api_endpoint );
|
|
|
|
// Get the HTTP status code from the response.
|
|
$status_code = wp_remote_retrieve_response_code( $response );
|
|
|
|
// Check if the status code is not 200 (OK).
|
|
if ( $status_code !== 200 ) {
|
|
return false; // Request was not successful.
|
|
}
|
|
|
|
// Check if the request was successful but the response is empty.
|
|
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
|
|
return false; // Empty or failed response.
|
|
}
|
|
|
|
// Parse the JSON response.
|
|
$data = json_decode( $response['body'], true );
|
|
|
|
// Check if JSON decoding was successful and if the data contains version information.
|
|
if ( $data && isset( $data['version'] ) && isset( $data['download_link'] ) ) {
|
|
$plugin_info = array(
|
|
'version' => $data['version'],
|
|
'download_link' => $data['download_link'],
|
|
);
|
|
return $plugin_info; // Return version and download link in an array.
|
|
} else {
|
|
return false; // Version information not found in the response.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $settings
|
|
* @param $type
|
|
* function to add/increment the update attempts count to the transients, which can be further used to limit repetitive attempts
|
|
* @return void
|
|
*/
|
|
public function add_update_attempts_count( $type, $settings ) {
|
|
// Implemented to add the attempt counts with the update attempts
|
|
$last_update_attempts = array();
|
|
|
|
if ( isset( $settings ) && is_array( $settings ) && ! empty( $settings ) ) {
|
|
unset( $settings['time'] );
|
|
$last_update = get_site_transient( 'ocvm_last_update_attempt' );
|
|
|
|
if ( ! $last_update ) {
|
|
foreach ( $settings as $success_attempt ) {
|
|
unset( $success_attempt['vuls'] );
|
|
$success_attempt['attempt_count'] = 1;
|
|
$last_update_attempts[ $type ][] = $success_attempt;
|
|
}
|
|
} else {
|
|
$last_update_attempts = json_decode( $last_update, true );
|
|
if ( $last_update_attempts !== null ) {
|
|
|
|
foreach ( $settings as $success_attempt ) {
|
|
|
|
if ( isset( $success_attempt['attempt_stored'] ) && $success_attempt['attempt_stored'] === true ) {
|
|
continue;
|
|
}
|
|
|
|
$found = false;
|
|
if ( isset( $last_update_attempts[ $type ] ) ) {
|
|
foreach ( $last_update_attempts[ $type ] as &$attempt ) {
|
|
|
|
if ( $success_attempt['slug'] === $attempt['slug'] &&
|
|
$success_attempt['new_version'] === $attempt['new_version'] &&
|
|
$success_attempt['old_version'] === $attempt['old_version'] ) {
|
|
// Increment attempt count
|
|
$attempt['attempt_count'] = ( $attempt['attempt_count'] ?? 0 ) + 1;
|
|
$found = true;
|
|
}
|
|
}
|
|
}
|
|
// If matching attempt not found, add a new attempt
|
|
if ( ! $found ) {
|
|
$new_attempt = $success_attempt;
|
|
unset( $new_attempt['vuls'] );
|
|
$new_attempt['attempt_count'] = 1;
|
|
$last_update_attempts[ $type ][] = $new_attempt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Store updated last_update_attempts array in transient
|
|
set_site_transient( 'ocvm_last_update_attempt', json_encode( $last_update_attempts ), 24 * HOUR_IN_SECONDS );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $slug
|
|
* @param $type
|
|
* @param $new_version
|
|
* @param $old_version
|
|
* function to verify the current attempt with the last successful attempt to prevent repetitive updates & notifications in case of fake success
|
|
* @return bool
|
|
*/
|
|
public function validate_against_last_update( $slug, $type, $new_version, $old_version ) {
|
|
$last_update = get_site_transient( 'ocvm_last_update_attempt' );
|
|
if ( $last_update ) {
|
|
$last_update_attempts = json_decode( $last_update, true );
|
|
|
|
if ( $last_update_attempts !== null ) {
|
|
foreach ( $last_update_attempts as $attempt_type ) {
|
|
foreach ( $attempt_type as $attempt ) {
|
|
if ( $slug === $attempt['slug'] &&
|
|
$new_version === $attempt['new_version'] &&
|
|
$old_version === $attempt['old_version'] &&
|
|
( $attempt['attempt_count'] >= 3 ) ) {
|
|
$type = ucfirst( $type );
|
|
error_log( "Skipping update by rule...[$type '{$slug}' update for the same version was attempted more than 3 times in last 24 hours ]" );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|