<?php | |
/** | |
* Main plugin code. | |
* | |
* @package FlorianBrinkmann\Copier | |
*/ | |
namespace FlorianBrinkmann\Copier; | |
use Symfony\Component\Filesystem\Exception\IOExceptionInterface; | |
use Symfony\Component\Filesystem\Filesystem; | |
use Symfony\Component\Finder\Finder; | |
/** | |
* Class Plugin | |
* | |
* @package FlorianBrinkmann\Copier | |
*/ | |
class Plugin { | |
/** | |
* Absolute path to the WordPress install. | |
* | |
* @var string | |
*/ | |
private $abspath = ''; | |
/** | |
* Absolute path where we want to copy the files to. | |
* | |
* @var string | |
*/ | |
private $dest = ''; | |
/** | |
* Name of destination directory. | |
* | |
* @var string | |
*/ | |
private $dest_dir_name = ''; | |
/** | |
* Array of default directories to exclude. | |
* | |
* @var array | |
*/ | |
private $default_exclude = []; | |
/** | |
* Array of additional to exclude. | |
* | |
* @var array | |
*/ | |
private $additional_exclude = []; | |
/** | |
* Path to status file. | |
* | |
* @var string | |
*/ | |
private $status_file; | |
/** | |
* Current file index. | |
* | |
* @var string | |
*/ | |
private $current_file_index = ''; | |
/** | |
* Current path. | |
* | |
* @var string | |
*/ | |
private $current_path = ''; | |
/** | |
* Tables of WordPress install. | |
* | |
* @var array | |
*/ | |
private $tables = []; | |
/** | |
* The current step of the process. | |
* | |
* @var string | |
*/ | |
private $step = ''; | |
/** | |
* Time limit in seconds. Default 30. | |
* | |
* @var int | |
*/ | |
private $time_limit = 30; | |
/** | |
* Unix timestamp of init event. | |
* | |
* @var int | |
*/ | |
private $timer; | |
/** | |
* Symfony filesystem object. | |
* | |
* @var Filesystem | |
*/ | |
private $filesystem; | |
/** | |
* List of directories and files. | |
* | |
* @var array | |
*/ | |
private $files_list; | |
public function init() { | |
// Set timer. | |
$this->timer = time(); | |
// Set filesystem property. | |
$this->filesystem = new Filesystem(); | |
$this->step = 'file-list-creation'; | |
// Set dest folder for copying. | |
$dest_dir_name = uniqid( 'uaas-copy-' ); | |
// Check if folder exists. If so, change name and check again. | |
// @todo: Only check for a limited time. | |
while ( $this->filesystem->exists( "$this->abspath/$dest_dir_name" ) ) { | |
$dest_dir_name = uniqid( 'uaas-copy-' ); | |
} | |
$this->dest_dir_name = $dest_dir_name; | |
$this->dest = trailingslashit( "$this->abspath{$dest_dir_name}" ); | |
// Create file list for cloning. | |
$this->default_exclude = [ 'wp-content/uploads', $this->dest_dir_name ]; | |
$this->create_file_list(); | |
// Create dest folder. | |
$this->filesystem->mkdir( $this->dest, 0755 ); | |
// Create status file. | |
$this->status_file = "{$this->dest}uaas-status.txt"; | |
$this->filesystem->dumpFile( $this->status_file, serialize( $this ) ); | |
// We have the files in the files_list property now, so we can clone! | |
$this->step = 'copying-files'; | |
$this->copy_files(); | |
} | |
/** | |
* Continue copying. | |
*/ | |
public function continue_copying() { | |
// Check for step. | |
if ( $this->step !== 'copying-files' ) { | |
return false; | |
} | |
// Set timer. | |
$this->timer = time(); | |
// Update file list. | |
$this->create_file_list(); | |
$this->copy_files(); | |
} | |
/** | |
* Create a list of the files that we want to copy. | |
*/ | |
private function create_file_list() { | |
$finder = new Finder(); | |
// @todo: check for modified uploads destination. | |
// @todo: ignore other directories like cache, backups, … | |
$exclude = array_merge( $this->default_exclude, $this->additional_exclude ); | |
$finder->files()->in( $this->abspath )->exclude( $exclude )->notPath( '/.*\/node_modules\/.*/' ); | |
if ( ! $finder->hasResults() ) { | |
// @todo: Add error, nothing found. | |
return; | |
} | |
$this->files_list = $finder; | |
} | |
/** | |
* Copy the files. | |
*/ | |
private function copy_files() { | |
// Loop the files list. | |
$already_processed = true; | |
foreach ( $this->files_list as $index => $file ) { | |
// Check if we already had that file. | |
if ( $this->current_file_index === '' ) { | |
$already_processed = false; | |
} else if ( $this->current_file_index === $index ) { | |
$already_processed = false; | |
} | |
if ( $already_processed ) { | |
continue; | |
} | |
$tmp = str_replace( '\\', '/', $file->getRelativePath() ); | |
// Check if current path is not empty. | |
if ( $this->current_path !== '' ) { | |
// Now check if the path of prev file | |
// does not exist in path of current file. | |
if ( strpos( $tmp, $this->current_path ) !== 0 ) { | |
$pushed_to_array = false; | |
// Add prev path to exclude in conditional cases. | |
// Add it if is wp-admin folder or a direct subfolder. | |
if ( strpos( $this->current_path, 'wp-admin' ) === 0 && substr_count( $this->current_path, '/' ) <= 1 ) { | |
array_push( $this->additional_exclude, untrailingslashit( $this->current_path ) ); | |
$pushed_to_array = true; | |
} | |
// Add it if is wp-content folder or up to two levels deeper. | |
if ( strpos( $this->current_path, 'wp-content' ) === 0 ) { | |
// Get first three directories of paths. | |
// https://stackoverflow.com/a/1935929/7774451 | |
$path_parts = explode( '/', $this->current_path, 4 ); | |
if ( isset ( $path_parts[3] ) ) { | |
unset( $path_parts[3] ); | |
} | |
$tmp_path_parts = explode( '/', $tmp, 4 ); | |
if ( isset ( $tmp_path_parts[3] ) ) { | |
unset( $tmp_path_parts[3] ); | |
} | |
// If both arrays would be the same, that means we are deeper than three subdirs. | |
// CHECK WHY THAT DOES NOT WORK FOR ANTISPAM-BEE AND ANTISPAM-BEE-3-0 | |
if ( $path_parts !== $tmp_path_parts ) { | |
// Push the path from $path_parts. | |
array_push( $this->additional_exclude, implode( '/', $path_parts ) ); | |
$pushed_to_array = true; | |
} | |
} | |
// Remove entries from exclude that are covered by more general rules. | |
if ( $pushed_to_array ) { | |
$filtered = array_filter( $this->additional_exclude, function( $var ) { | |
// Check if $var is equal with current_path. | |
if ( $var === untrailingslashit( $this->current_path ) ) { | |
return true; | |
} | |
// If $var contains $this->current_path, remove it. | |
if ( strpos( $var, untrailingslashit( $this->current_path ) ) === 0 ) { | |
return false; | |
} | |
return true; | |
} ); | |
$this->additional_exclude = $filtered; | |
} | |
} | |
} | |
$this->current_path = str_replace( '\\', '/', $file->getRelativePath() ); | |
$absolute_file_path = str_replace( '\\', '/', $file->getRealPath() ); | |
// Create dest path for file. | |
$dest_file_path = str_replace( $this->abspath, $this->dest, $absolute_file_path ); | |
// Copy the file. | |
try { | |
$this->filesystem->copy( $absolute_file_path, $dest_file_path ); | |
} catch ( IOExceptionInterface $exception ) { | |
// @todo: make something with the error. | |
} | |
// Check if we are near the self-defined script timeout limit. | |
if ( time() - $this->timer >= $this->time_limit - 1 ) { | |
$this->current_file_index = $index; | |
// Store the current object in a file. | |
$this->filesystem->dumpFile( $this->status_file, serialize( $this ) ); | |
// Add a new cron event in the near future to continue the copying. | |
wp_schedule_single_event( time(), 'flobn_uaas_continue_copying', [ $this->status_file ] ); | |
// Exit. | |
exit(); | |
} | |
} | |
error_log( 'Finished file copying' ); | |
} | |
public function set_abspath( string $abspath ) { | |
// Replace backslash with slash. | |
$abspath = str_replace( '\\', '/', $abspath ); | |
$this->abspath = trailingslashit( $abspath ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment