function vp_register_hooks()
{
    global $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);
    /** @var \VersionPress\Database\Database $database */
    $database = $versionPressContainer->resolve(VersionPressServices::DATABASE);
    /** @var ActionsInfoProvider $actionsInfoProvider */
    $actionsInfoProvider = $versionPressContainer->resolve(VersionPressServices::ACTIONSINFO_PROVIDER_ACTIVE_PLUGINS);
    if (!function_exists('get_plugins')) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }
    $plugins = wp_get_active_and_valid_plugins();
    foreach ($plugins as $pluginFile) {
        $pluginDir = dirname($pluginFile);
        $hooksFile = $pluginDir . '/.versionpress/hooks.php';
        if (file_exists($hooksFile)) {
            require_once $hooksFile;
        }
    }
    add_filter('update_feedback', function () {
        touch(ABSPATH . 'versionpress.maintenance');
    });
    WordPressMissingFunctions::pipeAction('_core_updated_successfully', 'vp_wordpress_updated');
    add_action('activated_plugin', function ($pluginFile) {
        $plugins = get_plugins();
        $pluginName = $plugins[$pluginFile]['Name'];
        do_action('vp_plugin_changed', 'activate', $pluginFile, $pluginName);
    });
    add_action('deactivated_plugin', function ($pluginFile) {
        $plugins = get_plugins();
        $pluginName = $plugins[$pluginFile]['Name'];
        do_action('vp_plugin_changed', 'deactivate', $pluginFile, $pluginName);
    });
    add_action('upgrader_process_complete', function ($upgrader, $hook_extra) {
        if ($hook_extra['type'] === 'theme') {
            $themes = isset($hook_extra['bulk']) && $hook_extra['bulk'] === true ? $hook_extra['themes'] : [$upgrader->result['destination_name']];
            foreach ($themes as $stylesheet) {
                $themeName = wp_get_theme($stylesheet)->get('Name');
                if ($themeName === $stylesheet && isset($upgrader->skin->api, $upgrader->skin->api->name)) {
                    $themeName = $upgrader->skin->api->name;
                }
                // action can be "install" or "update", see WP_Upgrader and search for `'hook_extra' =>`
                $action = $hook_extra['action'];
                do_action('vp_theme_changed', $action, $stylesheet, $themeName);
            }
        }
        if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'update')) {
            return;
            // handled by different hook
        }
        if (isset($hook_extra['bulk']) && $hook_extra['bulk'] === true) {
            $pluginFiles = $hook_extra['plugins'];
        } else {
            $pluginFiles = [$hook_extra['plugin']];
        }
        $plugins = get_plugins();
        foreach ($pluginFiles as $pluginFile) {
            $pluginName = $plugins[$pluginFile]['Name'];
            do_action('vp_plugin_changed', 'update', $pluginFile, $pluginName);
        }
    }, 10, 2);
    add_filter('upgrader_pre_install', function ($_, $hook_extra) {
        if (!(isset($hook_extra['type']) && $hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) {
            return;
        }
        $pluginsBeforeInstallation = get_plugins();
        $postInstallHook = function ($_, $hook_extra) use($pluginsBeforeInstallation, &$postInstallHook) {
            if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) {
                return;
            }
            wp_cache_delete('plugins', 'plugins');
            $pluginsAfterInstallation = get_plugins();
            $installedPlugins = array_diff_key($pluginsAfterInstallation, $pluginsBeforeInstallation);
            foreach ($installedPlugins as $pluginFile => $plugin) {
                do_action('vp_plugin_changed', 'install', $pluginFile, $plugin['Name']);
            }
            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;
            }
            $type = $hook_extra['language_update_type'];
            $languageCode = $hook_extra['language_update']->language;
            $name = $type === "core" ? null : $hook_extra['language_update']->slug;
            $action = in_array($languageCode, $languages) ? "update" : "install";
            do_action('vp_translation_changed', $action, $languageCode, $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) {
            wp_remote_get(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');
        do_action('vp_theme_changed', 'switch', $stylesheet, $themeName);
    });
    function _vp_get_language_name_by_code($code)
    {
        require_once ABSPATH . 'wp-admin/includes/translation-install.php';
        $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
        }
        do_action('vp_translation_changed', 'activate', $value);
    }, 10, 2);
    add_action('update_option_WPLANG', function ($oldValue, $newValue) use($committer) {
        do_action('vp_translation_changed', 'activate', $newValue);
    }, 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);
        }
    }, 10, 2);
    add_action('pre_delete_term', function ($termId, $taxonomy) use($committer, $vpidRepository, $dbSchemaInfo, $actionsInfoProvider) {
        $termVpid = $vpidRepository->getVpidForEntity('term', $termId);
        $term = get_term($termId, $taxonomy);
        $termEntityInfo = $dbSchemaInfo->getEntityInfo('term');
        $actionsInfo = $actionsInfoProvider->getActionsInfo('term');
        $changeInfo = new EntityChangeInfo($termEntityInfo, $actionsInfo, 'delete', $termVpid, ['VP-Term-Name' => $term->name, 'VP-Term-Taxonomy' => $taxonomy]);
        $committer->forceChangeInfo($changeInfo);
    }, 10, 2);
    add_filter('wp_save_image_editor_file', function ($saved, $filename, $image, $mime_type, $post_id) use($vpidRepository, $committer, $dbSchemaInfo, $actionsInfoProvider) {
        $vpid = $vpidRepository->getVpidForEntity('post', $post_id);
        $post = get_post($post_id);
        $actionsInfo = $actionsInfoProvider->getActionsInfo('post');
        $changeInfo = new EntityChangeInfo($dbSchemaInfo->getEntityInfo('post'), $actionsInfo, 'edit', $vpid, ['VP-Post-Type' => $post->post_type, 'VP-Post-Title' => $post->post_title]);
        $committer->forceChangeInfo($changeInfo);
    }, 10, 5);
    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';
            // @codingStandardsIgnoreLine
            $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';
            // @codingStandardsIgnoreLine
            $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>';
        }
        // @codingStandardsIgnoreLine
        $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';
            // @codingStandardsIgnoreLine
            $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';
            // @codingStandardsIgnoreLine
            $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;
        }
        // @codingStandardsIgnoreLine
        $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 ($modifiedFiles) {
        // 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();
        // Update composer dependencies
        if (array_search('composer.lock', $modifiedFiles) || array_search('composer.json', $modifiedFiles)) {
            putenv('COMPOSER_HOME=' . VP_PROJECT_ROOT . '/vendor/bin/composer');
            $originalCwd = getcwd();
            chdir(VP_PROJECT_ROOT);
            $input = new \Symfony\Component\Console\Input\ArrayInput(['command' => 'install']);
            $output = new \Symfony\Component\Console\Output\NullOutput();
            $application = new \Composer\Console\Application();
            $application->setAutoExit(false);
            // prevent `$application->run` method from exitting the script
            $application->run($input, $output);
            $application->getComposer();
            chdir($originalCwd);
        }
    });
    add_action('pre_delete_term', function ($term, $taxonomy) use($database, $wpdbMirrorBridge) {
        if (!is_taxonomy_hierarchical($taxonomy)) {
            return;
        }
        $term = get_term($term, $taxonomy);
        if (is_wp_error($term)) {
            return;
        }
        $wpdbMirrorBridge->update($database->term_taxonomy, ['parent' => $term->parent], ['parent' => $term->term_id]);
    }, 10, 2);
    add_action('before_delete_post', function ($postId) use($database, $wpdbMirrorBridge) {
        // Fixing bug in WP (#34803) and WP-CLI (#2246);
        $post = get_post($postId);
        if (!is_wp_error($post) && $post->post_type === 'nav_menu_item') {
            $newParent = get_post_meta($post->ID, '_menu_item_menu_item_parent', true);
            $wpdbMirrorBridge->update($database->postmeta, ['meta_value' => $newParent], ['meta_key' => '_menu_item_menu_item_parent', 'meta_value' => $post->ID]);
            $database->update($database->postmeta, ['meta_value' => $newParent], ['meta_key' => '_menu_item_menu_item_parent', 'meta_value' => $post->ID]);
        }
    });
    //----------------------------------------
    // URL and WP-CLI "hooks"
    //----------------------------------------
    $requestDetector = new \VersionPress\Utils\RequestDetector();
    if ($requestDetector->isThemeDeleteRequest()) {
        $themeIds = $requestDetector->getThemeStylesheets();
        foreach ($themeIds as $stylesheet) {
            $themeName = wp_get_theme($stylesheet)->get('Name');
            do_action('vp_theme_changed', 'delete', $stylesheet, $themeName);
        }
    }
    if ($requestDetector->isPluginDeleteRequest()) {
        $pluginNames = $requestDetector->getPluginNames();
        $plugins = get_plugins();
        foreach ($pluginNames as $plugin) {
            do_action('vp_plugin_changed', 'delete', $plugin, $plugins[$plugin]['Name']);
        }
    }
    if ($requestDetector->isCoreLanguageUninstallRequest()) {
        $languageCode = $requestDetector->getLanguageCode();
        do_action('vp_translation_changed', 'uninstall', $languageCode);
    }
    if (basename($_SERVER['PHP_SELF']) === 'theme-editor.php' && isset($_GET['updated']) && $_GET['updated'] === 'true') {
        $stylesheet = $_GET['theme'];
        $themeName = wp_get_theme($stylesheet)->get('Name');
        do_action('vp_theme_changed', 'edit', $stylesheet, $themeName);
    }
    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 = get_plugins();
        $pluginNames = array_keys($plugins);
        $bestRank = 0;
        $bestMatch = "";
        foreach ($pluginNames 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;
            }
        }
        do_action('vp_plugin_changed', 'edit', $bestMatch, $plugins[$bestMatch]['Name']);
    }
    add_filter('cron_schedules', function ($schedules) use($dbSchemaInfo) {
        $intervals = $dbSchemaInfo->getIntervalsForFrequentlyWrittenEntities();
        foreach ($intervals as $interval) {
            if (isset($schedules[$interval])) {
                continue;
            }
            $seconds = strtotime($interval, 0);
            $schedules[$interval] = ['interval' => $seconds, 'display' => $interval];
        }
        return $schedules;
    });
    $r = $dbSchemaInfo->getRulesForFrequentlyWrittenEntities();
    $groupedByInterval = [];
    foreach ($r as $entityName => $rules) {
        foreach ($rules as $rule) {
            $groupedByInterval[$rule['interval']][$entityName][] = $rule;
        }
    }
    foreach ($groupedByInterval as $interval => $allRulesInInterval) {
        $actionName = "vp_commit_frequently_written_entities_{$interval}";
        if (!wp_next_scheduled($actionName)) {
            wp_schedule_event(time(), $interval, $actionName);
        }
        add_action($actionName, function () use($allRulesInInterval) {
            vp_save_frequently_written_entities($allRulesInInterval);
        });
    }
    if (!function_exists('get_plugins')) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }
    register_shutdown_function([$committer, 'commit']);
}
Example #2
0
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'));
}