FileSystem::remove(VP_PROJECT_ROOT . '/.git'); } FileSystem::remove(VP_VPDB_DIR); unlink(VERSIONPRESS_PLUGIN_DIR . '/.abort-initialization'); } //---------------------------------------- // Hooks for VersionPress functionality //---------------------------------------- if (VersionPress::isActive()) { add_action('init', 'vp_register_hooks'); //---------------------------------- // Replacing wpdb //---------------------------------- register_shutdown_function(function () { if (!WpdbReplacer::isReplaced() && !defined('VP_DEACTIVATING') && VersionPress::isActive()) { WpdbReplacer::replaceMethods(); } }); //---------------------------------- // Flushing rewrite rules after clone / pull / push //---------------------------------- add_action('wp_loaded', function () { if (get_transient('vp_flush_rewrite_rules') && !defined('WP_CLI')) { require_once ABSPATH . 'wp-admin/includes/misc.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; flush_rewrite_rules(); delete_transient('vp_flush_rewrite_rules'); } }); } //----------------------------------
private function activateVersionPress() { WpdbReplacer::replaceMethods(); touch(VERSIONPRESS_ACTIVATION_FILE); $this->reportProgressChange(InitializerStates::VERSIONPRESS_ACTIVATED); }
function vp_register_hooks() { global $wpdb, $versionPressContainer; /** @var Committer $committer */ $committer = $versionPressContainer->resolve(VersionPressServices::COMMITTER); /** @var Mirror $mirror */ $mirror = $versionPressContainer->resolve(VersionPressServices::MIRROR); /** @var DbSchemaInfo $dbSchemaInfo */ $dbSchemaInfo = $versionPressContainer->resolve(VersionPressServices::DB_SCHEMA); /** @var VpidRepository $vpidRepository */ $vpidRepository = $versionPressContainer->resolve(VersionPressServices::VPID_REPOSITORY); /** @var WpdbMirrorBridge $wpdbMirrorBridge */ $wpdbMirrorBridge = $versionPressContainer->resolve(VersionPressServices::WPDB_MIRROR_BRIDGE); /** * Hook for saving taxonomies into files * WordPress creates plain INSERT query and executes it using wpdb::query method instead of wpdb::insert. * It's too difficult to parse every INSERT query, that's why the WordPress hook is used. */ add_action('save_post', createUpdatePostTermsHook($mirror, $vpidRepository)); add_filter('update_feedback', function () { touch(ABSPATH . 'versionpress.maintenance'); }); add_action('_core_updated_successfully', function () use($committer, $mirror) { require ABSPATH . 'wp-includes/version.php'; // load constants (like $wp_version) /** @var string $wp_version */ $changeInfo = new WordPressUpdateChangeInfo($wp_version); $committer->forceChangeInfo($changeInfo); $mirror->save('option', array('option_name' => 'db_version', 'option_value' => get_option('db_version'))); // We have to re-save the option because WP upgrader uses $wpdb->query() if (!WpdbReplacer::isReplaced()) { WpdbReplacer::replaceMethods(); } }); add_action('activated_plugin', function ($pluginName) use($committer) { $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'activate')); }); add_action('deactivated_plugin', function ($pluginName) use($committer) { $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'deactivate')); }); add_action('upgrader_process_complete', function ($upgrader, $hook_extra) use($committer) { if ($hook_extra['type'] === 'theme') { $themes = isset($hook_extra['bulk']) && $hook_extra['bulk'] === true ? $hook_extra['themes'] : array($upgrader->result['destination_name']); foreach ($themes as $theme) { $themeName = wp_get_theme($theme)->get('Name'); if ($themeName === $theme && isset($upgrader->skin->api, $upgrader->skin->api->name)) { $themeName = $upgrader->skin->api->name; } $action = $hook_extra['action']; // can be "install" or "update", see WP_Upgrader and search for `'hook_extra' =>` $committer->forceChangeInfo(new ThemeChangeInfo($theme, $action, $themeName)); } } if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'update')) { return; } // handled by different hook if (isset($hook_extra['bulk']) && $hook_extra['bulk'] === true) { $plugins = $hook_extra['plugins']; } else { $plugins = array($hook_extra['plugin']); } foreach ($plugins as $plugin) { $committer->forceChangeInfo(new PluginChangeInfo($plugin, 'update')); } }, 10, 2); add_action('added_option', function ($name) use($wpdb, $mirror) { $option = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}options WHERE option_name='{$name}'", ARRAY_A); $mirror->save("option", $option); }); add_filter('upgrader_pre_install', function ($_, $hook_extra) use($committer) { if (!(isset($hook_extra['type']) && $hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) { return; } $pluginsBeforeInstallation = get_plugins(); $postInstallHook = function ($_, $hook_extra) use($pluginsBeforeInstallation, $committer, &$postInstallHook) { if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) { return; } wp_cache_delete('plugins', 'plugins'); $pluginsAfterInstallation = get_plugins(); $installedPlugin = array_diff_key($pluginsAfterInstallation, $pluginsBeforeInstallation); reset($installedPlugin); $pluginName = key($installedPlugin); $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'install')); remove_filter('upgrader_post_install', $postInstallHook); }; add_filter('upgrader_post_install', $postInstallHook, 10, 2); }, 10, 2); add_filter('upgrader_pre_download', function ($reply, $_, $upgrader) use($committer) { if (!isset($upgrader->skin->language_update)) { return $reply; } $languages = get_available_languages(); $postInstallHook = function ($_, $hook_extra) use($committer, $languages, &$postInstallHook) { if (!isset($hook_extra['language_update_type'])) { return; } $translations = wp_get_available_translations(); $type = $hook_extra['language_update_type']; $languageCode = $hook_extra['language_update']->language; $languageName = isset($translations[$languageCode]) ? $translations[$languageCode]['native_name'] : 'English (United States)'; $name = $type === "core" ? null : $hook_extra['language_update']->slug; $action = in_array($languageCode, $languages) ? "update" : "install"; $committer->forceChangeInfo(new TranslationChangeInfo($action, $languageCode, $languageName, $type, $name)); remove_filter('upgrader_post_install', $postInstallHook); }; add_filter('upgrader_post_install', $postInstallHook, 10, 2); return false; }, 10, 3); add_action('switch_theme', function () use($committer) { if (defined('WP_CLI') && WP_CLI) { file_get_contents(admin_url()); // } else { $committer->disableCommit(); // the change will be committed on next load } }); add_action('after_switch_theme', function () use($committer) { $theme = wp_get_theme(); $stylesheet = $theme->get_stylesheet(); $themeName = $theme->get('Name'); $committer->forceChangeInfo(new ThemeChangeInfo($stylesheet, 'switch', $themeName)); }); add_action('customize_save_after', function ($customizeManager) use($committer) { /** @var WP_Customize_Manager $customizeManager */ $stylesheet = $customizeManager->theme()->get_stylesheet(); $committer->forceChangeInfo(new ThemeChangeInfo($stylesheet, 'customize')); register_shutdown_function(function () { wp_remote_get(admin_url("admin.php")); }); }); add_action('untrashed_post_comments', function ($postId) use($wpdb, $dbSchemaInfo, $wpdbMirrorBridge) { $commentsTable = $dbSchemaInfo->getPrefixedTableName("comment"); $commentStatusSql = "select comment_ID, comment_approved from {$commentsTable} where comment_post_ID = {$postId}"; $comments = $wpdb->get_results($commentStatusSql, ARRAY_A); foreach ($comments as $comment) { $wpdbMirrorBridge->update($commentsTable, array("comment_approved" => $comment["comment_approved"]), array("comment_ID" => $comment["comment_ID"])); } }); add_action('delete_post_meta', function ($metaIds) use($wpdbMirrorBridge, $dbSchemaInfo) { $idColumnName = $dbSchemaInfo->getEntityInfo("postmeta")->idColumnName; foreach ($metaIds as $metaId) { $wpdbMirrorBridge->delete($dbSchemaInfo->getPrefixedTableName("postmeta"), array($idColumnName => $metaId)); } }); add_action('delete_user_meta', function ($metaIds) use($wpdbMirrorBridge, $dbSchemaInfo) { $idColumnName = $dbSchemaInfo->getEntityInfo("usermeta")->idColumnName; foreach ($metaIds as $metaId) { $wpdbMirrorBridge->delete($dbSchemaInfo->getPrefixedTableName("usermeta"), array($idColumnName => $metaId)); } }); add_action('wp_ajax_save-widget', function () use($committer) { if (defined('DOING_AJAX') && DOING_AJAX && isset($_POST['delete_widget']) && $_POST['delete_widget']) { $committer->postponeCommit('widgets'); } }, 0); // zero because the default WP action with priority 1 calls wp_die() function _vp_get_language_name_by_code($code) { $translations = wp_get_available_translations(); return isset($translations[$code]) ? $translations[$code]['native_name'] : 'English (United States)'; } add_action('add_option_WPLANG', function ($option, $value) use($committer) { $defaultLanguage = defined('WPLANG') ? WPLANG : ''; if ($value === $defaultLanguage) { return; // It's just submitted settings form without changing language } $languageName = _vp_get_language_name_by_code($value); $committer->forceChangeInfo(new TranslationChangeInfo("activate", $value, $languageName)); }, 10, 2); add_action('update_option_WPLANG', function ($oldValue, $newValue) use($committer) { $languageName = _vp_get_language_name_by_code($newValue); $committer->forceChangeInfo(new TranslationChangeInfo("activate", $newValue, $languageName)); }, 10, 2); add_action('wp_update_nav_menu_item', function ($menu_id, $menu_item_db_id) use($committer) { $key = 'menu-item-' . $menu_item_db_id; if (defined('DOING_AJAX') && DOING_AJAX && isset($_POST['action']) && $_POST['action'] === 'add-menu-item') { $committer->postponeCommit($key); $committer->commit(); } elseif (isset($_POST['action']) && $_POST['action'] === 'update') { $committer->usePostponedChangeInfos($key); } if (!defined('DOING_AJAX')) { global $versionPressContainer; /** @var Mirror $mirror */ $mirror = $versionPressContainer->resolve(VersionPressServices::MIRROR); $vpidRepository = $versionPressContainer->resolve(VersionPressServices::VPID_REPOSITORY); $func = createUpdatePostTermsHook($mirror, $vpidRepository); $func($menu_item_db_id); } }, 10, 2); add_action('pre_delete_term', function ($termId, $taxonomy) use($committer, $vpidRepository) { $termVpid = $vpidRepository->getVpidForEntity('term', $termId); $term = get_term($termId, $taxonomy); $committer->forceChangeInfo(new \VersionPress\ChangeInfos\TermChangeInfo('delete', $termVpid, $term->name, $taxonomy)); }, 10, 2); add_action('set_object_terms', createUpdatePostTermsHook($mirror, $vpidRepository)); add_filter('plugin_install_action_links', function ($links, $plugin) { $compatibility = CompatibilityChecker::testCompatibilityBySlug($plugin['slug']); if ($compatibility === CompatibilityResult::COMPATIBLE) { $cssClass = 'vp-compatible'; $compatibilityAdjective = 'Compatible'; } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) { $cssClass = 'vp-incompatible'; $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin is not compatible with VersionPress. These plugins will not work correctly when used together.">Incompatible</a>'; } else { $cssClass = 'vp-untested'; $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin was not yet tested with VersionPress. Some functionality may not work as intended.">Untested</a>'; } $compatibilityNotice = '<span class="vp-compatibility %s" data-plugin-name="%s"><strong>%s</strong> with VersionPress</span>'; $links[] = sprintf($compatibilityNotice, $cssClass, $plugin['name'], $compatibilityAdjective); return $links; }, 10, 2); add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file, $plugin_data, $status) { if ($status === "dropins") { return $plugin_meta; } $compatibility = CompatibilityChecker::testCompatibilityByPluginFile($plugin_file); if ($compatibility === CompatibilityResult::COMPATIBLE) { $cssClass = 'vp-compatible'; $compatibilityAdjective = 'Compatible'; } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) { $cssClass = 'vp-incompatible'; $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin is not compatible with VersionPress. These plugins will not work correctly when used together.">Incompatible</a>'; } elseif ($compatibility === CompatibilityResult::UNTESTED) { $cssClass = 'vp-untested'; $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin was not yet tested with VersionPress. Some functionality may not work as intended.">Untested</a>'; } else { return $plugin_meta; } $compatibilityNotice = '<span class="vp-compatibility %s" data-plugin-name="%s"><strong>%s</strong> with VersionPress</span>'; $plugin_meta[] = sprintf($compatibilityNotice, $cssClass, $plugin_data['Name'], $compatibilityAdjective); return $plugin_meta; }, 10, 4); add_filter('plugin_action_links', function ($actions, $plugin_file) { $compatibility = CompatibilityChecker::testCompatibilityByPluginFile($plugin_file); if (isset($actions['activate'])) { if ($compatibility === CompatibilityResult::UNTESTED) { $actions['activate'] = "<span class=\"vp-plugin-list vp-untested\">{$actions['activate']}</span>"; } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) { $actions['activate'] = "<span class=\"vp-plugin-list vp-incompatible\">{$actions['activate']}</span>"; } } return $actions; }, 10, 2); add_action('vp_revert', function () { // We have to flush the rewrite rules in the next request, because // in the current one the changed rewrite rules are not yet effective. set_transient('vp_flush_rewrite_rules', 1); vp_flush_regenerable_options(); }); add_action('pre_delete_term', function ($term, $taxonomy) use($wpdb, $wpdbMirrorBridge) { if (!is_taxonomy_hierarchical($taxonomy)) { return; } $term = get_term($term, $taxonomy); if (is_wp_error($term)) { return; } $wpdbMirrorBridge->update($wpdb->term_taxonomy, array('parent' => $term->parent), array('parent' => $term->term_id)); }, 10, 2); add_action('before_delete_post', function ($postId) use($wpdb) { // Fixing bug in WP (#34803) and WP-CLI (#2246) $post = get_post($postId); if (!is_wp_error($post) && $post->post_type === 'nav_menu_item') { \Tracy\Debugger::log('Deleting menu item ' . $post->ID); $newParent = get_post_meta($post->ID, '_menu_item_menu_item_parent', true); $wpdb->update($wpdb->postmeta, array('meta_value' => $newParent), array('meta_key' => '_menu_item_menu_item_parent', 'meta_value' => $post->ID)); } }); //---------------------------------------- // URL and WP-CLI "hooks" //---------------------------------------- $requestDetector = new \VersionPress\Utils\RequestDetector(); if (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) && $_REQUEST['action'] === 'widgets-order') { $committer->usePostponedChangeInfos('widgets'); } if ($requestDetector->isThemeDeleteRequest()) { $themeIds = $requestDetector->getThemeStylesheets(); foreach ($themeIds as $themeId) { $committer->forceChangeInfo(new ThemeChangeInfo($themeId, 'delete')); } } if ($requestDetector->isPluginDeleteRequest()) { $plugins = $requestDetector->getPluginNames(); foreach ($plugins as $plugin) { $committer->forceChangeInfo(new PluginChangeInfo($plugin, 'delete')); } } if ($requestDetector->isCoreLanguageUninstallRequest()) { $languageCode = $requestDetector->getLanguageCode(); $translations = wp_get_available_translations(); $languageName = isset($translations[$languageCode]) ? $translations[$languageCode]['native_name'] : 'English (United States)'; $committer->forceChangeInfo(new TranslationChangeInfo('uninstall', $languageCode, $languageName, 'core')); } if (basename($_SERVER['PHP_SELF']) === 'theme-editor.php' && isset($_GET['updated']) && $_GET['updated'] === 'true') { $committer->forceChangeInfo(new ThemeChangeInfo($_GET['theme'], 'edit')); } if (basename($_SERVER['PHP_SELF']) === 'plugin-editor.php' && (isset($_POST['action']) && $_POST['action'] === 'update' || isset($_GET['liveupdate']))) { $committer->disableCommit(); } if (basename($_SERVER['PHP_SELF']) === 'plugin-editor.php' && isset($_GET['a']) && $_GET['a'] === 'te') { if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $editedFile = $_GET['file']; $editedFilePathParts = preg_split("~[/\\\\]~", $editedFile); $plugins = array_keys(get_plugins()); $bestRank = 0; $bestMatch = ""; foreach ($plugins as $plugin) { $rank = 0; $pluginPathParts = preg_split("~[/\\\\]~", $plugin); $maxEqualParts = min(count($editedFilePathParts), count($pluginPathParts)); for ($part = 0; $part < $maxEqualParts; $part++) { if ($editedFilePathParts[$part] !== $pluginPathParts[$part]) { break; } $rank += 1; } if ($rank > $bestRank) { $bestRank = $rank; $bestMatch = $plugin; } } $committer->forceChangeInfo(new PluginChangeInfo($bestMatch, 'edit')); } register_shutdown_function(array($committer, 'commit')); }
/** * Restores a WP site from Git repo / working directory. * * ## OPTIONS * * --siteurl=<url> * : The address of the restored site. * * [--yes] * : Answer yes to the confirmation message. * * ## DESCRIPTION * * The command will then do the following: * * * Drops all tables tracked by VersionPress. * * Recreates and fill them with data from repository. * * If you just cloned the site from another repository, run `wp core config` first. * * * @subcommand restore-site * * @when before_wp_load */ public function restoreSite($args, $assoc_args) { if (file_exists(getcwd() . '/composer.json')) { $proc = proc_open("composer install", [1 => ["pipe", "w"], ["pipe", "w"]], $_); $result = proc_close($proc); if ($result !== 0) { WP_CLI::error('Composer dependencies could not be restored.'); } } defined('SHORTINIT') or define('SHORTINIT', true); require_once __DIR__ . '/../Initialization/WpConfigSplitter.php'; $wpConfigPath = \WP_CLI\Utils\locate_wp_config(); $this->requireWpConfig($wpConfigPath, WpConfigSplitter::COMMON_CONFIG_NAME); require_once __DIR__ . '/../../bootstrap.php'; if (!VersionPress::isActive()) { WP_CLI::error('Unfortunately, this site was not tracked by VersionPress. Therefore, it cannot be restored.'); } // Check if the site is installed $process = VPCommandUtils::runWpCliCommand('core', 'is-installed'); if ($process->isSuccessful()) { $this->checkVpRequirements($assoc_args, RequirementsChecker::ENVIRONMENT); WP_CLI::confirm("It looks like the site is OK. Do you really want to run the 'restore-site' command?", $assoc_args); } $url = $assoc_args['siteurl']; // Update URLs in wp-config.php define('VP_INDEX_DIR', dirname(\WP_CLI\Utils\locate_wp_config())); // just for the following method $this->setConfigUrl('WP_CONTENT_URL', 'WP_CONTENT_DIR', ABSPATH . 'wp-content', $url); $this->setConfigUrl('WP_PLUGIN_URL', 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins', $url); $this->setConfigUrl('WP_HOME', 'VP_INDEX_DIR', VP_PROJECT_ROOT, $url); defined('WP_PLUGIN_DIR') || define('WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins'); WpConfigSplitter::ensureCommonConfigInclude($wpConfigPath); // Disable VersionPress tracking for a while WpdbReplacer::restoreOriginal(); unlink(VERSIONPRESS_ACTIVATION_FILE); // Create or empty database $this->prepareDatabase($assoc_args); // Create WP tables. // The only important thing is site URL, all else will be rewritten later during synchronization. $installArgs = ['url' => $url, 'title' => 'x', 'admin_user' => 'x', 'admin_password' => 'x', 'admin_email' => '*****@*****.**']; if (version_compare(WP_CLI_VERSION, '0.22.0', '>=')) { $installArgs['skip-email'] = null; } $process = VPCommandUtils::runWpCliCommand('core', 'install', $installArgs); if (!$process->isSuccessful()) { WP_CLI::log("Failed creating database tables"); WP_CLI::error($process->getConsoleOutput()); } else { WP_CLI::success("Database tables created"); } // Restores "wp-db.php", "wp-db.php.original" and ".active" - enables VP $resetCmd = 'git reset --hard'; $process = VPCommandUtils::exec($resetCmd); if (!$process->isSuccessful()) { WP_CLI::log("Could not clean working directory"); WP_CLI::error($process->getConsoleOutput()); } // Fail-safe for gitignored WordPress if (!WpdbReplacer::isReplaced()) { WpdbReplacer::replaceMethods(); } /* We need correct value in the `active_plugins` option before the synchronization run. * Without this option VersionPress doesn't know which schema.yml files it should load and consequently which * DB entities it should synchronize. */ $activePluginsOption = IniSerializer::deserialize(file_get_contents(VP_VPDB_DIR . '/options/ac/active_plugins.ini')); $activePlugins = json_encode(unserialize($activePluginsOption['active_plugins']['option_value'])); VPCommandUtils::runWpCliCommand('option', 'update', ['active_plugins', $activePlugins, 'autoload' => 'yes', 'format' => 'json', 'skip-plugins' => null]); // The next couple of the steps need to be done after WP is fully loaded; we use `finish-restore-site` for that // The main reason for this is that we need properly set WP_CONTENT_DIR constant for reading from storages $process = $this->runVPInternalCommand('finish-restore-site'); WP_CLI::log($process->getConsoleOutput()); if (!$process->isSuccessful()) { WP_CLI::error("Could not finish site restore"); } }