From 27288563ae14c8432b519fd3fb5c7605f0a358da Mon Sep 17 00:00:00 2001 From: Dale McGladdery Date: Sun, 3 May 2020 11:17:59 -0700 Subject: [PATCH] Inital commit (v0.1.0) --- .gitignore | 2 + README.md | 54 ++ build.php | 50 ++ composer.json | 19 + composer.lock | 1402 ++++++++++++++++++++++++++++++++++++++ init.php | 34 + src/Commands/InitCmd.php | 35 + src/Commands/PairCmd.php | 89 +++ src/Commands/SiteCmd.php | 132 ++++ src/Commands/SyncCmd.php | 99 +++ src/Defaults.php | 42 ++ src/Entity/Datastore.php | 205 ++++++ src/Entity/Entity.php | 48 ++ src/Entity/Pair.php | 48 ++ src/Entity/Site.php | 113 +++ src/Utilities.php | 65 ++ 16 files changed, 2437 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100755 init.php create mode 100644 src/Commands/InitCmd.php create mode 100644 src/Commands/PairCmd.php create mode 100644 src/Commands/SiteCmd.php create mode 100644 src/Commands/SyncCmd.php create mode 100644 src/Defaults.php create mode 100644 src/Entity/Datastore.php create mode 100644 src/Entity/Entity.php create mode 100644 src/Entity/Pair.php create mode 100644 src/Entity/Site.php create mode 100644 src/Utilities.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f9899e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor +/sitesync.phar \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a128f2c --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Robo Site Sync + +A Robo based utility designed to synchronize website content for MySQL based websites such as Drupal and WordPress. It's intended use is keeping local and development websites up-to-date with the live website. + +## Overview + +The utility is installed by placing the phar file in the computer path. + +Sites are defined indivually with the _site_ command. The _pair_ command creates a synchronization pair. The _sync_ command is then used synchronize the destination site to source site. + +The site and pair definition files are placed in a directory named .robo-site-sync in the account home directory. The site-sync utility has minimal assistance for maintaining these files. They are primarily maintained through a text editor. + +### Example + +The example below assumes the site-sync.phar file has not been placed in the system path. + +- `php site-sync.phar site create source-site` +- `php site-sync.phar site create destination-site` +- edit the config files +- `php site-sync.phar pair my-sync-pair source-site destination-site` +- edit the config file +- `php site-sync.phar sync my-sync-pair` + +## Installation + +- Download the site-sync.phar file +- Check if it works: + `php site-sync.phar --help` +- To be able to type `site-sync`, instead of `php site-sync.phar`, you need to make the file executable and move it to somewhere in your PATH. For example: + `chmod +x site-sync.phar` + `mv site-sync.phar ~/bin/site-sync` or `sudo mv site-sync.phar /usr/local/bin/site-sync` +- Test for successful installation: + `site-sync --help` + +## Development + +### Setup + +- git clone the repository +- cd into the project root +- run composer install + +### Creating a New phar File + +- cd to project root +- `composer build` + +### Running from the Project + +This is useful for testing code changes without recompiling the phar file + +- cd to project root +- `php init.php {command}` + \ No newline at end of file diff --git a/build.php b/build.php new file mode 100644 index 0000000..b15113d --- /dev/null +++ b/build.php @@ -0,0 +1,50 @@ +hasChildren() && !in_array($file->getFilename(), $exclude)) { + return true; + } + return $file->isFile() && !in_array($file->getFilename(), $exclude); +}; + +$iterator = new RecursiveIteratorIterator( + new RecursiveCallbackFilterIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + $filter + ) +); + +$phar = new Phar("sitesync.phar"); +$phar->setSignatureAlgorithm(\Phar::SHA1); +$phar->startBuffering(); +$phar->buildFromIterator($iterator, $dir); +//default executable +$phar->setStub( + "#!/usr/bin/php \n" . $phar->createDefaultStub('init.php') +); +$phar->stopBuffering(); \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a686a07 --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "vendor_name/package_name", + "description": "description_text", + "minimum-stability": "stable", + "license": "proprietary", + "require": { + "consolidation/robo": "^2.0" + }, + "autoload":{ + "psr-4":{ + "RoboSiteSync\\":"src" + } + }, + "scripts": { + "build": [ + "@php build.php" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f81ac0b --- /dev/null +++ b/composer.lock @@ -0,0 +1,1402 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e6aa7d364072161c81938a209c144323", + "packages": [ + { + "name": "consolidation/annotated-command", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "33e472d3cceb0f22a527d13ccfa3f76c4d21c178" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/33e472d3cceb0f22a527d13ccfa3f76c4d21c178", + "reference": "33e472d3cceb0f22a527d13ccfa3f76c4d21c178", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^4.1", + "php": ">=7.1.3", + "psr/log": "^1|^2", + "symfony/console": "^4|^5", + "symfony/event-dispatcher": "^4|^5", + "symfony/finder": "^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^3" + }, + "type": "library", + "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + } + } + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "time": "2020-02-07T03:35:30+00:00" + }, + { + "name": "consolidation/config", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1", + "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "grasmash/expander": "^1", + "php": ">=5.4.0" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5", + "squizlabs/php_codesniffer": "2.*", + "symfony/console": "^2.5|^3|^4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "suggest": { + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "scenarios": { + "symfony4": { + "require-dev": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require-dev": { + "symfony/console": "^2.8", + "symfony/event-dispatcher": "^2.8", + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "time": "2019-03-03T19:37:04+00:00" + }, + { + "name": "consolidation/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "446f804476db4f73957fa4bcb66ab2facf5397ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/446f804476db4f73957fa4bcb66ab2facf5397ff", + "reference": "446f804476db4f73957fa4bcb66ab2facf5397ff", + "shasum": "" + }, + "require": { + "php": ">=5.4.5", + "psr/log": "^1.0", + "symfony/console": "^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "time": "2020-02-07T01:22:27+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "eae721c3a916707c40d4390efbf48d4c799709cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/eae721c3a916707c40d4390efbf48d4c799709cc", + "reference": "eae721c3a916707c40d4390efbf48d4c799709cc", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=7.1.3", + "symfony/console": "^4|^5", + "symfony/finder": "^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^3", + "symfony/var-dumper": "^4", + "symfony/yaml": "^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + } + } + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2020-02-07T03:22:30+00:00" + }, + { + "name": "consolidation/robo", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/Robo.git", + "reference": "7bbc66f9485736e64df6fc946bee1b1deb722667" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/7bbc66f9485736e64df6fc946bee1b1deb722667", + "reference": "7bbc66f9485736e64df6fc946bee1b1deb722667", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^4.1", + "consolidation/config": "^1.2.1", + "consolidation/log": "^1.1.1|^2", + "consolidation/output-formatters": "^4.1", + "consolidation/self-update": "^1.1.5", + "grasmash/yaml-expander": "^1.4", + "league/container": "^2.4.1", + "php": ">=7.1.3", + "symfony/console": "^4.4.3", + "symfony/event-dispatcher": "^4.4.3", + "symfony/filesystem": "^4.4.3", + "symfony/finder": "^4.4.3", + "symfony/process": "^4.4.3" + }, + "conflict": { + "codegyre/robo": "*" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "natxet/cssmin": "3.0.4", + "patchwork/jsqueeze": "^2", + "pear/archive_tar": "^1.4.4", + "php-coveralls/php-coveralls": "^1", + "phpdocumentor/reflection-docblock": "^4.3.2", + "phpunit/phpunit": "^6.5.14", + "squizlabs/php_codesniffer": "^3" + }, + "suggest": { + "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", + "natxet/cssmin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." + }, + "bin": [ + "robo" + ], + "type": "library", + "extra": { + "scenarios": { + "php71": { + "require": { + "phpunit/phpunit": "^6", + "nikic/php-parser": "^2" + }, + "remove": [ + "codeception/phpunit-wrapper" + ], + "config": { + "platform": { + "php": "7.1.3" + } + } + } + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Robo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "Modern task runner", + "time": "2020-02-19T01:58:49+00:00" + }, + { + "name": "consolidation/self-update", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/self-update.git", + "reference": "dba6b2c0708f20fa3ba8008a2353b637578849b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/dba6b2c0708f20fa3ba8008a2353b637578849b4", + "reference": "dba6b2c0708f20fa3ba8008a2353b637578849b4", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4|^5", + "symfony/filesystem": "^2.5|^3|^4|^5" + }, + "bin": [ + "scripts/release" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "SelfUpdate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander Menk", + "email": "menk@mestrona.net" + }, + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provides a self:update command for Symfony Console applications.", + "time": "2020-04-13T02:49:20+00:00" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "abandoned": "psr/container", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "grasmash/expander", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "time": "2017-12-21T22:14:55+00:00" + }, + { + "name": "grasmash/yaml-expander", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-expander.git", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlExpander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in a yaml file.", + "time": "2017-12-16T16:06:03+00:00" + }, + { + "name": "league/container", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "time": "2017-05-10T09:20:27+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2020-03-30T11:41:10+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T09:54:03+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fe297193bf2e6866ed900ed2d5869362768df6a7", + "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "5729f943f9854c5781984ed4907bbb817735776b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b", + "reference": "5729f943f9854c5781984ed4907bbb817735776b", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/process", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2020-03-30T11:41:10+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/init.php b/init.php new file mode 100755 index 0000000..470a56f --- /dev/null +++ b/init.php @@ -0,0 +1,34 @@ +setClassLoader($classLoader) + ->execute( + $argv, + APP_NAME, + APP_VERSION, + new \Symfony\Component\Console\Output\ConsoleOutput() + ); +exit($statusCode); \ No newline at end of file diff --git a/src/Commands/InitCmd.php b/src/Commands/InitCmd.php new file mode 100644 index 0000000..25ff2ee --- /dev/null +++ b/src/Commands/InitCmd.php @@ -0,0 +1,35 @@ +say("A configuration directory exists. No action taken."); + return; + } + + $configDir = Defaults::getInstance()->getConfigDir(); + $this->say("The directory {$configDir} will be created."); + $answer = $this->ask("Create [y/n]?"); + if (strtolower($answer) == 'y') { + $status = mkdir(Defaults::getInstance()->getConfigDir()); + $message = ($status) ? 'Directory created' : 'Could not create directory'; + $this->say($message); + } + else { + $this->say('Directory not created'); + } + } + +} \ No newline at end of file diff --git a/src/Commands/PairCmd.php b/src/Commands/PairCmd.php new file mode 100644 index 0000000..82b9327 --- /dev/null +++ b/src/Commands/PairCmd.php @@ -0,0 +1,89 @@ + false] ) { + /* + * Validation + */ + if (!Datastore::exists()) { + $this->say("No configuration directory. Please use init to create."); + return; + } + if ( empty($action) ) { + $this->say('Please specific an action: ' . implode(' | ', $this->validActions)); + return; + } + + /* + * Action Dispatch + */ + switch ( $action ) { + case 'list': + $this->listPair( $pairname, $opts ); + break; + case 'create': + $this->createPair( $pairname, $opts ); + break; + case 'delete': + $this->deletePair( $pairname, $opts ); + break; + default: + $this->say("'$action' is not a valid action.\nValid actions: " . implode(' | ', $this->validActions)); + } + } + + protected function listPair( $pairName, $opts ) { + $datastore = new Datastore(); + if ($pairName == '') { + $output = array_reduce($datastore->getPairList(), + function($carry, $item) { + return $carry .= "{$item->name} ({$item->description})\n"; + }, "Pair List:\n" + ); + $this->say($output); + } + else { + $pair = $datastore->getPair($pairName); + $output = (is_null($pair)) ? "{$pairName} does not exist" : $pair->toPrint(); + $this->say($output); + } + } + + protected function createPair($pairName, $opts) { + //todo: add logic to validate name safe for file system + $initialData = [ + 'name' => $pairName, + ]; + if ($opts['prompt']) { + $initialData = Utilities::promptForProperties(Pair::class, $pairName); + } + (new Datastore())->savePair(new Pair($initialData)); + } + + protected function deletePair($pairName, $options) { + (new Datastore())->deletePair( $pairName ); + } + +} diff --git a/src/Commands/SiteCmd.php b/src/Commands/SiteCmd.php new file mode 100644 index 0000000..35ce3bf --- /dev/null +++ b/src/Commands/SiteCmd.php @@ -0,0 +1,132 @@ +datastore = new Datastore(); + } + + /** + * Site management + * + * Manage the configuration of sites used. + * + * @param $action list | create | delete | verify + * @param string $sitename + */ + public function site( $action = '', $sitename = '', $opts = ['prompt|p' => false] ) { + /* + * Validation + */ + if (!Datastore::exists()) { + $this->say("No configuration directory. Please use init to create."); + return; + } + if ( empty($action) ) { + $this->say('Please specific an action: ' . implode(' | ', $this->validActions)); + return; + } + + /* + * Action Dispatch + */ + switch ( $action ) { + case 'list': + $this->siteList($sitename, $opts); + break; + case 'create': + $this->createSite( $sitename, $opts ); + break; + case 'delete': + $this->deleteSite( $sitename, $opts ); + break; + case 'verify': + $this->verifySite( $sitename, $opts ); + break; + default: + $this->say("'$action' is not a valid action.\nValid actions: " . implode(' | ', $this->validActions)); + } + } + + protected function siteList($sitename, $opts) { + $datastore = new Datastore(); + if ($sitename == '') { + $output = array_reduce($datastore->getSiteList(), + function($carry, $item) { + return $carry .= "{$item->name} ({$item->description})\n"; + }, "Site List:\n" + ); + $this->say($output); + } + else { + $site = $datastore->getSite($sitename); + $output = (is_null($site)) ? "$site does not exist" : $site->toPrint(); + $this->say($output); + } + } + + + protected function createSite($sitename, $opts) { + //todo: add logic to make name safe for file system + $initialData = [ + 'name' => $sitename, + ]; + if ($opts['prompt']) { + $initialData = Utilities::promptForProperties(Site::class, $sitename); + } + $this->datastore->saveSite( new Site( $initialData ) ); + $saveFilePath = $this->datastore->getSiteConfigPath( $sitename ); + $this->say("Configuration file created in $saveFilePath" ); + if ( php_uname('s') == 'Darwin') { + $this->taskExec("open $saveFilePath")->run(); + } + } + + protected function deleteSite( $sitename, $opts ) { + $this->datastore->deleteSite( $sitename ); + } + + protected function verifySite( $sitename, $opts ) { + $status = []; + $site = $this->datastore->getSite( $sitename ); + + if ($site->hostDomain == 'localhost') { + // Test that directories exist + $status['projectDir'] = Utilities::verifyDirectory( $site->projectDir ); + $status['websiteDir'] = Utilities::verifyDirectory( $site->websiteDir ); + $status['backupDir'] = Utilities::verifyDirectory( $site->backupDir ); + } + else { + $dirTask = $this->taskExec('pwd'); + $result = $this->taskSshExec($site->hostDomain, $site->hostUser) + ->port((int) $site->hostSshPort) + ->exec('ls -alh')->quiet() + ->run(); + $status['hostTest']['getMessage'] = $result->getMessage(); + $status['hostTest']['getData'] = $result->getData(); + $status['hostTest']['getOutputData'] = $result->getOutputData(); + $status['hostTest']['getExitCode'] = $result->getExitCode(); + $status['hostTest']['wasSuccessful'] = $result->wasSuccessful(); + $status['hostTest']['getData'] = $result->getData(); + } + + $this->say(print_r($status, 1)); + } + +} diff --git a/src/Commands/SyncCmd.php b/src/Commands/SyncCmd.php new file mode 100644 index 0000000..e719669 --- /dev/null +++ b/src/Commands/SyncCmd.php @@ -0,0 +1,99 @@ +getPair( $pairname ); + + $source = $datastore->getSite( $syncPair->sourceSite ); + $dest = $datastore->getSite( $syncPair->destinationSite ); + + $backupFilename = $source->name .'-'. $source->role .'-'. date('Y-m-d') .'.sql.gz'; + $rmtBackupPath = $source->backupDir .'/'. $backupFilename; + + if (is_null($source)) { + $this->say("Invalid source: {$syncPair->sourceSite}"); + } + if (is_null($dest)) { + $this->say("Invalid destination: {$dest->sourceSite}"); + } + + if ( $source->cms != $dest->cms ) { + $this->say("Can not sync between different CMSs:\n{$source->name} CMS: {$source->cms} | {$dest->name} CMS: {$dest->cms}"); + return; + } + + if ($source->cms == 'drupal7') { + // todo: starting point assuming drupal sites have settings in default dir + $userToVarCmd = "export USERNAME=`grep \"^[[:space:]]*'username'\" sites/default/settings.php | awk -F \"'\" '{ print $4 }'`"; + $passToVarCmd = "export MYSQL_PWD=`grep \"^[[:space:]]*'password'\" sites/default/settings.php | awk -F \"'\" '{ print $4 }'`"; + $dbToVarCmd = "export DATABASE=`grep \"^[[:space:]]*'database'\" sites/default/settings.php | awk -F \"'\" '{ print $4 }'`"; + $hostToVarCmd = "export HOST=`grep \"^[[:space:]]*'host'\" sites/default/settings.php | awk -F \"'\" '{ print $4 }'`"; + } + elseif ($source->cms == 'wordpress' || $source->cms == 'wp') { + $userToVarCmd = "export USERNAME=`grep \"^[[:space:]]*define.*'DB_USER'\" wp-config.php | awk -F \"'\" '{ print $4 }'`"; + $passToVarCmd = "export MYSQL_PWD=`grep \"^[[:space:]]*define.*'DB_PASSWORD'\" wp-config.php | awk -F \"'\" '{ print $4 }'`"; + $dbToVarCmd = "export DATABASE=`grep \"^[[:space:]]*define.*'DB_NAME'\" wp-config.php | awk -F \"'\" '{ print $4 }'`"; + $hostToVarCmd = "export HOST=`grep \"^[[:space:]]*define.*'DB_HOST'\" wp-config.php | awk -F \"'\" '{ print $4 }'`"; + } + else { + $this->say("The {$source->cms} is not supported."); + return; + } + + $this->taskSshExec($source->hostDomain, $source->hostUser) + ->port((int) $source->hostSshPort) + ->exec("cd {$source->websiteDir}") + ->exec(str_replace("'", "'\\''", $userToVarCmd)) + ->exec(str_replace("'", "'\\''", $passToVarCmd)) + ->exec(str_replace("'", "'\\''", $dbToVarCmd)) + ->exec(str_replace("'", "'\\''", $hostToVarCmd)) + ->exec("mysqldump -u\$USERNAME -h\$HOST \$DATABASE | gzip > $rmtBackupPath") + ->run(); + + $this->taskExec("scp -P{$source->hostSshPort} {$source->hostUser}@{$source->hostDomain}:$rmtBackupPath $dest->backupDir/.")->run(); + + $this->taskSshExec($source->hostDomain, $source->hostUser) + ->port((int) $source->hostSshPort) + ->exec("rm -v $rmtBackupPath") + ->run(); + + // todo: add dropping database tables + $this->taskExecStack() + ->exec("cd {$dest->websiteDir}") + ->exec($userToVarCmd) + ->exec($passToVarCmd) + ->exec($dbToVarCmd) + ->exec($hostToVarCmd) + ->exec("gunzip -c {$dest->backupDir}/$backupFilename | mysql -u\$USERNAME -h\$HOSTNAME \$DATABASE") + ->run(); + + if ($source->cms == 'wordpress') { + $replaceUrl = $this->taskExecStack() + ->exec("cd {$dest->websiteDir}"); + if (parse_url($source->siteUrl, PHP_URL_SCHEME) != parse_url($dest->siteUrl, PHP_URL_SCHEME)) { + $replaceUrl->exec("wp search-replace --all-tables {$source->siteUrl} {$dest->siteUrl}"); + } + $sourceSiteHost = parse_url($source->siteUrl, PHP_URL_HOST); + $destSiteHost = parse_url($dest->siteUrl, PHP_URL_HOST); + $replaceUrl->exec("wp search-replace --all-tables {$sourceSiteHost} {$destSiteHost}"); + $replaceUrl->run(); + } + + $this->taskRsync() + ->remoteShell("ssh -p {$source->hostSshPort}") + ->fromPath("{$source->hostUser}@{$source->hostDomain}:{$source->websiteDir}/{$source->filesDir}/") + ->toPath("{$dest->websiteDir}/{$dest->filesDir}/") + ->recursive() + ->progress() + ->stats() + ->run(); + } + +} diff --git a/src/Defaults.php b/src/Defaults.php new file mode 100644 index 0000000..7872388 --- /dev/null +++ b/src/Defaults.php @@ -0,0 +1,42 @@ +configDirName; + } + + public function getConfigDir() { + return $this->getConfigParentDir() . '/' . $this->getConfigDirName(); + } + +} diff --git a/src/Entity/Datastore.php b/src/Entity/Datastore.php new file mode 100644 index 0000000..4d97084 --- /dev/null +++ b/src/Entity/Datastore.php @@ -0,0 +1,205 @@ +getConfigDir(); + } + return ( + file_exists($directory) + && is_dir($directory) + && is_readable($directory) + && is_writeable($directory) + ); + } + + /** + * DataStore constructor. + * + * @param null $directory + * + * @throws \Exception + */ + public function __construct( $directory = NULL ) { + if ( is_null( $directory ) ) { + $this->directory = Defaults::getInstance()->getConfigDir(); + } + else { + $this->directory = $directory; + } + if ( + ! file_exists($this->directory) + || ! is_dir($this->directory) + || ! is_readable($this->directory) + || ! is_writeable($this->directory) + ) { + throw new \Exception("Datastore is unavailable."); + } + } + + /** + * @param $sitename + * + * @return \RoboSiteSync\Entity\Site|null + */ + public function getSite( $sitename ) { + $yamlFilename = $this->directory . '/' . self::sitenameToFilename($sitename); + if (file_exists($yamlFilename)) { + return new Site($this->loadData($yamlFilename)); + } + else { + return NULL; + } + } + + public function getSiteConfigPath( $sitename ) { + return $yamlFilename = $this->directory . '/' . self::sitenameToFilename($sitename); + } + + public function saveSite( Site $site ) { + // The dumper can not add yaml comments. To work around this, add a token + // value that can be replaced with the comment. + $propertiesPlusTokens = []; + foreach ($site->toArray() as $key => $value) { + $propertiesPlusTokens["{$key}_description"] = ''; + $propertiesPlusTokens[$key] = $value; + } + + $storageString = Yaml::dump( $this->keysToSnakeCase( $propertiesPlusTokens ) ); + + // Replace tokens with comments + $descriptions = $this->keysToSnakeCase( Utilities::fetchPropertyDescriptions( Site::class ) ); + foreach ( $descriptions as $name => $description ) { + $storageString = str_replace("{$name}_description: ''", "\n# $description", $storageString); + } + $storageString = trim( $storageString ); + + $filepath = $this->directory . '/' . self::sitenameToFilename($site->name); + file_put_contents( $filepath, $storageString ); + } + + public function deleteSite( $sitename ) { + $filepath = $this->directory . '/' . self::sitenameToFilename($sitename); + unlink( $filepath ); + } + + /** + * Get Site List + * + * @return array + */ + public function getSiteList() { + $siteList = []; + foreach (glob($this->directory . '/site-*.yml') as $filename) { + $siteList[self::filenameToSitename($filename)] = new Site($this->loadData($filename)); + } + + return $siteList; + } + + /** + * Save sync pair object to the datastore. + * + * @param \RoboSiteSync\Entity\Pair $pair + */ + public function savePair( Pair $pair ) { + $filepath = $this->directory . '/' . self::pairNameToFilename( $pair->name ); + $storageString = Yaml::dump( $this->keysToSnakeCase( $pair->toArray() ) ); + file_put_contents( $filepath, $storageString ); + } + + public function getPair( $pairName ) { + $yamlFilename = $this->directory . '/' . self::pairNameToFilename($pairName); + if (file_exists($yamlFilename)) { + return new Pair($this->loadData($yamlFilename)); + } + else { + return NULL; + } + } + public function deletePair( $pairName ) { + $filepath = $this->directory . '/' . self::pairNameToFilename($pairName); + unlink( $filepath ); + } + + /** + * Get Pair List + * + * @return array + */ + public function getPairList() { + $pairList = []; + foreach (glob($this->directory . '/pair-*.yml') as $yamlFilename) { + $pairList[self::filenameToPairName($yamlFilename)] = new Pair($this->loadData($yamlFilename)); + } + + return $pairList; + } + + private function loadData( $yamlFilename ) { + $yamlString = file_get_contents($yamlFilename); + $dataArray = Yaml::parse( $yamlString ); + return $this->keysToCamelCase( $dataArray ); + } + + private function keysToSnakeCase( array $propertyArray ) { + $snakeCase = []; + foreach ($propertyArray as $name => $value) { + $snakeCase[$this->camelToSnakeCase($name)] = $value; + } + return $snakeCase; + } + + private function keysToCamelCase( array $propertyArray ) { + $camelCase = []; + foreach ($propertyArray as $name => $value) { + $camelCase[$this->snakeToCamelCase($name)] = $value; + } + return $camelCase; + } + + private function snakeToCamelCase( $snakeCase ) { + $camelCase = ''; + $parts = explode('_', $snakeCase); + foreach ($parts as $index => $part) { + if ($index > 0) { + $part = ucfirst($part); + } + $camelCase .= $part; + } + return $camelCase; + } + + private function camelToSnakeCase( $camelCase ) { + return strtolower(implode('_', preg_split('/(?=[A-Z])/', $camelCase))); + } +} diff --git a/src/Entity/Entity.php b/src/Entity/Entity.php new file mode 100644 index 0000000..4a3c568 --- /dev/null +++ b/src/Entity/Entity.php @@ -0,0 +1,48 @@ + $value) { + if (property_exists($this, $propName)) { + $this->{$propName} = $value; + } + } + } + } + + public function toArray() { + $properties = []; + $class = new \ReflectionClass($this); + foreach ($class->getProperties() as $property) { + $properties[$property->getName()] = $this->{$property->getName()}; + } + // Make sure 'name' is always first. + $name = ['name' => $properties['name']]; + unset($properties['name']); + $properties = array_merge($name, $properties); + return $properties; + } + + public function toPrint() { + return print_r($this->toArray(), 1); + } + +} \ No newline at end of file diff --git a/src/Entity/Pair.php b/src/Entity/Pair.php new file mode 100644 index 0000000..8003a0c --- /dev/null +++ b/src/Entity/Pair.php @@ -0,0 +1,48 @@ + $itemName, + ]; + + // Iterate over all the property classes. + $classProperties = (new \ReflectionClass( $entityClassName ))->getProperties(); + foreach ($classProperties as $property) { + // Skip asking the schemaVersion and name if it is already set. + if ( + ($property->getName() == 'name' && $properties['name'] != '') + || $property->getName() == 'schemaVersion' + ) { + continue; + } + + // Grab and format the description. + $description = explode("\n", $property->getDocComment())[1]; + $description = trim(substr($description, stripos($description, '*') + 1)); + $displayProperty = ucfirst(strtolower(implode(' ', preg_split('/(?=[A-Z])/', $property->getName())))); + + // Ask the question. + print "\n$description\n"; + $answer = readline($displayProperty . ': '); + $properties[$property->getName()] = $answer; + } + + return $properties; + } + + public static function verifyDirectory( $filepath ) { + $status = ( file_exists($filepath) && is_dir($filepath) && is_readable($filepath) && is_writeable($filepath) ); + return ($status) ? 'Good' : 'Bad'; + } + + public static function fetchPropertyDescriptions( $entityClassName ) { + $descriptions = []; + $classProperties = (new \ReflectionClass( $entityClassName ))->getProperties(); + foreach ( $classProperties as $property ) { + $name = $property->getName(); + $docComment = explode("\n", $property->getDocComment())[1] ?? ''; + $description = trim(substr($docComment, stripos($docComment, '*') + 1)); + $descriptions[$name] = $description; + } + return $descriptions; + } + +}