Skip to content

Instantly share code, notes, and snippets.

@florianbrinkmann
Created June 1, 2020 05:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save florianbrinkmann/b76a81a6a2af7f2d69b0d4f6ed1edb13 to your computer and use it in GitHub Desktop.
Save florianbrinkmann/b76a81a6a2af7f2d69b0d4f6ed1edb13 to your computer and use it in GitHub Desktop.
<?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