diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 18e5d23..e4663fb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -103,7 +103,7 @@ By Ronan: Fix issue with file timestamps not saving. #2851741 by vibrasphere: 'UTF8 is not supported by the MySQL server' - When database port was not set. #2732195 by szeidler: Fatal error: Call to a member function create() on a non- - object in backup_migrate\vendor\backupmigrate\core\src\Source\MySQLiSource.php + object in backup_migrate\vendor\Drupal\backup_migrate\Core\src\Source\MySQLiSource.php on line 48. By Ronan: Put in a check for mysqli extension. #2865949 by Dinu Rodnitchi, pmunch: Add Backup Destination: "Directory Path" @@ -116,7 +116,7 @@ Backup and Migrate 8.x-4.0-alpha2, 2017-04-04 #2809965 by szeidler: Restore - nothing happens #2833245: Error: Call to undefined method confSet() - prevents backup #2741245 by tobiberlin: On restore page: Notice: Undefined index: groups in - BackupMigrate\Drupal\Config\DrupalConfigHelper::addFieldsFromSchema(). + Drupal\backup_migrate\Drupal\Config\DrupalConfigHelper::addFieldsFromSchema(). #2741301 noise on destination page. #2820911 Web server detection failing for IIS. #2826107 by szeidler: Error msgs on settings page. diff --git a/backup_migrate.module b/backup_migrate.module index 911ed2f..f4624c5 100644 --- a/backup_migrate.module +++ b/backup_migrate.module @@ -5,9 +5,9 @@ * Primary hook implementations for Backup Migrate. */ -use BackupMigrate\Core\Config\Config; -use BackupMigrate\Core\Services\BackupMigrate; -use BackupMigrate\Core\Main\BackupMigrateInterface; +use Drupal\backup_migrate\Core\Config\Config; +use Drupal\backup_migrate\Core\Services\BackupMigrate; +use Drupal\backup_migrate\Core\Main\BackupMigrateInterface; use Drupal\backup_migrate\Entity\Destination; use Drupal\backup_migrate\Entity\Schedule; use Drupal\backup_migrate\Entity\Source; @@ -18,26 +18,18 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; use Drupal\Core\Link; -use BackupMigrate\Core\Service\TarArchiveReader; -use BackupMigrate\Core\Service\TarArchiveWriter; -use BackupMigrate\Drupal\Environment\DrupalSetMessageLogger; -use BackupMigrate\Drupal\Destination\DrupalBrowserDownloadDestination; -use BackupMigrate\Drupal\Destination\DrupalBrowserUploadDestination; -use BackupMigrate\Core\Filter\FileNamer; -use BackupMigrate\Core\Filter\CompressionFilter; -use BackupMigrate\Drupal\Filter\DrupalEncrypt; -use BackupMigrate\Drupal\Filter\DrupalUtils; -use BackupMigrate\Core\Filter\MetadataWriter; -use BackupMigrate\Core\File\TempFileManager; -use BackupMigrate\Drupal\File\DrupalTempFileAdapter; - -// Autoload the required classes. -// @todo Apparently this isn't working on Windows machines. -// @see https://www.drupal.org/node/2913021 -$loader = new Psr4ClassLoader(); -$loader->addPrefix('BackupMigrate\\Core\\', __DIR__ . '/lib/backup_migrate_core/src'); -$loader->addPrefix('BackupMigrate\\Drupal\\', __DIR__ . '/src'); -$loader->register(); +use Drupal\backup_migrate\Core\Service\TarArchiveReader; +use Drupal\backup_migrate\Core\Service\TarArchiveWriter; +use Drupal\backup_migrate\Drupal\Environment\DrupalSetMessageLogger; +use Drupal\backup_migrate\Drupal\Destination\DrupalBrowserDownloadDestination; +use Drupal\backup_migrate\Drupal\Destination\DrupalBrowserUploadDestination; +use Drupal\backup_migrate\Core\Filter\FileNamer; +use Drupal\backup_migrate\Core\Filter\CompressionFilter; +use Drupal\backup_migrate\Drupal\Filter\DrupalEncrypt; +use Drupal\backup_migrate\Drupal\Filter\DrupalUtils; +use Drupal\backup_migrate\Core\Filter\MetadataWriter; +use Drupal\backup_migrate\Core\File\TempFileManager; +use Drupal\backup_migrate\Drupal\File\DrupalTempFileAdapter; define('BACKUP_MIGRATE_MODULE_VERSION', '8.x-4.x-dev'); @@ -46,9 +38,9 @@ * * @param string $source_id * @param string|array $destination_id - * @param array $config + * @param array|null $config */ -function backup_migrate_perform_backup($source_id, $destination_id, array $config = []) { +function backup_migrate_perform_backup($source_id, $destination_id, $config = []) { try { // Retrieve the service. $bam = backup_migrate_get_service_object($config); @@ -68,10 +60,10 @@ function backup_migrate_perform_backup($source_id, $destination_id, array $confi * @param string $source_id * @param string|array $destination_id * @param string|null $file_id - * @param array $config * @param string $file_id + * @param array|null $config */ -function backup_migrate_perform_restore($source_id, $destination_id, $file_id = NULL, array $config = []) { +function backup_migrate_perform_restore($source_id, $destination_id, $file_id = NULL, $config = []) { try { // Retrieve the service. $bam = backup_migrate_get_service_object($config); @@ -89,20 +81,20 @@ function backup_migrate_perform_restore($source_id, $destination_id, $file_id = /** * Get a BackupMigrate service object. * - * @param array $config + * @param array|null $config * An array of configuration arrays, keyed by plugin id. - * @param array $options + * @param array|null $options * A keyed array of options. * - * @return \BackupMigrate\Core\Main\BackupMigrate + * @return \Drupal\backup_migrate\Core\Main\BackupMigrate */ -function backup_migrate_get_service_object(array $config = [], array $options = []) { +function backup_migrate_get_service_object($config = [], $options = []) { static $bam = NULL; // If the static cached object has not been loaded. if ($bam === NULL) { // Create the service object. - $bam = new \BackupMigrate\Core\Main\BackupMigrate(); + $bam = new \Drupal\backup_migrate\Core\Main\BackupMigrate(); // Allow other modules to alter the object. \Drupal::moduleHandler()->alter('backup_migrate_service_object', $bam, $options); @@ -121,10 +113,10 @@ function backup_migrate_get_service_object(array $config = [], array $options = * * Add the core Backup and Migrate plugins to the service object. * - * @param \BackupMigrate\Core\Main\BackupMigrateInterface $bam - * @param array $options + * @param \Drupal\backup_migrate\Core\Main\BackupMigrateInterface $bam + * @param array|null $options */ -function backup_migrate_backup_migrate_service_object_alter(BackupMigrateInterface &$bam, array $options = []) { +function backup_migrate_backup_migrate_service_object_alter(BackupMigrateInterface &$bam, $options = []) { $sources = $bam->sources(); $destinations = $bam->destinations(); $plugins = $bam->plugins(); diff --git a/backup_migrate.services.yml b/backup_migrate.services.yml index f0ab973..9c839a7 100644 --- a/backup_migrate.services.yml +++ b/backup_migrate.services.yml @@ -1,7 +1,7 @@ services: plugin.manager.backup_migrate_source: - class: BackupMigrate\Drupal\EntityPlugins\SourcePluginManager + class: Drupal\backup_migrate\Drupal\EntityPlugins\SourcePluginManager parent: default_plugin_manager plugin.manager.backup_migrate_destination: - class: BackupMigrate\Drupal\EntityPlugins\DestinationPluginManager + class: Drupal\backup_migrate\Drupal\EntityPlugins\DestinationPluginManager parent: default_plugin_manager diff --git a/lib/backup_migrate_core/README.md b/lib/backup_migrate_core/README.md deleted file mode 100644 index fe49733..0000000 --- a/lib/backup_migrate_core/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Backup and Migrate Core - -The core functionality for Backup and Migrate. - -Backup and Migrate Core is a PHP-based library which manages the backing up and restoring of resources such as databases and file directories. It is primarily intended for backing up content managed web sites and was originally written as [a Drupal module](https://www.drupal.org/project/backup_migrate). - -This library represents a ground up refactoring and abstraction which allows the core functionality to be used in plugins for other content management systems or for uses beyond CMS-managed websites. - -## Usage - -The following is a simplified version of how to call the library to perform a backup: - - [ - 'host' => '127.0.0.1', - 'database' => 'mydb', - 'user' => 'myuser', - 'password' => 'mypass', - 'port' => '8889', - ], - // Configure the destination. - 'mybackups' => [ - 'directory' => '~/mybackups', - ], - // Configure the compression filter. - 'compressor' => [ - 'compression' => 'gzip', - ], - // Configure the file namer. - 'name' => [ - 'filename' => 'backup', - 'timestamp' => true, - ], - ] - ); - - // Create a new Backup and Migrate object with this configuration. - $bam = new BackupMigrate(null, null, null, $config); - - // Add the database source. This will read the configuration with the same key - $bam->sources()->add('database1', new MySQLiSource()); - // Add the destination. - $bam->destinations()->add('mybackups', new DirectoryDestination()); - - // Add the filters. - $bam->plugins()->add('compression', new CompressionFilter()); - $bam->plugins()->add('name', new FileNamer()); - - // Backup from the 'database1' db to the 'mybackups' directory. - $bam->backup('databse1', 'mybackups'); - -## Reference Implementation -[Backup and Migrate CLI](https://github.com/backupmigrate/backup_migrate_cli) is a simple command-line tool which consumes the Backup and Migrate Core library. It serves as a simple reference implementation. - -## Concepts - -### Dependency Inversion -As much as possible, Backup and Migrate tries to embrace the [Dependency Inversion Principal](https://en.wikipedia.org/wiki/Dependency_inversion_principle). This means that Backup and Migrate Core relies on the consuming application to pass to it all of the pieces it needs to run. This allows the library to run in a wide variety of environments without requiring a lot of hacky internal business logic. This philosophy is balanced against the desire for a pleasant developer experience so that consuming the library does not an excess of tedious boilerplate glue code. - -### The BackupMigrate Object -This `\BackupMigrate\Core\Main\BackupMigrate` object is the main task-runner of the library. It is the primary object that a consuming application interacts with. It contains two primary operation methods: `backup()` and `restore()` which do exactly what you expect them to. The consuming application is responsible for injecting to this object the following: - -* All plugins (sources, destinations, filters) required to run. -* (Optional) The environment dependency injection container. -* (Optional) All necessary configuration. - -See: [Backup and Migrate](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Main) - -### Plugins -Plugins are the meat of the library. All of the actual work is done by plugins. Plugins come in three types: - -* **Sources** - Items which can be backed up and restored. (e.g: A MySQL database) -* **Destinations** - Places where backup files can be stored. (e.g: A directory on your server) -* **Filters** - Actions that can be performed on backup files after backup or before restore. (e.g: Gzip compression) - -While these three types of plugin are conceptually separate they are technically identical. - -See: [Plugins](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Plugin) - -##### Sources -Each backup and restore operation works on a single source. For simplicity more than one source may be added to the BackupMigrate object. The source to be backed up is identified by id when `backup()` or `restore()` is called. - -See: [Sources](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Source) - -##### Destinations -Destinations act the same way as sources. These are the places where the backup files are sent (during `backup()`) or from which they are loaded (during `restore()`). - -See: [Destinations](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Destination) - -##### Filters -Filters can alter backup files before `restore()` or after `backup()`. Unlike sources and destinations there can be many filters run per operation. - -#### Plugin Managers -A plugin manager maintains a list of injected plugins and configures them and injects services as needed. Consuming software interacts with the plugin manager by calling `plugins()` on the BackupMigrate object. This is the method used to inject plugins into the controller: - - // Create a new BackupMigrate controller. - $bam = new BackupMigrate(); - - // Add a new custom plugin with the id 'mycustomplugin' - $bam->plugins()->add('mycustomplugin', new CustomPlugin()); - -The controller also has a PluginManager for sources and one for destinations. - - // Add a source - $bam->sources()->add('source_id', new CustomSource()); - - // Add a destination - $bam->destinations()->add('destination_id', new CustomDestination()); - -### Configuration -Backup and Migrate Core has very little configuration management built in. It is the responsibility to inject the necessary configuration into the library as a `ConfigInterface` object. If no configuration object is provided then each plugin will use it's configuration defaults. - -See: [Configuration](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Config) - -### Services -Services are object that provide some global functionality such as logging or temporary file creation. Services are managed and automatically injected by the service manager. A consuming application can add services by passing them to the service manager of the `BackupMigrate` object: - - - // Create a new BackupMigrate controller. - $bam = new BackupMigrate(); - - // Add a new custom plugin with the id 'mycustomplugin' - $bam->services()->add('Logger', new MyCustomLogger()); - -See: [Configuration](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Services) diff --git a/lib/backup_migrate_core/src/Config/Config.php b/lib/backup_migrate_core/src/Config/Config.php deleted file mode 100644 index 201d690..0000000 --- a/lib/backup_migrate_core/src/Config/Config.php +++ /dev/null @@ -1,93 +0,0 @@ -fromArray($init->toArray()); - } - elseif (is_array($init)) { - $this->fromArray($init); - } - } - - /** - * Get a setting value. - * - * @param string $key - * The key for the setting. - * @param mixed $default - * The default to return if the value does not exist. - * - * @return mixed - * The value of the setting. - */ - public function get($key, $default = NULL) { - return $this->keyIsSet($key) ? $this->config[$key] : $default; - } - - /** - * Set a setting value. - * - * @param string $key - * The key for the setting. - * @param mixed $value - * The value for the setting. - */ - public function set($key, $value) { - $this->config[$key] = $value; - } - - /** - * Determine if the given key has had a value set for it. - * - * @param string $key - * The array key to check for. - * - * @return bool - * Whether the key is defined. - */ - public function keyIsSet($key) { - return isset($this->config[$key]); - } - - /** - * Get all settings as an associative array. - * - * @return array - * All of the settings in this profile. - */ - public function toArray() { - return $this->config; - } - - /** - * Set all from an array. - * - * @param array $values - * An associative array of settings. - */ - public function fromArray($values) { - $this->config = $values; - } - -} diff --git a/lib/backup_migrate_core/src/Config/ConfigInterface.php b/lib/backup_migrate_core/src/Config/ConfigInterface.php deleted file mode 100644 index c302a62..0000000 --- a/lib/backup_migrate_core/src/Config/ConfigInterface.php +++ /dev/null @@ -1,53 +0,0 @@ -init = $init; - - // Set the config to a blank object to populate all values with the initial - // and default values. - $this->setConfig(new Config()); - } - - /** - * Set the configuration for all plugins. - * - * @param ConfigInterface $config - * A configuration object containing only configuration for all plugins - */ - public function setConfig(ConfigInterface $config) { - // Set the configuration object to the one passed in. - $this->config = $config; - - // Add the init/default values to the config object so they will always exist. - // @todo Make this cascade happen when the config key is requested. - // That will allow read-only or runtime generation config object to be passed - // This would work by creating a CascadeConfig object which takes an array - // of ConfigInterface objects and queries each in order to find the given key. - $defaults = $this->configDefaults(); - $init = $this->init; - foreach ([$init, $defaults] as $config_object) { - foreach ($config_object->toArray() as $key => $value) { - if (!$this->config->keyIsSet($key)) { - $this->config->set($key, $value); - } - } - } - } - - /** - * Get the configuration object for this item. - * - * @return \BackupMigrate\Core\Config\ConfigInterface - */ - public function config() { - return $this->config ? $this->config : new Config(); - } - - /** - * Get the default values for the plugin. - * - * @return \BackupMigrate\Core\Config\Config - */ - public function configDefaults() { - return new Config(); - } - - /** - * Get a default (blank) schema. - * - * @param array $params - * The parameters including: - * - operation - The operation being performed, will be one of: - * - 'backup': Configuration needed during a backup operation - * - 'restore': Configuration needed during a restore - * - 'initialize': Core configuration always needed by this item - * - * @return array - */ - public function configSchema(array $params = []) { - return []; - } - - /** - * Get any validation errors in the config. - * - * @param array $params - * - * @return array - */ - public function configErrors($params = []) { - $out = []; - - // Do some basic validation based on length and regex matching. - $schema = $this->configSchema($params); - - // Check each specified field. - foreach ($schema['fields'] as $key => $field) { - $value = $this->confGet($key); - - // Check if it's required. - if (!empty($field['required']) && empty($value)) { - $out[] = new ValidationError($key, $this->t('%title is required.'), ['%title' => $field['title']]); - } - - // Check it for length. - if (!empty($field['min_length']) && strlen($value) < $field['min_length']) { - $out[] = new ValidationError($key, $this->t('%title must be at least %count characters.'), ['%title' => $field['title'], '%count' => $field['min_length']]); - } - if (!empty($field['max_length']) && strlen($value) > $field['max_length']) { - $out[] = new ValidationError($key, $this->t('%title must be at no more than %count characters.'), ['%title' => $field['title'], '%count' => $field['max_length']]); - } - - // Check for the regular expression match. - if (!empty($field['must_match']) && !preg_match($field['must_match'], $value)) { - if (!empty($field['must_match_error'])) { - $out[] = new ValidationError($key, $field['must_match_error'], ['%title' => $field['title']]); - } - else { - $out[] = new ValidationError($key, $this->t('%title contains invalid characters.'), ['%title' => $field['title']]); - } - } - } - return $out; - } - - /** - * Get a specific value from the configuration. - * - * @param string $key The configuration object key to retrieve. - * - * @return mixed The configuration value. - */ - public function confGet($key) { - return $this->config()->get($key); - } - -} diff --git a/lib/backup_migrate_core/src/Config/README.md b/lib/backup_migrate_core/src/Config/README.md deleted file mode 100644 index 323224b..0000000 --- a/lib/backup_migrate_core/src/Config/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration - -Backup and Migrate core is configured by the consuming software when the library is instantiated using a `\BackupMigrate\Core\Config\ConfigInterface` object. This object is a simple key-value store which should contain the configuration for each of the available plugins (sources, destinations and filters). Each plugin should have it's own entry in the config object which contains an array of all of the configuration for that item. The key for this entry must be the same as the key assigned to the plugin when it is added to the `BackupMigrate` object using `->plugins()->add()`. - -Any object that implements the `\BackupMigrate\Core\Config\ConfigInterface` may be used to configure Backup and Migrate. For example, a consuming application may want to implement a class that directly accesses the application's persistence layer to retrieve configuration values. In many cases, however the simple default `\BackupMigrate\Core\Config\Config` will suffice. - -## The Config Class -The built in `\BackupMigrate\Core\Config\Config` is a simple implementation of the configuration interface which can be instantiated using a PHP associative array: - - [ - 'host' => '127.0.0.1', - 'database' => 'mydb', - 'user' => 'myuser', - 'password' => 'mypass', - 'port' => '8889', - ], - // Configure the compression filter. - 'compressor' => [ - 'compression' => 'gzip', - ], - // Add more filter, source and destination configuration. - ] - ); - - $plugins = new PluginManager(); - - // Add the database source. This will read the configuration with the same key ('database1') - plugins->add( - 'database1', - new \BackupMigrate\Core\Source\MySQLiSource() - ); - // Add the compression plugin. - plugins->add( - 'compressor', - new \BackupMigrate\Core\Filter\CompressionFilter() - ); - // Add more filters and a destination. - ... - - - // Create a new Backup and Migrate object with this configuration. - $bam = new BackupMigrate($plugins); - - $bam->backup('database1', 'somedestination'); - -## Initial Config vs. Run-time Config ## - -A plugin may have two types of configuration: initial configuration, added when the plugin is created, and run-time configuration, added later by the plugin manager. Initial configuration can be overriden by run-time configuration but it cannot be overwritten by run-time config. That means that you can reconfigure plugins after the plugin manager has been created but the initial configuration will not be permanently overwriten. - -An example that illustrates the difference is a database source plugin. The database connection information should not change per operation and should be considered initial configuration. The list of tables to exclude during a backup, or whether the tables should be locked during a restore may change from run to run and should be run-time configuration. - -To specify initial configuration pass it to the plugin's constructor: - - // The db credentials are passed in to the constructor and are permanent. - $plugins->add( - 'main_database', - new MySQLiSource(new Config([ - 'database' => '...', - 'username' => '...', - ... - ]) - ); - - // Setting this configuration will not overwrite the db credentials. - $plugins->setConfig(new Config([ - 'main_database' => [ - 'exclude_tables' => [...], - ]); - diff --git a/lib/backup_migrate_core/src/Config/ValidationError.php b/lib/backup_migrate_core/src/Config/ValidationError.php deleted file mode 100644 index a795a22..0000000 --- a/lib/backup_migrate_core/src/Config/ValidationError.php +++ /dev/null @@ -1,68 +0,0 @@ -field_key = $field_key; - $this->message = $message; - $this->replacement = $replacement; - } - - /** - * @return string - */ - public function getMessage() { - return $this->message; - } - - /** - * @return array - */ - public function getReplacement() { - return $this->replacement; - } - - /** - * @return string - */ - public function getFieldKey() { - return $this->field_key; - } - - /** - * String representation of the exception. - * - * @return string the string representation of the exception. - */ - public function __toString() { - return strtr($this->getMessage(), $this->getReplacement()); - } - -} diff --git a/lib/backup_migrate_core/src/Config/ValidationErrorInterface.php b/lib/backup_migrate_core/src/Config/ValidationErrorInterface.php deleted file mode 100644 index a5b3a1a..0000000 --- a/lib/backup_migrate_core/src/Config/ValidationErrorInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Content-Disposition', 'value' => 'attachment; filename="' . $file->getFullName() . '"'], - ['key' => 'Cache-Control', 'value' => 'no-cache'], - ]; - - // Set a mime-type header. - if ($mime = $file->getMeta('mimetype')) { - $headers[] = ['key' => 'Content-Type', 'value' => $mime]; - } - else { - // Get the mime type for this file if possible. - $mime = 'application/octet-stream'; - $mime = $this->plugins()->call('alterMime', $mime, ['ext' => $file->getExtLast()]); - - $headers[] = ['key' => 'Content-Type', 'value' => $mime]; - } - - // In some circumstances, web-servers will double compress gzipped files. - // This may help aleviate that issue by disabling mod-deflate. - if ($file->getMeta('mimetype') == 'application/x-gzip') { - if (function_exists('apache_setenv')) { - apache_setenv('no-gzip', '1'); - } - $headers[] = ['key' => 'Content-Encoding', 'value' => 'gzip']; - } - if ($size = $file->getMeta('filesize')) { - $headers[] = ['key' => 'Content-Length', 'value' => $size]; - } - - // Suppress the warning you get when the buffer is empty. - @ob_end_clean(); - - if ($file->openForRead()) { - foreach ($headers as $header) { - // To prevent HTTP header injection, we delete new lines that are - // not followed by a space or a tab. - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - $header['value'] = preg_replace('/\r?\n(?!\t| )/', '', $header['value']); - header($header['key'] . ': ' . $header['value']); - } - // Transfer file in 1024 byte chunks to save memory usage. - while ($data = $file->readBytes(1024 * 512)) { - print $data; - } - $file->close(); - } - // @todo Throw exception. - } - - /** - * {@inheritdoc} - */ - public function checkWritable() { - // Check that we're running as a web process via a browser. - // @todo we could check if the 'HTTP_ACCEPT' header contains the right mime but that is probably overkill. - if (!isset($_SERVER['REQUEST_METHOD'])) { - throw new DestinationNotWritableException( - "The download destination only works when accessed through a http client." - ); - } - } - -} diff --git a/lib/backup_migrate_core/src/Destination/DebugDestination.php b/lib/backup_migrate_core/src/Destination/DebugDestination.php deleted file mode 100644 index ba16415..0000000 --- a/lib/backup_migrate_core/src/Destination/DebugDestination.php +++ /dev/null @@ -1,74 +0,0 @@ -confGet('format') == 'html') { - print '
';
- }
-
- // Output the metadata.
- if ($this->confGet('showmeta')) {
- print "---------------------\n";
- print "Metadata: \n";
- print_r($file->getMetaAll());
- print "---------------------\n";
- }
-
- // Output the body.
- if ($this->confGet('showbody')) {
- print "---------------------\n";
- print "Body: \n";
-
- $max = $this->confGet('maxbody');
- $chunk = min($max, 1024);
- if ($file->openForRead()) {
- // Transfer file in 1024 byte chunks to save memory usage.
- while ($max > 0 && $data = $file->readBytes($chunk)) {
- print $data;
- $max -= $chunk;
- }
- $file->close();
- }
- print "---------------------\n";
- }
-
- // Quick and dirty way to html format this output.
- if ($this->confGet('format') == 'html') {
- print '';
- }
-
- exit;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'showmeta' => TRUE,
- 'showbody' => TRUE,
- 'maxbody' => 1024 * 16,
- 'format' => 'text',
- ]);
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Destination/DestinationBase.php b/lib/backup_migrate_core/src/Destination/DestinationBase.php
deleted file mode 100644
index bbb7cf5..0000000
--- a/lib/backup_migrate_core/src/Destination/DestinationBase.php
+++ /dev/null
@@ -1,99 +0,0 @@
-_saveFile($file);
- $this->_saveFileMetadata($file);
- }
-
- /**
- * {@inheritdoc}
- */
- public function loadFileMetadata(BackupFileInterface $file) {
- // If this file is already loaded, simply return it.
- // @todo fix this inappropriate use of file metadata.
- if (!$file->getMeta('metadata_loaded')) {
- $metadata = $this->_loadFileMetadataArray($file);
- $file->setMetaMultiple($metadata);
- $file->setMeta('metadata_loaded', TRUE);
- }
- return $file;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteFile($id) {
- return $this->_deleteFile($id);
- }
-
- /**
- * {@inheritdoc}
- */
- public function isRemote() {
- return FALSE;
- }
-
- /**
- * {@inheritdoc}
- */
- public function checkWritable() {
- throw new DestinationNotWritableException('The specified destination cannot be written to.');
- }
-
- /**
- * Do the actual delete for a file.
- *
- * @param string $id The id of the file to delete.
- */
- abstract protected function _deleteFile($id);
-
- /**
- * Do the actual file save. Should take care of the actual creation of a file
- * in the destination without regard for metadata.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- */
- abstract protected function _saveFile(BackupFileReadableInterface $file);
-
- /**
- * Do the metadata save. This function is called to save the data file AND
- * the metadata sidecar file.
- *
- * @param \BackupMigrate\Core\File\BackupFileInterface $file
- */
- abstract protected function _saveFileMetadata(BackupFileInterface $file);
-
- /**
- * Load the actual metadata for the file.
- *
- * @param \BackupMigrate\Core\File\BackupFileInterface $file
- */
- abstract protected function _loadFileMetadataArray(BackupFileInterface $file);
-
-}
diff --git a/lib/backup_migrate_core/src/Destination/DestinationInterface.php b/lib/backup_migrate_core/src/Destination/DestinationInterface.php
deleted file mode 100644
index eb3715a..0000000
--- a/lib/backup_migrate_core/src/Destination/DestinationInterface.php
+++ /dev/null
@@ -1,14 +0,0 @@
-_saveFile($file);
- $this->_saveFileMetadata($file);
- }
-
- /**
- * {@inheritdoc}
- */
- public function checkWritable() {
- $this->checkDirectory();
- }
-
- /**
- * Get a definition for user-configurable settings.
- *
- * @param array $params
- *
- * @return array
- */
- public function configSchema(array $params = []) {
- $schema = [];
-
- // Init settings.
- if ($params['operation'] == 'initialize') {
- $schema['fields']['directory'] = [
- 'type' => 'text',
- 'title' => $this->t('Directory Path'),
- ];
- }
-
- return $schema;
- }
-
-
- /**
- * Do the actual file save. This function is called to save the data file AND
- * the metadata sidecar file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- *
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- */
- function _saveFile(BackupFileReadableInterface $file) {
- // Check if the directory exists.
- $this->checkDirectory();
-
- copy($file->realpath(), $this->idToPath($file->getFullName()));
- // @todo use copy/unlink if the temp file and the destination do not share a stream wrapper.
- }
-
- /**
- * Check that the directory can be used for backup.
- *
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- */
- protected function checkDirectory() {
- $dir = $this->confGet('directory');
-
- // Check if the directory exists.
- if (!file_exists($dir)) {
- throw new DestinationNotWritableException(
- "The backup file could not be saved to '%dir' because it does not exist.",
- ['%dir' => $dir]
- );
- }
-
- // Check if the directory is writable.
- if (!is_writable($this->confGet('directory'))) {
- throw new DestinationNotWritableException(
- "The backup file could not be saved to '%dir' because Backup and Migrate does not have write access to that directory.",
- ['%dir' => $dir]
- );
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFile($id) {
- if ($this->fileExists($id)) {
- $out = new BackupFile();
- $out->setMeta('id', $id);
- $out->setFullName($id);
- return $out;
- }
- return NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function loadFileForReading(BackupFileInterface $file) {
- // If this file is already readable, simply return it.
- if ($file instanceof BackupFileReadableInterface) {
- return $file;
- }
-
- $id = $file->getMeta('id');
- if ($this->fileExists($id)) {
- return new ReadableStreamBackupFile($this->idToPath($id));
- }
- return NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function listFiles() {
- $dir = $this->confGet('directory');
- $out = [];
-
- // Get the entire list of filenames.
- $files = $this->getAllFileNames();
-
- foreach ($files as $file) {
- $filepath = $dir . '/' . $file;
- $out[$file] = new ReadableStreamBackupFile($filepath);
- }
-
- return $out;
- }
-
- /**
- * {@inheritdoc}
- */
- public function queryFiles(array $filters = [], $sort = 'datestamp', $sort_direction = SORT_DESC, $count = 100, $start = 0) {
- // Get the full list of files.
- $out = $this->listFiles($count + $start);
- foreach ($out as $key => $file) {
- $out[$key] = $this->loadFileMetadata($file);
- }
-
- // Filter the output.
- if ($filters) {
- $out = array_filter($out, function($file) use ($filters) {
- foreach ($filters as $key => $value) {
- if ($file->getMeta($key) !== $value) {
- return FALSE;
- }
- }
- return TRUE;
- });
- }
-
- // Sort the files.
- if ($sort && $sort_direction) {
- uasort($out, function ($a, $b) use ($sort, $sort_direction) {
- if ($sort_direction == SORT_DESC) {
- return $b->getMeta($sort) < $b->getMeta($sort);
- }
- else {
- return $b->getMeta($sort) > $b->getMeta($sort);
- }
- });
- }
-
- // Slice the return array.
- if ($count || $start) {
- $out = array_slice($out, $start, $count);
- }
-
- return $out;
- }
-
- /**
- * @return int The number of files in the destination.
- */
- public function countFiles() {
- $files = $this->getAllFileNames();
- return count($files);
- }
-
- /**
- * {@inheritdoc}
- */
- public function fileExists($id) {
- return file_exists($this->idToPath($id));
- }
-
- /**
- * {@inheritdoc}
- */
- public function _deleteFile($id) {
- if ($file = $this->getFile($id)) {
- if ($file = $this->loadFileForReading($file)) {
- return unlink($file->realpath());
- }
- }
- return FALSE;
- }
-
- /**
- * Return a file path for the given file id.
- *
- * @param $id
- *
- * @return string
- */
- protected function idToPath($id) {
- return rtrim($this->confGet('directory'), '/') . '/' . $id;
- }
-
- /**
- * Get the entire file list from this destination.
- *
- * @return array
- */
- protected function getAllFileNames() {
- $files = [];
-
- // Read the list of files from the directory.
- $dir = $this->confGet('directory');
-
- /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
- $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
- $scheme = $stream_wrapper_manager->isValidScheme($dir);
-
- // Ensure the stream is configured.
- if (!$stream_wrapper_manager->isValidScheme($scheme)) {
- \Drupal::messenger()->addMessage(t('Your @scheme stream is not configured.', [
- '@scheme' => $scheme . '://'
- ]), 'warning');
- return $files;
- }
-
- if ($handle = opendir($dir)) {
- while (FALSE !== ($file = readdir($handle))) {
- $filepath = $dir . '/' . $file;
- // Don't show hidden, unreadable or metadata files.
- if (substr($file, 0, 1) !== '.' && is_readable($filepath) && substr($file, strlen($file) - 5) !== '.info') {
- $files[] = $file;
- }
- }
- }
-
- return $files;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Destination/ListableDestinationInterface.php b/lib/backup_migrate_core/src/Destination/ListableDestinationInterface.php
deleted file mode 100644
index 2706993..0000000
--- a/lib/backup_migrate_core/src/Destination/ListableDestinationInterface.php
+++ /dev/null
@@ -1,62 +0,0 @@
-destinations()->add('destination1', new MyDestinationPlugin());
-
-A single Backup and Migrate instance can have more than one destination of a given type. Each destination will have a unique key that will be used to pass the configuration to the destination object as well as to specify the destination(s) when running a `backup()` or `restore()` operation. Only one destination will be used during each backup or restore operation.
diff --git a/lib/backup_migrate_core/src/Destination/ReadableDestinationInterface.php b/lib/backup_migrate_core/src/Destination/ReadableDestinationInterface.php
deleted file mode 100644
index 792eee9..0000000
--- a/lib/backup_migrate_core/src/Destination/ReadableDestinationInterface.php
+++ /dev/null
@@ -1,56 +0,0 @@
-getFullName();
- $filename = $id . '.info';
- if ($this->fileExists($filename)) {
- $meta_file = $this->getFile($filename);
- $meta_file = $this->loadFileForReading($meta_file);
- $info = $this->_INIToArray($meta_file->readAll());
- }
- return $info;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function _saveFileMetadata(BackupFileInterface $file) {
- // Get the file metadata and convert to INI format.
- $meta = $file->getMetaAll();
- $ini = $this->_arrayToINI($meta);
-
- // Create an info file.
- $meta_file = $this->getTempFileManager()->pushExt($file, 'info');
- $meta_file->write($ini);
-
- // Save the metadata.
- $this->_saveFile($meta_file);
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteFile($id) {
- $this->_deleteFile($id);
- $this->_deleteFile($id . '.info');
- }
-
- /**
- * Parse an INI file's contents.
- *
- * For simplification this function only parses the simple subset of INI
- * syntax generated by SidecarMetadataDestinationTrait::_arrayToINI();
- *
- * @param $ini
- *
- * @return array
- */
- protected function _INIToArray($ini) {
- $out = [];
- $lines = explode("\n", $ini);
- foreach ($lines as $line) {
- $line = trim($line);
- // Skip comments (even though there probably won't be any.
- if (substr($line, 0, 1) == ';') {
- continue;
- }
-
- // Match the key and value using a simplified syntax.
- $matches = [];
- if (preg_match('/^([^=]+)\s?=\s?"(.*)"$/', $line, $matches)) {
- $key = $matches[1];
- $val = $matches[2];
-
- // Break up a key in the form a[b][c]
- $keys = explode('[', $key);
- $insert = &$out;
- foreach ($keys as $part) {
- $part = trim($part, ' ]');
- $insert[$part] = '';
- $insert = &$insert[$part];
- }
- $insert = $val;
- }
- }
-
- return $out;
- }
-
- /**
- * @param $data
- * @param string $prefix
- * @return string
- */
- protected function _arrayToINI($data, $prefix = '') {
- $content = "";
- foreach ($data as $key => $val) {
- if ($prefix) {
- $key = $prefix . '[' . $key . ']';
- }
- if (is_array($val)) {
- $content .= $this->_arrayToINI($val, $key);
- }
- else {
- $content .= $key . " = \"" . $val . "\"\n";
- }
- }
- return $content;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Destination/StreamDestination.php b/lib/backup_migrate_core/src/Destination/StreamDestination.php
deleted file mode 100644
index eff0531..0000000
--- a/lib/backup_migrate_core/src/Destination/StreamDestination.php
+++ /dev/null
@@ -1,73 +0,0 @@
-confGet('streamuri');
- if ($fp_out = fopen($stream_uri, 'w')) {
- $file->openForRead();
- while ($data = $file->readBytes(1024 * 512)) {
- fwrite($fp_out, $data);
- }
- fclose($fp_out);
- $file->close();
- }
- else {
- throw new \Exception("Cannot open the file $stream_uri for writing");
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function checkWritable() {
- $stream_uri = $this->confGet('streamuri');
-
- // The stream must exist.
- if (!file_exists($stream_uri)) {
- throw new DestinationNotWritableException('The file stream !uri does not exist.', ['%uri' => $stream_uri]);
- }
-
- // The stream must be writable.
- if (!file_exists($stream_uri)) {
- throw new DestinationNotWritableException('The file stream !uri cannot be written to.', ['%uri' => $stream_uri]);
- }
- }
- /**
- * {@inheritdoc}
- */
- public function getFile($id) {
- return NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function loadFileMetadata(BackupFileInterface $file) {
- return $file;
- }
-
- /**
- * {@inheritdoc}
- */
- public function loadFileForReading(BackupFileInterface $file) {
- return $file;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Destination/WritableDestinationInterface.php b/lib/backup_migrate_core/src/Destination/WritableDestinationInterface.php
deleted file mode 100644
index 8174807..0000000
--- a/lib/backup_migrate_core/src/Destination/WritableDestinationInterface.php
+++ /dev/null
@@ -1,22 +0,0 @@
-replacement = $replacement;
- $this->message_raw = $message;
-
- // Send the replaced message to the parent constructor to act as normal in most cases.
- parent::__construct(strtr($message, $replacement), $code);
- }
-
- /**
- * Get the unmodified message with replacement tokens.
- *
- * @return null|string
- */
- public function getMessageRaw() {
- return $this->message_raw;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Exception/DestinationNotWritableException.php b/lib/backup_migrate_core/src/Exception/DestinationNotWritableException.php
deleted file mode 100644
index 294fcdd..0000000
--- a/lib/backup_migrate_core/src/Exception/DestinationNotWritableException.php
+++ /dev/null
@@ -1,14 +0,0 @@
-metadata[$key]) ? $this->metadata[$key] : NULL;
- }
-
- /**
- * Set a metadata value.
- *
- * @param string $key The key for the metadata item.
- * @param mixed $value The value for the metadata item.
- */
- public function setMeta($key, $value) {
- $this->metadata[$key] = $value;
- }
-
- /**
- * Set a metadata value.
- *
- * @param array $values An array of key-value pairs for the file metadata.
- */
- public function setMetaMultiple($values) {
- foreach ((array) $values as $key => $value) {
- $this->setMeta($key, $value);
- }
- }
-
- /**
- * Get all metadata.
- *
- * @param array $values An array of key-value pairs for the file metadata.
- *
- * @return array
- */
- public function getMetaAll() {
- return $this->metadata;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setName($name) {
- $this->name = $name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFullName() {
- return rtrim($this->name . '.' . implode('.', $this->getExtList()));
- }
-
- /**
- * {@inheritdoc}
- */
- public function setFullName($fullname) {
- // Break the file name into name and extension array.
- $parts = explode('.', $fullname);
- $this->setName(array_shift($parts));
- $this->setExtList($parts);
- }
-
-
- /**
- * {@inheritdoc}
- */
- public function getExtList() {
- return $this->ext;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getExtLast() {
- return end($this->ext);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getExt() {
- return implode('.', $this->getExtList());
- }
-
- /**
- * @param array $ext
- * The list of file extensions for the file* The list of file extensions for the file
- */
- public function setExtList($ext) {
- $this->ext = array_filter($ext);
- }
-
-}
diff --git a/lib/backup_migrate_core/src/File/BackupFileInterface.php b/lib/backup_migrate_core/src/File/BackupFileInterface.php
deleted file mode 100644
index 316cd6b..0000000
--- a/lib/backup_migrate_core/src/File/BackupFileInterface.php
+++ /dev/null
@@ -1,112 +0,0 @@
- '~/mybackups']);
- $file = $destination->getFile('databse.mysql');
-
- // This object has metadata but the contents cannot neccessarily be read.
- if ($file && $file->getMeta('filesize') > 1000) {
-
- // To read the file we must allow the destination to load it for us if needed.
- $file = $destination->loadFileForReading($file);
-
- // The file contents should now be available to us.
- if ($file) {
- echo $file->readAll();
- }
- }
-
-### BackupFileWriteableInterface
-This subclass can be read from AND written to. Writable files in Backup and Migrate are always temporary files and must be created by the TempFileManager. Source plugins will create an empty temporary file to write the backup to while file filter plugins (like compression or encryption filters) will create a new temporary file and copy the contents from the input file to the new output file. The file that results at the end of the plugin chain will either be used to restore to the source (restore operation) or sent to a destination to be persisted (backup operation). Because plugins are responsible for creating new temporary writable files as needed, they should never require a writable file as input or promise one as a return value.
-
-## The Temporary File Manager
-All writable files must be created by the Temporary File Manager. This class can create a new blank file with a given file extension. The standard flow of file filters is a chain where one filter hands a file to the next which copies the data to a new file and hands that on. For example, the MySQL source generates a new database dump file which gets handed to an encryption filter which copies the metadata to a new file containing the encrypted data. That file is then passed to a compression filter which creates a new compressed version of the file which is finally handed off to a destination for saving. At each step along the way a new file is created with an a new extension appended to the end:
-
- file.mysql -> file.mysql.aes -> file.mysql.aes.gz
-
-To facilitate this the Temporary File Manager takes care of the details of copying file metadata and provisioning a new temporary file with the new file extension to write the modified data to. A compressor plugin might do something like this:
-
- function afterBackup($file_in) {
- // Get a new file with '.gz' added to the end of the filename.
- $file_out = $this->getTempFileManager()->pushExt($file_in, 'gz');
- if ($this->doCompress($file_in, $file_out)) {
- return $file_out;
- }
- // Compression failed, return the original
- return $file_in;
- }
-
-Similarly `$this->getTempFileManager()->popExt()` will pull the last item from the file extension and return a blank file for decompression prior to import.
-
-See [Plugins](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Plugin) for details on how to make the Temporary File Manager accessible within a plugin.
-
-### The Temporary File Adapter ###
-While the file manager takes care of the metadata of temporary files, it cannot provision actual on-disk files to write to. That is because that operation will be different depending on where the code is run and is therefore the responsibility of the [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment) object. The environment provides a service called called the Temporary File Adaptor (an object whose class which implements `\BackupMigrate\Core\Services\TempFileAdapterInterface`). The job of this class is to provision actual temporary files in the host operating system that can be written to and read from. That service is also responsible for tracking all of the files that have been created during the running of an operation and deleting those files when the operation completes. Backup and Migrate core comes with a basic adapter which accepts any writable directory as an argument and creates new temporary files within that directory. This implementation should suffice for most consuming software but can be replaced with another adapter if needed.
-
-See: [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment)
diff --git a/lib/backup_migrate_core/src/File/ReadableStreamBackupFile.php b/lib/backup_migrate_core/src/File/ReadableStreamBackupFile.php
deleted file mode 100644
index 9f5ea62..0000000
--- a/lib/backup_migrate_core/src/File/ReadableStreamBackupFile.php
+++ /dev/null
@@ -1,193 +0,0 @@
-path = $filepath;
-
- // Get the basename and extensions.
- $this->setFullName(basename($filepath));
-
- // Get the basic file stats since this is probably a read-only file option and these won't change.
- $this->_loadFileStats();
- }
-
- /**
- * Destructor.
- */
- function __destruct() {
- // Close the handle if we've opened it.
- $this->close();
- }
-
- /**
- * Get the realpath of the file.
- *
- * @return string The path or stream URI to the file or NULL if the file does not exist.
- */
- function realpath() {
- if (file_exists($this->path)) {
- return $this->path;
- }
- return NULL;
- }
-
- /**
- * Open a file for reading or writing.
- *
- * @param bool $binary If true open as a binary file
- *
- * @return resource
- *
- * @throws \Exception
- */
- function openForRead($binary = FALSE) {
- if (!$this->isOpen()) {
- $path = $this->realpath();
-
- if (!is_readable($path)) {
- // @todo Throw better exception
- throw new \Exception('Cannot read file.');
- }
-
- // Open the file.
- $mode = "r" . ($binary ? "b" : "");
- $this->handle = fopen($path, $mode);
- if (!$this->handle) {
- throw new \Exception('Cannot open file.');
- }
- }
- // If the file is already open, rewind it.
- $this->rewind();
- return $this->handle;
- }
-
- /**
- * Close a file when we're done reading/writing.
- */
- function close() {
- if ($this->isOpen()) {
- fclose($this->handle);
- $this->handle = NULL;
- }
- }
-
- /**
- * Is this file open for reading/writing.
- *
- * Return bool True if the file is open, false if not.
- */
- function isOpen() {
- return !empty($this->handle) && get_resource_type($this->handle) == 'stream';
- }
-
- /**
- * Read a line from the file.
- *
- * @param int $size The number of bites to read or 0 to read the whole file
- *
- * @return string The data read from the file or NULL if the file can't be read or is at the end of the file.
- */
- function readBytes($size = 1024, $binary = FALSE) {
- if (!$this->isOpen()) {
- $this->openForRead($binary);
- }
- if ($this->handle && !feof($this->handle)) {
- return fread($this->handle, $size);
- }
- return NULL;
- }
-
-
- /**
- * Read a single line from the file.
- *
- * @return string The data read from the file or NULL if the file can't be read or is at the end of the file.
- */
- public function readLine() {
- if (!$this->isOpen()) {
- $this->openForRead();
- }
- return fgets($this->handle);
- }
-
- /**
- * Read a line from the file.
- *
- * @return string The data read from the file or NULL if the file can't be read.
- */
- public function readAll() {
- if (!$this->isOpen()) {
- $this->openForRead();
- }
- $this->rewind();
- return stream_get_contents($this->handle);
- }
-
- /**
- * Move the file pointer forward a given number of bytes.
- *
- * @param int $bytes
- *
- * @return int
- * The number of bytes moved or -1 if the operation failed.
- */
- public function seekBytes($bytes) {
- if ($this->isOpen()) {
- return fseek($this->handle, $bytes);
- }
- return -1;
- }
-
- /**
- * Rewind the file handle to the start of the file.
- */
- function rewind() {
- if ($this->isOpen()) {
- rewind($this->handle);
- }
- }
-
- /**
- * Get info about the file and load them as metadata.
- */
- protected function _loadFileStats() {
- clearstatcache();
- $this->setMeta('filesize', filesize($this->realpath()));
- $this->setMeta('datestamp', filectime($this->realpath()));
- }
-
-}
diff --git a/lib/backup_migrate_core/src/File/TempFileAdapter.php b/lib/backup_migrate_core/src/File/TempFileAdapter.php
deleted file mode 100644
index 679ff80..0000000
--- a/lib/backup_migrate_core/src/File/TempFileAdapter.php
+++ /dev/null
@@ -1,111 +0,0 @@
-dir = $dir;
- $this->prefix = $prefix;
- $this->tempfiles = [];
- // @todo check that temp direcory is writeable or throw an exception.
- }
-
- /**
- * Destruct the manager. Delete all the temporary files when this manager is destroyed.
- */
- public function __destruct() {
- $this->deleteAllTempFiles();
- }
-
- /**
- * {@inheritdoc}
- */
- public function createTempFile($ext = '') {
- // Add a dot to the file extension.
- $ext = $ext ? '.' . $ext : '';
-
- // Find an unused random file name.
- $try = 5;
- do {
- $out = $this->dir . $this->prefix . mt_rand() . $ext;
- $fp = @fopen($out, 'x');
- } while (!$fp && $try-- > 0);
- if ($fp) {
- fclose($fp);
- }
- else {
- throw new \Exception('Could not create a temporary file to write to.');
- }
-
- $this->tempfiles[] = $out;
- return $out;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteTempFile($filename) {
- // Only delete files that were created by this manager.
- if (in_array($filename, $this->tempfiles)) {
- if (file_exists($filename)) {
- if (is_writable($filename)) {
- unlink($filename);
- }
- else {
- throw new BackupMigrateException('Could not delete the temp file: %file because it is not writable', ['%file' => $filename]);
- }
- }
- // Remove the item from the list.
- $this->tempfiles = array_diff($this->tempfiles, [$filename]);
- return;
- }
- throw new BackupMigrateException('Attempting to delete a temp file not managed by this codebase: %file', ['%file' => $filename]);
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteAllTempFiles() {
- foreach ($this->tempfiles as $file) {
- $this->deleteTempFile($file);
- }
- }
-
-}
diff --git a/lib/backup_migrate_core/src/File/TempFileAdapterInterface.php b/lib/backup_migrate_core/src/File/TempFileAdapterInterface.php
deleted file mode 100644
index 510abb4..0000000
--- a/lib/backup_migrate_core/src/File/TempFileAdapterInterface.php
+++ /dev/null
@@ -1,31 +0,0 @@
-adapter = $adapter;
- }
-
- /**
- * Create a brand new temp file with the given extension (if specified). The
- * new file should be writable.
- *
- * @param string $ext The file extension for this file (optional)
- *
- * @return BackupFileWritableInterface
- */
- public function create($ext = '') {
- $file = new WritableStreamBackupFile($this->adapter->createTempFile($ext));
- $file->setExtList(explode('.', $ext));
- return $file;
- }
-
- /**
- * Return a new file based on the passed in file with the given file extension.
- * This should maintain the metadata of the file passed in with the new file
- * extension added after the old one.
- * For example: xxx.mysql would become xxx.mysql.gz.
- *
- * @param \BackupMigrate\Core\File\BackupFileInterface $file
- * The file to add the extension to.
- * @param $ext
- * The new file extension.
- *
- * @return \BackupMigrate\Core\File\BackupFileWritableInterface
- * A new writable backup file with the new extension and all of the metadata
- * from the previous file.
- */
- public function pushExt(BackupFileInterface $file, $ext) {
- // Push the new extension on to the new file.
- $parts = $file->getExtList();
- array_push($parts, $ext);
- $new_ext = implode('.', $parts);
-
- // Copy the file metadata to a new TempFile.
- $out = new WritableStreamBackupFile($this->adapter->createTempFile($new_ext));
-
- // Copy the file metadata to a new TempFile.
- $out->setMetaMultiple($file->getMetaAll());
- $out->setName($file->getName());
- $out->setExtList($parts);
-
- return $out;
- }
-
- /**
- * Return a new file based on the one passed in but with the last part of the
- * file extension removed.
- * For example: xxx.mysql.gz would become xxx.mysql.
- *
- * @param \BackupMigrate\Core\File\BackupFileInterface $file
- *
- * @return \BackupMigrate\Core\File\BackupFileWritableInterface
- * A new writable backup file with the last extension removed and
- * all of the metadata from the previous file.
- */
- public function popExt(BackupFileInterface $file) {
- // Pop the last extension from the last of the file.
- $parts = $file->getExtList();
- array_pop($parts);
- $new_ext = implode('.', $parts);
-
- // Create a new temp file with the new extension.
- $out = new WritableStreamBackupFile($this->adapter->createTempFile($new_ext));
-
- // Copy the file metadata to a new TempFile.
- $out->setMetaMultiple($file->getMetaAll());
- $out->setName($file->getName());
- $out->setExtList($parts);
-
- return $out;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/File/TempFileManagerInterface.php b/lib/backup_migrate_core/src/File/TempFileManagerInterface.php
deleted file mode 100644
index b48eebf..0000000
--- a/lib/backup_migrate_core/src/File/TempFileManagerInterface.php
+++ /dev/null
@@ -1,60 +0,0 @@
-isOpen()) {
- $path = $this->realpath();
-
- // Check if the file can be read/written.
- if ((file_exists($path) && !is_writable($path)) || (!file_exists($path) && !is_writable(dirname($path)))) {
- // @todo Throw better exception
- throw new BackupMigrateException('Cannot write to file: %path', ['%path' => $path]);
- }
-
- // Open the file.
- $mode = "w" . ($binary ? "b" : "");
- $this->handle = fopen($path, $mode);
- if (!$this->handle) {
- throw new BackupMigrateException('Cannot open file: %path', ['%path' => $path]);
- }
- }
- }
-
- /**
- * Write a line to the file.
- *
- * @param string $data A string to write to the file.
- *
- * @throws \Exception
- */
- function write($data) {
- if (!$this->isOpen()) {
- $this->openForWrite();
- }
-
- if ($this->handle) {
- if (fwrite($this->handle, $data) === FALSE) {
- throw new \Exception('Cannot write to file: ' . $this->realpath());
- }
- else {
- $this->dirty = TRUE;
- }
- }
- else {
- throw new \Exception('File not open for writing.');
- }
- }
-
-
- /**
- * Update the file time and size when the file is closed.
- */
- function close() {
- parent::close();
-
- // If the file has been modified, update the stats from disk.
- if ($this->dirty) {
- $this->_loadFileStats();
- $this->dirty = FALSE;
- }
- }
-
- /**
- * A shorthand function to open the file, write the given contents and close
- * the file. Used for small amounts of data that can fit in memory.
- *
- * @param $data
- */
- public function writeAll($data) {
- $this->openForWrite();
- $this->write($data);
- $this->close();
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/CompressionFilter.php b/lib/backup_migrate_core/src/Filter/CompressionFilter.php
deleted file mode 100644
index f545ab3..0000000
--- a/lib/backup_migrate_core/src/Filter/CompressionFilter.php
+++ /dev/null
@@ -1,386 +0,0 @@
- ['weight' => 100],
- * 'restore' => ['weight' => -100],
- * ];
- *
- * @return array
- */
- public function supportedOps() {
- return [
- 'getFileTypes' => [],
- 'backupSettings' => [],
- 'afterBackup' => ['weight' => 100],
- 'beforeRestore' => ['weight' => -100],
- ];
- }
-
- /**
- * Return the filetypes supported by this filter.
- */
- public function getFileTypes() {
- return [
- [
- "gzip" => [
- "extension" => "gz",
- "filemime" => "application/x-gzip",
- 'ops' => [
- 'backup',
- 'restore',
- ],
- ],
- "bzip" => [
- "extension" => "bz",
- "filemime" => "application/x-bzip",
- 'ops' => [
- 'backup',
- 'restore',
- ],
- ],
- "bzip2" => [
- "extension" => "bz2",
- "filemime" => "application/x-bzip",
- 'ops' => [
- 'backup',
- 'restore',
- ],
- ],
- "zip" => [
- "extension" => "zip",
- "filemime" => "application/zip",
- 'ops' => [
- 'backup',
- 'restore',
- ],
- ],
- ],
- ];
- }
-
-
- /**
- * Get a definition for user-configurable settings.
- *
- * @return array
- */
- public function configSchema(array $params = []) {
- $schema = [];
-
- if ($params['operation'] == 'backup') {
- $schema['groups']['file'] = [
- 'title' => 'Backup File',
- ];
- $compression_options = $this->_availableCompressionAlgorithms();
- $schema['fields']['compression'] = [
- 'group' => 'file',
- 'type' => 'enum',
- 'title' => 'Compression',
- 'options' => $compression_options,
- 'actions' => ['backup']
- ];
- }
-
- return $schema;
- }
-
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'compression' => $this->_defaultCompressionAlgorithm(),
- ]);
- }
-
-
- /**
- * Run on a backup.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- *
- * @return \BackupMigrate\Core\File\BackupFileReadableInterface
- */
- public function afterBackup(BackupFileReadableInterface $file) {
- $out = $success = FALSE;
- if ($this->confGet('compression') == 'gzip') {
- $out = $this->getTempFileManager()->pushExt($file, 'gz');
- $success = $this->gzipEncode($file, $out);
- }
- if ($this->confGet('compression') == 'bzip') {
- $out = $this->getTempFileManager()->pushExt($file, 'bz2');
- $success = $this->bzipEncode($file, $out);
- }
- if ($this->confGet('compression') == 'zip') {
- $out = $this->getTempFileManager()->pushExt($file, 'zip');
- $success = $this->zipEncode($file, $out);
- }
-
- // If the file was successfully compressed.
- if ($out && $success) {
- $out->setMeta('filesize_uncompressed', $file->getMeta('filesize'));
- $out->setMeta('compression', $this->confGet('compression'));
- return $out;
- }
-
- // Return the original if we were not able to compress it.
- return $file;
- }
-
- /**
- * Run on a restore.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- *
- * @return \BackupMigrate\Core\File\BackupFileReadableInterface
- */
- public function beforeRestore(BackupFileReadableInterface $file) {
- // If the file is not a supported compression type then simply return the
- // same input file.
- $out = $file;
-
- $type = $file->getExtLast();
-
- switch (strtolower($type)) {
- case "gz":
- case "gzip":
- $out = $this->getTempFileManager()->popExt($file);
- $this->_gzipDecode($file, $out);
- break;
-
- case "bz":
- case "bz2":
- case "bzip":
- case "bzip2":
- $out = $this->getTempFileManager()->popExt($file);
- $this->_bzipDecode($file, $out);
- break;
-
- case "zip":
- $out = $this->getTempFileManager()->popExt($file);
- $this->_ZipDecode($file, $out);
- break;
- }
- return $out;
- }
-
-
- /**
- * Gzip encode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function gzipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
-
- if (!$success && function_exists("gzopen")) {
- if (($fp_out = gzopen($to->realpath(), 'wb9')) && $from->openForRead()) {
- while ($data = $from->readBytes(1024 * 512)) {
- gzwrite($fp_out, $data);
- }
- $success = TRUE;
- $from->close();
- gzclose($fp_out);
-
- // Get the compressed filesize and set it.
- $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
- $to->setMeta('filesize', $fileszc);
- }
- }
-
- return $success;
- }
-
- /**
- * Gzip decode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function _gzipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
-
- if (!$success && function_exists("gzopen")) {
- if ($fp_in = gzopen($from->realpath(), 'rb9')) {
- while (!feof($fp_in)) {
- $to->write(gzread($fp_in, 1024 * 512));
- }
- $success = TRUE;
- gzclose($fp_in);
- $to->close();
- }
- }
-
- return $success;
- }
-
- /**
- * BZip encode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function bzipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
- if (!$success && function_exists("bzopen")) {
- if (($fp_out = bzopen($to->realpath(), 'w')) && $from->openForRead()) {
- while ($data = $from->readBytes(1024 * 512)) {
- bzwrite($fp_out, $data);
- }
- $success = TRUE;
- $from->close();
- bzclose($fp_out);
-
- // Get the compressed filesize and set it.
- $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
- $to->setMeta('filesize', $fileszc);
- }
- }
-
- return $success;
- }
-
- /**
- * BZip decode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function _bzipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
-
- if (!$success && function_exists("bzopen")) {
- if ($fp_in = bzopen($from->realpath(), 'r')) {
- while (!feof($fp_in)) {
- $to->write(bzread($fp_in, 1024 * 512));
- }
- $success = TRUE;
- bzclose($fp_in);
- $to->close();
- }
- }
-
- return $success;
- }
-
- /**
- * Zip encode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- *
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function zipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
-
- if (class_exists('ZipArchive')) {
- $zip = new \ZipArchive();
- $res = $zip->open(\Drupal::service('file_system')->realpath($to->realpath()), constant("ZipArchive::CREATE"));
- if ($res === TRUE) {
- $zip->addFile(\Drupal::service('file_system')->realpath($from->realpath()), $from->getFullName());
- }
- $success = $zip->close();
- }
- // Get the compressed filesize and set it.
- $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
- $to->setMeta('filesize', $fileszc);
-
- return $success;
- }
-
- /**
- * Gzip decode a file.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $from
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $to
- *
- * @return bool
- */
- protected function _ZipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
- $success = FALSE;
- if (class_exists('ZipArchive')) {
- $zip = new \ZipArchive();
- if ($zip->open(\Drupal::service('file_system')->realpath($from->realpath()))) {
- $filename = ($zip->getNameIndex(0));
- if ($fp_in = $zip->getStream($filename)) {
- while (!feof($fp_in)) {
- $to->write(fread($fp_in, 1024 * 512));
- }
- fclose($fp_in);
- $success = $to->close();
- }
- }
- return $success;
- }
- }
-
- /**
- * Get the compression options as an options array for a form item.
- *
- * @return array
- */
- protected function _availableCompressionAlgorithms() {
- $compression_options = ["none" => ("No Compression")];
- if (function_exists("gzencode")) {
- $compression_options['gzip'] = ("GZip");
- }
- if (function_exists("bzcompress")) {
- $compression_options['bzip'] = ("BZip");
- }
- if (class_exists('ZipArchive')) {
- $compression_options['zip'] = ("Zip");
- }
- return $compression_options;
- }
-
- /**
- * Get the default compression algorithm based on those available.
- *
- * @return string
- * The machine name of the algorithm.
- */
- protected function _defaultCompressionAlgorithm() {
- $available = array_keys($this->_availableCompressionAlgorithms());
- // Remove the 'none' option.
- array_shift($available);
- $out = array_shift($available);
- // Return the first available algorithm or 'none' of none other exist.
- return $out ? $out : 'none';
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/DBExcludeFilter.php b/lib/backup_migrate_core/src/Filter/DBExcludeFilter.php
deleted file mode 100644
index 38a0539..0000000
--- a/lib/backup_migrate_core/src/Filter/DBExcludeFilter.php
+++ /dev/null
@@ -1,116 +0,0 @@
-confGet('exclude_tables');
- $nodata = $this->confGet('nodata_tables');
- if (in_array($table['name'], $exclude)) {
- $table['exclude'] = TRUE;
- }
- if (in_array($table['name'], $nodata)) {
- $table['nodata'] = TRUE;
- }
- return $table;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'source' => '',
- 'exclude_tables' => [],
- 'nodata_tables' => [],
- ]);
- }
-
- /**
- * Get a definition for user-configurable settings.
- *
- * @param array $params
- *
- * @return array
- */
- public function configSchema(array $params = []) {
- $schema = [];
-
- if ($params['operation'] == 'backup') {
- $tables = [];
-
- foreach ($this->sources()->getAll() as $source_key => $source) {
- if ($source instanceof DatabaseSourceInterface) {
- $tables += $source->getTableNames();
- }
-
- if ($tables) {
- // Backup settings.
- $schema['groups']['default'] = [
- 'title' => $this->t('Exclude database tables'),
- ];
-
- $table_select = [
- 'type' => 'enum',
- 'multiple' => TRUE,
- 'options' => $tables,
- 'actions' => ['backup'],
- 'group' => 'default'
- ];
- $schema['fields']['exclude_tables'] = $table_select + [
- 'title' => $this->t('Exclude these tables entirely'),
- ];
-
- $schema['fields']['nodata_tables'] = $table_select + [
- 'title' => $this->t('Exclude data from these tables'),
- ];
-
- }
- }
- }
- return $schema;
- }
-
- /**
- * @return PluginManager
- */
- public function sources() {
- return $this->source_manager ? $this->source_manager : new PluginManager();
- }
-
- /**
- * @param PluginManager $source_manager
- */
- public function setSourceManager($source_manager) {
- $this->source_manager = $source_manager;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/FileExcludeFilter.php b/lib/backup_migrate_core/src/Filter/FileExcludeFilter.php
deleted file mode 100644
index 0fe250c..0000000
--- a/lib/backup_migrate_core/src/Filter/FileExcludeFilter.php
+++ /dev/null
@@ -1,128 +0,0 @@
-confGet('source');
- if ($source && $source == $params['source']) {
- $exclude = $this->confGet('exclude_filepaths');
- $exclude = $this->compileExcludePatterns($exclude);
-
- if ($this->matchPath($path, $exclude, $params['base_path'])) {
- return NULL;
- }
- }
- return $path;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'source' => '',
- 'exclude_filepaths' => [],
- ]);
- }
-
- /**
- * Convert an array of glob patterns to an array of regex patterns for file name exclusion.
- *
- * @param array $exclude
- * A list of patterns with glob wildcards
- *
- * @return array
- * A list of patterns as regular expressions
- */
- private function compileExcludePatterns($exclude) {
- if ($this->patterns !== NULL) {
- return $this->patterns;
- }
- foreach ($exclude as $pattern) {
- // Convert Glob wildcards to a regex per http://php.net/manual/en/function.fnmatch.php#71725
- $this->patterns[] = "#^" . strtr(preg_quote($pattern, '#'), ['\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']']) . "$#i";
- }
- return $this->patterns;
- }
-
- /**
- * Match a path to the list of exclude patterns.
- *
- * @param string $path
- * The path to match.
- * @param array $exclude
- * An array of regular expressions to match against.
- * @param string $base_path
- *
- * @return bool
- */
- private function matchPath($path, $exclude, $base_path = '') {
- $path = substr($path, strlen($base_path));
-
- if ($exclude) {
- foreach ($exclude as $pattern) {
- if (preg_match($pattern, $path)) {
- return TRUE;
- }
- }
- }
- return FALSE;
- }
-
- /**
- * Get a definition for user-configurable settings.
- *
- * @param array $params
- *
- * @return array
- */
- public function configSchema(array $params = []) {
- $schema = [];
-
- $source = $this->confGet('source');
-
- // Backup settings.
- if (!empty($source) && $params['operation'] == 'backup') {
- $schema['groups']['default'] = [
- 'title' => $this->t('Exclude Files from %source', ['%source' => $source->confGet('name')]),
- ];
- // Backup settings.
- if ($params['operation'] == 'backup') {
- $schema['fields']['exclude_filepaths'] = [
- 'type' => 'text',
- 'title' => $this->t('Exclude these files'),
- 'multiple' => TRUE,
- 'group' => 'default'
- ];
- }
- }
- return $schema;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/FileNamer.php b/lib/backup_migrate_core/src/Filter/FileNamer.php
deleted file mode 100644
index 5993797..0000000
--- a/lib/backup_migrate_core/src/Filter/FileNamer.php
+++ /dev/null
@@ -1,114 +0,0 @@
-moduleExists('token')) {
- $must_match = '/^[\w\-_:\[\]]+$/';
- $must_match_err = $this->t('%title must contain only letters, numbers, dashes (-) and underscores (_). And Site Tokens.');
- }
- else {
- $must_match = '/^[\w\-_:]+$/';
- $must_match_err = $this->t('%title must contain only letters, numbers, dashes (-) and underscores (_).');
- }
- // Backup configuration.
- if ($params['operation'] == 'backup') {
- $schema['groups']['file'] = [
- 'title' => 'Backup File',
- ];
- $schema['fields']['filename'] = [
- 'group' => 'file',
- 'type' => 'text',
- 'title' => 'File Name',
- 'must_match' => $must_match,
- 'must_match_error' => $must_match_err,
- 'min_length' => 1,
- // Allow a 200 character backup name leaving a generous 55 characters
- // for timestamp and extension.
- 'max_length' => 200,
- 'required' => TRUE,
- ];
- $schema['fields']['timestamp'] = [
- 'group' => 'file',
- 'type' => 'boolean',
- 'title' => 'Append a timestamp',
- ];
- $schema['fields']['timestamp_format'] = [
- 'group' => 'file',
- 'type' => 'text',
- 'title' => 'Timestamp Format',
- 'max_length' => 32,
- 'dependencies' => ['timestamp' => TRUE],
- 'description' => $this->t('Use PHP Date formatting.'),
- ];
- }
- return $schema;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'filename' => 'backup',
- 'timestamp' => TRUE,
- 'timestamp_format' => 'Y-m-d\TH-i-s',
- ]);
- }
-
- /**
- * Get a list of supported operations and their weight.
- *
- * @return array
- */
- public function supportedOps() {
- return [
- 'afterBackup' => [],
- ];
- }
-
- /**
- * Run on a backup. Name the backup file according to the configuration.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- *
- * @return \BackupMigrate\Core\File\BackupFileReadableInterface
- */
- public function afterBackup(BackupFileReadableInterface $file) {
- if (\Drupal::moduleHandler()->moduleExists('token')) {
- $token = \Drupal::token();
- $name = $token->replace($this->confGet('filename'));
- }
- else {
- $name = $this->confGet('filename');
- }
- if ($this->confGet('timestamp')) {
- $name .= '-' . date($this->confGet('timestamp_format'));
- }
- $file->setName($name);
- return $file;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/MetadataWriter.php b/lib/backup_migrate_core/src/Filter/MetadataWriter.php
deleted file mode 100644
index aff19fe..0000000
--- a/lib/backup_migrate_core/src/Filter/MetadataWriter.php
+++ /dev/null
@@ -1,106 +0,0 @@
- 'Advanced Settings',
- ];
- $schema['fields']['description'] = [
- 'group' => 'advanced',
- 'type' => 'text',
- 'title' => 'Description',
- 'multiline' => TRUE,
- ];
- }
- return $schema;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'description' => '',
- 'generator' => 'Backup and Migrate',
- 'generatorversion' => defined('BACKUP_MIGRATE_CORE_VERSION') ? constant('BACKUP_MIGRATE_CORE_VERSION') : 'unknown',
- 'generatorurl' => 'https://github.com/backupmigrate',
- 'bam_sourceid' => '',
- ]);
- }
-
- /**
- * Generate a list of metadata keys to be stored with the backup.
- *
- * @return array
- */
- protected function getMetaKeys() {
- return [
- 'description',
- 'generator',
- 'generatorversion',
- 'generatorurl',
- 'bam_sourceid',
- 'bam_scheduleid',
- ];
- }
-
-
- /**
- * Run before the backup/restore begins.
- */
- public function setUp($operand, $options) {
- if ($options['operation'] == 'backup' && $options['source_id']) {
- $this->config()->set('bam_sourceid', $options['source_id']);
- if ($source = $this->plugins()->get($options['source_id'])) {
- // @todo Query the source for it's type and name.
- }
- }
- return $operand;
- }
-
- /**
- * Run after a backup. Add metadata to the file.
- *
- * @param \BackupMigrate\Core\File\BackupFileWritableInterface $file
- *
- * @return \BackupMigrate\Core\File\BackupFileWritableInterface
- */
- public function afterBackup(BackupFileWritableInterface $file) {
- // Add the various metadata.
- foreach ($this->getMetaKeys() as $key) {
- $value = $this->confGet($key);
- $file->setMeta($key, $value);
- }
- return $file;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Filter/Notify.php b/lib/backup_migrate_core/src/Filter/Notify.php
deleted file mode 100644
index 965a1c5..0000000
--- a/lib/backup_migrate_core/src/Filter/Notify.php
+++ /dev/null
@@ -1,96 +0,0 @@
- ['weight' => -100000],
- 'beforeRestore' => ['weight' => -100000],
- ];
- }
-
- /**
- * @var StashLogger
- */
- protected $logstash;
-
- public function beforeBackup() {
- $this->addLogger();
- }
-
- public function beforeRestore() {
- $this->addLogger();
- }
-
- public function backupSucceed() {
- $this->sendNotification('Backup finished sucessfully');
- }
-
- public function backupFail(Exception $e) {
-
- }
-
- public function restoreSucceed() {
- }
-
- public function restoreFail() {
- }
-
- /**
- * @param $subject
- * @param $body
- * @param $messages
- */
- protected function sendNotification($subject) {
- $messages = $this->logstash->getAll();
- $body = $subject . "\n";
- if (count($messages)) {
-
- }
- // $body .=
- }
-
- /**
- * add our stash logger to the service locator to capture all logged messages.
- */
- protected function addLogger() {
- $services = $this->plugins()->services();
-
- // Get the current logger.
- $logger = $services->get('Logger');
-
- // Create a new stash logger to save messages.
- $this->logstash = new StashLogger();
-
- // Add a tee to send logs to both the regular logger and our stash.
- $services->add('Logger', new TeeLogger([$logger, $this->logstash]));
-
- // Add the services back into the plugin manager to re-inject existing plugins
- $this->plugins()->setServiceLocator($services);
- }
-
- // @todo Add a tee to the logger to capture all messages.
- // @todo Implement backup/restore fail/succeed ops and send a notification.
-}
\ No newline at end of file
diff --git a/lib/backup_migrate_core/src/Main/BackupMigrate.php b/lib/backup_migrate_core/src/Main/BackupMigrate.php
deleted file mode 100644
index e0eb9f5..0000000
--- a/lib/backup_migrate_core/src/Main/BackupMigrate.php
+++ /dev/null
@@ -1,232 +0,0 @@
-setServiceManager(new ServiceManager());
- $services = $this->services();
-
- $services->add('PluginManager', new PluginManager($services));
- $services->add('SourceManager', new PluginManager($services));
- $services->add('DestinationManager', new PluginManager($services));
-
- // Add these services back into this object using the service manager.
- $services->addClient($this);
- }
-
- /**
- * {@inheritdoc}
- */
- public function backup($source_id, $destination_id) {
- try {
-
- // Allow the plugins to set up.
- $this->plugins()->call('setUp', NULL, ['operation' => 'backup', 'source_id' => $source_id, 'destination_id' => $destination_id]);
-
- // Get the source and the destination to use.
- $source = $this->sources()->get($source_id);
- $destinations = [];
-
- // Allow a single destination or multiple destinations.
- foreach ((array) $destination_id as $id) {
- $destinations[$id] = $this->destinations()->get($id);
-
- // Check that the destination is valid.
- if (!$destinations[$id]) {
- throw new BackupMigrateException('The destination !id does not exist.', ['!id' => $destination_id]);
- }
-
- // Check that the destination can be written to.
- // @todo Catch exceptions and continue if at least one destination is valid.
- $destinations[$id]->checkWritable();
- }
-
- // Check that the source is valid.
- if (!$source) {
- throw new BackupMigrateException('The source !id does not exist.', ['!id' => $source_id]);
- }
-
- // Run each of the installed plugins which implements the 'beforeBackup' operation.
- $this->plugins()->call('beforeBackup');
-
- // Do the actual backup.
- $file = $source->exportToFile();
-
- // Run each of the installed plugins which implements the 'afterBackup' operation.
- $file = $this->plugins()->call('afterBackup', $file);
-
- // Save the file to each destination.
- foreach ($destinations as $destination) {
- $destination->saveFile($file);
- }
-
- // Let plugins react to a successful operation.
- $this->plugins()->call('backupSucceed', $file);
- }
- catch (\Exception $e) {
- // Let plugins react to a failed operation.
- $this->plugins()->call('backupFail', $e);
-
- // The consuming software needs to deal with this.
- throw $e;
- }
-
- // Allow the plugins to tear down.
- $this->plugins()->call('tearDown', NULL, ['operation' => 'backup', 'source_id' => $source_id, 'destination_id' => $destination_id]);
-
- }
-
- /**
- * {@inheritdoc}
- */
- public function restore($source_id, $destination_id, $file_id = NULL) {
- try {
- // Get the source and the destination to use.
- $source = $this->sources()->get($source_id);
- $destination = $this->destinations()->get($destination_id);
-
- if (!$source) {
- throw new BackupMigrateException('The source !id does not exist.', ['!id' => $source_id]);
- }
- if (!$destination) {
- throw new BackupMigrateException('The destination !id does not exist.', ['!id' => $destination_id]);
- }
-
- // Load the file from the destination.
- $file = $destination->getFile($file_id);
- if (!$file) {
- throw new BackupMigrateException('The file !id does not exist.', ['!id' => $file_id]);
- }
-
- // Prepare the file for reading.
- $file = $destination->loadFileForReading($file);
- if (!$file) {
- throw new BackupMigrateException('The file !id could not be opened for reading.', ['!id' => $file_id]);
- }
-
- // Run each of the installed plugins which implements the 'backup' operation.
- $file = $this->plugins()->call('beforeRestore', $file);
-
- // Do the actual source restore.
- $import_result = $source->importFromFile($file);
- if (!$import_result) {
- throw new BackupMigrateException('The file could not be imported.');
- }
-
- // Run each of the installed plugins which implements the 'beforeBackup' operation.
- $this->plugins()->call('afterRestore');
-
- // Let plugins react to a successful operation.
- $this->plugins()->call('restoreSucceed', $file);
- }
- catch (\Exception $e) {
- // Let plugins react to a failed operation.
- $this->plugins()->call('restoreFail', $e);
-
- // The consuming software needs to deal with this.
- throw $e;
- }
- }
-
- /**
- * Set the configuration for the service. This simply passes the configuration
- * on to the plugin manager as all work is done by plugins.
- *
- * This can be called after the service is instantiated to pass new configuration
- * to the plugins.
- *
- * @param \BackupMigrate\Core\Config\ConfigInterface $config
- */
- public function setConfig(ConfigInterface $config) {
- $this->plugins()->setConfig($config);
- }
-
- /**
- * Get the list of available destinations.
- *
- * @return PluginManagerInterface
- */
- public function destinations() {
- return $this->destinations;
- }
-
- /**
- * Set the destinations plugin manager.
- *
- * @param PluginManagerInterface $destinations
- */
- public function setDestinationManager(PluginManagerInterface $destinations) {
- $this->destinations = $destinations;
- }
-
- /**
- * Get the list of sources.
- *
- * @return PluginManagerInterface
- */
- public function sources() {
- return $this->sources;
- }
-
- /**
- * Set the sources plugin manager.
- *
- * @param PluginManagerInterface $sources
- */
- public function setSourceManager(PluginManagerInterface $sources) {
- $this->sources = $sources;
- }
-
- /**
- * Get the service locator.
- *
- * @return ServiceManager
- */
- public function services() {
- return $this->services;
- }
-
- /**
- * Set the service locator.
- *
- * @param ServiceManager $services
- */
- public function setServiceManager($services) {
- $this->services = $services;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Main/BackupMigrateInterface.php b/lib/backup_migrate_core/src/Main/BackupMigrateInterface.php
deleted file mode 100644
index ca5a07f..0000000
--- a/lib/backup_migrate_core/src/Main/BackupMigrateInterface.php
+++ /dev/null
@@ -1,81 +0,0 @@
-plugins()` method. The `add()`
-method can then be used to add additional plugins. Each added plugin must be given a unique ID when added. This ID will be used
-to configure the plugin and to specify which source and destination are used during the operation.
-
-
- // ...
-
- // Create a Backup and Migrate Service object
- $bam = new BackupMigrate($);
-
- // Create a service locator
- $services = new ServiceManager();
-
- // Add necessary services
- $services->add('TempFileManager',
- new TempFileManager(new TempFileAdapter('/tmp'))
- );
- $services->add('Logger',
- new Logger()
- );
-
- // Create a plugin manager
- $plugins = new PluginManager($services);
-
- // Add a source:
- $plugins->add('db1', new MySQLiSource());
-
- // Add some destinations
- $plugins->add('download', new BrowserDownloadDestination());
- $plugins->add('mydirectory', new DirectoryDestination());
-
- // Add some filters
- $plugins->add('compress', new CompressionFilter());
- $plugins->add('namer', new FileNamer());
-
- $bam = new BackupMigrate($plugins);
-
-See: [Plugins](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Plugin)
-
-### Providing Services
-
-If the consuming application needs to use any plugins that must talk to the greater environment (saving state, emailing
-users, creating temporary files) it must provide services to Backup and Migrate that allow it to do so. These services
-are contained in an object called the environment. A new environment object should be created and passed to the service
-constructor. If you do not pass an environment then a basic one will be created which should work in the simplest
-environments.
-
-Providing an environment.
-
- use BackupMigrate\Core\Services\BackupMigrate;
- use MyAPP\Environment\MyEnvironment;
-
- // Create a custom environment with whatever services or configuration are needed for the application
- $env = new MyEnvironment(...);
-
- // Pass the environment to the service
- $bam = new BackupMigrate($env);
-
-See: [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment)
-
-### Configuring the Object
-
-The `BackupMigrate` object does not have any configuration but the injected plugins and services may. Services should be configured before they are passed to the `ServiceManager`. Plugins can be configured when they are created and passed to the plugin manager or additional configuration can be passed in by calling `setConfig` on the plugin manager. Often combination of these techniques will be used. Base configuration is passed to the plugin when it is instantiated and run-time configuration is passed in later.
-
-See: [Configuration](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Config)
-
-
-## Operations
-The Backup and Migrate service provides two main operations:
-
-* `backup($source_id, $destination_id)`
-* `restore($source_id, $destination_id, $file_id)`
-
-### The Backup Operation
-
-The `backup()` operation creates a backup file from the specified source, post-processes the file with all installed
-filters and saves the file to the specified destination. The parameters for this operation are:
-
-* **$source_id** ***(string)*** - The id of the source as specified when it is added to the plugin manager.
-* **$destination_id** ***(string|array)*** - The id of the destination as specified when it is added to the plugin manager.
-This can also be an array of destination ids to send the backup to multiple destinations.
-
-There is no return value but it may throw an exception if there is an error.
-
- // ...
-
- // Create a Backup and Migrate Service object
- $bam = new BackupMigrate($plugins);
-
- // Run the backup.
- $bam->backup('db1', 'mydirectory');
-
-
-### The Restore Operation
-
-The `restore()` operation loads the specified file from the specified destination, pre-processes the file with all
-installed filters and restores the data to the specified source. The parameters are:
-
-* **$source_id** ***(string)*** - The id of the source as specified when it is added to the plugin manager.
-* **$destination_id** ***(string)*** - The id of the destination as specified when it is added to the plugin manager.
-* **$file_id** ***(string)*** - The id of the file within the destination. This is usually the file name but can be any
-unique string specified by the destination.
-
-
- // ...
-
- // Create a Backup and Migrate Service object
- $bam = new BackupMigrate($plugins);
-
- // Run the restore.
- $bam->restore('db1', 'mydirectory', 'backup.mysql.gz');
diff --git a/lib/backup_migrate_core/src/Plugin/FileProcessorInterface.php b/lib/backup_migrate_core/src/Plugin/FileProcessorInterface.php
deleted file mode 100644
index 23d465c..0000000
--- a/lib/backup_migrate_core/src/Plugin/FileProcessorInterface.php
+++ /dev/null
@@ -1,32 +0,0 @@
-tempfilemanager = $tempfilemanager;
- }
-
- /**
- * Get the temp file manager.
- *
- * @return \BackupMigrate\Core\File\TempFileManagerInterface
- */
- public function getTempFileManager() {
- return $this->tempfilemanager;
- }
-
- /**
- * Provide the file mime for the given file extension if known.
- *
- * @param string $filemime
- * The best guess so far for the file's mime type.
- * @param array $params
- * A list of parameters where
- * 'ext' is the file extension we are testing.
- *
- * @return string
- * The mime type of the file (or the passed in mime type if unknown)
- */
- public function alterMime($filemime, $params) {
- // Check all of the provided file types for the given extension.
- if (method_exists($this, 'getFileTypes')) {
- $file_types = $this->getFileTypes();
- foreach ($file_types as $info) {
- if (isset($info['extension']) && $info['extension'] == $params['ext'] && isset($info['filemime'])) {
- return $info['filemime'];
- }
- }
- }
- return $filemime;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Plugin/PluginBase.php b/lib/backup_migrate_core/src/Plugin/PluginBase.php
deleted file mode 100644
index cc8b043..0000000
--- a/lib/backup_migrate_core/src/Plugin/PluginBase.php
+++ /dev/null
@@ -1,63 +0,0 @@
- ['weight' => 100],
- * 'restore' => ['weight' => -100],
- * ];
- *
- * @return array
- */
- public function supportedOps() {
- return [];
- }
-
- /**
- * Does this plugin implement the given operation.
- *
- * @param $op string The name of the operation
- *
- * @return bool
- */
- public function supportsOp($op) {
- // If the function has the method then it supports the op.
- if (method_exists($this, $op)) {
- return TRUE;
- }
- // If the supported ops array contains the op then it is supported.
- $ops = $this->supportedOps();
- return isset($ops[$op]);
- }
-
- /**
- * What is the weight of the given operation for this plugin.
- * * @param $op string The name of the operation.
- *
- * @return int
- */
- public function opWeight($op) {
- $ops = $this->supportedOps();
- if (isset($ops[$op]['weight'])) {
- return $ops[$op]['weight'];
- }
- return 0;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Plugin/PluginCallerInterface.php b/lib/backup_migrate_core/src/Plugin/PluginCallerInterface.php
deleted file mode 100644
index fde8a62..0000000
--- a/lib/backup_migrate_core/src/Plugin/PluginCallerInterface.php
+++ /dev/null
@@ -1,30 +0,0 @@
-plugins = $plugins;
- }
-
- /**
- * Get the plugin manager.
- *
- * @return \BackupMigrate\Core\Plugin\PluginManagerInterface
- */
- public function plugins() {
- // Return the list of plugins or a blank placeholder.
- return $this->plugins ? $this->plugins : new PluginManager();
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Plugin/PluginInterface.php b/lib/backup_migrate_core/src/Plugin/PluginInterface.php
deleted file mode 100644
index 0b01b21..0000000
--- a/lib/backup_migrate_core/src/Plugin/PluginInterface.php
+++ /dev/null
@@ -1,56 +0,0 @@
- ['weight' => 100],
- * 'restore' => ['weight' => -100],
- * ];
- *
- * @return array
- */
- public function supportedOps();
-
- /**
- * Does this plugin implement the given operation.
- *
- * @param $op string The name of the operation
- *
- * @return bool
- */
- public function supportsOp($op);
-
- /**
- * What is the weight of the given operation for this plugin.
- * * @param $op string The name of the operation.
- *
- * @return int
- */
- public function opWeight($op);
-
-}
diff --git a/lib/backup_migrate_core/src/Plugin/PluginManager.php b/lib/backup_migrate_core/src/Plugin/PluginManager.php
deleted file mode 100644
index 16630a4..0000000
--- a/lib/backup_migrate_core/src/Plugin/PluginManager.php
+++ /dev/null
@@ -1,194 +0,0 @@
-setServiceManager($services ? $services : new ServiceManager());
-
- // Set the configuration or a null object if no config was specified.
- $this->setConfig($config ? $config : new Config());
-
- // Create an array to store the plugins themselves.
- $this->items = [];
- }
-
-
- /**
- * Set the configuration. Reconfigure all of the installed plugins.
- *
- * @param \BackupMigrate\Core\Config\ConfigInterface $config
- */
- public function setConfig(ConfigInterface $config) {
- // Set the configuration object to the one passed in.
- $this->config = $config;
-
- // Pass the appropriate configuration to each of the installed plugins.
- foreach ($this->getAll() as $key => $plugin) {
- $this->_configurePlugin($plugin, $key);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function add($id, PluginInterface $item) {
- $this->_preparePlugin($item, $id);
- $this->items[$id] = $item;
- }
-
- /**
- * {@inheritdoc}
- **/
- public function get($id) {
- return isset($this->items[$id]) ? $this->items[$id] : NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getAll() {
- return empty($this->items) ? [] : $this->items;
- }
-
- /**
- * Get all plugins that implement the given operation.
- *
- * @param string $op The name of the operation.
- *
- * @return \BackupMigrate\Core\Plugin\PluginInterface[]
- */
- public function getAllByOp($op) {
- $out = [];
- $weights = [];
-
- foreach ($this->getAll() as $key => $plugin) {
- if ($plugin->supportsOp($op)) {
- $out[$key] = $plugin;
- $weights[$key] = $plugin->opWeight($op);
- }
- }
- array_multisort($weights, $out);
- return $out;
- }
-
- /**
- * {@inheritdoc}
- */
- public function call($op, $operand = NULL, $params = []) {
-
- // Run each of the installed plugins which implements the given operation.
- foreach ($this->getAllByOp($op) as $plugin) {
- $operand = $plugin->{$op}($operand, $params);
- }
-
- return $operand;
- }
-
- /**
- * {@inheritdoc}
- */
- public function map($op, $params = []) {
- $out = [];
-
- // Run each of the installed plugins which implements the given operation.
- foreach ($this->getAllByOp($op) as $key => $plugin) {
- $out[$key] = $plugin->{$op}($params);
- }
-
- return $out;
- }
-
-
- /**
- * Prepare the plugin for use. This is called when a plugin is added to the
- * manager and it configures the plugin according to the config object
- * injected into the manager. It also injects other dependencies as needed.
- *
- * @param \BackupMigrate\Core\Plugin\PluginInterface $plugin
- * The plugin to prepare for use.
- * @param string $id
- * The id of the plugin (to extract the correct settings).
- */
- protected function _preparePlugin(PluginInterface $plugin, $id) {
- // If this plugin can be configured, then pass in the configuration.
- $this->_configurePlugin($plugin, $id);
-
- // Inject the available services.
- $this->services()->addClient($plugin);
- }
-
- /**
- * Set the configuration for the given plugin.
- *
- * @param $plugin
- * @param $id
- */
- protected function _configurePlugin(PluginInterface $plugin, $id) {
- // If this plugin can be configured, then pass in the configuration.
- if ($plugin instanceof ConfigurableInterface) {
- // Configure the plugin with the appropriate subset of the configuration.
- $config = $this->confGet($id);
-
- // Set the config for the plugin.
- $plugin->setConfig(new Config($config));
-
- // Get the configuration back from the plugin to populate defaults within the manager.
- $this->config()->set($id, $plugin->config());
- }
- }
-
- /**
- * @return ServiceManagerInterface
- */
- public function services() {
- return $this->services;
- }
-
- /**
- * @param ServiceManagerInterface $services
- */
- public function setServiceManager($services) {
- $this->services = $services;
-
- // Inject or re-inject the services.
- foreach ($this->getAll() as $key => $plugin) {
- $this->services()->addClient($plugin);
- }
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Plugin/PluginManagerInterface.php b/lib/backup_migrate_core/src/Plugin/PluginManagerInterface.php
deleted file mode 100644
index 3f8d48d..0000000
--- a/lib/backup_migrate_core/src/Plugin/PluginManagerInterface.php
+++ /dev/null
@@ -1,103 +0,0 @@
-plugins()->add('demoplugin', new MyPlugin());
-
-To configure this plugin the consuming application would have a section called 'demoplugin' in the plugin manager configuration object:
-
- $conf = new Config([
- 'demoplugin' => ['foo => 'bar']
- ]);
-
- $plugins = new PluginManager(NULL, $conf);
- $backup_migrate = new BackupMigrate($plugins);
-
-### Calling Plugins ###
-Internally the plugin manager is used to run all plugins for a given operation. This is done using the `call()` method:
-
- $file = $this->plugins()->call('afterBackup', $file);
-
-The call method takes 3 parameters:
-
-* **Operation**: the name of the operation to call
-* **Operand**: The object being operated on (optional)
-* **Params**: An associative array of additional parameters
-
-Each plugin that implements the **operation** will be called in order. The **operand** will be passed to the plugin and will be overwritten by the return value from the plugin. In this way plugin operations are chained. A plugin is responsible for returning the operand that was passed in if it does not wish to overwrite it. The **params** array can contain additional information needed to run the operation but it cannot be modified by plugins.
-
-### Implementing Operations ###
-If a plugin wishes to be called for a given operation it simply needs to define a method with the same name as the operation. For example, to compress a backup file after it has been created, the plugin must have a method called `afterBackup()` which takes a file as the operand and returns the a new, compressed file.
-
-#### Operation Weights ####
-The order in which plugins are called cannot be guaranteed. However, if a plugin needs to run in a specific order it may specify a weight for each operation it implements. To specify a weight it must implement a `opWeight()` method which takes an operation name and returns a numerical weight. Plugins are called from lowest to highest and plugins which do not specify a weight are considered to have a weight of `0`.
-
-To specify the weight of may operations it may be easier to extend the `\BackupMigrate\Core\Plugin\PluginBase` class and override the `supportedOps()` method which returns an array of supported operations and their weight:
-
- public function supportedOps() {
- return [
- 'afterBackup' => ['weight' => 100],
- 'beforeRestore' => ['weight' => -100],
- ];
- }
-
-### Calling Other Plugins ###
-Plugins can call other plugins using the Plugin Manager. For example, a source plugin might want to expose a line-item filter operation to allow other plugins to alter single values before they are added to the backup file. An encryption plugin may want to delegate the actual work of encrypting to other sub-plugins for better code organization and extendability.
-
-By default plugins are not given access to the plugin manager. However, if a plugin implements the `\BackupMigrate\Core\Plugin\PluginCallerInterface` then the plugin manager will inject itself into the plugin for use when the plugin is prepared for use. The `\BackupMigrate\Core\Plugin\PluginCallerTrait` can be used to implement the actual requirements of the interface. Plugins with this interface and trait will be able to use `$this->plugins()` to access the plugin manager:
-
- class MyPlugin implements PluginCallerInterface {
- use PluginCallerTrait;
-
- function someOperation() {
- $this->plugins()->call(...);
- }
- }
-
-### Accessing Services ###
-If a plugin requires the use of a cache, logger, state storage, mailer or any other backing service it must have the service injected into it by the plugin manger. To make a service avaible to the plugin manager it may be added to an object which implenents `ServiceManagerInterface`. That service locater may be passed to the plugin manager though the constructor or it can be passed in later using `setServiceManager()`.
-
-Any service provided by the service locator will be injected into a plugin when it is added to the plugin manager if the name of the service matches a setter present in the plugin. For example: if a plugin has a method called `setLogger` and the service locator has a service called 'Logger' then the logger service will be injected via the `setLogger` method:
-
- $services = new ServiceManager();
- $services->add('Logger', new FileLogger('/path/to/log.txt'));
-
- $plugins = new PluginManager($services);
-
- // If this plugin has a `setLogger` the logger will be injected.
- $plugins->add('test', new TestPlugin());
-
-See: [Services](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Service)
-
-### Creating New Temporary Files ###
-If a plugin needs to create a new temporary file (for example to decompress a backup file). It may request that the TempFileManager be injected by implementing `\BackupMigrate\Core\Plugin\FileProcessorInterface` and using the `\BackupMigrate\Core\Plugin\FileProcessorTrait`. This will allow the following:
-
- class MyFilePlugin implements FileProcessorInterface {
- use FileProcessorTrait;
-
- function someOperation($file_in) {
- $file_out = $this->getTempFileManager()->popExt($file_in);
- // ...
-
- // Return the new file and so it overwrites the old file
- // during plugin chaining.
- return $file_out;
- }
- }
-
-
-See: [Backup Files](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/File)
-
-## Sources and Destinations ##
-
-Sources and destinations are special case plugins. While they technically identical to filter plugins they are not called using the plugin manager's `call()` method. Only one source and one destination can be use for each backup or restore operation so they are called individually rather than being chained like most plugin operations. These plugin types are different by convention only and are injected and configured in the same way as filters.
-
-See: [Sources](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Source), [Destinations](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Destination)
diff --git a/lib/backup_migrate_core/src/Service/ArchiveReaderInterface.php b/lib/backup_migrate_core/src/Service/ArchiveReaderInterface.php
deleted file mode 100644
index 3c5e9a1..0000000
--- a/lib/backup_migrate_core/src/Service/ArchiveReaderInterface.php
+++ /dev/null
@@ -1,46 +0,0 @@
-getCurlResource($url);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
-
- return $this->curlExec($ch);
- }
-
- /**
- * Post a file along with other data (as an array).
- *
- * @param $url
- * @param \BackupMigrate\Core\File\ReadableStreamBackupFile $file
- * @param $data
- *
- * @return mixed
- */
- public function postFile($url, ReadableStreamBackupFile $file, $data) {
- $data['file'] = new \CURLFile($file->realpath());
- $data['file']->setPostFilename($file->getFullName());
- return $this->post($url, $data);
- }
-
- /**
- * Get the CURL Resource with default options.
- *
- * @param $url
- *
- * @return resource
- */
- protected function getCurlResource($url) {
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
- return $ch;
- }
-
- /**
- * Perform the http action and return the body or throw an exception.
- *
- * @param $ch
- *
- * @return mixed
- *
- * @throws \BackupMigrate\Core\Exception\HttpClientException
- */
- protected function curlExec($ch) {
- $body = curl_exec($ch);
- if ($msg = curl_error($ch)) {
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- if (!$code) {
- $info['code'] = curl_errno($ch);
- }
- throw new HttpClientException($msg, [], $code);
- }
- return $body;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Service/README.md b/lib/backup_migrate_core/src/Service/README.md
deleted file mode 100644
index e1bbf8d..0000000
--- a/lib/backup_migrate_core/src/Service/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Services #
-
-If a plugin needs to access the greater environment to write logs, store data, etc. it should rely on service objects which may be injected into the plugin at run time.
-
-## Service Manager ##
-
-The `ServiceManagerInterface` defines a very simple service container and dependency injector which stores a keyed list of services available to plugins which need them. The built in `ServiceManager` class implements this interface in the most basic way possible. A consuming application may choose to implement a manager using a more sophisticated dependency management and configuration solution such as [Pimple](http://pimple.sensiolabs.org/), [PHP-DI](http://php-di.org/) or [Symfony's DependencyInjection Component](http://symfony.com/doc/current/components/dependency_injection/introduction.html). The built in locator simply takes a list of already configured services and returns them when requested or automatically injects them as described below.
-
-## Service Injection ##
-
-It is not necessary to use automatic service injection. A consuming application can simply instantiate plugins and pass the necessary services directly to them. However, a simple service injection mechanism is provided by the service manager which can make dynamically creating plugins much simpler.
-
-Plugins can request that a service be injected by defining a setter with called `setServiceName` where 'ServiceName' is replaced with the name of the given service. Here is an pseudo-code example:
-
- class MyPlugin implements PluginInterface {
-
- // Logger service setter
- public function setLogger(LoggerInterface $logger) {
- $this->logger = $logger;
- }
-
- // ...
- }
-
-This plugin will have a logger injected if one is available:
-
- $bam = new BackupMigrate();
-
- // The key 'Logger' must match 'setLogger'
- $bam->services()->add('Logger', new MyLogger());
-
- // The manager will inject the logger automatically.
- $bam->plugins()->add('myplugin', new MyPlugin());
-
-
\ No newline at end of file
diff --git a/lib/backup_migrate_core/src/Service/ServiceManager.php b/lib/backup_migrate_core/src/Service/ServiceManager.php
deleted file mode 100644
index b926051..0000000
--- a/lib/backup_migrate_core/src/Service/ServiceManager.php
+++ /dev/null
@@ -1,98 +0,0 @@
-services = [];
-
- // Allow the locator to inject itself.
- $this->services['ServiceManager'] = $this;
- }
-
- /**
- * Add a fully configured service to the service locator.
- *
- * @param string $type
- * The service type identifier.
- * @param mixed $service
- *
- * @return null
- */
- public function add($type, $service) {
- $this->services[$type] = $service;
-
- // Add this service as a client so it can have dependencies injected.
- $this->addClient($service);
-
- // Update any plugins that have already had this service injected.
- if (isset($this->clients[$type])) {
- foreach ($this->clients[$type] as $client) {
- $client->{'set' . $type}($service);
- }
- }
- }
-
- /**
- * Retrieve a service from the locator.
- *
- * @param string $type
- * The service type identifier
- *
- * @return mixed
- */
- public function get($type) {
- return $this->services[$type];
- }
-
- /**
- * Get an array of keys for all available services.
- *
- * @return array
- */
- public function keys() {
- return array_keys($this->services);
- }
-
- /**
- * Inject all available services into the give plugin.
- *
- * @param object $client
- *
- * @return mixed|void
- */
- public function addClient($client) {
- // Inject available services.
- foreach ($this->keys() as $type) {
- if (method_exists($client, 'set' . $type) && $service = $this->get($type)) {
- // Save the plugin so it can be updated if this service is updated.
- $this->clients[$type][] = $client;
-
- $client->{'set' . $type}($service);
- }
- }
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Service/ServiceManagerInterface.php b/lib/backup_migrate_core/src/Service/ServiceManagerInterface.php
deleted file mode 100644
index 5500822..0000000
--- a/lib/backup_migrate_core/src/Service/ServiceManagerInterface.php
+++ /dev/null
@@ -1,38 +0,0 @@
-logs[] = ['level' => $level, 'message' => $message, 'context' => $context];
- }
-
- /**
- * Get all of the log messages that were saved to this stash.
- *
- * @return array
- */
- public function getAll() {
- return $this->logs;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Service/TarArchiveReader.php b/lib/backup_migrate_core/src/Service/TarArchiveReader.php
deleted file mode 100644
index 5c3e5a7..0000000
--- a/lib/backup_migrate_core/src/Service/TarArchiveReader.php
+++ /dev/null
@@ -1,394 +0,0 @@
-archive = $out;
- }
-
-
- /**
- * Extract all files to the given directory.
- *
- * @param $directory
- *
- * @return mixed
- */
- public function extractTo($directory) {
- $this->archive->openForRead(TRUE);
-
- $result = $this->extractAllToDirectory($directory);
-
- $this->archive->close();
-
- return $result;
- }
-
- /**
- * @param $directory
- * The directory to extract the files to.
- * @return bool
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- */
- private function extractAllToDirectory($directory) {
- clearstatcache();
-
- // Read a header block.
- while (strlen($block = $this->archive->readBytes(512)) != 0) {
- $header = $this->readHeader($block);
- if (!$header) {
- return FALSE;
- }
-
- if ($header['filename'] == '') {
- continue;
- }
-
- // Check for potentially malicious files (containing '..' etc.).
- if ($this->maliciousFilename($header['filename'])) {
- throw new BackupMigrateException(
- 'Malicious .tar detected, file %filename. Will not install in desired directory tree',
- ['%filename' => $header['filename']]
- );
- }
-
- // ignore extended / pax headers.
- if ($header['typeflag'] == 'x' || $header['typeflag'] == 'g') {
- $this->archive->seekBytes(ceil(($header['size'] / 512)));
- continue;
- }
-
- // Add the destination directory to the path.
- if (substr($header['filename'], 0, 1) == '/') {
- $header['filename'] = $directory . $header['filename'];
- }
- else {
- $header['filename'] = $directory . '/' . $header['filename'];
- }
-
- // If the file already exists, make sure we can overwrite it.
- if (file_exists($header['filename'])) {
- // Cannot overwrite a directory with a file.
- if ((@is_dir($header['filename']))
- && ($header['typeflag'] == '')
- ) {
- throw new BackupMigrateException(
- 'File %filename already exists as a directory',
- ['%filename' => $header['filename']]
- );
- }
- // Cannot overwrite a file with a directory.
- if (@is_file($header['filename']) && !@is_link($header['filename'])
- && ($header['typeflag'] == "5")
- ) {
- throw new BackupMigrateException(
- 'Directory %filename already exists as file',
- ['%filename' => $header['filename']]
- );
- }
- // Cannot overwrite a read-only file.
- if (!is_writeable($header['filename'])) {
- throw new BackupMigrateException(
- 'File %filename already exists and is write protected',
- ['%filename' => $header['filename']]
- );
- }
- }
-
- // Extract a directory.
- if ($header['typeflag'] == "5") {
- if (!$this->createDir($header['filename'])) {
- throw new BackupMigrateException(
- 'Unable to create directory %filename',
- ['%filename' => $header['filename']]
- );
- }
- }
- // Extract a file/symlink
- else {
- if (!$this->createDir(dirname($header['filename']))) {
- throw new BackupMigrateException(
- 'Unable to create directory for %filename',
- ['%filename' => $header['filename']]
- );
- }
-
- // Symlink.
- if ($header['typeflag'] == "2") {
- if (@file_exists($header['filename'])) {
- @unlink($header['filename']);
- }
- if (!@symlink($header['link'], $header['filename'])) {
- throw new BackupMigrateException(
- 'Unable to extract symbolic link: %filename',
- ['%filename' => $header['filename']]
- );
- }
- }
- // Regular file.
- else {
- // Open the file for writing.
- if (($dest_file = @fopen($header['filename'], "wb")) == 0) {
- throw new BackupMigrateException(
- 'Error while opening %filename in write binary mode',
- ['%filename' => $header['filename']]
- );
- }
-
- // Write the file.
- $n = floor($header['size'] / 512);
- for ($i = 0; $i < $n; $i++) {
- $content = $this->archive->readBytes(512);
- fwrite($dest_file, $content, 512);
- }
- if (($header['size'] % 512) != 0) {
- $content = $this->archive->readBytes(512);
- fwrite($dest_file, $content, ($header['size'] % 512));
- }
-
- @fclose($dest_file);
-
- // Change the file mode, mtime.
- @touch($header['filename'], $header['mtime']);
- if ($header['mode'] & 0111) {
- // make file executable, obey umask.
- $mode = fileperms($header['filename']) | (~umask() & 0111);
- @chmod($header['filename'], $mode);
- }
-
- clearstatcache();
-
- // Check if the file exists.
- if (!is_file($header['filename'])) {
- throw new BackupMigrateException(
- 'Extracted file %filename does not exist. Archive may be corrupted.',
- ['%filename' => $header['filename']]
- );
- }
-
- // Check the file size.
- $file_size = filesize($header['filename']);
- if ($file_size != $header['size']) {
- throw new BackupMigrateException(
- 'Extracted file %filename does not have the correct file size. File is %actual bytes (%expected bytes expected). Archive may be corrupted',
- ['%filename' => $header['filename'], '%expected' => (int) $header['size'], (int) '%actual' => $file_size]
- );
- }
- }
- }
- }
-
- return TRUE;
- }
-
- /**
- * Create a directory or return true if it already exists.
- *
- * @param $directory
- *
- * @return boolean
- */
- private function createDir($directory) {
- if ((@is_dir($directory)) || ($directory == '')) {
- return TRUE;
- }
- $parent = dirname($directory);
-
- if (
- ($parent != $directory) &&
- ($parent != '') &&
- (!$this->createDir($parent))
- ) {
- return FALSE;
- }
- if (@!mkdir($directory, 0777)) {
- return FALSE;
- }
- return TRUE;
- }
-
- /**
- * Read a tar file header block.
- *
- * @param $block
- * @param array $header
- *
- * @return array
- *
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- */
- private function readHeader($block, $header = []) {
- if (strlen($block) == 0) {
- $header['filename'] = '';
- return TRUE;
- }
-
- if (strlen($block) != 512) {
- $header['filename'] = '';
- throw new BackupMigrateException(
- 'Invalid block size: %size bytes',
- ['%size' => strlen($block)]
- );
- }
-
- if (!is_array($header)) {
- $header = [];
- }
-
- // Calculate the checksum.
- $checksum = 0;
- // First part of the header.
- for ($i = 0; $i < 148; $i++) {
- $checksum += ord(substr($block, $i, 1));
- }
- // Ignore the checksum value and replace it by ' ' (space).
- for ($i = 148; $i < 156; $i++) {
- $checksum += ord(' ');
- }
- // Last part of the header.
- for ($i = 156; $i < 512; $i++) {
- $checksum += ord(substr($block, $i, 1));
- }
-
- if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
- $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
- "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
- "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
- }
- else {
- $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
- "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
- "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
- }
- $data = unpack($fmt, $block);
-
- if (strlen($data["prefix"]) > 0) {
- $data["filename"] = "$data[prefix]/$data[filename]";
- }
-
- // Extract the checksum.
- $header['checksum'] = octdec(trim($data['checksum']));
- if ($header['checksum'] != $checksum) {
- $header['filename'] = '';
-
- // Look for last block (empty block).
- if (($checksum == 256) && ($header['checksum'] == 0)) {
- return $header;
- }
-
- throw new BackupMigrateException(
- 'Invalid checksum for file %filename',
- ['%filename' => $data['filename']]
- );
- }
-
- // Extract the properties.
- $header['filename'] = rtrim($data['filename'], "\0");
- $header['mode'] = octdec(trim($data['mode']));
- $header['uid'] = octdec(trim($data['uid']));
- $header['gid'] = octdec(trim($data['gid']));
- $header['size'] = octdec(trim($data['size']));
- $header['mtime'] = octdec(trim($data['mtime']));
- if (($header['typeflag'] = $data['typeflag']) == "5") {
- $header['size'] = 0;
- }
- $header['link'] = trim($data['link']);
-
- // Look for long filename.
- if ($header['typeflag'] == 'L') {
- $header = $this->readLongHeader($header);
- }
-
- return $header;
- }
-
- /**
- * Read a tar file header block for files with long names.
- *
- * @param $header
- *
- * @return array
- *
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- */
- private function readLongHeader($header) {
- $filename = '';
- $filesize = $header['size'];
- $n = floor($header['size'] / 512);
- for ($i = 0; $i < $n; $i++) {
- $content = $this->archive->readBytes(512);
- $filename .= $content;
- }
- if (($header['size'] % 512) != 0) {
- $content = $this->archive->readBytes(512);
- $filename .= $content;
- }
-
- $filename = rtrim(substr($filename, 0, $filesize), "\0");
-
- // Read the next header.
- $data = $this->archive->readBytes(512);
- $header = $this->readHeader($data, $header);
- $header['filename'] = $filename;
-
- return $header;
- }
-
- /**
- * Detect and report a malicious file name.
- *
- * @param string $file
- *
- * @return bool
- */
- private function maliciousFilename($file) {
- if (strpos($file, '/../') !== FALSE) {
- return TRUE;
- }
- if (strpos($file, '../') === 0) {
- return TRUE;
- }
- return FALSE;
- }
-
- /**
- * This will be called when all files have been added. It gives the implementation
- * a chance to clean up and commit the changes if needed.
- *
- * @return mixed
- */
- public function closeArchive() {
- if ($this->archive) {
- $this->archive->close();
- }
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Service/TarArchiveWriter.php b/lib/backup_migrate_core/src/Service/TarArchiveWriter.php
deleted file mode 100644
index 6189c6b..0000000
--- a/lib/backup_migrate_core/src/Service/TarArchiveWriter.php
+++ /dev/null
@@ -1,206 +0,0 @@
-archive = $out;
- }
-
- /**
- * {@inheritdoc}
- */
- public function addFile($real_path, $new_path = '') {
- $this->archive->openForWrite(TRUE);
-
- $new_path = $new_path ? $new_path : $real_path;
-
- $this->writeHeader($real_path, $new_path);
-
- $fp = @fopen($real_path, "rb");
- while (($v_buffer = fread($fp, 512)) != '') {
- $v_binary_data = pack("a512", "$v_buffer");
- $this->archive->write($v_binary_data);
- }
- fclose($fp);
- }
-
- /**
- * @param $real_path
- * @param $new_path
- * @return bool
- */
- protected function writeHeader($real_path, $new_path) {
- if (strlen($new_path) > 99) {
- $this->writeLongHeader($new_path);
- }
-
- $v_info = lstat($real_path);
-
- $v_uid = sprintf("%6s ", decoct($v_info[4]));
- $v_gid = sprintf("%6s ", decoct($v_info[5]));
- $v_perms = sprintf("%6s ", decoct($v_info['mode']));
- $v_mtime = sprintf("%11s", decoct($v_info['mtime']));
-
- $v_linkname = '';
-
- if (@is_link($real_path)) {
- $v_typeflag = '2';
- $v_linkname = readlink($real_path);
- $v_size = sprintf("%11s ", decoct(0));
- }
- elseif (@is_dir($real_path)) {
- $v_typeflag = "5";
- $v_size = sprintf("%11s ", decoct(0));
- }
- else {
- $v_typeflag = '';
- clearstatcache(TRUE, $real_path);
- $v_size = sprintf("%11s ", decoct($v_info['size']));
- }
-
- $v_magic = '';
- $v_version = '';
- $v_uname = '';
- $v_gname = '';
- $v_devmajor = '';
- $v_devminor = '';
- $v_prefix = '';
-
- $v_binary_data_first = pack("a100a8a8a8a12A12",
- $new_path, $v_perms, $v_uid,
- $v_gid, $v_size, $v_mtime);
- $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
- $v_typeflag, $v_linkname, $v_magic,
- $v_version, $v_uname, $v_gname,
- $v_devmajor, $v_devminor, $v_prefix, '');
-
- // ----- Calculate the checksum.
- $v_checksum = 0;
- // ..... First part of the header.
- for ($i = 0; $i < 148; $i++) {
- $v_checksum += ord(substr($v_binary_data_first, $i, 1));
- }
- // ..... Ignore the checksum value and replace it by ' ' (space).
- for ($i = 148; $i < 156; $i++) {
- $v_checksum += ord(' ');
- }
- // ..... Last part of the header.
- for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
- $v_checksum += ord(substr($v_binary_data_last, $j, 1));
- }
-
- // ----- Write the first 148 bytes of the header in the archive.
- $this->archive->write($v_binary_data_first, 148);
-
- // ----- Write the calculated checksum.
- $v_checksum = sprintf("%6s ", decoct($v_checksum));
- $v_binary_data = pack("a8", $v_checksum);
- $this->archive->write($v_binary_data, 8);
-
- // ----- Write the last 356 bytes of the header in the archive.
- $this->archive->write($v_binary_data_last, 356);
- }
-
- /**
- * @param $new_path
- * @return bool
- */
- function writeLongHeader($new_path) {
- $v_size = sprintf("%11s ", decoct(strlen($new_path)));
-
- $v_typeflag = 'L';
- $v_linkname = '';
- $v_magic = '';
- $v_version = '';
- $v_uname = '';
- $v_gname = '';
- $v_devmajor = '';
- $v_devminor = '';
- $v_prefix = '';
-
- $v_binary_data_first = pack("a100a8a8a8a12A12",
- '././@LongLink', 0, 0, 0, $v_size, 0);
- $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
- $v_typeflag, $v_linkname, $v_magic,
- $v_version, $v_uname, $v_gname,
- $v_devmajor, $v_devminor, $v_prefix, '');
-
- // ----- Calculate the checksum.
- $v_checksum = 0;
- // ..... First part of the header.
- for ($i = 0; $i < 148; $i++) {
- $v_checksum += ord(substr($v_binary_data_first, $i, 1));
- }
- // ..... Ignore the checksum value and replace it by ' ' (space).
- for ($i = 148; $i < 156; $i++) {
- $v_checksum += ord(' ');
- }
- // ..... Last part of the header.
- for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
- $v_checksum += ord(substr($v_binary_data_last, $j, 1));
- }
-
- // ----- Write the first 148 bytes of the header in the archive.
- $this->archive->write($v_binary_data_first, 148);
-
- // ----- Write the calculated checksum.
- $v_checksum = sprintf("%6s ", decoct($v_checksum));
- $v_binary_data = pack("a8", $v_checksum);
- $this->archive->write($v_binary_data, 8);
-
- // ----- Write the last 356 bytes of the header in the archive.
- $this->archive->write($v_binary_data_last, 356);
-
- // ----- Write the filename as content of the block.
- $i = 0;
- while (($v_buffer = substr($new_path, (($i++) * 512), 512)) != '') {
- $v_binary_data = pack("a512", "$v_buffer");
- $this->archive->write($v_binary_data);
- }
- }
-
- /**
- * Write a footer to mark the end of the archive.
- */
- private function writeFooter() {
- // ----- Write the last 0 filled block for end of archive.
- $v_binary_data = pack('a1024', '');
- $this->archive->write($v_binary_data);
- }
-
- /**
- * {@inheritdoc}
- */
- public function closeArchive() {
- $this->writeFooter();
- $this->archive->close();
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Service/TeeLogger.php b/lib/backup_migrate_core/src/Service/TeeLogger.php
deleted file mode 100644
index 54681d4..0000000
--- a/lib/backup_migrate_core/src/Service/TeeLogger.php
+++ /dev/null
@@ -1,63 +0,0 @@
-setLoggers($loggers);
- }
-
- /**
- * Logs with an arbitrary level.
- *
- * @param mixed $level
- * @param string $message
- * @param array $context
- *
- * @return null
- */
- public function log($level, $message, array $context = []) {
- foreach ($this->getLoggers() as $logger) {
- $logger->log($level, $message, $context);
- }
- }
-
- /**
- * @return \Psr\Log\LoggerInterface[]
- */
- public function getLoggers() {
- return $this->loggers;
- }
-
- /**
- * @param \Psr\Log\LoggerInterface[] $loggers
- */
- public function setLoggers($loggers) {
- $this->loggers = $loggers;
- }
-
- /**
- * @param \Psr\Log\LoggerInterface $logger
- */
- public function addLogger(LoggerInterface $logger) {
- $this->loggers[] = $logger;
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Source/DatabaseSource.php b/lib/backup_migrate_core/src/Source/DatabaseSource.php
deleted file mode 100644
index 86c7832..0000000
--- a/lib/backup_migrate_core/src/Source/DatabaseSource.php
+++ /dev/null
@@ -1,118 +0,0 @@
- 'text',
- 'title' => 'Hostname'
- ];
- $schema['fields']['database'] = [
- 'type' => 'text',
- 'title' => 'Database'
- ];
- $schema['fields']['username'] = [
- 'type' => 'text',
- 'title' => 'Username',
- ];
- $schema['fields']['password'] = [
- 'type' => 'password',
- 'title' => 'Password'
- ];
- $schema['fields']['port'] = [
- 'type' => 'number',
- 'min' => 1,
- 'max' => 65535,
- 'title' => 'Port',
- ];
- }
-
- return $schema;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'generator' => 'Backup and Migrate Core',
- ]);
- }
-
- /**
- * Get a list of tables in this source.
- */
- public function getTableNames() {
- try {
- return $this->_getTableNames();
- }
- catch (\Exception $e) {
- // Todo: Log this exception.
- return [];
- }
- }
-
- /**
- * Get an array of tables with some info. Each entry must have at least a
- * 'name' key containing the table name.
- *
- * @return array
- */
- public function getTables() {
- try {
- return $this->_getTables();
- }
- catch (\Exception $e) {
- // Todo: Log this exception.
- return [];
- }
- }
-
-
- /**
- * Get the list of tables from this db.
- *
- * @return array
- */
- protected function _getTableNames() {
- $out = [];
- foreach ($this->_getTables() as $table) {
- $out[$table['name']] = $table['name'];
- }
- return $out;
- }
-
- /**
- * Internal overridable function to actually generate table info.
- *
- * @return array
- */
- abstract protected function _getTables();
-
-}
diff --git a/lib/backup_migrate_core/src/Source/DatabaseSourceInterface.php b/lib/backup_migrate_core/src/Source/DatabaseSourceInterface.php
deleted file mode 100644
index f993927..0000000
--- a/lib/backup_migrate_core/src/Source/DatabaseSourceInterface.php
+++ /dev/null
@@ -1,25 +0,0 @@
- [],
- 'importFromFile' => []
- ];
- }
-
- /**
- * {@inheritdoc}
- */
- public function exportToFile() {
- if ($directory = $this->confGet('directory')) {
- // Make sure the directory ends in exactly 1 slash:
- if (substr($directory, -1) !== '/') {
- $directory = $directory . '/';
- }
-
- if (!$writer = $this->getArchiveWriter()) {
- throw new BackupMigrateException('A file directory source requires an archive writer object.');
- }
- $ext = $writer->getFileExt();
- $file = $this->getTempFileManager()->create($ext);
-
- if ($files = $this->getFilesToBackup($directory)) {
- $writer->setArchive($file);
- foreach ($files as $new => $real) {
- $writer->addFile($real, $new);
- }
- $writer->closeArchive();
- return $file;
- }
- throw new BackupMigrateException('The directory %dir does not not have any files to be backed up.',
- ['%dir' => $directory]);
- }
- return FALSE;
- }
-
- /**
- * {@inheritdoc}
- */
- public function importFromFile(BackupFileReadableInterface $file) {
- if ($directory = $this->confGet('directory')) {
- // Make sure the directory ends in exactly 1 slash:
- if (substr($directory, -1) !== '/') {
- $directory = $directory . '/';
- }
-
- if (!file_exists($directory)) {
- throw new BackupMigrateException('The directory %dir does not exist to restore to.',
- ['%dir' => $directory]);
- }
- if (!is_writable($directory)) {
- throw new BackupMigrateException('The directory %dir cannot be written to because of the operating system file permissions.',
- ['%dir' => $directory]);
- }
-
- if (!$reader = $this->getArchiveReader()) {
- throw new BackupMigrateException('A file directory source requires an archive reader object.');
- }
- // Check that the file endings match.
- if ($reader->getFileExt() !== $file->getExtLast()) {
- throw new BackupMigrateException('This source expects a .%ext file.', ['%ext' => $reader->getFileExt()]);
- }
-
- $reader->setArchive($file);
- $reader->extractTo($directory);
- $reader->closeArchive();
-
- return TRUE;
- }
- return FALSE;
- }
-
- /**
- * Get a list if files to be backed up from the given directory.
- *
- * @param string $dir The name of the directory to list.
- *
- * @return array
- *
- * @throws \BackupMigrate\Core\Exception\BackupMigrateException
- * @throws \BackupMigrate\Core\Exception\IgnorableException
- *
- * @internal param $directory
- */
- protected function getFilesToBackup($dir) {
- // Add a trailing slash if there is none.
- if (substr($dir, -1) !== '/') {
- $dir .= '/';
- }
-
- if (!file_exists($dir)) {
- throw new BackupMigrateException('Directory %dir does not exist.',
- ['%dir' => $dir]);
- }
- if (!is_dir($dir)) {
- throw new BackupMigrateException('The file %dir is not a directory.',
- ['%dir' => $dir]);
- }
- if (!is_readable($dir)) {
- throw new BackupMigrateException('Directory %dir could not be read from.',
- ['%dir' => $dir]);
- }
-
- // Get a filtered list if files from the directory.
- list($out, $errors) = $this->_getFilesFromDirectory($dir);
-
- // Alert the user to any errors there might have been.
- if ($errors) {
- $count = count($errors);
- $file_list = implode(', ', array_slice($errors, 0, 5));
- if ($count > 5) {
- $file_list .= ', ...';
- }
-
- if (!$this->confGet('ignore_errors')) {
- throw new IgnorableException('The backup could not be completed because !count files could not be read: (!files).',
- ['!count' => $count, '!files' => $file_list]);
- }
- else {
- // throw new IgnorableException('!count files could not be read: (!files).', ['!files' => $filesmsg]);
- // @todo Log the ignored files.
- }
- }
-
- return $out;
- }
-
- /**
- * @param $base_path
- * The name of the directory to list. This must always end in '/'.
- * @param string $subdir
- * @return array
- * @internal param string $dir
- */
- protected function _getFilesFromDirectory($base_path, $subdir = '') {
- $out = $errors = [];
-
- // Open the directory.
- if (!$handle = opendir($base_path . $subdir)) {
- $errors[] = $base_path . $subdir;
- }
- else {
- while (($file = readdir($handle)) !== FALSE) {
- // If not a dot file and the file name isn't excluded.
- if ($file != '.' && $file != '..') {
-
- // Get the full path of the file.
- $path = $base_path . $subdir . $file;
-
- // Allow filters to modify or exclude this path.
- $path = $this->plugins()->call('beforeFileBackup', $path, ['source' => $this, 'base_path' => $base_path]);
- if ($path) {
- if (is_dir($path)) {
- list($sub_files, $sub_errors) =
- $this->_getFilesFromDirectory($base_path, $subdir . $file . '/');
-
- // Add the directory if it is empty.
- if (empty($sub_files)) {
- $out[$subdir . $file] = $path;
- }
-
- // Add the sub-files to the output.
- $out = array_merge($out, $sub_files);
- $errors = array_merge($errors, $sub_errors);
- }
- else {
- if (is_readable($path)) {
- $out[$subdir . $file] = $path;
- }
- else {
- $errors[] = $path;
- }
- }
- }
- }
- }
- closedir($handle);
- }
-
- return [$out, $errors];
- }
-
- /**
- * @param \BackupMigrate\Core\Service\ArchiveWriterInterface $writer
- */
- public function setArchiveWriter(ArchiveWriterInterface $writer) {
- $this->archive_writer = $writer;
- }
-
- /**
- * @return \BackupMigrate\Core\Service\ArchiveWriterInterface
- */
- public function getArchiveWriter() {
- return $this->archive_writer;
- }
-
- /**
- * @return \BackupMigrate\Core\Service\ArchiveReaderInterface
- */
- public function getArchiveReader() {
- return $this->archive_reader;
- }
-
- /**
- * @param \BackupMigrate\Core\Service\ArchiveReaderInterface $archive_reader
- */
- public function setArchiveReader($archive_reader) {
- $this->archive_reader = $archive_reader;
- }
-
- /**
- * Get a definition for user-configurable settings.
- *
- * @param array $params
- *
- * @return array
- */
- public function configSchema(array $params = []) {
- $schema = [];
-
- // Init settings.
- if ($params['operation'] == 'initialize') {
- $schema['fields']['directory'] = [
- 'type' => 'text',
- 'title' => $this->t('Directory Path'),
- ];
- }
-
- return $schema;
- }
-
- /**
- * Get the default values for the plugin.
- *
- * @return \BackupMigrate\Core\Config\Config
- */
- public function configDefaults() {
- return new Config([
- 'directory' => '',
- ]);
- }
-
-}
diff --git a/lib/backup_migrate_core/src/Source/MySQLiSource.php b/lib/backup_migrate_core/src/Source/MySQLiSource.php
deleted file mode 100644
index 016e1a1..0000000
--- a/lib/backup_migrate_core/src/Source/MySQLiSource.php
+++ /dev/null
@@ -1,498 +0,0 @@
- [],
- 'importFromFile' => []
- ];
- }
-
- /**
- * Export this source to the given temp file. This should be the main
- * back up function for this source.
- *
- * @return \BackupMigrate\Core\File\BackupFileReadableInterface $file
- * A backup file with the contents of the source dumped to it..
- */
- public function exportToFile() {
- if ($connection = $this->_getConnection()) {
- $file = $this->getTempFileManager()->create('mysql');
-
- $exclude = (array) $this->confGet('exclude_tables');
- $nodata = (array) $this->confGet('nodata_tables');
-
- $file->write($this->_getSQLHeader());
- $tables = $this->_getTables();
-
- $lines = 0;
- foreach ($tables as $table) {
- // @todo reenable this.
- // if (_backup_migrate_check_timeout()) {
- // return FALSE;
- // }
- $table = $this->plugins()->call('beforeDBTableBackup', $table, ['source' => $this]);
- if ($table['name'] && !isset($exclude[$table['name']]) && empty($table['exclude'])) {
- $file->write($this->_getTableCreateSQL($table));
- $lines++;
- if (empty($table['nodata']) && !in_array($table['name'], $nodata)) {
- $lines += $this->_dumpTableSQLToFile($file, $table);
- }
- }
- }
-
- $file->write($this->_getSQLFooter());
- $file->close();
- return $file;
- }
- else {
- // @todo Throw exception
- return $this->getTempFileManager()->create('mysql');
- }
-
- }
-
- /**
- * Import to this source from the given backup file. This is the main restore
- * function for this source.
- *
- * @param \BackupMigrate\Core\File\BackupFileReadableInterface $file
- * The file to read the backup from. It will not be opened for reading
- *
- * @return bool|int
- */
- public function importFromFile(BackupFileReadableInterface $file) {
- $num = 0;
-
- if ($conn = $this->_getConnection()) {
- // Open (or rewind) the file.
- $file->openForRead();
-
- // Read one line at a time and run the query.
- while ($line = $this->_readSQLCommand($file)) {
- // if (_backup_migrate_check_timeout()) {
- // return FALSE;
- // }
- if ($line) {
- // Execute the sql query from the file.
- $conn->query($line);
- $num++;
- }
- }
- // Close the file, we're done reading it.
- $file->close();
- }
- return $num;
- }
-
-
- /**
- * Get the db connection for the specified db.
- *
- * @return \mysqli Connection object.
- *
- * @throws \Exception
- */
- protected function _getConnection() {
- if (!$this->connection) {
- if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
- throw new BackupMigrateException('Cannot connect to the database because the MySQLi extension is missing.');
- }
-
- $pdo_config = $this->confGet('pdo');
-
- $ssl_config = [
- 'key' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_KEY])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_KEY] : NULL,
- 'cert' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CERT])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CERT] : NULL,
- 'ca' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CA])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CA] : NULL,
- 'capath' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CAPATH])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CAPATH] : NULL,
- 'cypher' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CIPHER])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CIPHER] : NULL,
- ];
-
- if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) {
- $ssl_config['verify_server_cert'] = (isset($pdo_config[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] : TRUE;
- }
- else {
- $ssl_config['verify_server_cert'] = TRUE;
- }
-
- if ($ssl_config['key'] || $ssl_config['cert'] || $ssl_config['ca'] || $ssl_config['capath'] || $ssl_config['cypher']) {
-
- // Provide a workaround for PHP7 peer certificate verification issues:
- // - https://bugs.php.net/bug.php?id=68344
- // - https://bugs.php.net/bug.php?id=71003
- if ($ssl_config['verify_server_cert']) {
- $flags = MYSQLI_CLIENT_SSL;
- }
- else {
- $flags = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
- }
-
- // Connect using PDO SSL config.
- $this->connection = new \mysqli();
-
- $this->connection->ssl_set($ssl_config['key'], $ssl_config['cert'], $ssl_config['ca'], $ssl_config['capath'], $ssl_config['cypher']);
-
- $this->connection->real_connect(
- $this->confGet('host'),
- $this->confGet('username'),
- $this->confGet('password'),
- $this->confGet('database'),
- $this->confGet('port'),
- $this->confGet('socket'),
- $flags
- );
- }
- else {
- $this->connection = new \mysqli(
- $this->confGet('host'),
- $this->confGet('username'),
- $this->confGet('password'),
- $this->confGet('database'),
- $this->confGet('port'),
- $this->confGet('socket')
- );
- }
-
- // Throw an error on fail.
- if ($this->connection->connect_errno || !$this->connection->ping()) {
- throw new BackupMigrateException("Failed to connect to MySQL server.");
- }
- // Ensure, that the character set is UTF8.
- if (!$this->connection->set_charset('utf8mb4')) {
- if (!$this->connection->set_charset('utf8')) {
- throw new BackupMigrateException('UTF8 is not supported by the MySQL server.');
- }
- }
- }
- return $this->connection;
- }
-
-
- /**
- * Get the header for the top of the SQL file.
- *
- * @return string
- */
- protected function _getSQLHeader() {
- $info = $this->_dbInfo();
- $version = $info['version'];
- $host = $this->confGet('host');
- $db = $this->confGet('database');
- $timestamp = gmdate('r');
- $generator = $this->confGet('generator');
-
- return <<';
+ }
+
+ // Output the metadata.
+ if ($this->confGet('showmeta')) {
+ print "---------------------\n";
+ print "Metadata: \n";
+ print_r($file->getMetaAll());
+ print "---------------------\n";
+ }
+
+ // Output the body.
+ if ($this->confGet('showbody')) {
+ print "---------------------\n";
+ print "Body: \n";
+
+ $max = $this->confGet('maxbody');
+ $chunk = min($max, 1024);
+ if ($file->openForRead()) {
+ // Transfer file in 1024 byte chunks to save memory usage.
+ while ($max > 0 && $data = $file->readBytes($chunk)) {
+ print $data;
+ $max -= $chunk;
+ }
+ $file->close();
+ }
+ print "---------------------\n";
+ }
+
+ // Quick and dirty way to html format this output.
+ if ($this->confGet('format') == 'html') {
+ print '';
+ }
+
+ exit;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'showmeta' => TRUE,
+ 'showbody' => TRUE,
+ 'maxbody' => 1024 * 16,
+ 'format' => 'text',
+ ]);
+ }
+
+}
diff --git a/src/Core/Destination/DestinationBase.php b/src/Core/Destination/DestinationBase.php
new file mode 100644
index 0000000..afdbe09
--- /dev/null
+++ b/src/Core/Destination/DestinationBase.php
@@ -0,0 +1,99 @@
+_saveFile($file);
+ $this->_saveFileMetadata($file);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadFileMetadata(BackupFileInterface $file) {
+ // If this file is already loaded, simply return it.
+ // @todo fix this inappropriate use of file metadata.
+ if (!$file->getMeta('metadata_loaded')) {
+ $metadata = $this->_loadFileMetadataArray($file);
+ $file->setMetaMultiple($metadata);
+ $file->setMeta('metadata_loaded', TRUE);
+ }
+ return $file;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteFile($id) {
+ return $this->_deleteFile($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isRemote() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function checkWritable() {
+ throw new DestinationNotWritableException('The specified destination cannot be written to.');
+ }
+
+ /**
+ * Do the actual delete for a file.
+ *
+ * @param string $id The id of the file to delete.
+ */
+ abstract protected function _deleteFile($id);
+
+ /**
+ * Do the actual file save. Should take care of the actual creation of a file
+ * in the destination without regard for metadata.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ */
+ abstract protected function _saveFile(BackupFileReadableInterface $file);
+
+ /**
+ * Do the metadata save. This function is called to save the data file AND
+ * the metadata sidecar file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileInterface $file
+ */
+ abstract protected function _saveFileMetadata(BackupFileInterface $file);
+
+ /**
+ * Load the actual metadata for the file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileInterface $file
+ */
+ abstract protected function _loadFileMetadataArray(BackupFileInterface $file);
+
+}
diff --git a/src/Core/Destination/DestinationInterface.php b/src/Core/Destination/DestinationInterface.php
new file mode 100644
index 0000000..580e33d
--- /dev/null
+++ b/src/Core/Destination/DestinationInterface.php
@@ -0,0 +1,14 @@
+_saveFile($file);
+ $this->_saveFileMetadata($file);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function checkWritable() {
+ $this->checkDirectory();
+ }
+
+ /**
+ * Get a definition for user-configurable settings.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ public function configSchema(array $params = []) {
+ $schema = [];
+
+ // Init settings.
+ if ($params['operation'] == 'initialize') {
+ $schema['fields']['directory'] = [
+ 'type' => 'text',
+ 'title' => $this->t('Directory Path'),
+ ];
+ }
+
+ return $schema;
+ }
+
+
+ /**
+ * Do the actual file save. This function is called to save the data file AND
+ * the metadata sidecar file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ */
+ function _saveFile(BackupFileReadableInterface $file) {
+ // Check if the directory exists.
+ $this->checkDirectory();
+
+ copy($file->realpath(), $this->idToPath($file->getFullName()));
+ // @todo use copy/unlink if the temp file and the destination do not share a stream wrapper.
+ }
+
+ /**
+ * Check that the directory can be used for backup.
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ */
+ protected function checkDirectory() {
+ $dir = $this->confGet('directory');
+
+ // Check if the directory exists.
+ if (!file_exists($dir)) {
+ throw new DestinationNotWritableException(
+ "The backup file could not be saved to '%dir' because it does not exist.",
+ ['%dir' => $dir]
+ );
+ }
+
+ // Check if the directory is writable.
+ if (!is_writable($this->confGet('directory'))) {
+ throw new DestinationNotWritableException(
+ "The backup file could not be saved to '%dir' because Backup and Migrate does not have write access to that directory.",
+ ['%dir' => $dir]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFile($id) {
+ if ($this->fileExists($id)) {
+ $out = new BackupFile();
+ $out->setMeta('id', $id);
+ $out->setFullName($id);
+ return $out;
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadFileForReading(BackupFileInterface $file) {
+ // If this file is already readable, simply return it.
+ if ($file instanceof BackupFileReadableInterface) {
+ return $file;
+ }
+
+ $id = $file->getMeta('id');
+ if ($this->fileExists($id)) {
+ return new ReadableStreamBackupFile($this->idToPath($id));
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listFiles() {
+ $dir = $this->confGet('directory');
+ $out = [];
+
+ // Get the entire list of filenames.
+ $files = $this->getAllFileNames();
+
+ foreach ($files as $file) {
+ $filepath = $dir . '/' . $file;
+ $out[$file] = new ReadableStreamBackupFile($filepath);
+ }
+
+ return $out;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function queryFiles(array $filters = [], $sort = 'datestamp', $sort_direction = SORT_DESC, $count = 100, $start = 0) {
+ // Get the full list of files.
+ $out = $this->listFiles($count + $start);
+ foreach ($out as $key => $file) {
+ $out[$key] = $this->loadFileMetadata($file);
+ }
+
+ // Filter the output.
+ if ($filters) {
+ $out = array_filter($out, function($file) use ($filters) {
+ foreach ($filters as $key => $value) {
+ if ($file->getMeta($key) !== $value) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ });
+ }
+
+ // Sort the files.
+ if ($sort && $sort_direction) {
+ uasort($out, function ($a, $b) use ($sort, $sort_direction) {
+ if ($sort_direction == SORT_DESC) {
+ return $b->getMeta($sort) < $b->getMeta($sort);
+ }
+ else {
+ return $b->getMeta($sort) > $b->getMeta($sort);
+ }
+ });
+ }
+
+ // Slice the return array.
+ if ($count || $start) {
+ $out = array_slice($out, $start, $count);
+ }
+
+ return $out;
+ }
+
+ /**
+ * @return int The number of files in the destination.
+ */
+ public function countFiles() {
+ $files = $this->getAllFileNames();
+ return count($files);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fileExists($id) {
+ return file_exists($this->idToPath($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function _deleteFile($id) {
+ if ($file = $this->getFile($id)) {
+ if ($file = $this->loadFileForReading($file)) {
+ return unlink($file->realpath());
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Return a file path for the given file id.
+ *
+ * @param $id
+ *
+ * @return string
+ */
+ protected function idToPath($id) {
+ return rtrim($this->confGet('directory'), '/') . '/' . $id;
+ }
+
+ /**
+ * Get the entire file list from this destination.
+ *
+ * @return array
+ */
+ protected function getAllFileNames() {
+ $files = [];
+
+ // Read the list of files from the directory.
+ $dir = $this->confGet('directory');
+
+ /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
+ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
+ $scheme = $stream_wrapper_manager->isValidScheme($dir);
+
+ // Ensure the stream is configured.
+ if (!$stream_wrapper_manager->isValidScheme($scheme)) {
+ \Drupal::messenger()->addMessage(t('Your @scheme stream is not configured.', [
+ '@scheme' => $scheme . '://'
+ ]), 'warning');
+ return $files;
+ }
+
+ if ($handle = opendir($dir)) {
+ while (FALSE !== ($file = readdir($handle))) {
+ $filepath = $dir . '/' . $file;
+ // Don't show hidden, unreadable or metadata files.
+ if (substr($file, 0, 1) !== '.' && is_readable($filepath) && substr($file, strlen($file) - 5) !== '.info') {
+ $files[] = $file;
+ }
+ }
+ }
+
+ return $files;
+ }
+
+}
diff --git a/src/Core/Destination/ListableDestinationInterface.php b/src/Core/Destination/ListableDestinationInterface.php
new file mode 100644
index 0000000..00f5e8b
--- /dev/null
+++ b/src/Core/Destination/ListableDestinationInterface.php
@@ -0,0 +1,62 @@
+destinations()->add('destination1', new MyDestinationPlugin());
+
+A single Backup and Migrate instance can have more than one destination of a given type. Each destination will have a unique key that will be used to pass the configuration to the destination object as well as to specify the destination(s) when running a `backup()` or `restore()` operation. Only one destination will be used during each backup or restore operation.
diff --git a/src/Core/Destination/ReadableDestinationInterface.php b/src/Core/Destination/ReadableDestinationInterface.php
new file mode 100644
index 0000000..f9517b2
--- /dev/null
+++ b/src/Core/Destination/ReadableDestinationInterface.php
@@ -0,0 +1,56 @@
+getFullName();
+ $filename = $id . '.info';
+ if ($this->fileExists($filename)) {
+ $meta_file = $this->getFile($filename);
+ $meta_file = $this->loadFileForReading($meta_file);
+ $info = $this->_INIToArray($meta_file->readAll());
+ }
+ return $info;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _saveFileMetadata(BackupFileInterface $file) {
+ // Get the file metadata and convert to INI format.
+ $meta = $file->getMetaAll();
+ $ini = $this->_arrayToINI($meta);
+
+ // Create an info file.
+ $meta_file = $this->getTempFileManager()->pushExt($file, 'info');
+ $meta_file->write($ini);
+
+ // Save the metadata.
+ $this->_saveFile($meta_file);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteFile($id) {
+ $this->_deleteFile($id);
+ $this->_deleteFile($id . '.info');
+ }
+
+ /**
+ * Parse an INI file's contents.
+ *
+ * For simplification this function only parses the simple subset of INI
+ * syntax generated by SidecarMetadataDestinationTrait::_arrayToINI();
+ *
+ * @param $ini
+ *
+ * @return array
+ */
+ protected function _INIToArray($ini) {
+ $out = [];
+ $lines = explode("\n", $ini);
+ foreach ($lines as $line) {
+ $line = trim($line);
+ // Skip comments (even though there probably won't be any.
+ if (substr($line, 0, 1) == ';') {
+ continue;
+ }
+
+ // Match the key and value using a simplified syntax.
+ $matches = [];
+ if (preg_match('/^([^=]+)\s?=\s?"(.*)"$/', $line, $matches)) {
+ $key = $matches[1];
+ $val = $matches[2];
+
+ // Break up a key in the form a[b][c]
+ $keys = explode('[', $key);
+ $insert = &$out;
+ foreach ($keys as $part) {
+ $part = trim($part, ' ]');
+ $insert[$part] = '';
+ $insert = &$insert[$part];
+ }
+ $insert = $val;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * @param $data
+ * @param string $prefix
+ * @return string
+ */
+ protected function _arrayToINI($data, $prefix = '') {
+ $content = "";
+ foreach ($data as $key => $val) {
+ if ($prefix) {
+ $key = $prefix . '[' . $key . ']';
+ }
+ if (is_array($val)) {
+ $content .= $this->_arrayToINI($val, $key);
+ }
+ else {
+ $content .= $key . " = \"" . $val . "\"\n";
+ }
+ }
+ return $content;
+ }
+
+}
diff --git a/src/Core/Destination/StreamDestination.php b/src/Core/Destination/StreamDestination.php
new file mode 100644
index 0000000..5a06011
--- /dev/null
+++ b/src/Core/Destination/StreamDestination.php
@@ -0,0 +1,73 @@
+confGet('streamuri');
+ if ($fp_out = fopen($stream_uri, 'w')) {
+ $file->openForRead();
+ while ($data = $file->readBytes(1024 * 512)) {
+ fwrite($fp_out, $data);
+ }
+ fclose($fp_out);
+ $file->close();
+ }
+ else {
+ throw new \Exception("Cannot open the file $stream_uri for writing");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function checkWritable() {
+ $stream_uri = $this->confGet('streamuri');
+
+ // The stream must exist.
+ if (!file_exists($stream_uri)) {
+ throw new DestinationNotWritableException('The file stream !uri does not exist.', ['%uri' => $stream_uri]);
+ }
+
+ // The stream must be writable.
+ if (!file_exists($stream_uri)) {
+ throw new DestinationNotWritableException('The file stream !uri cannot be written to.', ['%uri' => $stream_uri]);
+ }
+ }
+ /**
+ * {@inheritdoc}
+ */
+ public function getFile($id) {
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadFileMetadata(BackupFileInterface $file) {
+ return $file;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadFileForReading(BackupFileInterface $file) {
+ return $file;
+ }
+
+}
diff --git a/src/Core/Destination/WritableDestinationInterface.php b/src/Core/Destination/WritableDestinationInterface.php
new file mode 100644
index 0000000..41adc8f
--- /dev/null
+++ b/src/Core/Destination/WritableDestinationInterface.php
@@ -0,0 +1,22 @@
+replacement = $replacement;
+ $this->message_raw = $message;
+
+ // Send the replaced message to the parent constructor to act as normal in most cases.
+ parent::__construct(strtr($message, $replacement), $code);
+ }
+
+ /**
+ * Get the unmodified message with replacement tokens.
+ *
+ * @return null|string
+ */
+ public function getMessageRaw() {
+ return $this->message_raw;
+ }
+
+}
diff --git a/src/Core/Exception/DestinationNotWritableException.php b/src/Core/Exception/DestinationNotWritableException.php
new file mode 100644
index 0000000..944e8a5
--- /dev/null
+++ b/src/Core/Exception/DestinationNotWritableException.php
@@ -0,0 +1,14 @@
+metadata[$key]) ? $this->metadata[$key] : NULL;
+ }
+
+ /**
+ * Set a metadata value.
+ *
+ * @param string $key The key for the metadata item.
+ * @param mixed $value The value for the metadata item.
+ */
+ public function setMeta($key, $value) {
+ $this->metadata[$key] = $value;
+ }
+
+ /**
+ * Set a metadata value.
+ *
+ * @param array $values An array of key-value pairs for the file metadata.
+ */
+ public function setMetaMultiple($values) {
+ foreach ((array) $values as $key => $value) {
+ $this->setMeta($key, $value);
+ }
+ }
+
+ /**
+ * Get all metadata.
+ *
+ * @param array $values An array of key-value pairs for the file metadata.
+ *
+ * @return array
+ */
+ public function getMetaAll() {
+ return $this->metadata;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name) {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFullName() {
+ return rtrim($this->name . '.' . implode('.', $this->getExtList()));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFullName($fullname) {
+ // Break the file name into name and extension array.
+ $parts = explode('.', $fullname);
+ $this->setName(array_shift($parts));
+ $this->setExtList($parts);
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExtList() {
+ return $this->ext;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExtLast() {
+ return end($this->ext);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExt() {
+ return implode('.', $this->getExtList());
+ }
+
+ /**
+ * @param array $ext
+ * The list of file extensions for the file* The list of file extensions for the file
+ */
+ public function setExtList($ext) {
+ $this->ext = array_filter($ext);
+ }
+
+}
diff --git a/src/Core/File/BackupFileInterface.php b/src/Core/File/BackupFileInterface.php
new file mode 100644
index 0000000..faabaa9
--- /dev/null
+++ b/src/Core/File/BackupFileInterface.php
@@ -0,0 +1,112 @@
+ '~/mybackups']);
+ $file = $destination->getFile('databse.mysql');
+
+ // This object has metadata but the contents cannot neccessarily be read.
+ if ($file && $file->getMeta('filesize') > 1000) {
+
+ // To read the file we must allow the destination to load it for us if needed.
+ $file = $destination->loadFileForReading($file);
+
+ // The file contents should now be available to us.
+ if ($file) {
+ echo $file->readAll();
+ }
+ }
+
+### BackupFileWriteableInterface
+This subclass can be read from AND written to. Writable files in Backup and Migrate are always temporary files and must be created by the TempFileManager. Source plugins will create an empty temporary file to write the backup to while file filter plugins (like compression or encryption filters) will create a new temporary file and copy the contents from the input file to the new output file. The file that results at the end of the plugin chain will either be used to restore to the source (restore operation) or sent to a destination to be persisted (backup operation). Because plugins are responsible for creating new temporary writable files as needed, they should never require a writable file as input or promise one as a return value.
+
+## The Temporary File Manager
+All writable files must be created by the Temporary File Manager. This class can create a new blank file with a given file extension. The standard flow of file filters is a chain where one filter hands a file to the next which copies the data to a new file and hands that on. For example, the MySQL source generates a new database dump file which gets handed to an encryption filter which copies the metadata to a new file containing the encrypted data. That file is then passed to a compression filter which creates a new compressed version of the file which is finally handed off to a destination for saving. At each step along the way a new file is created with an a new extension appended to the end:
+
+ file.mysql -> file.mysql.aes -> file.mysql.aes.gz
+
+To facilitate this the Temporary File Manager takes care of the details of copying file metadata and provisioning a new temporary file with the new file extension to write the modified data to. A compressor plugin might do something like this:
+
+ function afterBackup($file_in) {
+ // Get a new file with '.gz' added to the end of the filename.
+ $file_out = $this->getTempFileManager()->pushExt($file_in, 'gz');
+ if ($this->doCompress($file_in, $file_out)) {
+ return $file_out;
+ }
+ // Compression failed, return the original
+ return $file_in;
+ }
+
+Similarly `$this->getTempFileManager()->popExt()` will pull the last item from the file extension and return a blank file for decompression prior to import.
+
+See [Plugins](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Plugin) for details on how to make the Temporary File Manager accessible within a plugin.
+
+### The Temporary File Adapter ###
+While the file manager takes care of the metadata of temporary files, it cannot provision actual on-disk files to write to. That is because that operation will be different depending on where the code is run and is therefore the responsibility of the [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment) object. The environment provides a service called called the Temporary File Adaptor (an object whose class which implements `\Drupal\backup_migrate\Core\Services\TempFileAdapterInterface`). The job of this class is to provision actual temporary files in the host operating system that can be written to and read from. That service is also responsible for tracking all of the files that have been created during the running of an operation and deleting those files when the operation completes. Backup and Migrate core comes with a basic adapter which accepts any writable directory as an argument and creates new temporary files within that directory. This implementation should suffice for most consuming software but can be replaced with another adapter if needed.
+
+See: [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment)
diff --git a/src/Core/File/ReadableStreamBackupFile.php b/src/Core/File/ReadableStreamBackupFile.php
new file mode 100644
index 0000000..495f84d
--- /dev/null
+++ b/src/Core/File/ReadableStreamBackupFile.php
@@ -0,0 +1,193 @@
+path = $filepath;
+
+ // Get the basename and extensions.
+ $this->setFullName(basename($filepath));
+
+ // Get the basic file stats since this is probably a read-only file option and these won't change.
+ $this->_loadFileStats();
+ }
+
+ /**
+ * Destructor.
+ */
+ function __destruct() {
+ // Close the handle if we've opened it.
+ $this->close();
+ }
+
+ /**
+ * Get the realpath of the file.
+ *
+ * @return string The path or stream URI to the file or NULL if the file does not exist.
+ */
+ function realpath() {
+ if (file_exists($this->path)) {
+ return $this->path;
+ }
+ return NULL;
+ }
+
+ /**
+ * Open a file for reading or writing.
+ *
+ * @param bool $binary If true open as a binary file
+ *
+ * @return resource
+ *
+ * @throws \Exception
+ */
+ function openForRead($binary = FALSE) {
+ if (!$this->isOpen()) {
+ $path = $this->realpath();
+
+ if (!is_readable($path)) {
+ // @todo Throw better exception
+ throw new \Exception('Cannot read file.');
+ }
+
+ // Open the file.
+ $mode = "r" . ($binary ? "b" : "");
+ $this->handle = fopen($path, $mode);
+ if (!$this->handle) {
+ throw new \Exception('Cannot open file.');
+ }
+ }
+ // If the file is already open, rewind it.
+ $this->rewind();
+ return $this->handle;
+ }
+
+ /**
+ * Close a file when we're done reading/writing.
+ */
+ function close() {
+ if ($this->isOpen()) {
+ fclose($this->handle);
+ $this->handle = NULL;
+ }
+ }
+
+ /**
+ * Is this file open for reading/writing.
+ *
+ * Return bool True if the file is open, false if not.
+ */
+ function isOpen() {
+ return !empty($this->handle) && get_resource_type($this->handle) == 'stream';
+ }
+
+ /**
+ * Read a line from the file.
+ *
+ * @param int $size The number of bites to read or 0 to read the whole file
+ *
+ * @return string The data read from the file or NULL if the file can't be read or is at the end of the file.
+ */
+ function readBytes($size = 1024, $binary = FALSE) {
+ if (!$this->isOpen()) {
+ $this->openForRead($binary);
+ }
+ if ($this->handle && !feof($this->handle)) {
+ return fread($this->handle, $size);
+ }
+ return NULL;
+ }
+
+
+ /**
+ * Read a single line from the file.
+ *
+ * @return string The data read from the file or NULL if the file can't be read or is at the end of the file.
+ */
+ public function readLine() {
+ if (!$this->isOpen()) {
+ $this->openForRead();
+ }
+ return fgets($this->handle);
+ }
+
+ /**
+ * Read a line from the file.
+ *
+ * @return string The data read from the file or NULL if the file can't be read.
+ */
+ public function readAll() {
+ if (!$this->isOpen()) {
+ $this->openForRead();
+ }
+ $this->rewind();
+ return stream_get_contents($this->handle);
+ }
+
+ /**
+ * Move the file pointer forward a given number of bytes.
+ *
+ * @param int $bytes
+ *
+ * @return int
+ * The number of bytes moved or -1 if the operation failed.
+ */
+ public function seekBytes($bytes) {
+ if ($this->isOpen()) {
+ return fseek($this->handle, $bytes);
+ }
+ return -1;
+ }
+
+ /**
+ * Rewind the file handle to the start of the file.
+ */
+ function rewind() {
+ if ($this->isOpen()) {
+ rewind($this->handle);
+ }
+ }
+
+ /**
+ * Get info about the file and load them as metadata.
+ */
+ protected function _loadFileStats() {
+ clearstatcache();
+ $this->setMeta('filesize', filesize($this->realpath()));
+ $this->setMeta('datestamp', filectime($this->realpath()));
+ }
+
+}
diff --git a/src/Core/File/TempFileAdapter.php b/src/Core/File/TempFileAdapter.php
new file mode 100644
index 0000000..2dedfda
--- /dev/null
+++ b/src/Core/File/TempFileAdapter.php
@@ -0,0 +1,111 @@
+dir = $dir;
+ $this->prefix = $prefix;
+ $this->tempfiles = [];
+ // @todo check that temp direcory is writeable or throw an exception.
+ }
+
+ /**
+ * Destruct the manager. Delete all the temporary files when this manager is destroyed.
+ */
+ public function __destruct() {
+ $this->deleteAllTempFiles();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTempFile($ext = '') {
+ // Add a dot to the file extension.
+ $ext = $ext ? '.' . $ext : '';
+
+ // Find an unused random file name.
+ $try = 5;
+ do {
+ $out = $this->dir . $this->prefix . mt_rand() . $ext;
+ $fp = @fopen($out, 'x');
+ } while (!$fp && $try-- > 0);
+ if ($fp) {
+ fclose($fp);
+ }
+ else {
+ throw new \Exception('Could not create a temporary file to write to.');
+ }
+
+ $this->tempfiles[] = $out;
+ return $out;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteTempFile($filename) {
+ // Only delete files that were created by this manager.
+ if (in_array($filename, $this->tempfiles)) {
+ if (file_exists($filename)) {
+ if (is_writable($filename)) {
+ unlink($filename);
+ }
+ else {
+ throw new BackupMigrateException('Could not delete the temp file: %file because it is not writable', ['%file' => $filename]);
+ }
+ }
+ // Remove the item from the list.
+ $this->tempfiles = array_diff($this->tempfiles, [$filename]);
+ return;
+ }
+ throw new BackupMigrateException('Attempting to delete a temp file not managed by this codebase: %file', ['%file' => $filename]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteAllTempFiles() {
+ foreach ($this->tempfiles as $file) {
+ $this->deleteTempFile($file);
+ }
+ }
+
+}
diff --git a/src/Core/File/TempFileAdapterInterface.php b/src/Core/File/TempFileAdapterInterface.php
new file mode 100644
index 0000000..67624c9
--- /dev/null
+++ b/src/Core/File/TempFileAdapterInterface.php
@@ -0,0 +1,31 @@
+adapter = $adapter;
+ }
+
+ /**
+ * Create a brand new temp file with the given extension (if specified). The
+ * new file should be writable.
+ *
+ * @param string $ext The file extension for this file (optional)
+ *
+ * @return BackupFileWritableInterface
+ */
+ public function create($ext = '') {
+ $file = new WritableStreamBackupFile($this->adapter->createTempFile($ext));
+ $file->setExtList(explode('.', $ext));
+ return $file;
+ }
+
+ /**
+ * Return a new file based on the passed in file with the given file extension.
+ * This should maintain the metadata of the file passed in with the new file
+ * extension added after the old one.
+ * For example: xxx.mysql would become xxx.mysql.gz.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileInterface $file
+ * The file to add the extension to.
+ * @param $ext
+ * The new file extension.
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileWritableInterface
+ * A new writable backup file with the new extension and all of the metadata
+ * from the previous file.
+ */
+ public function pushExt(BackupFileInterface $file, $ext) {
+ // Push the new extension on to the new file.
+ $parts = $file->getExtList();
+ array_push($parts, $ext);
+ $new_ext = implode('.', $parts);
+
+ // Copy the file metadata to a new TempFile.
+ $out = new WritableStreamBackupFile($this->adapter->createTempFile($new_ext));
+
+ // Copy the file metadata to a new TempFile.
+ $out->setMetaMultiple($file->getMetaAll());
+ $out->setName($file->getName());
+ $out->setExtList($parts);
+
+ return $out;
+ }
+
+ /**
+ * Return a new file based on the one passed in but with the last part of the
+ * file extension removed.
+ * For example: xxx.mysql.gz would become xxx.mysql.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileInterface $file
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileWritableInterface
+ * A new writable backup file with the last extension removed and
+ * all of the metadata from the previous file.
+ */
+ public function popExt(BackupFileInterface $file) {
+ // Pop the last extension from the last of the file.
+ $parts = $file->getExtList();
+ array_pop($parts);
+ $new_ext = implode('.', $parts);
+
+ // Create a new temp file with the new extension.
+ $out = new WritableStreamBackupFile($this->adapter->createTempFile($new_ext));
+
+ // Copy the file metadata to a new TempFile.
+ $out->setMetaMultiple($file->getMetaAll());
+ $out->setName($file->getName());
+ $out->setExtList($parts);
+
+ return $out;
+ }
+
+}
diff --git a/src/Core/File/TempFileManagerInterface.php b/src/Core/File/TempFileManagerInterface.php
new file mode 100644
index 0000000..f7b0c00
--- /dev/null
+++ b/src/Core/File/TempFileManagerInterface.php
@@ -0,0 +1,60 @@
+isOpen()) {
+ $path = $this->realpath();
+
+ // Check if the file can be read/written.
+ if ((file_exists($path) && !is_writable($path)) || (!file_exists($path) && !is_writable(dirname($path)))) {
+ // @todo Throw better exception
+ throw new BackupMigrateException('Cannot write to file: %path', ['%path' => $path]);
+ }
+
+ // Open the file.
+ $mode = "w" . ($binary ? "b" : "");
+ $this->handle = fopen($path, $mode);
+ if (!$this->handle) {
+ throw new BackupMigrateException('Cannot open file: %path', ['%path' => $path]);
+ }
+ }
+ }
+
+ /**
+ * Write a line to the file.
+ *
+ * @param string $data A string to write to the file.
+ *
+ * @throws \Exception
+ */
+ function write($data) {
+ if (!$this->isOpen()) {
+ $this->openForWrite();
+ }
+
+ if ($this->handle) {
+ if (fwrite($this->handle, $data) === FALSE) {
+ throw new \Exception('Cannot write to file: ' . $this->realpath());
+ }
+ else {
+ $this->dirty = TRUE;
+ }
+ }
+ else {
+ throw new \Exception('File not open for writing.');
+ }
+ }
+
+
+ /**
+ * Update the file time and size when the file is closed.
+ */
+ function close() {
+ parent::close();
+
+ // If the file has been modified, update the stats from disk.
+ if ($this->dirty) {
+ $this->_loadFileStats();
+ $this->dirty = FALSE;
+ }
+ }
+
+ /**
+ * A shorthand function to open the file, write the given contents and close
+ * the file. Used for small amounts of data that can fit in memory.
+ *
+ * @param $data
+ */
+ public function writeAll($data) {
+ $this->openForWrite();
+ $this->write($data);
+ $this->close();
+ }
+
+}
diff --git a/src/Core/Filter/CompressionFilter.php b/src/Core/Filter/CompressionFilter.php
new file mode 100644
index 0000000..92e689d
--- /dev/null
+++ b/src/Core/Filter/CompressionFilter.php
@@ -0,0 +1,386 @@
+ ['weight' => 100],
+ * 'restore' => ['weight' => -100],
+ * ];
+ *
+ * @return array
+ */
+ public function supportedOps() {
+ return [
+ 'getFileTypes' => [],
+ 'backupSettings' => [],
+ 'afterBackup' => ['weight' => 100],
+ 'beforeRestore' => ['weight' => -100],
+ ];
+ }
+
+ /**
+ * Return the filetypes supported by this filter.
+ */
+ public function getFileTypes() {
+ return [
+ [
+ "gzip" => [
+ "extension" => "gz",
+ "filemime" => "application/x-gzip",
+ 'ops' => [
+ 'backup',
+ 'restore',
+ ],
+ ],
+ "bzip" => [
+ "extension" => "bz",
+ "filemime" => "application/x-bzip",
+ 'ops' => [
+ 'backup',
+ 'restore',
+ ],
+ ],
+ "bzip2" => [
+ "extension" => "bz2",
+ "filemime" => "application/x-bzip",
+ 'ops' => [
+ 'backup',
+ 'restore',
+ ],
+ ],
+ "zip" => [
+ "extension" => "zip",
+ "filemime" => "application/zip",
+ 'ops' => [
+ 'backup',
+ 'restore',
+ ],
+ ],
+ ],
+ ];
+ }
+
+
+ /**
+ * Get a definition for user-configurable settings.
+ *
+ * @return array
+ */
+ public function configSchema(array $params = []) {
+ $schema = [];
+
+ if ($params['operation'] == 'backup') {
+ $schema['groups']['file'] = [
+ 'title' => 'Backup File',
+ ];
+ $compression_options = $this->_availableCompressionAlgorithms();
+ $schema['fields']['compression'] = [
+ 'group' => 'file',
+ 'type' => 'enum',
+ 'title' => 'Compression',
+ 'options' => $compression_options,
+ 'actions' => ['backup']
+ ];
+ }
+
+ return $schema;
+ }
+
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'compression' => $this->_defaultCompressionAlgorithm(),
+ ]);
+ }
+
+
+ /**
+ * Run on a backup.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileReadableInterface
+ */
+ public function afterBackup(BackupFileReadableInterface $file) {
+ $out = $success = FALSE;
+ if ($this->confGet('compression') == 'gzip') {
+ $out = $this->getTempFileManager()->pushExt($file, 'gz');
+ $success = $this->gzipEncode($file, $out);
+ }
+ if ($this->confGet('compression') == 'bzip') {
+ $out = $this->getTempFileManager()->pushExt($file, 'bz2');
+ $success = $this->bzipEncode($file, $out);
+ }
+ if ($this->confGet('compression') == 'zip') {
+ $out = $this->getTempFileManager()->pushExt($file, 'zip');
+ $success = $this->zipEncode($file, $out);
+ }
+
+ // If the file was successfully compressed.
+ if ($out && $success) {
+ $out->setMeta('filesize_uncompressed', $file->getMeta('filesize'));
+ $out->setMeta('compression', $this->confGet('compression'));
+ return $out;
+ }
+
+ // Return the original if we were not able to compress it.
+ return $file;
+ }
+
+ /**
+ * Run on a restore.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileReadableInterface
+ */
+ public function beforeRestore(BackupFileReadableInterface $file) {
+ // If the file is not a supported compression type then simply return the
+ // same input file.
+ $out = $file;
+
+ $type = $file->getExtLast();
+
+ switch (strtolower($type)) {
+ case "gz":
+ case "gzip":
+ $out = $this->getTempFileManager()->popExt($file);
+ $this->_gzipDecode($file, $out);
+ break;
+
+ case "bz":
+ case "bz2":
+ case "bzip":
+ case "bzip2":
+ $out = $this->getTempFileManager()->popExt($file);
+ $this->_bzipDecode($file, $out);
+ break;
+
+ case "zip":
+ $out = $this->getTempFileManager()->popExt($file);
+ $this->_ZipDecode($file, $out);
+ break;
+ }
+ return $out;
+ }
+
+
+ /**
+ * Gzip encode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function gzipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+
+ if (!$success && function_exists("gzopen")) {
+ if (($fp_out = gzopen($to->realpath(), 'wb9')) && $from->openForRead()) {
+ while ($data = $from->readBytes(1024 * 512)) {
+ gzwrite($fp_out, $data);
+ }
+ $success = TRUE;
+ $from->close();
+ gzclose($fp_out);
+
+ // Get the compressed filesize and set it.
+ $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
+ $to->setMeta('filesize', $fileszc);
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Gzip decode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function _gzipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+
+ if (!$success && function_exists("gzopen")) {
+ if ($fp_in = gzopen($from->realpath(), 'rb9')) {
+ while (!feof($fp_in)) {
+ $to->write(gzread($fp_in, 1024 * 512));
+ }
+ $success = TRUE;
+ gzclose($fp_in);
+ $to->close();
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * BZip encode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function bzipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+ if (!$success && function_exists("bzopen")) {
+ if (($fp_out = bzopen($to->realpath(), 'w')) && $from->openForRead()) {
+ while ($data = $from->readBytes(1024 * 512)) {
+ bzwrite($fp_out, $data);
+ }
+ $success = TRUE;
+ $from->close();
+ bzclose($fp_out);
+
+ // Get the compressed filesize and set it.
+ $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
+ $to->setMeta('filesize', $fileszc);
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * BZip decode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function _bzipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+
+ if (!$success && function_exists("bzopen")) {
+ if ($fp_in = bzopen($from->realpath(), 'r')) {
+ while (!feof($fp_in)) {
+ $to->write(bzread($fp_in, 1024 * 512));
+ }
+ $success = TRUE;
+ bzclose($fp_in);
+ $to->close();
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Zip encode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function zipEncode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+
+ if (class_exists('ZipArchive')) {
+ $zip = new \ZipArchive();
+ $res = $zip->open(\Drupal::service('file_system')->realpath($to->realpath()), constant("ZipArchive::CREATE"));
+ if ($res === TRUE) {
+ $zip->addFile(\Drupal::service('file_system')->realpath($from->realpath()), $from->getFullName());
+ }
+ $success = $zip->close();
+ }
+ // Get the compressed filesize and set it.
+ $fileszc = filesize(\Drupal::service('file_system')->realpath($to->realpath()));
+ $to->setMeta('filesize', $fileszc);
+
+ return $success;
+ }
+
+ /**
+ * Gzip decode a file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $from
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $to
+ *
+ * @return bool
+ */
+ protected function _ZipDecode(BackupFileReadableInterface $from, BackupFileWritableInterface $to) {
+ $success = FALSE;
+ if (class_exists('ZipArchive')) {
+ $zip = new \ZipArchive();
+ if ($zip->open(\Drupal::service('file_system')->realpath($from->realpath()))) {
+ $filename = ($zip->getNameIndex(0));
+ if ($fp_in = $zip->getStream($filename)) {
+ while (!feof($fp_in)) {
+ $to->write(fread($fp_in, 1024 * 512));
+ }
+ fclose($fp_in);
+ $success = $to->close();
+ }
+ }
+ return $success;
+ }
+ }
+
+ /**
+ * Get the compression options as an options array for a form item.
+ *
+ * @return array
+ */
+ protected function _availableCompressionAlgorithms() {
+ $compression_options = ["none" => ("No Compression")];
+ if (function_exists("gzencode")) {
+ $compression_options['gzip'] = ("GZip");
+ }
+ if (function_exists("bzcompress")) {
+ $compression_options['bzip'] = ("BZip");
+ }
+ if (class_exists('ZipArchive')) {
+ $compression_options['zip'] = ("Zip");
+ }
+ return $compression_options;
+ }
+
+ /**
+ * Get the default compression algorithm based on those available.
+ *
+ * @return string
+ * The machine name of the algorithm.
+ */
+ protected function _defaultCompressionAlgorithm() {
+ $available = array_keys($this->_availableCompressionAlgorithms());
+ // Remove the 'none' option.
+ array_shift($available);
+ $out = array_shift($available);
+ // Return the first available algorithm or 'none' of none other exist.
+ return $out ? $out : 'none';
+ }
+
+}
diff --git a/src/Core/Filter/DBExcludeFilter.php b/src/Core/Filter/DBExcludeFilter.php
new file mode 100644
index 0000000..57790e1
--- /dev/null
+++ b/src/Core/Filter/DBExcludeFilter.php
@@ -0,0 +1,116 @@
+confGet('exclude_tables');
+ $nodata = $this->confGet('nodata_tables');
+ if (in_array($table['name'], $exclude)) {
+ $table['exclude'] = TRUE;
+ }
+ if (in_array($table['name'], $nodata)) {
+ $table['nodata'] = TRUE;
+ }
+ return $table;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'source' => '',
+ 'exclude_tables' => [],
+ 'nodata_tables' => [],
+ ]);
+ }
+
+ /**
+ * Get a definition for user-configurable settings.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ public function configSchema(array $params = []) {
+ $schema = [];
+
+ if ($params['operation'] == 'backup') {
+ $tables = [];
+
+ foreach ($this->sources()->getAll() as $source_key => $source) {
+ if ($source instanceof DatabaseSourceInterface) {
+ $tables += $source->getTableNames();
+ }
+
+ if ($tables) {
+ // Backup settings.
+ $schema['groups']['default'] = [
+ 'title' => $this->t('Exclude database tables'),
+ ];
+
+ $table_select = [
+ 'type' => 'enum',
+ 'multiple' => TRUE,
+ 'options' => $tables,
+ 'actions' => ['backup'],
+ 'group' => 'default'
+ ];
+ $schema['fields']['exclude_tables'] = $table_select + [
+ 'title' => $this->t('Exclude these tables entirely'),
+ ];
+
+ $schema['fields']['nodata_tables'] = $table_select + [
+ 'title' => $this->t('Exclude data from these tables'),
+ ];
+
+ }
+ }
+ }
+ return $schema;
+ }
+
+ /**
+ * @return PluginManager
+ */
+ public function sources() {
+ return $this->source_manager ? $this->source_manager : new PluginManager();
+ }
+
+ /**
+ * @param PluginManager $source_manager
+ */
+ public function setSourceManager($source_manager) {
+ $this->source_manager = $source_manager;
+ }
+
+}
diff --git a/src/Core/Filter/FileExcludeFilter.php b/src/Core/Filter/FileExcludeFilter.php
new file mode 100644
index 0000000..e92db1b
--- /dev/null
+++ b/src/Core/Filter/FileExcludeFilter.php
@@ -0,0 +1,128 @@
+confGet('source');
+ if ($source && $source == $params['source']) {
+ $exclude = $this->confGet('exclude_filepaths');
+ $exclude = $this->compileExcludePatterns($exclude);
+
+ if ($this->matchPath($path, $exclude, $params['base_path'])) {
+ return NULL;
+ }
+ }
+ return $path;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'source' => '',
+ 'exclude_filepaths' => [],
+ ]);
+ }
+
+ /**
+ * Convert an array of glob patterns to an array of regex patterns for file name exclusion.
+ *
+ * @param array $exclude
+ * A list of patterns with glob wildcards
+ *
+ * @return array
+ * A list of patterns as regular expressions
+ */
+ private function compileExcludePatterns($exclude) {
+ if ($this->patterns !== NULL) {
+ return $this->patterns;
+ }
+ foreach ($exclude as $pattern) {
+ // Convert Glob wildcards to a regex per http://php.net/manual/en/function.fnmatch.php#71725
+ $this->patterns[] = "#^" . strtr(preg_quote($pattern, '#'), ['\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']']) . "$#i";
+ }
+ return $this->patterns;
+ }
+
+ /**
+ * Match a path to the list of exclude patterns.
+ *
+ * @param string $path
+ * The path to match.
+ * @param array $exclude
+ * An array of regular expressions to match against.
+ * @param string $base_path
+ *
+ * @return bool
+ */
+ private function matchPath($path, $exclude, $base_path = '') {
+ $path = substr($path, strlen($base_path));
+
+ if ($exclude) {
+ foreach ($exclude as $pattern) {
+ if (preg_match($pattern, $path)) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Get a definition for user-configurable settings.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ public function configSchema(array $params = []) {
+ $schema = [];
+
+ $source = $this->confGet('source');
+
+ // Backup settings.
+ if (!empty($source) && $params['operation'] == 'backup') {
+ $schema['groups']['default'] = [
+ 'title' => $this->t('Exclude Files from %source', ['%source' => $source->confGet('name')]),
+ ];
+ // Backup settings.
+ if ($params['operation'] == 'backup') {
+ $schema['fields']['exclude_filepaths'] = [
+ 'type' => 'text',
+ 'title' => $this->t('Exclude these files'),
+ 'multiple' => TRUE,
+ 'group' => 'default'
+ ];
+ }
+ }
+ return $schema;
+ }
+
+}
diff --git a/src/Core/Filter/FileNamer.php b/src/Core/Filter/FileNamer.php
new file mode 100644
index 0000000..c5623fe
--- /dev/null
+++ b/src/Core/Filter/FileNamer.php
@@ -0,0 +1,114 @@
+moduleExists('token')) {
+ $must_match = '/^[\w\-_:\[\]]+$/';
+ $must_match_err = $this->t('%title must contain only letters, numbers, dashes (-) and underscores (_). And Site Tokens.');
+ }
+ else {
+ $must_match = '/^[\w\-_:]+$/';
+ $must_match_err = $this->t('%title must contain only letters, numbers, dashes (-) and underscores (_).');
+ }
+ // Backup configuration.
+ if ($params['operation'] == 'backup') {
+ $schema['groups']['file'] = [
+ 'title' => 'Backup File',
+ ];
+ $schema['fields']['filename'] = [
+ 'group' => 'file',
+ 'type' => 'text',
+ 'title' => 'File Name',
+ 'must_match' => $must_match,
+ 'must_match_error' => $must_match_err,
+ 'min_length' => 1,
+ // Allow a 200 character backup name leaving a generous 55 characters
+ // for timestamp and extension.
+ 'max_length' => 200,
+ 'required' => TRUE,
+ ];
+ $schema['fields']['timestamp'] = [
+ 'group' => 'file',
+ 'type' => 'boolean',
+ 'title' => 'Append a timestamp',
+ ];
+ $schema['fields']['timestamp_format'] = [
+ 'group' => 'file',
+ 'type' => 'text',
+ 'title' => 'Timestamp Format',
+ 'max_length' => 32,
+ 'dependencies' => ['timestamp' => TRUE],
+ 'description' => $this->t('Use PHP Date formatting.'),
+ ];
+ }
+ return $schema;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'filename' => 'backup',
+ 'timestamp' => TRUE,
+ 'timestamp_format' => 'Y-m-d\TH-i-s',
+ ]);
+ }
+
+ /**
+ * Get a list of supported operations and their weight.
+ *
+ * @return array
+ */
+ public function supportedOps() {
+ return [
+ 'afterBackup' => [],
+ ];
+ }
+
+ /**
+ * Run on a backup. Name the backup file according to the configuration.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileReadableInterface
+ */
+ public function afterBackup(BackupFileReadableInterface $file) {
+ if (\Drupal::moduleHandler()->moduleExists('token')) {
+ $token = \Drupal::token();
+ $name = $token->replace($this->confGet('filename'));
+ }
+ else {
+ $name = $this->confGet('filename');
+ }
+ if ($this->confGet('timestamp')) {
+ $name .= '-' . date($this->confGet('timestamp_format'));
+ }
+ $file->setName($name);
+ return $file;
+ }
+
+}
diff --git a/src/Core/Filter/MetadataWriter.php b/src/Core/Filter/MetadataWriter.php
new file mode 100644
index 0000000..0222e56
--- /dev/null
+++ b/src/Core/Filter/MetadataWriter.php
@@ -0,0 +1,106 @@
+ 'Advanced Settings',
+ ];
+ $schema['fields']['description'] = [
+ 'group' => 'advanced',
+ 'type' => 'text',
+ 'title' => 'Description',
+ 'multiline' => TRUE,
+ ];
+ }
+ return $schema;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'description' => '',
+ 'generator' => 'Backup and Migrate',
+ 'generatorversion' => defined('BACKUP_MIGRATE_CORE_VERSION') ? constant('BACKUP_MIGRATE_CORE_VERSION') : 'unknown',
+ 'generatorurl' => 'https://github.com/backupmigrate',
+ 'bam_sourceid' => '',
+ ]);
+ }
+
+ /**
+ * Generate a list of metadata keys to be stored with the backup.
+ *
+ * @return array
+ */
+ protected function getMetaKeys() {
+ return [
+ 'description',
+ 'generator',
+ 'generatorversion',
+ 'generatorurl',
+ 'bam_sourceid',
+ 'bam_scheduleid',
+ ];
+ }
+
+
+ /**
+ * Run before the backup/restore begins.
+ */
+ public function setUp($operand, $options) {
+ if ($options['operation'] == 'backup' && $options['source_id']) {
+ $this->config()->set('bam_sourceid', $options['source_id']);
+ if ($source = $this->plugins()->get($options['source_id'])) {
+ // @todo Query the source for it's type and name.
+ }
+ }
+ return $operand;
+ }
+
+ /**
+ * Run after a backup. Add metadata to the file.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileWritableInterface $file
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileWritableInterface
+ */
+ public function afterBackup(BackupFileWritableInterface $file) {
+ // Add the various metadata.
+ foreach ($this->getMetaKeys() as $key) {
+ $value = $this->confGet($key);
+ $file->setMeta($key, $value);
+ }
+ return $file;
+ }
+
+}
diff --git a/src/Core/Filter/Notify.php b/src/Core/Filter/Notify.php
new file mode 100644
index 0000000..1eee6d3
--- /dev/null
+++ b/src/Core/Filter/Notify.php
@@ -0,0 +1,96 @@
+ ['weight' => -100000],
+ 'beforeRestore' => ['weight' => -100000],
+ ];
+ }
+
+ /**
+ * @var StashLogger
+ */
+ protected $logstash;
+
+ public function beforeBackup() {
+ $this->addLogger();
+ }
+
+ public function beforeRestore() {
+ $this->addLogger();
+ }
+
+ public function backupSucceed() {
+ $this->sendNotification('Backup finished sucessfully');
+ }
+
+ public function backupFail(Exception $e) {
+
+ }
+
+ public function restoreSucceed() {
+ }
+
+ public function restoreFail() {
+ }
+
+ /**
+ * @param $subject
+ * @param $body
+ * @param $messages
+ */
+ protected function sendNotification($subject) {
+ $messages = $this->logstash->getAll();
+ $body = $subject . "\n";
+ if (count($messages)) {
+
+ }
+ // $body .=
+ }
+
+ /**
+ * add our stash logger to the service locator to capture all logged messages.
+ */
+ protected function addLogger() {
+ $services = $this->plugins()->services();
+
+ // Get the current logger.
+ $logger = $services->get('Logger');
+
+ // Create a new stash logger to save messages.
+ $this->logstash = new StashLogger();
+
+ // Add a tee to send logs to both the regular logger and our stash.
+ $services->add('Logger', new TeeLogger([$logger, $this->logstash]));
+
+ // Add the services back into the plugin manager to re-inject existing plugins
+ $this->plugins()->setServiceLocator($services);
+ }
+
+ // @todo Add a tee to the logger to capture all messages.
+ // @todo Implement backup/restore fail/succeed ops and send a notification.
+}
\ No newline at end of file
diff --git a/src/Core/Main/BackupMigrate.php b/src/Core/Main/BackupMigrate.php
new file mode 100644
index 0000000..0a9d563
--- /dev/null
+++ b/src/Core/Main/BackupMigrate.php
@@ -0,0 +1,232 @@
+setServiceManager(new ServiceManager());
+ $services = $this->services();
+
+ $services->add('PluginManager', new PluginManager($services));
+ $services->add('SourceManager', new PluginManager($services));
+ $services->add('DestinationManager', new PluginManager($services));
+
+ // Add these services back into this object using the service manager.
+ $services->addClient($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function backup($source_id, $destination_id) {
+ try {
+
+ // Allow the plugins to set up.
+ $this->plugins()->call('setUp', NULL, ['operation' => 'backup', 'source_id' => $source_id, 'destination_id' => $destination_id]);
+
+ // Get the source and the destination to use.
+ $source = $this->sources()->get($source_id);
+ $destinations = [];
+
+ // Allow a single destination or multiple destinations.
+ foreach ((array) $destination_id as $id) {
+ $destinations[$id] = $this->destinations()->get($id);
+
+ // Check that the destination is valid.
+ if (!$destinations[$id]) {
+ throw new BackupMigrateException('The destination !id does not exist.', ['!id' => $destination_id]);
+ }
+
+ // Check that the destination can be written to.
+ // @todo Catch exceptions and continue if at least one destination is valid.
+ $destinations[$id]->checkWritable();
+ }
+
+ // Check that the source is valid.
+ if (!$source) {
+ throw new BackupMigrateException('The source !id does not exist.', ['!id' => $source_id]);
+ }
+
+ // Run each of the installed plugins which implements the 'beforeBackup' operation.
+ $this->plugins()->call('beforeBackup');
+
+ // Do the actual backup.
+ $file = $source->exportToFile();
+
+ // Run each of the installed plugins which implements the 'afterBackup' operation.
+ $file = $this->plugins()->call('afterBackup', $file);
+
+ // Save the file to each destination.
+ foreach ($destinations as $destination) {
+ $destination->saveFile($file);
+ }
+
+ // Let plugins react to a successful operation.
+ $this->plugins()->call('backupSucceed', $file);
+ }
+ catch (\Exception $e) {
+ // Let plugins react to a failed operation.
+ $this->plugins()->call('backupFail', $e);
+
+ // The consuming software needs to deal with this.
+ throw $e;
+ }
+
+ // Allow the plugins to tear down.
+ $this->plugins()->call('tearDown', NULL, ['operation' => 'backup', 'source_id' => $source_id, 'destination_id' => $destination_id]);
+
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function restore($source_id, $destination_id, $file_id = NULL) {
+ try {
+ // Get the source and the destination to use.
+ $source = $this->sources()->get($source_id);
+ $destination = $this->destinations()->get($destination_id);
+
+ if (!$source) {
+ throw new BackupMigrateException('The source !id does not exist.', ['!id' => $source_id]);
+ }
+ if (!$destination) {
+ throw new BackupMigrateException('The destination !id does not exist.', ['!id' => $destination_id]);
+ }
+
+ // Load the file from the destination.
+ $file = $destination->getFile($file_id);
+ if (!$file) {
+ throw new BackupMigrateException('The file !id does not exist.', ['!id' => $file_id]);
+ }
+
+ // Prepare the file for reading.
+ $file = $destination->loadFileForReading($file);
+ if (!$file) {
+ throw new BackupMigrateException('The file !id could not be opened for reading.', ['!id' => $file_id]);
+ }
+
+ // Run each of the installed plugins which implements the 'backup' operation.
+ $file = $this->plugins()->call('beforeRestore', $file);
+
+ // Do the actual source restore.
+ $import_result = $source->importFromFile($file);
+ if (!$import_result) {
+ throw new BackupMigrateException('The file could not be imported.');
+ }
+
+ // Run each of the installed plugins which implements the 'beforeBackup' operation.
+ $this->plugins()->call('afterRestore');
+
+ // Let plugins react to a successful operation.
+ $this->plugins()->call('restoreSucceed', $file);
+ }
+ catch (\Exception $e) {
+ // Let plugins react to a failed operation.
+ $this->plugins()->call('restoreFail', $e);
+
+ // The consuming software needs to deal with this.
+ throw $e;
+ }
+ }
+
+ /**
+ * Set the configuration for the service. This simply passes the configuration
+ * on to the plugin manager as all work is done by plugins.
+ *
+ * This can be called after the service is instantiated to pass new configuration
+ * to the plugins.
+ *
+ * @param \Drupal\backup_migrate\Core\Config\ConfigInterface $config
+ */
+ public function setConfig(ConfigInterface $config) {
+ $this->plugins()->setConfig($config);
+ }
+
+ /**
+ * Get the list of available destinations.
+ *
+ * @return PluginManagerInterface
+ */
+ public function destinations() {
+ return $this->destinations;
+ }
+
+ /**
+ * Set the destinations plugin manager.
+ *
+ * @param PluginManagerInterface $destinations
+ */
+ public function setDestinationManager(PluginManagerInterface $destinations) {
+ $this->destinations = $destinations;
+ }
+
+ /**
+ * Get the list of sources.
+ *
+ * @return PluginManagerInterface
+ */
+ public function sources() {
+ return $this->sources;
+ }
+
+ /**
+ * Set the sources plugin manager.
+ *
+ * @param PluginManagerInterface $sources
+ */
+ public function setSourceManager(PluginManagerInterface $sources) {
+ $this->sources = $sources;
+ }
+
+ /**
+ * Get the service locator.
+ *
+ * @return ServiceManager
+ */
+ public function services() {
+ return $this->services;
+ }
+
+ /**
+ * Set the service locator.
+ *
+ * @param ServiceManager $services
+ */
+ public function setServiceManager($services) {
+ $this->services = $services;
+ }
+
+}
diff --git a/src/Core/Main/BackupMigrateInterface.php b/src/Core/Main/BackupMigrateInterface.php
new file mode 100644
index 0000000..cbed8a1
--- /dev/null
+++ b/src/Core/Main/BackupMigrateInterface.php
@@ -0,0 +1,81 @@
+plugins()` method. The `add()`
+method can then be used to add additional plugins. Each added plugin must be given a unique ID when added. This ID will be used
+to configure the plugin and to specify which source and destination are used during the operation.
+
+
+ // ...
+
+ // Create a Backup and Migrate Service object
+ $bam = new BackupMigrate($);
+
+ // Create a service locator
+ $services = new ServiceManager();
+
+ // Add necessary services
+ $services->add('TempFileManager',
+ new TempFileManager(new TempFileAdapter('/tmp'))
+ );
+ $services->add('Logger',
+ new Logger()
+ );
+
+ // Create a plugin manager
+ $plugins = new PluginManager($services);
+
+ // Add a source:
+ $plugins->add('db1', new MySQLiSource());
+
+ // Add some destinations
+ $plugins->add('download', new BrowserDownloadDestination());
+ $plugins->add('mydirectory', new DirectoryDestination());
+
+ // Add some filters
+ $plugins->add('compress', new CompressionFilter());
+ $plugins->add('namer', new FileNamer());
+
+ $bam = new BackupMigrate($plugins);
+
+See: [Plugins](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Plugin)
+
+### Providing Services
+
+If the consuming application needs to use any plugins that must talk to the greater environment (saving state, emailing
+users, creating temporary files) it must provide services to Backup and Migrate that allow it to do so. These services
+are contained in an object called the environment. A new environment object should be created and passed to the service
+constructor. If you do not pass an environment then a basic one will be created which should work in the simplest
+environments.
+
+Providing an environment.
+
+ use Drupal\backup_migrate\Core\Services\BackupMigrate;
+ use MyAPP\Environment\MyEnvironment;
+
+ // Create a custom environment with whatever services or configuration are needed for the application
+ $env = new MyEnvironment(...);
+
+ // Pass the environment to the service
+ $bam = new BackupMigrate($env);
+
+See: [Environment](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Environment)
+
+### Configuring the Object
+
+The `BackupMigrate` object does not have any configuration but the injected plugins and services may. Services should be configured before they are passed to the `ServiceManager`. Plugins can be configured when they are created and passed to the plugin manager or additional configuration can be passed in by calling `setConfig` on the plugin manager. Often combination of these techniques will be used. Base configuration is passed to the plugin when it is instantiated and run-time configuration is passed in later.
+
+See: [Configuration](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Config)
+
+
+## Operations
+The Backup and Migrate service provides two main operations:
+
+* `backup($source_id, $destination_id)`
+* `restore($source_id, $destination_id, $file_id)`
+
+### The Backup Operation
+
+The `backup()` operation creates a backup file from the specified source, post-processes the file with all installed
+filters and saves the file to the specified destination. The parameters for this operation are:
+
+* **$source_id** ***(string)*** - The id of the source as specified when it is added to the plugin manager.
+* **$destination_id** ***(string|array)*** - The id of the destination as specified when it is added to the plugin manager.
+This can also be an array of destination ids to send the backup to multiple destinations.
+
+There is no return value but it may throw an exception if there is an error.
+
+ // ...
+
+ // Create a Backup and Migrate Service object
+ $bam = new BackupMigrate($plugins);
+
+ // Run the backup.
+ $bam->backup('db1', 'mydirectory');
+
+
+### The Restore Operation
+
+The `restore()` operation loads the specified file from the specified destination, pre-processes the file with all
+installed filters and restores the data to the specified source. The parameters are:
+
+* **$source_id** ***(string)*** - The id of the source as specified when it is added to the plugin manager.
+* **$destination_id** ***(string)*** - The id of the destination as specified when it is added to the plugin manager.
+* **$file_id** ***(string)*** - The id of the file within the destination. This is usually the file name but can be any
+unique string specified by the destination.
+
+
+ // ...
+
+ // Create a Backup and Migrate Service object
+ $bam = new BackupMigrate($plugins);
+
+ // Run the restore.
+ $bam->restore('db1', 'mydirectory', 'backup.mysql.gz');
diff --git a/src/Core/Plugin/FileProcessorInterface.php b/src/Core/Plugin/FileProcessorInterface.php
new file mode 100644
index 0000000..b979ab8
--- /dev/null
+++ b/src/Core/Plugin/FileProcessorInterface.php
@@ -0,0 +1,32 @@
+tempfilemanager = $tempfilemanager;
+ }
+
+ /**
+ * Get the temp file manager.
+ *
+ * @return \Drupal\backup_migrate\Core\File\TempFileManagerInterface
+ */
+ public function getTempFileManager() {
+ return $this->tempfilemanager;
+ }
+
+ /**
+ * Provide the file mime for the given file extension if known.
+ *
+ * @param string $filemime
+ * The best guess so far for the file's mime type.
+ * @param array $params
+ * A list of parameters where
+ * 'ext' is the file extension we are testing.
+ *
+ * @return string
+ * The mime type of the file (or the passed in mime type if unknown)
+ */
+ public function alterMime($filemime, $params) {
+ // Check all of the provided file types for the given extension.
+ if (method_exists($this, 'getFileTypes')) {
+ $file_types = $this->getFileTypes();
+ foreach ($file_types as $info) {
+ if (isset($info['extension']) && $info['extension'] == $params['ext'] && isset($info['filemime'])) {
+ return $info['filemime'];
+ }
+ }
+ }
+ return $filemime;
+ }
+
+}
diff --git a/src/Core/Plugin/PluginBase.php b/src/Core/Plugin/PluginBase.php
new file mode 100644
index 0000000..775a962
--- /dev/null
+++ b/src/Core/Plugin/PluginBase.php
@@ -0,0 +1,63 @@
+ ['weight' => 100],
+ * 'restore' => ['weight' => -100],
+ * ];
+ *
+ * @return array
+ */
+ public function supportedOps() {
+ return [];
+ }
+
+ /**
+ * Does this plugin implement the given operation.
+ *
+ * @param $op string The name of the operation
+ *
+ * @return bool
+ */
+ public function supportsOp($op) {
+ // If the function has the method then it supports the op.
+ if (method_exists($this, $op)) {
+ return TRUE;
+ }
+ // If the supported ops array contains the op then it is supported.
+ $ops = $this->supportedOps();
+ return isset($ops[$op]);
+ }
+
+ /**
+ * What is the weight of the given operation for this plugin.
+ * * @param $op string The name of the operation.
+ *
+ * @return int
+ */
+ public function opWeight($op) {
+ $ops = $this->supportedOps();
+ if (isset($ops[$op]['weight'])) {
+ return $ops[$op]['weight'];
+ }
+ return 0;
+ }
+
+}
diff --git a/src/Core/Plugin/PluginCallerInterface.php b/src/Core/Plugin/PluginCallerInterface.php
new file mode 100644
index 0000000..6f6ecf2
--- /dev/null
+++ b/src/Core/Plugin/PluginCallerInterface.php
@@ -0,0 +1,30 @@
+plugins = $plugins;
+ }
+
+ /**
+ * Get the plugin manager.
+ *
+ * @return \Drupal\backup_migrate\Core\Plugin\PluginManagerInterface
+ */
+ public function plugins() {
+ // Return the list of plugins or a blank placeholder.
+ return $this->plugins ? $this->plugins : new PluginManager();
+ }
+
+}
diff --git a/src/Core/Plugin/PluginInterface.php b/src/Core/Plugin/PluginInterface.php
new file mode 100644
index 0000000..7c08e24
--- /dev/null
+++ b/src/Core/Plugin/PluginInterface.php
@@ -0,0 +1,56 @@
+ ['weight' => 100],
+ * 'restore' => ['weight' => -100],
+ * ];
+ *
+ * @return array
+ */
+ public function supportedOps();
+
+ /**
+ * Does this plugin implement the given operation.
+ *
+ * @param $op string The name of the operation
+ *
+ * @return bool
+ */
+ public function supportsOp($op);
+
+ /**
+ * What is the weight of the given operation for this plugin.
+ * * @param $op string The name of the operation.
+ *
+ * @return int
+ */
+ public function opWeight($op);
+
+}
diff --git a/src/Core/Plugin/PluginManager.php b/src/Core/Plugin/PluginManager.php
new file mode 100644
index 0000000..ae98b86
--- /dev/null
+++ b/src/Core/Plugin/PluginManager.php
@@ -0,0 +1,194 @@
+setServiceManager($services ? $services : new ServiceManager());
+
+ // Set the configuration or a null object if no config was specified.
+ $this->setConfig($config ? $config : new Config());
+
+ // Create an array to store the plugins themselves.
+ $this->items = [];
+ }
+
+
+ /**
+ * Set the configuration. Reconfigure all of the installed plugins.
+ *
+ * @param \Drupal\backup_migrate\Core\Config\ConfigInterface $config
+ */
+ public function setConfig(ConfigInterface $config) {
+ // Set the configuration object to the one passed in.
+ $this->config = $config;
+
+ // Pass the appropriate configuration to each of the installed plugins.
+ foreach ($this->getAll() as $key => $plugin) {
+ $this->_configurePlugin($plugin, $key);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($id, PluginInterface $item) {
+ $this->_preparePlugin($item, $id);
+ $this->items[$id] = $item;
+ }
+
+ /**
+ * {@inheritdoc}
+ **/
+ public function get($id) {
+ return isset($this->items[$id]) ? $this->items[$id] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAll() {
+ return empty($this->items) ? [] : $this->items;
+ }
+
+ /**
+ * Get all plugins that implement the given operation.
+ *
+ * @param string $op The name of the operation.
+ *
+ * @return \Drupal\backup_migrate\Core\Plugin\PluginInterface[]
+ */
+ public function getAllByOp($op) {
+ $out = [];
+ $weights = [];
+
+ foreach ($this->getAll() as $key => $plugin) {
+ if ($plugin->supportsOp($op)) {
+ $out[$key] = $plugin;
+ $weights[$key] = $plugin->opWeight($op);
+ }
+ }
+ array_multisort($weights, $out);
+ return $out;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function call($op, $operand = NULL, $params = []) {
+
+ // Run each of the installed plugins which implements the given operation.
+ foreach ($this->getAllByOp($op) as $plugin) {
+ $operand = $plugin->{$op}($operand, $params);
+ }
+
+ return $operand;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function map($op, $params = []) {
+ $out = [];
+
+ // Run each of the installed plugins which implements the given operation.
+ foreach ($this->getAllByOp($op) as $key => $plugin) {
+ $out[$key] = $plugin->{$op}($params);
+ }
+
+ return $out;
+ }
+
+
+ /**
+ * Prepare the plugin for use. This is called when a plugin is added to the
+ * manager and it configures the plugin according to the config object
+ * injected into the manager. It also injects other dependencies as needed.
+ *
+ * @param \Drupal\backup_migrate\Core\Plugin\PluginInterface $plugin
+ * The plugin to prepare for use.
+ * @param string $id
+ * The id of the plugin (to extract the correct settings).
+ */
+ protected function _preparePlugin(PluginInterface $plugin, $id) {
+ // If this plugin can be configured, then pass in the configuration.
+ $this->_configurePlugin($plugin, $id);
+
+ // Inject the available services.
+ $this->services()->addClient($plugin);
+ }
+
+ /**
+ * Set the configuration for the given plugin.
+ *
+ * @param $plugin
+ * @param $id
+ */
+ protected function _configurePlugin(PluginInterface $plugin, $id) {
+ // If this plugin can be configured, then pass in the configuration.
+ if ($plugin instanceof ConfigurableInterface) {
+ // Configure the plugin with the appropriate subset of the configuration.
+ $config = $this->confGet($id);
+
+ // Set the config for the plugin.
+ $plugin->setConfig(new Config($config));
+
+ // Get the configuration back from the plugin to populate defaults within the manager.
+ $this->config()->set($id, $plugin->config());
+ }
+ }
+
+ /**
+ * @return ServiceManagerInterface
+ */
+ public function services() {
+ return $this->services;
+ }
+
+ /**
+ * @param ServiceManagerInterface $services
+ */
+ public function setServiceManager($services) {
+ $this->services = $services;
+
+ // Inject or re-inject the services.
+ foreach ($this->getAll() as $key => $plugin) {
+ $this->services()->addClient($plugin);
+ }
+ }
+
+}
diff --git a/src/Core/Plugin/PluginManagerInterface.php b/src/Core/Plugin/PluginManagerInterface.php
new file mode 100644
index 0000000..ca6bb29
--- /dev/null
+++ b/src/Core/Plugin/PluginManagerInterface.php
@@ -0,0 +1,103 @@
+plugins()->add('demoplugin', new MyPlugin());
+
+To configure this plugin the consuming application would have a section called 'demoplugin' in the plugin manager configuration object:
+
+ $conf = new Config([
+ 'demoplugin' => ['foo => 'bar']
+ ]);
+
+ $plugins = new PluginManager(NULL, $conf);
+ $backup_migrate = new BackupMigrate($plugins);
+
+### Calling Plugins ###
+Internally the plugin manager is used to run all plugins for a given operation. This is done using the `call()` method:
+
+ $file = $this->plugins()->call('afterBackup', $file);
+
+The call method takes 3 parameters:
+
+* **Operation**: the name of the operation to call
+* **Operand**: The object being operated on (optional)
+* **Params**: An associative array of additional parameters
+
+Each plugin that implements the **operation** will be called in order. The **operand** will be passed to the plugin and will be overwritten by the return value from the plugin. In this way plugin operations are chained. A plugin is responsible for returning the operand that was passed in if it does not wish to overwrite it. The **params** array can contain additional information needed to run the operation but it cannot be modified by plugins.
+
+### Implementing Operations ###
+If a plugin wishes to be called for a given operation it simply needs to define a method with the same name as the operation. For example, to compress a backup file after it has been created, the plugin must have a method called `afterBackup()` which takes a file as the operand and returns the a new, compressed file.
+
+#### Operation Weights ####
+The order in which plugins are called cannot be guaranteed. However, if a plugin needs to run in a specific order it may specify a weight for each operation it implements. To specify a weight it must implement a `opWeight()` method which takes an operation name and returns a numerical weight. Plugins are called from lowest to highest and plugins which do not specify a weight are considered to have a weight of `0`.
+
+To specify the weight of may operations it may be easier to extend the `\Drupal\backup_migrate\Core\Plugin\PluginBase` class and override the `supportedOps()` method which returns an array of supported operations and their weight:
+
+ public function supportedOps() {
+ return [
+ 'afterBackup' => ['weight' => 100],
+ 'beforeRestore' => ['weight' => -100],
+ ];
+ }
+
+### Calling Other Plugins ###
+Plugins can call other plugins using the Plugin Manager. For example, a source plugin might want to expose a line-item filter operation to allow other plugins to alter single values before they are added to the backup file. An encryption plugin may want to delegate the actual work of encrypting to other sub-plugins for better code organization and extendability.
+
+By default plugins are not given access to the plugin manager. However, if a plugin implements the `\Drupal\backup_migrate\Core\Plugin\PluginCallerInterface` then the plugin manager will inject itself into the plugin for use when the plugin is prepared for use. The `\Drupal\backup_migrate\Core\Plugin\PluginCallerTrait` can be used to implement the actual requirements of the interface. Plugins with this interface and trait will be able to use `$this->plugins()` to access the plugin manager:
+
+ class MyPlugin implements PluginCallerInterface {
+ use PluginCallerTrait;
+
+ function someOperation() {
+ $this->plugins()->call(...);
+ }
+ }
+
+### Accessing Services ###
+If a plugin requires the use of a cache, logger, state storage, mailer or any other backing service it must have the service injected into it by the plugin manger. To make a service avaible to the plugin manager it may be added to an object which implenents `ServiceManagerInterface`. That service locater may be passed to the plugin manager though the constructor or it can be passed in later using `setServiceManager()`.
+
+Any service provided by the service locator will be injected into a plugin when it is added to the plugin manager if the name of the service matches a setter present in the plugin. For example: if a plugin has a method called `setLogger` and the service locator has a service called 'Logger' then the logger service will be injected via the `setLogger` method:
+
+ $services = new ServiceManager();
+ $services->add('Logger', new FileLogger('/path/to/log.txt'));
+
+ $plugins = new PluginManager($services);
+
+ // If this plugin has a `setLogger` the logger will be injected.
+ $plugins->add('test', new TestPlugin());
+
+See: [Services](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Service)
+
+### Creating New Temporary Files ###
+If a plugin needs to create a new temporary file (for example to decompress a backup file). It may request that the TempFileManager be injected by implementing `\Drupal\backup_migrate\Core\Plugin\FileProcessorInterface` and using the `\Drupal\backup_migrate\Core\Plugin\FileProcessorTrait`. This will allow the following:
+
+ class MyFilePlugin implements FileProcessorInterface {
+ use FileProcessorTrait;
+
+ function someOperation($file_in) {
+ $file_out = $this->getTempFileManager()->popExt($file_in);
+ // ...
+
+ // Return the new file and so it overwrites the old file
+ // during plugin chaining.
+ return $file_out;
+ }
+ }
+
+
+See: [Backup Files](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/File)
+
+## Sources and Destinations ##
+
+Sources and destinations are special case plugins. While they technically identical to filter plugins they are not called using the plugin manager's `call()` method. Only one source and one destination can be use for each backup or restore operation so they are called individually rather than being chained like most plugin operations. These plugin types are different by convention only and are injected and configured in the same way as filters.
+
+See: [Sources](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Source), [Destinations](https://github.com/backupmigrate/backup_migrate_core/tree/master/src/Destination)
diff --git a/src/Core/Service/ArchiveReaderInterface.php b/src/Core/Service/ArchiveReaderInterface.php
new file mode 100644
index 0000000..6497b92
--- /dev/null
+++ b/src/Core/Service/ArchiveReaderInterface.php
@@ -0,0 +1,46 @@
+getCurlResource($url);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+
+ return $this->curlExec($ch);
+ }
+
+ /**
+ * Post a file along with other data (as an array).
+ *
+ * @param $url
+ * @param \Drupal\backup_migrate\Core\File\ReadableStreamBackupFile $file
+ * @param $data
+ *
+ * @return mixed
+ */
+ public function postFile($url, ReadableStreamBackupFile $file, $data) {
+ $data['file'] = new \CURLFile($file->realpath());
+ $data['file']->setPostFilename($file->getFullName());
+ return $this->post($url, $data);
+ }
+
+ /**
+ * Get the CURL Resource with default options.
+ *
+ * @param $url
+ *
+ * @return resource
+ */
+ protected function getCurlResource($url) {
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
+ return $ch;
+ }
+
+ /**
+ * Perform the http action and return the body or throw an exception.
+ *
+ * @param $ch
+ *
+ * @return mixed
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\HttpClientException
+ */
+ protected function curlExec($ch) {
+ $body = curl_exec($ch);
+ if ($msg = curl_error($ch)) {
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ if (!$code) {
+ $info['code'] = curl_errno($ch);
+ }
+ throw new HttpClientException($msg, [], $code);
+ }
+ return $body;
+ }
+
+}
diff --git a/src/Core/Service/README.md b/src/Core/Service/README.md
new file mode 100644
index 0000000..e1bbf8d
--- /dev/null
+++ b/src/Core/Service/README.md
@@ -0,0 +1,35 @@
+# Services #
+
+If a plugin needs to access the greater environment to write logs, store data, etc. it should rely on service objects which may be injected into the plugin at run time.
+
+## Service Manager ##
+
+The `ServiceManagerInterface` defines a very simple service container and dependency injector which stores a keyed list of services available to plugins which need them. The built in `ServiceManager` class implements this interface in the most basic way possible. A consuming application may choose to implement a manager using a more sophisticated dependency management and configuration solution such as [Pimple](http://pimple.sensiolabs.org/), [PHP-DI](http://php-di.org/) or [Symfony's DependencyInjection Component](http://symfony.com/doc/current/components/dependency_injection/introduction.html). The built in locator simply takes a list of already configured services and returns them when requested or automatically injects them as described below.
+
+## Service Injection ##
+
+It is not necessary to use automatic service injection. A consuming application can simply instantiate plugins and pass the necessary services directly to them. However, a simple service injection mechanism is provided by the service manager which can make dynamically creating plugins much simpler.
+
+Plugins can request that a service be injected by defining a setter with called `setServiceName` where 'ServiceName' is replaced with the name of the given service. Here is an pseudo-code example:
+
+ class MyPlugin implements PluginInterface {
+
+ // Logger service setter
+ public function setLogger(LoggerInterface $logger) {
+ $this->logger = $logger;
+ }
+
+ // ...
+ }
+
+This plugin will have a logger injected if one is available:
+
+ $bam = new BackupMigrate();
+
+ // The key 'Logger' must match 'setLogger'
+ $bam->services()->add('Logger', new MyLogger());
+
+ // The manager will inject the logger automatically.
+ $bam->plugins()->add('myplugin', new MyPlugin());
+
+
\ No newline at end of file
diff --git a/src/Core/Service/ServiceManager.php b/src/Core/Service/ServiceManager.php
new file mode 100644
index 0000000..3efacc2
--- /dev/null
+++ b/src/Core/Service/ServiceManager.php
@@ -0,0 +1,98 @@
+services = [];
+
+ // Allow the locator to inject itself.
+ $this->services['ServiceManager'] = $this;
+ }
+
+ /**
+ * Add a fully configured service to the service locator.
+ *
+ * @param string $type
+ * The service type identifier.
+ * @param mixed $service
+ *
+ * @return null
+ */
+ public function add($type, $service) {
+ $this->services[$type] = $service;
+
+ // Add this service as a client so it can have dependencies injected.
+ $this->addClient($service);
+
+ // Update any plugins that have already had this service injected.
+ if (isset($this->clients[$type])) {
+ foreach ($this->clients[$type] as $client) {
+ $client->{'set' . $type}($service);
+ }
+ }
+ }
+
+ /**
+ * Retrieve a service from the locator.
+ *
+ * @param string $type
+ * The service type identifier
+ *
+ * @return mixed
+ */
+ public function get($type) {
+ return $this->services[$type];
+ }
+
+ /**
+ * Get an array of keys for all available services.
+ *
+ * @return array
+ */
+ public function keys() {
+ return array_keys($this->services);
+ }
+
+ /**
+ * Inject all available services into the give plugin.
+ *
+ * @param object $client
+ *
+ * @return mixed|void
+ */
+ public function addClient($client) {
+ // Inject available services.
+ foreach ($this->keys() as $type) {
+ if (method_exists($client, 'set' . $type) && $service = $this->get($type)) {
+ // Save the plugin so it can be updated if this service is updated.
+ $this->clients[$type][] = $client;
+
+ $client->{'set' . $type}($service);
+ }
+ }
+ }
+
+}
diff --git a/src/Core/Service/ServiceManagerInterface.php b/src/Core/Service/ServiceManagerInterface.php
new file mode 100644
index 0000000..84002d5
--- /dev/null
+++ b/src/Core/Service/ServiceManagerInterface.php
@@ -0,0 +1,38 @@
+logs[] = ['level' => $level, 'message' => $message, 'context' => $context];
+ }
+
+ /**
+ * Get all of the log messages that were saved to this stash.
+ *
+ * @return array
+ */
+ public function getAll() {
+ return $this->logs;
+ }
+
+}
diff --git a/src/Core/Service/TarArchiveReader.php b/src/Core/Service/TarArchiveReader.php
new file mode 100644
index 0000000..44b3416
--- /dev/null
+++ b/src/Core/Service/TarArchiveReader.php
@@ -0,0 +1,394 @@
+archive = $out;
+ }
+
+
+ /**
+ * Extract all files to the given directory.
+ *
+ * @param $directory
+ *
+ * @return mixed
+ */
+ public function extractTo($directory) {
+ $this->archive->openForRead(TRUE);
+
+ $result = $this->extractAllToDirectory($directory);
+
+ $this->archive->close();
+
+ return $result;
+ }
+
+ /**
+ * @param $directory
+ * The directory to extract the files to.
+ * @return bool
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ */
+ private function extractAllToDirectory($directory) {
+ clearstatcache();
+
+ // Read a header block.
+ while (strlen($block = $this->archive->readBytes(512)) != 0) {
+ $header = $this->readHeader($block);
+ if (!$header) {
+ return FALSE;
+ }
+
+ if ($header['filename'] == '') {
+ continue;
+ }
+
+ // Check for potentially malicious files (containing '..' etc.).
+ if ($this->maliciousFilename($header['filename'])) {
+ throw new BackupMigrateException(
+ 'Malicious .tar detected, file %filename. Will not install in desired directory tree',
+ ['%filename' => $header['filename']]
+ );
+ }
+
+ // ignore extended / pax headers.
+ if ($header['typeflag'] == 'x' || $header['typeflag'] == 'g') {
+ $this->archive->seekBytes(ceil(($header['size'] / 512)));
+ continue;
+ }
+
+ // Add the destination directory to the path.
+ if (substr($header['filename'], 0, 1) == '/') {
+ $header['filename'] = $directory . $header['filename'];
+ }
+ else {
+ $header['filename'] = $directory . '/' . $header['filename'];
+ }
+
+ // If the file already exists, make sure we can overwrite it.
+ if (file_exists($header['filename'])) {
+ // Cannot overwrite a directory with a file.
+ if ((@is_dir($header['filename']))
+ && ($header['typeflag'] == '')
+ ) {
+ throw new BackupMigrateException(
+ 'File %filename already exists as a directory',
+ ['%filename' => $header['filename']]
+ );
+ }
+ // Cannot overwrite a file with a directory.
+ if (@is_file($header['filename']) && !@is_link($header['filename'])
+ && ($header['typeflag'] == "5")
+ ) {
+ throw new BackupMigrateException(
+ 'Directory %filename already exists as file',
+ ['%filename' => $header['filename']]
+ );
+ }
+ // Cannot overwrite a read-only file.
+ if (!is_writeable($header['filename'])) {
+ throw new BackupMigrateException(
+ 'File %filename already exists and is write protected',
+ ['%filename' => $header['filename']]
+ );
+ }
+ }
+
+ // Extract a directory.
+ if ($header['typeflag'] == "5") {
+ if (!$this->createDir($header['filename'])) {
+ throw new BackupMigrateException(
+ 'Unable to create directory %filename',
+ ['%filename' => $header['filename']]
+ );
+ }
+ }
+ // Extract a file/symlink
+ else {
+ if (!$this->createDir(dirname($header['filename']))) {
+ throw new BackupMigrateException(
+ 'Unable to create directory for %filename',
+ ['%filename' => $header['filename']]
+ );
+ }
+
+ // Symlink.
+ if ($header['typeflag'] == "2") {
+ if (@file_exists($header['filename'])) {
+ @unlink($header['filename']);
+ }
+ if (!@symlink($header['link'], $header['filename'])) {
+ throw new BackupMigrateException(
+ 'Unable to extract symbolic link: %filename',
+ ['%filename' => $header['filename']]
+ );
+ }
+ }
+ // Regular file.
+ else {
+ // Open the file for writing.
+ if (($dest_file = @fopen($header['filename'], "wb")) == 0) {
+ throw new BackupMigrateException(
+ 'Error while opening %filename in write binary mode',
+ ['%filename' => $header['filename']]
+ );
+ }
+
+ // Write the file.
+ $n = floor($header['size'] / 512);
+ for ($i = 0; $i < $n; $i++) {
+ $content = $this->archive->readBytes(512);
+ fwrite($dest_file, $content, 512);
+ }
+ if (($header['size'] % 512) != 0) {
+ $content = $this->archive->readBytes(512);
+ fwrite($dest_file, $content, ($header['size'] % 512));
+ }
+
+ @fclose($dest_file);
+
+ // Change the file mode, mtime.
+ @touch($header['filename'], $header['mtime']);
+ if ($header['mode'] & 0111) {
+ // make file executable, obey umask.
+ $mode = fileperms($header['filename']) | (~umask() & 0111);
+ @chmod($header['filename'], $mode);
+ }
+
+ clearstatcache();
+
+ // Check if the file exists.
+ if (!is_file($header['filename'])) {
+ throw new BackupMigrateException(
+ 'Extracted file %filename does not exist. Archive may be corrupted.',
+ ['%filename' => $header['filename']]
+ );
+ }
+
+ // Check the file size.
+ $file_size = filesize($header['filename']);
+ if ($file_size != $header['size']) {
+ throw new BackupMigrateException(
+ 'Extracted file %filename does not have the correct file size. File is %actual bytes (%expected bytes expected). Archive may be corrupted',
+ ['%filename' => $header['filename'], '%expected' => (int) $header['size'], (int) '%actual' => $file_size]
+ );
+ }
+ }
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Create a directory or return true if it already exists.
+ *
+ * @param $directory
+ *
+ * @return boolean
+ */
+ private function createDir($directory) {
+ if ((@is_dir($directory)) || ($directory == '')) {
+ return TRUE;
+ }
+ $parent = dirname($directory);
+
+ if (
+ ($parent != $directory) &&
+ ($parent != '') &&
+ (!$this->createDir($parent))
+ ) {
+ return FALSE;
+ }
+ if (@!mkdir($directory, 0777)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Read a tar file header block.
+ *
+ * @param $block
+ * @param array $header
+ *
+ * @return array
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ */
+ private function readHeader($block, $header = []) {
+ if (strlen($block) == 0) {
+ $header['filename'] = '';
+ return TRUE;
+ }
+
+ if (strlen($block) != 512) {
+ $header['filename'] = '';
+ throw new BackupMigrateException(
+ 'Invalid block size: %size bytes',
+ ['%size' => strlen($block)]
+ );
+ }
+
+ if (!is_array($header)) {
+ $header = [];
+ }
+
+ // Calculate the checksum.
+ $checksum = 0;
+ // First part of the header.
+ for ($i = 0; $i < 148; $i++) {
+ $checksum += ord(substr($block, $i, 1));
+ }
+ // Ignore the checksum value and replace it by ' ' (space).
+ for ($i = 148; $i < 156; $i++) {
+ $checksum += ord(' ');
+ }
+ // Last part of the header.
+ for ($i = 156; $i < 512; $i++) {
+ $checksum += ord(substr($block, $i, 1));
+ }
+
+ if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
+ $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
+ "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
+ "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
+ }
+ else {
+ $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
+ "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
+ "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
+ }
+ $data = unpack($fmt, $block);
+
+ if (strlen($data["prefix"]) > 0) {
+ $data["filename"] = "$data[prefix]/$data[filename]";
+ }
+
+ // Extract the checksum.
+ $header['checksum'] = octdec(trim($data['checksum']));
+ if ($header['checksum'] != $checksum) {
+ $header['filename'] = '';
+
+ // Look for last block (empty block).
+ if (($checksum == 256) && ($header['checksum'] == 0)) {
+ return $header;
+ }
+
+ throw new BackupMigrateException(
+ 'Invalid checksum for file %filename',
+ ['%filename' => $data['filename']]
+ );
+ }
+
+ // Extract the properties.
+ $header['filename'] = rtrim($data['filename'], "\0");
+ $header['mode'] = octdec(trim($data['mode']));
+ $header['uid'] = octdec(trim($data['uid']));
+ $header['gid'] = octdec(trim($data['gid']));
+ $header['size'] = octdec(trim($data['size']));
+ $header['mtime'] = octdec(trim($data['mtime']));
+ if (($header['typeflag'] = $data['typeflag']) == "5") {
+ $header['size'] = 0;
+ }
+ $header['link'] = trim($data['link']);
+
+ // Look for long filename.
+ if ($header['typeflag'] == 'L') {
+ $header = $this->readLongHeader($header);
+ }
+
+ return $header;
+ }
+
+ /**
+ * Read a tar file header block for files with long names.
+ *
+ * @param $header
+ *
+ * @return array
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ */
+ private function readLongHeader($header) {
+ $filename = '';
+ $filesize = $header['size'];
+ $n = floor($header['size'] / 512);
+ for ($i = 0; $i < $n; $i++) {
+ $content = $this->archive->readBytes(512);
+ $filename .= $content;
+ }
+ if (($header['size'] % 512) != 0) {
+ $content = $this->archive->readBytes(512);
+ $filename .= $content;
+ }
+
+ $filename = rtrim(substr($filename, 0, $filesize), "\0");
+
+ // Read the next header.
+ $data = $this->archive->readBytes(512);
+ $header = $this->readHeader($data, $header);
+ $header['filename'] = $filename;
+
+ return $header;
+ }
+
+ /**
+ * Detect and report a malicious file name.
+ *
+ * @param string $file
+ *
+ * @return bool
+ */
+ private function maliciousFilename($file) {
+ if (strpos($file, '/../') !== FALSE) {
+ return TRUE;
+ }
+ if (strpos($file, '../') === 0) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * This will be called when all files have been added. It gives the implementation
+ * a chance to clean up and commit the changes if needed.
+ *
+ * @return mixed
+ */
+ public function closeArchive() {
+ if ($this->archive) {
+ $this->archive->close();
+ }
+ }
+
+}
diff --git a/src/Core/Service/TarArchiveWriter.php b/src/Core/Service/TarArchiveWriter.php
new file mode 100644
index 0000000..aff3ce4
--- /dev/null
+++ b/src/Core/Service/TarArchiveWriter.php
@@ -0,0 +1,206 @@
+archive = $out;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFile($real_path, $new_path = '') {
+ $this->archive->openForWrite(TRUE);
+
+ $new_path = $new_path ? $new_path : $real_path;
+
+ $this->writeHeader($real_path, $new_path);
+
+ $fp = @fopen($real_path, "rb");
+ while (($v_buffer = fread($fp, 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->archive->write($v_binary_data);
+ }
+ fclose($fp);
+ }
+
+ /**
+ * @param $real_path
+ * @param $new_path
+ * @return bool
+ */
+ protected function writeHeader($real_path, $new_path) {
+ if (strlen($new_path) > 99) {
+ $this->writeLongHeader($new_path);
+ }
+
+ $v_info = lstat($real_path);
+
+ $v_uid = sprintf("%6s ", decoct($v_info[4]));
+ $v_gid = sprintf("%6s ", decoct($v_info[5]));
+ $v_perms = sprintf("%6s ", decoct($v_info['mode']));
+ $v_mtime = sprintf("%11s", decoct($v_info['mtime']));
+
+ $v_linkname = '';
+
+ if (@is_link($real_path)) {
+ $v_typeflag = '2';
+ $v_linkname = readlink($real_path);
+ $v_size = sprintf("%11s ", decoct(0));
+ }
+ elseif (@is_dir($real_path)) {
+ $v_typeflag = "5";
+ $v_size = sprintf("%11s ", decoct(0));
+ }
+ else {
+ $v_typeflag = '';
+ clearstatcache(TRUE, $real_path);
+ $v_size = sprintf("%11s ", decoct($v_info['size']));
+ }
+
+ $v_magic = '';
+ $v_version = '';
+ $v_uname = '';
+ $v_gname = '';
+ $v_devmajor = '';
+ $v_devminor = '';
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ $new_path, $v_perms, $v_uid,
+ $v_gid, $v_size, $v_mtime);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum.
+ $v_checksum = 0;
+ // ..... First part of the header.
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data_first, $i, 1));
+ }
+ // ..... Ignore the checksum value and replace it by ' ' (space).
+ for ($i = 148; $i < 156; $i++) {
+ $v_checksum += ord(' ');
+ }
+ // ..... Last part of the header.
+ for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
+ $v_checksum += ord(substr($v_binary_data_last, $j, 1));
+ }
+
+ // ----- Write the first 148 bytes of the header in the archive.
+ $this->archive->write($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum.
+ $v_checksum = sprintf("%6s ", decoct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->archive->write($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive.
+ $this->archive->write($v_binary_data_last, 356);
+ }
+
+ /**
+ * @param $new_path
+ * @return bool
+ */
+ function writeLongHeader($new_path) {
+ $v_size = sprintf("%11s ", decoct(strlen($new_path)));
+
+ $v_typeflag = 'L';
+ $v_linkname = '';
+ $v_magic = '';
+ $v_version = '';
+ $v_uname = '';
+ $v_gname = '';
+ $v_devmajor = '';
+ $v_devminor = '';
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ '././@LongLink', 0, 0, 0, $v_size, 0);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum.
+ $v_checksum = 0;
+ // ..... First part of the header.
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data_first, $i, 1));
+ }
+ // ..... Ignore the checksum value and replace it by ' ' (space).
+ for ($i = 148; $i < 156; $i++) {
+ $v_checksum += ord(' ');
+ }
+ // ..... Last part of the header.
+ for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
+ $v_checksum += ord(substr($v_binary_data_last, $j, 1));
+ }
+
+ // ----- Write the first 148 bytes of the header in the archive.
+ $this->archive->write($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum.
+ $v_checksum = sprintf("%6s ", decoct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->archive->write($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive.
+ $this->archive->write($v_binary_data_last, 356);
+
+ // ----- Write the filename as content of the block.
+ $i = 0;
+ while (($v_buffer = substr($new_path, (($i++) * 512), 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->archive->write($v_binary_data);
+ }
+ }
+
+ /**
+ * Write a footer to mark the end of the archive.
+ */
+ private function writeFooter() {
+ // ----- Write the last 0 filled block for end of archive.
+ $v_binary_data = pack('a1024', '');
+ $this->archive->write($v_binary_data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function closeArchive() {
+ $this->writeFooter();
+ $this->archive->close();
+ }
+
+}
diff --git a/src/Core/Service/TeeLogger.php b/src/Core/Service/TeeLogger.php
new file mode 100644
index 0000000..66f7ccd
--- /dev/null
+++ b/src/Core/Service/TeeLogger.php
@@ -0,0 +1,63 @@
+setLoggers($loggers);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return null
+ */
+ public function log($level, $message, array $context = []) {
+ foreach ($this->getLoggers() as $logger) {
+ $logger->log($level, $message, $context);
+ }
+ }
+
+ /**
+ * @return \Psr\Log\LoggerInterface[]
+ */
+ public function getLoggers() {
+ return $this->loggers;
+ }
+
+ /**
+ * @param \Psr\Log\LoggerInterface[] $loggers
+ */
+ public function setLoggers($loggers) {
+ $this->loggers = $loggers;
+ }
+
+ /**
+ * @param \Psr\Log\LoggerInterface $logger
+ */
+ public function addLogger(LoggerInterface $logger) {
+ $this->loggers[] = $logger;
+ }
+
+}
diff --git a/src/Core/Source/DatabaseSource.php b/src/Core/Source/DatabaseSource.php
new file mode 100644
index 0000000..1cdedc4
--- /dev/null
+++ b/src/Core/Source/DatabaseSource.php
@@ -0,0 +1,118 @@
+ 'text',
+ 'title' => 'Hostname'
+ ];
+ $schema['fields']['database'] = [
+ 'type' => 'text',
+ 'title' => 'Database'
+ ];
+ $schema['fields']['username'] = [
+ 'type' => 'text',
+ 'title' => 'Username',
+ ];
+ $schema['fields']['password'] = [
+ 'type' => 'password',
+ 'title' => 'Password'
+ ];
+ $schema['fields']['port'] = [
+ 'type' => 'number',
+ 'min' => 1,
+ 'max' => 65535,
+ 'title' => 'Port',
+ ];
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'generator' => 'Backup and Migrate Core',
+ ]);
+ }
+
+ /**
+ * Get a list of tables in this source.
+ */
+ public function getTableNames() {
+ try {
+ return $this->_getTableNames();
+ }
+ catch (\Exception $e) {
+ // Todo: Log this exception.
+ return [];
+ }
+ }
+
+ /**
+ * Get an array of tables with some info. Each entry must have at least a
+ * 'name' key containing the table name.
+ *
+ * @return array
+ */
+ public function getTables() {
+ try {
+ return $this->_getTables();
+ }
+ catch (\Exception $e) {
+ // Todo: Log this exception.
+ return [];
+ }
+ }
+
+
+ /**
+ * Get the list of tables from this db.
+ *
+ * @return array
+ */
+ protected function _getTableNames() {
+ $out = [];
+ foreach ($this->_getTables() as $table) {
+ $out[$table['name']] = $table['name'];
+ }
+ return $out;
+ }
+
+ /**
+ * Internal overridable function to actually generate table info.
+ *
+ * @return array
+ */
+ abstract protected function _getTables();
+
+}
diff --git a/src/Core/Source/DatabaseSourceInterface.php b/src/Core/Source/DatabaseSourceInterface.php
new file mode 100644
index 0000000..0dfbb25
--- /dev/null
+++ b/src/Core/Source/DatabaseSourceInterface.php
@@ -0,0 +1,25 @@
+ [],
+ 'importFromFile' => []
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exportToFile() {
+ if ($directory = $this->confGet('directory')) {
+ // Make sure the directory ends in exactly 1 slash:
+ if (substr($directory, -1) !== '/') {
+ $directory = $directory . '/';
+ }
+
+ if (!$writer = $this->getArchiveWriter()) {
+ throw new BackupMigrateException('A file directory source requires an archive writer object.');
+ }
+ $ext = $writer->getFileExt();
+ $file = $this->getTempFileManager()->create($ext);
+
+ if ($files = $this->getFilesToBackup($directory)) {
+ $writer->setArchive($file);
+ foreach ($files as $new => $real) {
+ $writer->addFile($real, $new);
+ }
+ $writer->closeArchive();
+ return $file;
+ }
+ throw new BackupMigrateException('The directory %dir does not not have any files to be backed up.',
+ ['%dir' => $directory]);
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function importFromFile(BackupFileReadableInterface $file) {
+ if ($directory = $this->confGet('directory')) {
+ // Make sure the directory ends in exactly 1 slash:
+ if (substr($directory, -1) !== '/') {
+ $directory = $directory . '/';
+ }
+
+ if (!file_exists($directory)) {
+ throw new BackupMigrateException('The directory %dir does not exist to restore to.',
+ ['%dir' => $directory]);
+ }
+ if (!is_writable($directory)) {
+ throw new BackupMigrateException('The directory %dir cannot be written to because of the operating system file permissions.',
+ ['%dir' => $directory]);
+ }
+
+ if (!$reader = $this->getArchiveReader()) {
+ throw new BackupMigrateException('A file directory source requires an archive reader object.');
+ }
+ // Check that the file endings match.
+ if ($reader->getFileExt() !== $file->getExtLast()) {
+ throw new BackupMigrateException('This source expects a .%ext file.', ['%ext' => $reader->getFileExt()]);
+ }
+
+ $reader->setArchive($file);
+ $reader->extractTo($directory);
+ $reader->closeArchive();
+
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Get a list if files to be backed up from the given directory.
+ *
+ * @param string $dir The name of the directory to list.
+ *
+ * @return array
+ *
+ * @throws \Drupal\backup_migrate\Core\Exception\BackupMigrateException
+ * @throws \Drupal\backup_migrate\Core\Exception\IgnorableException
+ *
+ * @internal param $directory
+ */
+ protected function getFilesToBackup($dir) {
+ // Add a trailing slash if there is none.
+ if (substr($dir, -1) !== '/') {
+ $dir .= '/';
+ }
+
+ if (!file_exists($dir)) {
+ throw new BackupMigrateException('Directory %dir does not exist.',
+ ['%dir' => $dir]);
+ }
+ if (!is_dir($dir)) {
+ throw new BackupMigrateException('The file %dir is not a directory.',
+ ['%dir' => $dir]);
+ }
+ if (!is_readable($dir)) {
+ throw new BackupMigrateException('Directory %dir could not be read from.',
+ ['%dir' => $dir]);
+ }
+
+ // Get a filtered list if files from the directory.
+ list($out, $errors) = $this->_getFilesFromDirectory($dir);
+
+ // Alert the user to any errors there might have been.
+ if ($errors) {
+ $count = count($errors);
+ $file_list = implode(', ', array_slice($errors, 0, 5));
+ if ($count > 5) {
+ $file_list .= ', ...';
+ }
+
+ if (!$this->confGet('ignore_errors')) {
+ throw new IgnorableException('The backup could not be completed because !count files could not be read: (!files).',
+ ['!count' => $count, '!files' => $file_list]);
+ }
+ else {
+ // throw new IgnorableException('!count files could not be read: (!files).', ['!files' => $filesmsg]);
+ // @todo Log the ignored files.
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * @param $base_path
+ * The name of the directory to list. This must always end in '/'.
+ * @param string $subdir
+ * @return array
+ * @internal param string $dir
+ */
+ protected function _getFilesFromDirectory($base_path, $subdir = '') {
+ $out = $errors = [];
+
+ // Open the directory.
+ if (!$handle = opendir($base_path . $subdir)) {
+ $errors[] = $base_path . $subdir;
+ }
+ else {
+ while (($file = readdir($handle)) !== FALSE) {
+ // If not a dot file and the file name isn't excluded.
+ if ($file != '.' && $file != '..') {
+
+ // Get the full path of the file.
+ $path = $base_path . $subdir . $file;
+
+ // Allow filters to modify or exclude this path.
+ $path = $this->plugins()->call('beforeFileBackup', $path, ['source' => $this, 'base_path' => $base_path]);
+ if ($path) {
+ if (is_dir($path)) {
+ list($sub_files, $sub_errors) =
+ $this->_getFilesFromDirectory($base_path, $subdir . $file . '/');
+
+ // Add the directory if it is empty.
+ if (empty($sub_files)) {
+ $out[$subdir . $file] = $path;
+ }
+
+ // Add the sub-files to the output.
+ $out = array_merge($out, $sub_files);
+ $errors = array_merge($errors, $sub_errors);
+ }
+ else {
+ if (is_readable($path)) {
+ $out[$subdir . $file] = $path;
+ }
+ else {
+ $errors[] = $path;
+ }
+ }
+ }
+ }
+ }
+ closedir($handle);
+ }
+
+ return [$out, $errors];
+ }
+
+ /**
+ * @param \Drupal\backup_migrate\Core\Service\ArchiveWriterInterface $writer
+ */
+ public function setArchiveWriter(ArchiveWriterInterface $writer) {
+ $this->archive_writer = $writer;
+ }
+
+ /**
+ * @return \Drupal\backup_migrate\Core\Service\ArchiveWriterInterface
+ */
+ public function getArchiveWriter() {
+ return $this->archive_writer;
+ }
+
+ /**
+ * @return \Drupal\backup_migrate\Core\Service\ArchiveReaderInterface
+ */
+ public function getArchiveReader() {
+ return $this->archive_reader;
+ }
+
+ /**
+ * @param \Drupal\backup_migrate\Core\Service\ArchiveReaderInterface $archive_reader
+ */
+ public function setArchiveReader($archive_reader) {
+ $this->archive_reader = $archive_reader;
+ }
+
+ /**
+ * Get a definition for user-configurable settings.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ public function configSchema(array $params = []) {
+ $schema = [];
+
+ // Init settings.
+ if ($params['operation'] == 'initialize') {
+ $schema['fields']['directory'] = [
+ 'type' => 'text',
+ 'title' => $this->t('Directory Path'),
+ ];
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Get the default values for the plugin.
+ *
+ * @return \Drupal\backup_migrate\Core\Config\Config
+ */
+ public function configDefaults() {
+ return new Config([
+ 'directory' => '',
+ ]);
+ }
+
+}
diff --git a/src/Core/Source/MySQLiSource.php b/src/Core/Source/MySQLiSource.php
new file mode 100644
index 0000000..af8be9d
--- /dev/null
+++ b/src/Core/Source/MySQLiSource.php
@@ -0,0 +1,498 @@
+ [],
+ 'importFromFile' => []
+ ];
+ }
+
+ /**
+ * Export this source to the given temp file. This should be the main
+ * back up function for this source.
+ *
+ * @return \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ * A backup file with the contents of the source dumped to it..
+ */
+ public function exportToFile() {
+ if ($connection = $this->_getConnection()) {
+ $file = $this->getTempFileManager()->create('mysql');
+
+ $exclude = (array) $this->confGet('exclude_tables');
+ $nodata = (array) $this->confGet('nodata_tables');
+
+ $file->write($this->_getSQLHeader());
+ $tables = $this->_getTables();
+
+ $lines = 0;
+ foreach ($tables as $table) {
+ // @todo reenable this.
+ // if (_backup_migrate_check_timeout()) {
+ // return FALSE;
+ // }
+ $table = $this->plugins()->call('beforeDBTableBackup', $table, ['source' => $this]);
+ if ($table['name'] && !isset($exclude[$table['name']]) && empty($table['exclude'])) {
+ $file->write($this->_getTableCreateSQL($table));
+ $lines++;
+ if (empty($table['nodata']) && !in_array($table['name'], $nodata)) {
+ $lines += $this->_dumpTableSQLToFile($file, $table);
+ }
+ }
+ }
+
+ $file->write($this->_getSQLFooter());
+ $file->close();
+ return $file;
+ }
+ else {
+ // @todo Throw exception
+ return $this->getTempFileManager()->create('mysql');
+ }
+
+ }
+
+ /**
+ * Import to this source from the given backup file. This is the main restore
+ * function for this source.
+ *
+ * @param \Drupal\backup_migrate\Core\File\BackupFileReadableInterface $file
+ * The file to read the backup from. It will not be opened for reading
+ *
+ * @return bool|int
+ */
+ public function importFromFile(BackupFileReadableInterface $file) {
+ $num = 0;
+
+ if ($conn = $this->_getConnection()) {
+ // Open (or rewind) the file.
+ $file->openForRead();
+
+ // Read one line at a time and run the query.
+ while ($line = $this->_readSQLCommand($file)) {
+ // if (_backup_migrate_check_timeout()) {
+ // return FALSE;
+ // }
+ if ($line) {
+ // Execute the sql query from the file.
+ $conn->query($line);
+ $num++;
+ }
+ }
+ // Close the file, we're done reading it.
+ $file->close();
+ }
+ return $num;
+ }
+
+
+ /**
+ * Get the db connection for the specified db.
+ *
+ * @return \mysqli Connection object.
+ *
+ * @throws \Exception
+ */
+ protected function _getConnection() {
+ if (!$this->connection) {
+ if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
+ throw new BackupMigrateException('Cannot connect to the database because the MySQLi extension is missing.');
+ }
+
+ $pdo_config = $this->confGet('pdo');
+
+ $ssl_config = [
+ 'key' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_KEY])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_KEY] : NULL,
+ 'cert' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CERT])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CERT] : NULL,
+ 'ca' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CA])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CA] : NULL,
+ 'capath' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CAPATH])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CAPATH] : NULL,
+ 'cypher' => (!empty($pdo_config[PDO::MYSQL_ATTR_SSL_CIPHER])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_CIPHER] : NULL,
+ ];
+
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) {
+ $ssl_config['verify_server_cert'] = (isset($pdo_config[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT])) ? $pdo_config[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] : TRUE;
+ }
+ else {
+ $ssl_config['verify_server_cert'] = TRUE;
+ }
+
+ if ($ssl_config['key'] || $ssl_config['cert'] || $ssl_config['ca'] || $ssl_config['capath'] || $ssl_config['cypher']) {
+
+ // Provide a workaround for PHP7 peer certificate verification issues:
+ // - https://bugs.php.net/bug.php?id=68344
+ // - https://bugs.php.net/bug.php?id=71003
+ if ($ssl_config['verify_server_cert']) {
+ $flags = MYSQLI_CLIENT_SSL;
+ }
+ else {
+ $flags = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
+ }
+
+ // Connect using PDO SSL config.
+ $this->connection = new \mysqli();
+
+ $this->connection->ssl_set($ssl_config['key'], $ssl_config['cert'], $ssl_config['ca'], $ssl_config['capath'], $ssl_config['cypher']);
+
+ $this->connection->real_connect(
+ $this->confGet('host'),
+ $this->confGet('username'),
+ $this->confGet('password'),
+ $this->confGet('database'),
+ $this->confGet('port'),
+ $this->confGet('socket'),
+ $flags
+ );
+ }
+ else {
+ $this->connection = new \mysqli(
+ $this->confGet('host'),
+ $this->confGet('username'),
+ $this->confGet('password'),
+ $this->confGet('database'),
+ $this->confGet('port'),
+ $this->confGet('socket')
+ );
+ }
+
+ // Throw an error on fail.
+ if ($this->connection->connect_errno || !$this->connection->ping()) {
+ throw new BackupMigrateException("Failed to connect to MySQL server.");
+ }
+ // Ensure, that the character set is UTF8.
+ if (!$this->connection->set_charset('utf8mb4')) {
+ if (!$this->connection->set_charset('utf8')) {
+ throw new BackupMigrateException('UTF8 is not supported by the MySQL server.');
+ }
+ }
+ }
+ return $this->connection;
+ }
+
+
+ /**
+ * Get the header for the top of the SQL file.
+ *
+ * @return string
+ */
+ protected function _getSQLHeader() {
+ $info = $this->_dbInfo();
+ $version = $info['version'];
+ $host = $this->confGet('host');
+ $db = $this->confGet('database');
+ $timestamp = gmdate('r');
+ $generator = $this->confGet('generator');
+
+ return <<