/** * Creates a commit. * * Uses a temporary file to construct the commit message because on Windows, multi-line * commit message cannot be created on CLI and it's generally a more flexible solution * (very long commit messages, etc.). * * @param CommitMessage|string $message * @param string $authorName * @param string $authorEmail */ public function commit($message, $authorName, $authorEmail) { if (is_string($message)) { $commitMessage = $message; $body = null; } else { $subject = $message->getSubject(); $body = $message->getBody(); $commitMessage = $this->commitMessagePrefix . $subject; } if ($body != null) { $commitMessage .= "\n\n" . $body; } $tempCommitMessageFilename = md5(rand()); $tempCommitMessagePath = $this->tempDirectory . '/' . $tempCommitMessageFilename; file_put_contents($tempCommitMessagePath, $commitMessage); // Unfortunatelly, `git commit --author=...` is not enough. // It doesn't work with empty both local and global config. $localConfigUserName = $this->runShellCommandWithStandardOutput('git config --local user.name'); $localConfigUserEmail = $this->runShellCommandWithStandardOutput('git config --local user.email'); $this->runShellCommand('git config --local user.name %s', $authorName); $this->runShellCommand('git config --local user.email %s', $authorEmail); $this->runShellCommand("git commit --file=%s", $tempCommitMessagePath); FileSystem::remove($tempCommitMessagePath); if ($localConfigUserName === null) { $this->runShellCommand('git config --local --unset user.name'); } else { $this->runShellCommand('git config --local user.name %s', $localConfigUserName); } if ($localConfigUserEmail === null) { $this->runShellCommand('git config --local --unset user.email'); } else { $this->runShellCommand('git config --local user.email %s', $localConfigUserEmail); } }
protected function setUp() { parent::setUp(); FileSystem::removeContent(self::$repositoryPath); FileSystem::removeContent(self::$tempPath); self::$repository->init(); }
protected function setUp() { parent::setUp(); FileSystem::removeContent(self::$repositoryPath); FileSystem::removeContent(self::$tempPath); self::$repository->init(); $this->commitFile('initial-file', 'Initial commit'); }
private function prepareBugReport($bugReportDir, $zipFile) { FileSystem::mkdir($bugReportDir); FileSystem::copyDir(VERSIONPRESS_PLUGIN_DIR . '/log', $bugReportDir . '/log'); $this->savePhpinfo($bugReportDir); $this->saveWordPressSpecificInfo($bugReportDir); Zip::zipDirectory($bugReportDir, $zipFile); }
public function restoreAllDefinitionFilesFromHistory() { FileSystem::removeContent($this->directory); $definitionFilesWildcard = WP_PLUGIN_DIR . '/*/.versionpress/actions.yml'; $modifications = $this->gitRepository->getFileModifications($definitionFilesWildcard); $modifications = array_filter($modifications, function ($modification) { return $modification['status'] !== 'D'; }); $lastModifications = ArrayUtils::unique($modifications, function ($modification) { return $modification['path']; }); foreach ($lastModifications as $modification) { $fileContent = $this->gitRepository->getFileInRevision($modification['path'], $modification['commit']); $plugin = basename(dirname(dirname($modification['path']))); $targetFile = $this->getDefinitionFileName($plugin); file_put_contents($targetFile, $fileContent); } $this->saveDefinitionForPlugin('versionpress/versionpress.php'); }
public function prepareStorage() { FileSystem::mkdir($this->directory); }
/** * Checks if there is any change in the `$mirror` and commits it. If there was a forced * change set, it takes precedence. */ public function commit() { if ($this->commitDisabled) { return; } if (count($this->forcedChangeInfos) > 0) { $changeInfoList = $this->forcedChangeInfos; } elseif ($this->shouldCommit()) { $changeInfoList = array_merge($this->postponedChangeInfos, $this->mirror->getChangeList()); if (empty($changeInfoList)) { return; } } else { return; } if ($this->commitPostponed) { $this->postponeChangeInfo($changeInfoList); $this->commitPostponed = false; $this->postponeKey = null; $this->flushChangeLists(); return; } if (is_user_logged_in() && is_admin()) { $currentUser = wp_get_current_user(); /** @noinspection PhpUndefinedFieldInspection */ $authorName = $currentUser->display_name; /** @noinspection PhpUndefinedFieldInspection */ $authorEmail = $currentUser->user_email; } else { if (defined('WP_CLI') && WP_CLI) { $authorName = GitConfig::$wpcliUserName; $authorEmail = GitConfig::$wpcliUserEmail; } else { $authorName = "Non-admin action"; $authorEmail = "*****@*****.**"; } } $changeInfoLists = $this->preprocessChangeInfoList($changeInfoList); $mutex = new Mutex(VERSIONPRESS_TEMP_DIR, 'committer-stage-commit'); $mutex->lock(); if (count($this->forcedChangeInfos) === 1) { // If there is one forced change info, we can commit all changes made by change info objects emitted from // storages. If there will be more forced change info objects in the future, we have to come up with // something smarter. For now, it solves WP-430. $this->stageRelatedFiles(new ChangeInfoEnvelope($this->mirror->getChangeList())); } foreach ($changeInfoLists as $listToCommit) { $changeInfoEnvelope = new ChangeInfoEnvelope($listToCommit); $this->stageRelatedFiles($changeInfoEnvelope); $this->repository->commit($changeInfoEnvelope->getCommitMessage(), $authorName, $authorEmail); } $mutex->release(); if (count($this->forcedChangeInfos) > 0 && $this->forcedChangeInfos[0]->getScope() === 'wordpress') { FileSystem::remove(ABSPATH . 'versionpress.maintenance'); } $this->flushChangeLists(); }
/** * Ensures that the clean installation of WordPress is available locally. If not, * downloads it from wp.org and stores it as `<clean-installations-dir>/<version>`. */ private function ensureCleanInstallationIsAvailable() { $cleanInstallationPath = $this->getCleanInstallationPath(); if (!$this->isCorrectlyDownloaded($cleanInstallationPath)) { FileSystem::remove($cleanInstallationPath); FileSystem::mkdir($cleanInstallationPath); $wpVersion = $this->siteConfig->wpVersion; $wpLocale = $this->siteConfig->wpLocale; $downloadCommand = "wp core download --path=\"{$cleanInstallationPath}\" --version=\"{$wpVersion}\""; if ($wpLocale) { $downloadCommand .= " --locale={$wpLocale}"; } $this->exec($downloadCommand, null); } }
/** * Clones site to a new folder and database. * * ## OPTIONS * * --name=<name> * : Name of the clone. Used as a directory name, part of the DB prefix * and an argument to the pull & push commands later. * * --siteurl=<url> * : URL of the clone. By default, the original URL is searched for <cwd> * and replaced with the clone name. * * --dbname=<dbname> * : Database name for the clone. * * --dbuser=<dbuser> * : Database user for the clone. * * --dbpass=<dbpass> * : Database user password for the clone. * * --dbhost=<dbhost> * : Database host for the clone. * * --dbprefix=<dbprefix> * : Database table prefix for the clone. * * --dbcharset=<dbcharset> * : Database charset for the clone. * * --dbcollate=<dbcollate> * : Database collation for the clone. * * --force * : Forces cloning even if the target directory or DB tables exists. * Basically provides --yes to all warnings / confirmations. * * --yes * : Another way to force the clone * * ## EXAMPLES * * The main site lives in a directory 'wpsite', uses the 'wp_' database table prefix and is * accessible via 'http://localhost/wpsite'. The command * * wp vp clone --name=myclone * * does the following: * * - Creates new directory 'myclone' next to the current one * - Clones the files there * - Creates new database tables prefixed with 'wp_myclone_' * - Populates database tables with data * - Makes the site accessible as 'http://localhost/myclone' * * @synopsis --name=<name> [--siteurl=<url>] [--dbname=<dbname>] [--dbuser=<dbuser>] [--dbpass=<dbpass>] [--dbhost=<dbhost>] [--dbprefix=<dbprefix>] [--dbcharset=<dbcharset>] [--dbcollate=<dbcollate>] [--force] [--yes] * * @subcommand clone */ public function clone_($args = array(), $assoc_args = array()) { global $table_prefix; if (isset($assoc_args['force'])) { $assoc_args['yes'] = 1; } $name = $assoc_args['name']; if (preg_match('/[^a-zA-Z0-9-_]/', $name)) { WP_CLI::error("Clone name '{$name}' is not valid. It can only contain letters, numbers, hyphens and underscores."); } $currentWpPath = get_home_path(); $cloneDirName = $name; $clonePath = dirname($currentWpPath) . '/' . $cloneDirName; $cloneDbUser = isset($assoc_args['dbuser']) ? $assoc_args['dbuser'] : DB_USER; $cloneDbPassword = isset($assoc_args['dbpass']) ? $assoc_args['dbpass'] : DB_PASSWORD; $cloneDbName = isset($assoc_args['dbname']) ? $assoc_args['dbname'] : DB_NAME; $cloneDbHost = isset($assoc_args['dbhost']) ? $assoc_args['dbhost'] : DB_HOST; $cloneDbPrefix = isset($assoc_args['dbprefix']) ? $assoc_args['dbprefix'] : $table_prefix . $name . '_'; $cloneDbCharset = isset($assoc_args['dbcharset']) ? $assoc_args['dbcharset'] : DB_CHARSET; $cloneDbCollate = isset($assoc_args['dbcollate']) ? $assoc_args['dbcollate'] : DB_COLLATE; // Checking the DB prefix, regex from wp-admin/setup-config.php if (isset($assoc_args['dbprefix']) && preg_match('|[^a-z0-9_]|i', $cloneDbPrefix)) { WP_CLI::error("Table prefix '{$cloneDbPrefix}' is not valid. It can only contain letters, numbers and underscores. Please choose different one."); } $prefixChanged = false; if (Strings::contains($cloneDbPrefix, '-')) { $cloneDbPrefix = str_replace('-', '_', $cloneDbPrefix); $prefixChanged = true; } $currentUrl = get_site_url(); $suggestedUrl = $this->suggestCloneUrl($currentUrl, basename($currentWpPath), $cloneDirName); if (!$suggestedUrl && !isset($assoc_args['siteurl'])) { WP_CLI::error("The command cannot derive default clone URL. Please specify the --siteurl parameter."); } $cloneUrl = isset($assoc_args['siteurl']) ? $assoc_args['siteurl'] : $suggestedUrl; $urlChanged = !isset($assoc_args['siteurl']) && !Strings::contains($cloneUrl, $cloneDirName); if (is_dir($clonePath)) { WP_CLI::confirm("Directory '" . basename($clonePath) . "' already exists, it will be deleted before cloning. Proceed?", $assoc_args); } if ($this->someWpTablesExist($cloneDbUser, $cloneDbPassword, $cloneDbName, $cloneDbHost, $cloneDbPrefix)) { WP_CLI::confirm("Database tables for the clone already exist, they will be dropped and re-created. Proceed?", $assoc_args); } if (is_dir($clonePath)) { try { FileSystem::removeContent($clonePath); } catch (IOException $e) { WP_CLI::error("Could not delete directory '" . basename($clonePath) . "'. Please do it manually."); } } // Clone the site $cloneCommand = sprintf("git clone %s %s", escapeshellarg($currentWpPath), escapeshellarg($clonePath)); $process = VPCommandUtils::exec($cloneCommand, $currentWpPath); if (!$process->isSuccessful()) { WP_CLI::error($process->getConsoleOutput(), false); WP_CLI::error("Cloning Git repo failed"); } else { WP_CLI::success("Site files cloned"); } // Adding the clone as a remote for the convenience of the `vp pull` command - its `--from` // parameter can then be just the name of the clone, not a path to it $addRemoteCommand = sprintf("git remote add %s %s", escapeshellarg($name), escapeshellarg($clonePath)); $process = VPCommandUtils::exec($addRemoteCommand, $currentWpPath); if (!$process->isSuccessful()) { $overwriteRemote = VPCommandUtils::cliQuestion("The Git repo of this site already defines remote '{$name}', overwrite it?", array("y", "n"), $assoc_args); if ($overwriteRemote == "y") { $addRemoteCommand = str_replace(" add ", " set-url ", $addRemoteCommand); $process = VPCommandUtils::exec($addRemoteCommand, $currentWpPath); if (!$process->isSuccessful()) { WP_CLI::error("Could not update remote's URL"); } else { WP_CLI::success("Updated remote configuration"); } } } else { WP_CLI::success("Clone added as a remote"); } // Enable pushing to origin $configCommand = "git config receive.denyCurrentBranch ignore"; $process = VPCommandUtils::exec($configCommand); if ($process->isSuccessful()) { WP_CLI::success("Enabled pushing to the original repository"); } else { WP_CLI::error("Cannot enable pushing to the original repository"); } // Enable pushing to clone $configCommand = "git config receive.denyCurrentBranch ignore"; $process = VPCommandUtils::exec($configCommand, $clonePath); if ($process->isSuccessful()) { WP_CLI::success("Enabled pushing to the clone"); } else { WP_CLI::error("Cannot enable pushing to the clone"); } // Copy & Update wp-config $wpConfigFile = $clonePath . '/wp-config.php'; copy($currentWpPath . '/wp-config.php', $wpConfigFile); $this->updateConfig($wpConfigFile, $cloneDbUser, $cloneDbPassword, $cloneDbName, $cloneDbHost, $cloneDbPrefix, $cloneDbCharset, $cloneDbCollate); // Copy VersionPress FileSystem::copyDir(VERSIONPRESS_PLUGIN_DIR, $clonePath . '/wp-content/plugins/versionpress'); WP_CLI::success("Copied VersionPress"); // Finish the process by doing the standard restore-site $process = VPCommandUtils::runWpCliCommand('vp', 'restore-site', array('siteurl' => $cloneUrl, 'yes' => null, 'require' => __FILE__), $clonePath); WP_CLI::log(trim($process->getConsoleOutput())); if ($process->isSuccessful()) { WP_CLI::success("All done. Clone created here:"); WP_CLI::log(""); WP_CLI::log("Path: {$clonePath}"); WP_CLI::log("URL: {$cloneUrl}"); if ($urlChanged) { WP_CLI::log("Note: Underscores changed to hyphens for URL."); } if ($prefixChanged) { WP_CLI::log("Note: Hyphens changed to underscores for DB prefix."); } } }
/** * Most of the actual deactivation work is done here. Called either as a response * to the user confirming the deactivation on `?page=versionpress/admin/deactivate.php` * or is called directly from vp_deactivate() if the confirmation screen was not necessary. */ function vp_admin_post_confirm_deactivation() { define('VP_DEACTIVATING', true); if (WpdbReplacer::isReplaced()) { WpdbReplacer::restoreOriginal(); } if (file_exists(VERSIONPRESS_ACTIVATION_FILE)) { FileSystem::remove(VERSIONPRESS_ACTIVATION_FILE); } FileSystem::remove(VERSIONPRESS_MIRRORING_DIR); global $versionPressContainer; /** @var Committer $committer */ $committer = $versionPressContainer->resolve(VersionPressServices::COMMITTER); $committer->forceChangeInfo(new VersionPressChangeInfo("deactivate")); /** @var WpdbMirrorBridge $wpdbMirrorBridge */ $wpdbMirrorBridge = $versionPressContainer->resolve(VersionPressServices::WPDB_MIRROR_BRIDGE); $wpdbMirrorBridge->disable(); global $wpdb; $table_prefix = $wpdb->prefix; $queries[] = "DROP TABLE IF EXISTS `{$table_prefix}vp_id`"; $vpOptionsReflection = new ReflectionClass('VersionPress\\Initialization\\VersionPressOptions'); $usermetaToDelete = array_values($vpOptionsReflection->getConstants()); $queryRestriction = '"' . join('", "', $usermetaToDelete) . '"'; $queries[] = "DELETE FROM `{$table_prefix}usermeta` WHERE meta_key IN ({$queryRestriction})"; foreach ($queries as $query) { $wpdb->query($query); } delete_option('vp_rest_api_plugin_version'); deactivate_plugins("versionpress/versionpress.php", true); if (defined('WP_ADMIN')) { wp_redirect(admin_url("plugins.php")); } }
/** * Saves all eligible entities into the file system storage (the 'db' folder) */ private function saveDatabaseToStorages() { if (is_dir(VP_VPDB_DIR)) { FileSystem::remove(VP_VPDB_DIR); } FileSystem::mkdir(VP_VPDB_DIR); $entityNames = $this->synchronizerFactory->getSynchronizationSequence(); foreach ($entityNames as $entityName) { $this->createVpidsForEntitiesOfType($entityName); $this->saveEntitiesOfTypeToStorage($entityName); } $mnReferenceDetails = $this->dbSchema->getAllMnReferences(); foreach ($mnReferenceDetails as $referenceDetail) { $this->saveMnReferences($referenceDetail); } }
/** * Installs Gitignore to the repository root, or does nothing if the file already exists. */ private function installGitignore() { FileSystem::copy(__DIR__ . '/.gitignore.tpl', ABSPATH . '.gitignore', false); }
function vp_disable_maintenance() { FileSystem::remove(ABSPATH . '.maintenance'); }
public static function tearDownAfterClass() { FileSystem::remove(self::$repositoryDir); }
public static function destroyRepository() { FileSystem::remove(self::$repositoryDir); }
private static function getPermissionInfo() { $proc = proc_open('whoami', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes); $procOpenUser = trim(stream_get_contents($pipes[1])); $processInfo = ['exec-user' => exec('whoami'), 'proc_open-user' => $procOpenUser]; $writeTargets = ['ABSPATH' => ABSPATH, 'WP_CONTENT_DIR' => WP_CONTENT_DIR, 'sys_temp_dir' => sys_get_temp_dir()]; foreach ($writeTargets as $target => $directory) { $filePath = $directory . '/' . '.vp-try-write-php'; /** @noinspection PhpUsageOfSilenceOperatorInspection */ @file_put_contents($filePath, ""); $processInfo['php-can-write'][$target] = is_file($filePath); FileSystem::remove($filePath); $processInfo['php-can-delete'][$target] = !is_file($filePath); $filePath = $directory . '/' . '.vp-try-write-process'; $process = new Process(sprintf("echo test > %s", ProcessUtils::escapeshellarg($filePath))); $process->run(); $processInfo['process-can-write'][$target] = is_file($filePath); try { FileSystem::remove($filePath); $processInfo['php-can-delete-file-created-by-process'][$target] = !is_file($filePath); } catch (IOException $ex) { $processInfo['php-can-delete-file-created-by-process'][$target] = false; } } return $processInfo; }
<?php /** * Uninstallation script for VersionPress. Most things already happened in the * `vp_admin_post_confirm_deactivation` hook; here, we just move the .git repo. * * Testing tip: place exit() at the end of the script and then in the browser * just go back and try again. * * @see vp_admin_post_confirm_deactivation() */ use VersionPress\Utils\FileSystem; use VersionPress\Utils\SecurityUtils; use VersionPress\Utils\UninstallationUtil; defined('WP_UNINSTALL_PLUGIN') or die('Direct access not allowed'); require_once dirname(__FILE__) . '/bootstrap.php'; if (UninstallationUtil::uninstallationShouldRemoveGitRepo()) { $backupsDir = WP_CONTENT_DIR . '/vpbackups'; if (!file_exists($backupsDir)) { FileSystem::mkdir($backupsDir); file_put_contents($backupsDir . '/.gitignore', 'git-backup-*'); SecurityUtils::protectDirectory($backupsDir); } $backupPath = $backupsDir . '/git-backup-' . date("YmdHis"); FileSystem::rename(ABSPATH . '.git', $backupPath, true); $productionGitignore = ABSPATH . '.gitignore'; $templateGitignore = __DIR__ . '/src/Initialization/.gitignore.tpl'; if (FileSystem::filesHaveSameContents($productionGitignore, $templateGitignore)) { FileSystem::remove($productionGitignore); } }
private function tryWrite() { $filename = ".vp-try-write"; $testPaths = [ABSPATH, WP_CONTENT_DIR, sys_get_temp_dir()]; $writable = true; foreach ($testPaths as $directory) { $filePath = $directory . '/' . $filename; /** @noinspection PhpUsageOfSilenceOperatorInspection */ @file_put_contents($filePath, ""); $writable &= is_file($filePath); FileSystem::remove($filePath); // Trying to create file from process (issue #522) $process = new Process(sprintf("echo test > %s", ProcessUtils::escapeshellarg($filePath))); $process->run(); $writable &= is_file($filePath); try { FileSystem::remove($filePath); } catch (IOException $ex) { $writable = false; // the file could not be deleted - the permissions are wrong } } return $writable; }
/** * Most of the actual deactivation work is done here. Called either as a response * to the user confirming the deactivation on `?page=versionpress/admin/deactivate.php` * or is called directly from vp_deactivate() if the confirmation screen was not necessary. */ function vp_admin_post_confirm_deactivation() { //nonce verification is performed according to 'deactivate-plugin_versionpress/versionpress.php' // as a standard deactivation token for which nonce is generated if (!defined('WP_CLI')) { vp_verify_nonce('deactivate-plugin_versionpress/versionpress.php'); vp_check_permissions(); } define('VP_DEACTIVATING', true); if (WpdbReplacer::isReplaced()) { WpdbReplacer::restoreOriginal(); } if (file_exists(VERSIONPRESS_ACTIVATION_FILE)) { FileSystem::remove(VERSIONPRESS_ACTIVATION_FILE); } $filesChangedByDeactivation = [["type" => "path", "path" => VP_VPDB_DIR . "/*"], ["type" => "path", "path" => ABSPATH . WPINC . "/wp-db.php"], ["type" => "path", "path" => ABSPATH . WPINC . "/wp-db.php.original"], ["type" => "path", "path" => ABSPATH . "/.gitattributes"]]; vp_force_action('versionpress', 'deactivate', null, [], $filesChangedByDeactivation); MergeDriverInstaller::uninstallMergeDriver(VP_PROJECT_ROOT, VERSIONPRESS_PLUGIN_DIR, VP_VPDB_DIR); deactivate_plugins("versionpress/versionpress.php", true); if (defined('WP_ADMIN')) { wp_safe_redirect(admin_url("plugins.php")); exit; } }
protected function tearDown() { parent::tearDown(); FileSystem::remove(__DIR__ . '/entities'); }
public function deleteAll() { FileSystem::removeContent($this->directory); }
/** * Removes everything created by VP, leaves site fresh for new testing. * * ## DETAILS * * Basically does plugin deactivation, removing the Git repo and plugin activation. * Deactivation does things like removing `vpdb`, `db.php`, VersionPress db tables etc. * * @subcommand start-over */ public function startOver($args, $assoc_args) { vp_admin_post_confirm_deactivation(); FileSystem::remove(ABSPATH . '.git'); activate_plugin('versionpress/versionpress.php'); }