File: /home/www/torresncgolf/wp-content/plugins/wp-migrate-db/class/Common/Transfers/Files/Util.php
<?php
namespace DeliciousBrains\WPMDB\Common\Transfers\Files;
use DeliciousBrains\WPMDB\Common\Error\ErrorLog;
use DeliciousBrains\WPMDB\Common\Filesystem\Filesystem;
use DeliciousBrains\WPMDB\Common\Http\Helper;
use DeliciousBrains\WPMDB\Common\Http\Http;
use DeliciousBrains\WPMDB\Common\Http\RemotePost;
use DeliciousBrains\WPMDB\Common\MigrationPersistence\Persistence;
use DeliciousBrains\WPMDB\Common\MigrationState\MigrationStateManager;
use DeliciousBrains\WPMDB\Common\Settings\Settings;
use DeliciousBrains\WPMDB\Common\Util\ZipAndEncode;
use DeliciousBrains\WPMDB\Common\Util\Util as Common_Util;
use DeliciousBrains\WPMDB\Pro\Transfers\Receiver;
use DirectoryIterator;
class Util
{
public $filesystem;
/**
* @var Http
*/
private $http;
/**
* @var ErrorLog
*/
private $error_log;
/**
* @var Helper
*/
private $http_helper;
/**
* @var RemotePost
*/
private $remote_post;
/**
* @var Settings
*/
private $settings;
/**
* @var MigrationStateManager
*/
private $migration_state_manager;
/**
* @var \DeliciousBrains\WPMDB\Common\Util\Util
*/
private $util;
public function __construct(
Filesystem $filesystem,
Http $http,
ErrorLog $error_log,
Helper $http_helper,
RemotePost $remote_post,
Settings $settings,
MigrationStateManager $migration_state_manager,
\DeliciousBrains\WPMDB\Common\Util\Util $util
) {
$this->filesystem = $filesystem;
$this->http = $http;
$this->error_log = $error_log;
$this->http_helper = $http_helper;
$this->remote_post = $remote_post;
$this->settings = $settings->get_settings();
$this->migration_state_manager = $migration_state_manager;
$this->util = $util;
add_filter( 'wpmdb_theoretical_transfer_bottleneck', function ( $bottleneck ) {
return $this->get_transfer_bottleneck();
} );
}
public function get_remote_files(array $directories, $action, $excludes, $date = null, $timezone = null)
{
// POST to remote to get list of files
$state_data = $this->migration_state_manager->set_post_data();
$data = array();
$data['action'] = $action;
$data['intent'] = $state_data['intent'];
$data['folders'] = json_encode($directories);
$data['excludes'] = json_encode($excludes);
$data['stage'] = $state_data['stage'];
$data['is_cli_migration'] = isset($state_data['is_cli_migration']) ? (int)$state_data['is_cli_migration'] : 0;
$data['sig'] = $this->http_helper->create_signature($data, $state_data['key']);
if (!is_null($date)) {
$data['date'] = $date;
}
if (!is_null($timezone)) {
$data['timezone'] = $timezone;
}
$ajax_url = trailingslashit($state_data['url']) . 'wp-admin/admin-ajax.php';
$response = $this->remote_post->post($ajax_url, $data, __FUNCTION__);
$response = $this->remote_post->verify_remote_post_response($response);
$response['data'] = json_decode(ZipAndEncode::decode($response['data']), true);
if (isset($response['wpmdb_error'])) {
return $response;
}
if (!$response['success']) {
return $this->http->end_ajax(new \WP_Error('wpmdbtransfers_invalid_file_list', $response['data']));
}
return $response['data'];
}
/**
* @param array $queue_status
* @param string $action
*
* @return array|bool|mixed|object|string
* @throws \Exception
*/
public function save_queue_status_to_remote(array $queue_status, $action)
{
$state_data = $this->migration_state_manager->set_post_data();
$data = array();
$data['action'] = $action;
$data['intent'] = $state_data['intent'];
$data['stage'] = $state_data['stage'];
$data['remote_state_id'] = $state_data['migration_state_id'];
$data['sig'] = $this->http_helper->create_signature($data, $state_data['key']);
$data['queue_status'] = base64_encode(gzencode(json_encode($queue_status)));
$ajax_url = trailingslashit($state_data['url']) . 'wp-admin/admin-ajax.php';
$response = $this->remote_post_and_verify($ajax_url, $data);
return $response;
}
/**
* Fire POST at remote and check for the 'wpmdb_error' key in response
*
* @param string $ajax_url
* @param array $data
*
* @return array|bool|mixed|object|string
* @throws \Exception
*/
public function remote_post_and_verify($ajax_url, $data, $headers = array())
{
$requests_options = $this->get_requests_options();
$response = null;
try {
$response = \Requests::post($ajax_url, $headers, $data, $requests_options);
} catch (\Exception $e) {
$this->catch_general_error($e->getMessage());
}
$response_body = json_decode($response->body, true);
if (isset($response_body['wpmdb_error'])) {
throw new \Exception($response_body['body']);
}
return $response;
}
/**
* @param $msg
* @param array $data
*
* @return mixed
*/
public function ajax_error($msg, $data = array())
{
$this->error_log->log_error($msg, $data);
return $this->http->end_ajax(new \WP_Error('wpmdb_transfer_error', $msg));
}
/**
*
* Handles individual file transfer errors
*
* @param string $message
*
* @return array
*/
public function fire_transfer_errors($message)
{
error_log($message);
$this->error_log->log_error($message);
return [
'error' => true,
'message' => $message,
];
}
/**
*
*
* @param $message
*
* @return null
*/
public function catch_general_error($message)
{
$return = [
'wpmdb_error' => true,
'msg' => $message,
];
$this->error_log->log_error($message);
$this->http->end_ajax(json_encode($return));
}
/**
* @return array
*/
public function get_requests_options()
{
// Listen to SSL verify setting
$wpmdb_settings = $this->settings;
$sslverify = 1 === $wpmdb_settings['verify_ssl'];
$requests_options = [];
// Make Requests cURL transport wait 45s for timeouts
$hooks = new \Requests_Hooks();
$hooks->register(
'curl.before_send',
function ($handle) {
$remote_cookie = Persistence::getRemoteWPECookie();
if (false !== $remote_cookie) {
curl_setopt($handle, CURLOPT_COOKIE, 'wpe-auth=' . $remote_cookie);
}
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 45);
curl_setopt($handle, CURLOPT_TIMEOUT, 45);
curl_setopt($handle, CURLOPT_ENCODING, 'gzip,deflate');
}
);
$requests_options['hooks'] = $hooks;
if (!$sslverify) {
$requests_options['verify'] = false;
}
return $requests_options;
}
/**
* Check's that files migrated match the .manifest file. Always fires at the migration destination
*
* @param array $files
* @param string $stage
*
* @throws \Exception
*/
public function check_manifest($files, $stage)
{
$failures = [];
foreach ($files as $file) {
$file_path = Common_Util::get_stage_base_dir($stage);
if (!file_exists($file_path)) {
$failures[] = $file_path;
}
}
if (!empty($failures)) {
throw new \Exception(sprintf(__('The following files failed to transfer: <br> %s', 'wp-migrate-db'), implode('<br>', $failures)));
}
}
/**
* Merges a stored queue status (if exists) with the provided one.
*
* @param array $queue_status
* @param string $stage
* @param string $migration_state_id
* @return array
*/
public function concat_existing_remote_items($queue_status, $stage, $migration_state_id)
{
$stored_queue = $this->get_queue_status($stage, $migration_state_id);
if (false !== $stored_queue) {
$queue_status['total'] += $stored_queue['total'];
$queue_status['size'] += $stored_queue['size'];
$queue_status['manifest'] = array_merge($stored_queue['manifest'], $queue_status['manifest']);
$this->remove_tmp_folder($stage);
}
return $queue_status;
}
/**
* Saves queue data to the manifest file
*
* @param array $data
* @param $stage
* @param string $migration_state_id
* @param bool $full_site_export
*
* @return bool|int
* @throws \Exception
*/
public function save_queue_status(array $data, $stage, $migration_state_id, $full_site_export = false)
{
$tmp_path = $this->get_queue_tmp_path($stage, $full_site_export);
if (!$this->filesystem->mkdir($tmp_path)) {
throw new \Exception(sprintf(__('Unable to create folder for file transfers: %s'), $tmp_path));
}
$filename = $this->get_queue_manifest_file_name($migration_state_id);
$manifest = @file_put_contents($tmp_path . DIRECTORY_SEPARATOR . $filename, json_encode($data));
if (!$manifest) {
throw new \Exception(sprintf(__('Unable to create the transfer manifest file. Verify the web server can write to this file/folder: `%s`'), $tmp_path . DIRECTORY_SEPARATOR . '.manifest'));
}
return $manifest;
}
/**
* Get stored queue manifest array.
*
* @param string $stage
* @param string $migration_state_id
* @param bool $full_site_export
*
* @return array|false
*/
public function get_queue_status($stage, $migration_state_id, $full_site_export = false)
{
$filename = $this->get_queue_manifest_file_name($migration_state_id);
$tmp_path = $this->get_queue_tmp_path($stage, $full_site_export);
$file_path = $tmp_path . DIRECTORY_SEPARATOR . $filename;
$manifest = is_file($file_path) ? @file_get_contents($file_path) : false;
if (false !== $manifest) {
return json_decode($manifest, true);
}
return false;
}
/**
* Get queue tmp path.
*
* @param string $stage
* @param bool $full_site_export
* @return string
*/
private function get_queue_tmp_path($stage, $full_site_export = false)
{
//@todo avoid passing full_site_export down to here.
if ($full_site_export === true || $stage === 'media_files') {
return self::get_wp_uploads_dir();
}
return self::get_temp_dir($stage);
}
/**
* Get manifest file name.
*
* @param $migration_state_id
* @return string
*/
private function get_queue_manifest_file_name($migration_state_id)
{
return '.' . $migration_state_id . '-manifest';
}
public function cleanup_media_migration()
{
$uploads = self::get_wp_uploads_dir();
$this->remove_manifests($uploads);
return true;
}
/**
* Will look for a tmp folder to remove based on the $stage param (themes, plugins)
*
* @param $stage
*
* @return bool
*/
public function remove_tmp_folder($stage)
{
$fs = $this->filesystem;
if ($stage === 'media_files') {
return $this->cleanup_media_migration();
}
$tmp_folder = self::get_temp_dir($stage);
if ($fs->file_exists($tmp_folder)) {
if ($fs->is_dir($tmp_folder)) {
return $fs->rmdir($tmp_folder, true);
}
}
return true;
}
/**
*
* Verify a file is the correct size
*
* @param string $filepath
* @param int $expected_size
*
* @return bool
*/
public function verify_file($filepath, $expected_size)
{
if (!file_exists($filepath)) {
return false;
}
$filesystem_size = filesize($filepath);
if ($filesystem_size !== (int)$expected_size) {
return false;
}
return true;
}
public function enqueue_files($files, $queue_manager)
{
foreach ($files as $file) {
$queue_manager->enqueue_file($file);
}
}
/**
* Determine folder tranferred numbers for client.
*
* @param array $data
* @param int $bytes_transferred
* @param array $state_data
*
* @return array
*/
public function process_queue_data($data, $state_data, $bytes_transferred = 0)
{
$result_set = [];
if (empty($data)) {
return array($result_set, 0);
}
$stage = $state_data['stage'];
// Could be empty - stores progress of folder migrations between requests. Generally, the size of batch is 100 files and each file could be from a separate folder
$folder_transfer_status = get_site_option("wpmdb_folder_transfers_{$stage}_" . $state_data['migration_state_id']);
if (empty($folder_transfer_status)) {
$folder_transfer_status = [];
}
$total_transferred = 0;
$batch_size = 0;
foreach ($data as $key => $record) {
$is_chunked = isset($record['chunked']) && $record['chunked'];
$dirname = $record['folder_name'];
$keys = array_keys($result_set);
// This method is called in WPMDBPro_Theme_Plugin_Files_Local::ajax_initiate_file_migration()
// $bytes_transferred = 0 and we don't need to iterate over _all_ the files
if (0 === $bytes_transferred && \in_array($dirname, $keys)) {
continue;
}
if (0 !== $bytes_transferred) {
if (!isset($folder_transfer_status[$dirname])) {
$batch_size = 0;
$folder_transfer_status[$dirname] = [
'folder_transferred' => 0,
'folder_percent_transferred' => 0,
];
}
$item_size = $record['size'];
if ($is_chunked) {
$item_size = $record['chunk_size'];
}
$folder_transfer_status[$dirname]['folder_transferred'] += $item_size;
if (!$is_chunked) {
$batch_size += $item_size;
} else {
$batch_size = $item_size;
}
$transferred_percentage = $record['folder_size'] > 0 ? $folder_transfer_status[$dirname]['folder_transferred'] / $record['folder_size'] : 0;
$folder_transfer_status[$dirname]['folder_percent_transferred'] = $transferred_percentage;
}
$is_dir = $this->filesystem->is_dir($record['folder_abs_path'] . DIRECTORY_SEPARATOR . $dirname);
$result_set[$dirname] = [
'nice_name' => $record['nice_name'],
'relative_path' => DIRECTORY_SEPARATOR . $dirname,
'is_dir' => $is_dir,
'absolute_path' => $record['folder_abs_path'],
'item_size' => $record['size'],
'size' => $record['folder_size'],
'batch_size' => $batch_size,
'folder_transferred' => isset($folder_transfer_status[$dirname]['folder_transferred']) ? $folder_transfer_status[$dirname]['folder_transferred'] : 0,
'folder_percent_transferred' => isset($folder_transfer_status[$dirname]['folder_percent_transferred']) ? $folder_transfer_status[$dirname]['folder_percent_transferred'] : 0,
'total_transferred' => $bytes_transferred,
];
}
//Updates folder status transient
$this->update_folder_status($state_data, $result_set, $bytes_transferred);
// Maybe compute folder percent transferred here?
return $result_set;
}
/**
* @param array $state_data
* @param array $result_set
* @param int $bytes_transferred
*
* @return bool
*/
public function update_folder_status($state_data, $result_set, $bytes_transferred)
{
if (0 === $bytes_transferred) {
return false;
}
$folders_in_progress = [];
foreach ($result_set as $key => $folder) {
if ($folder['folder_transferred'] < $folder['size']) {
$folders_in_progress[$key] = $folder;
}
}
$stage = $state_data['stage'];
if (empty($folders_in_progress) && 0 !== $bytes_transferred) {
delete_site_option("wpmdb_folder_transfers_{$stage}_" . $state_data['migration_state_id']);
} else {
update_site_option("wpmdb_folder_transfers_{$stage}_" . $state_data['migration_state_id'], $folders_in_progress);
}
return true;
}
public function cleanup_temp_chunks($dir, $suffix)
{
$iterator = new DirectoryIterator($dir);
foreach ($iterator as $fileInfo) {
if (!$fileInfo->isDot()) {
$name = $fileInfo->getFilename();
if (preg_match("/(([a-z0-9]+-){5})$suffix/", $name) && $fileInfo->isFile()) {
$this->filesystem->unlink($dir . $name);
}
}
}
return null;
}
public function get_folder_name($local_path, $dest_path, $temp_path, $manifest)
{
$last = basename(str_replace('\\', '/', $local_path));
if ($this->filesystem->file_exists($temp_path . $last)) {
return $last;
}
foreach ($manifest as $key => $item) {
$manifest_item = explode(DIRECTORY_SEPARATOR, $item);
unset($manifest_item[count($manifest_item) - 1]);
$imploded = implode(DIRECTORY_SEPARATOR, $manifest_item);
if (stripos($item, 'style.css') !== false && stripos($local_path, $imploded) !== false) {
$pieces = explode(DIRECTORY_SEPARATOR, $item);
if (isset($pieces[1]) && $this->filesystem->file_exists($temp_path . $pieces[1])) {
return $pieces[1];
}
}
}
return false;
}
public function load_manifest($stage, $migration_id)
{
$filename = '.' . $migration_id . '-manifest';
$manifest_path = self::get_temp_dir($stage) . $filename;
$contents = file_get_contents($manifest_path);
if (!$contents) {
$this->http->end_ajax(new \WP_Error('wpmdb_load_manifest_failed', __("Failed to load manifest file.")));
}
$queue_info = json_decode($contents, true);
if (!$queue_info) {
$this->http->end_ajax(new \WP_Error('wpmdb_parse_manifest_failed', __("Failed to parse manifest file.")));
}
return $queue_info;
}
/**
* Process data
*
* @param array $data
*
* @return array
*/
public function process_file_data($data)
{
$result_set = [];
if (!empty($data)) {
foreach ($data as $size => $record) {
$display_path = $record->file['subpath'];
$record->file['relative_path'] = $display_path;
$result_set[] = $record->file;
}
}
return $result_set;
}
public static function get_wp_uploads_dir()
{
$upload_info = wp_get_upload_dir();
return $upload_info['basedir'];
}
/**
* @param $directory
*/
public function remove_manifests($directory)
{
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileInfo) {
if (!$fileInfo->isDot()) {
$name = $fileInfo->getFilename();
if (preg_match("/(([a-z0-9]+-){5})manifest/", $name) && $fileInfo->isFile()) {
$this->filesystem->unlink($directory . DIRECTORY_SEPARATOR . $name);
}
}
}
}
/**
*
* @return int
*/
public function get_transfer_bottleneck()
{
$bottleneck = $this->util->get_max_upload_size();
// Subtract 250 KB from min for overhead
$bottleneck -= 250000;
return $bottleneck;
}
/**
* Enables the bottleneck-ed recursive file scanner.
*/
public static function enable_scandir_bottleneck() {
add_filter('wpmdb_bottleneck_dir_scan', function ($bottleneck) {
return true;
});
}
/**
* @param string $base
*
* @return array
*/
public function is_tmp_folder_writable( $base = 'theme_files' ) {
$options_to_dirs = [
'theme_files' => 'themes',
'plugin_files' => 'plugins',
'muplugin_files' => 'muplugins',
'other_files' => 'others'
];
$tmp = self::get_temp_dir($options_to_dirs[$base]);
$test_file = $tmp . '/test.php';
$renamed_file = $tmp . '/test-2.php';
$return = [
'status' => true,
];
if ( ! $this->filesystem->mkdir( $tmp ) ) {
$message = sprintf( __( 'File transfer error - Unable to create a temporary folder. (%s)', 'wp-migrate-db' ), $tmp );
$this->error_log->log_error( $message );
return [
'status' => false,
'message' => $message,
];
}
if ( method_exists('WpeCommon', 'get_wpe_auth_cookie_value') ) {
return $return;
}
if ( ! $this->filesystem->touch( $test_file ) ) {
$message = sprintf( __( 'File transfer error - Unable to create a PHP file on the server. (%s)', 'wp-migrate-db' ), $test_file );
$this->error_log->log_error( $message );
return [
'status' => false,
'message' => $message,
];
}
if ( ! file_put_contents( $test_file, 'test' ) ) {
$message = sprintf( __( 'File transfer error - Unable to update file contents using using PHP\'s file_put_contents() function. (%s)', 'wp-migrate-db' ), $test_file );
$this->error_log->log_error( $message );
return [
'status' => false,
'message' => $message,
];
}
if ( ! rename( $test_file, $renamed_file ) ) {
$message = sprintf( __( 'File transfer error - Unable to move file to the correct location using PHP\'s rename() function. (%s)', 'wp-migrate-db' ), $renamed_file );
$this->error_log->log_error( $message );
return [
'status' => false,
'message' => $message,
];
}
//Clean up
if ( ! $this->remove_tmp_folder( $options_to_dirs[$base] ) ) {
$message = sprintf( __( 'File transfer error - Unable to delete file using PHP\'s unlink() function. (%s)', 'wp-migrate-db' ), $renamed_file );
$this->error_log->log_error( $message );
return [
'status' => false,
'message' => $message,
];
}
return $return;
}
/**
* Where to store files as they're being transferred
*
* @param string $stage
* @return bool|mixed|void
*/
public static function get_temp_dir($stage) {
$temp_dir = Common_Util::get_stage_base_dir($stage) . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
return apply_filters('wpmdb_transfers_temp_dir', $temp_dir);
}
/**
* Sanitizes a provided file path.
* If the filename includes a path, it will get split and only the last part will be sanitized.
*
* @param string $file_path
*
* @return string
*/
public static function sanitize_file_path($file_path) {
//split path
$split = explode(DIRECTORY_SEPARATOR, $file_path);
//sanitize last part
$file_path = array_pop($split);
$split[] = sanitize_file_name($file_path);
return implode(DIRECTORY_SEPARATOR, $split);
}
}