├── src ├── Rollbacks │ ├── resources │ │ ├── index.js │ │ ├── styles │ │ │ ├── main.scss │ │ │ ├── _premium-upsell.scss │ │ │ └── _free-complete-template.scss │ │ ├── layout │ │ │ ├── Layout.jsx │ │ │ └── Header.jsx │ │ ├── utils │ │ │ └── version-formatter.js │ │ ├── App.jsx │ │ ├── pages │ │ │ ├── ThemeList.jsx │ │ │ ├── PluginList.jsx │ │ │ ├── RollbackContent.jsx │ │ │ ├── Dashboard.jsx │ │ │ └── Rollbacks.jsx │ │ ├── Routes.jsx │ │ ├── modals │ │ │ ├── registerTemplates.js │ │ │ └── Templates │ │ │ │ └── FreeCompleteTemplate.jsx │ │ └── components │ │ │ ├── PremiumRollbackInlineUpsell.jsx │ │ │ └── LogoFree.jsx │ ├── ThemeRollback │ │ ├── Views │ │ │ ├── resources │ │ │ │ └── themesAdminPage.js │ │ │ ├── components │ │ │ │ └── ThemeRollbackButton.jsx │ │ │ └── ThemeRollbackButton.php │ │ └── Actions │ │ │ └── AddMultisiteThemeRollbackLinks.php │ ├── PluginRollback │ │ └── Actions │ │ │ ├── PreCurrentActivePlugins.php │ │ │ └── AddPluginRollbackLinks.php │ ├── MultisiteSupport.php │ ├── Actions │ │ └── RegisterAdminMenu.php │ ├── PluginRollback.php │ ├── RollbackSteps │ │ └── UpsellValidatePackage.php │ └── ServiceProvider.php ├── Core │ ├── Constants.php │ ├── Request.php │ └── ServiceProvider.php └── PluginSetup │ ├── PluginScripts.php │ ├── Language.php │ ├── PluginMeta.php │ └── PluginSetup.php ├── .github ├── workflows │ ├── generate-zip.yml │ └── release.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── package.json ├── wp-rollback.php ├── README.md └── readme.txt /src/Rollbacks/resources/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WP Rollback Free Plugin 3 | * Resources Export File 4 | */ 5 | 6 | // Main exports 7 | export { default as Routes } from './Routes'; 8 | -------------------------------------------------------------------------------- /src/Rollbacks/ThemeRollback/Views/resources/themesAdminPage.js: -------------------------------------------------------------------------------- 1 | import { BaseThemeRollbackHandler } from '@wp-rollback/shared-core/handlers/BaseThemeRollbackHandler'; 2 | 3 | /** 4 | * Theme Specific WP Rollback 5 | * 6 | * Adds a rollback option to themes using the base handler 7 | */ 8 | new BaseThemeRollbackHandler().initialize(); 9 | -------------------------------------------------------------------------------- /.github/workflows/generate-zip.yml: -------------------------------------------------------------------------------- 1 | name: Generate Plugin Zip 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | ref: 7 | description: 'Git Commit Ref (branch, tag, or hash)' 8 | required: true 9 | type: string 10 | 11 | 12 | jobs: 13 | build: 14 | uses: impress-org/givewp-github-actions/.github/workflows/generate-zip.yml@master 15 | with: 16 | ref: ${{ github.event.inputs.ref }} 17 | plugin_slug: wp-rollback 18 | install_composer_packages: false 19 | -------------------------------------------------------------------------------- /src/Rollbacks/PluginRollback/Actions/PreCurrentActivePlugins.php: -------------------------------------------------------------------------------- 1 | { 12 | return ( 13 | <> 14 |
15 |
{ children }
16 | 17 | ); 18 | }; 19 | 20 | export default Layout; 21 | -------------------------------------------------------------------------------- /src/Rollbacks/MultisiteSupport.php: -------------------------------------------------------------------------------- 1 | make(PluginRollback::class); 28 | $updatePlugins(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .DS_Store 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | .idea 12 | 13 | # Compiled binary addons (https://nodejs.org/api/addons.html) 14 | /build 15 | 16 | # Dependency directories 17 | node_modules 18 | 19 | # Optional npm cache directory 20 | .npm 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Output of 'npm pack' 26 | *.tgz 27 | 28 | # dotenv environment variables files and directories 29 | .env 30 | .prettierignore 31 | .prettierrc 32 | .gitignore 33 | /.idea/ 34 | .vscode 35 | .DS_Store 36 | 37 | # No composer stuff 38 | /vendor/ 39 | 40 | # No language files 41 | /languages/* 42 | 43 | # Catch alls 44 | wp-rollback.zip 45 | -------------------------------------------------------------------------------- /src/Core/Constants.php: -------------------------------------------------------------------------------- 1 | { 14 | // Use shared core utility 15 | return formatVersion( version ); 16 | }; 17 | 18 | /** 19 | * Format a version with plugin name 20 | * 21 | * @param {string} version The version to format 22 | * @param {string} name Plugin name 23 | * @return {string} The formatted version with name 24 | */ 25 | export const displayVersionWithName = ( version, name ) => { 26 | return `${ name } ${ formatVersion( version ) }`; 27 | }; 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## How Has This Been Tested? 5 | 6 | 7 | 8 | 9 | ## Screenshots (jpeg or gifs if applicable): 10 | 11 | ## Types of changes 12 | 13 | 14 | 15 | 16 | 17 | ## Checklist: 18 | - [ ] My code is tested. 19 | - [ ] My code follows the WordPress code style. 20 | - [ ] My code follows has proper inline documentation. -------------------------------------------------------------------------------- /src/Rollbacks/Actions/RegisterAdminMenu.php: -------------------------------------------------------------------------------- 1 | 11 | As a ________, I want ________ so that ________. 12 | 13 | 14 | 15 | ## Visuals 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | ## Related 22 | 23 | 24 | ## Acceptance Criteria 25 | 26 | - [ ] e.g. Something happens when an action is taken. 27 | - [ ] e.g. Something does not happen when an action is taken. 28 | - [ ] e.g. Implementing feature in Component A does not affect existing behavior in Component B. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Issue Overview 2 | 3 | 4 | ## Expected Behavior 5 | 6 | 7 | 8 | ## Current Behavior 9 | 10 | 11 | 12 | ## Possible Solution 13 | 14 | 15 | 16 | ## Steps to Reproduce (for bugs) 17 | 18 | 19 | 1. 20 | 2. 21 | 3. 22 | 4. 23 | 24 | ## Related Issues and/or PRs 25 | 26 | 27 | ## Todos 28 | - [ ] Tests 29 | - [ ] Documentation -------------------------------------------------------------------------------- /src/Rollbacks/resources/App.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * WP Rollback Free Plugin 3 | * Frontend Application Entry Point 4 | */ 5 | 6 | import { HashRouter } from 'react-router-dom'; 7 | import { createRoot } from '@wordpress/element'; 8 | import domReady from '@wordpress/dom-ready'; 9 | import { addFilter } from '@wordpress/hooks'; 10 | import Routes from './Routes'; 11 | import registerTemplates from './modals/registerTemplates'; 12 | 13 | import '@wp-rollback/shared-core/styles/main.scss'; 14 | import './styles/main.scss'; 15 | 16 | // Register templates before initializing the app 17 | addFilter( 'wpRollback.templates', 'wpRollback-free/registerTemplates', registerTemplates, 10 ); 18 | 19 | /** 20 | * Initialize the React app on DOM ready 21 | */ 22 | domReady( function () { 23 | const container = document.getElementById( 'root-wp-rollback-admin' ); 24 | if ( ! container ) { 25 | return; 26 | } 27 | 28 | createRoot( container ).render( 29 | 30 | 31 | 32 | ); 33 | } ); 34 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/pages/ThemeList.jsx: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import Layout from '../layout/Layout'; 4 | import ThemesDataView from '@wp-rollback/shared-core/components/ThemesDataView'; 5 | /** 6 | * ThemeList component displays a list of themes that can be rolled back 7 | * 8 | * @return {JSX.Element} The theme list component 9 | */ 10 | export const ThemeList = () => { 11 | const navigate = useNavigate(); 12 | 13 | const handleNavigateToRollback = ( type, slug ) => { 14 | navigate( `/rollback/${ type }/${ slug }` ); 15 | }; 16 | 17 | return ( 18 | 19 |
20 |

{ __( 'Themes', 'wp-rollback' ) }

21 |

{ __( 'Select a theme below to rollback to a previous version.', 'wp-rollback' ) }

22 |
23 | 24 |
25 | 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/pages/PluginList.jsx: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import Layout from '../layout/Layout'; 4 | import PluginsDataView from '@wp-rollback/shared-core/components/PluginsDataView'; 5 | 6 | /** 7 | * PluginList component displays a list of plugins that can be rolled back 8 | * 9 | * @return {JSX.Element} The plugin list component 10 | */ 11 | export const PluginList = () => { 12 | const navigate = useNavigate(); 13 | 14 | const handleNavigateToRollback = ( type, slug ) => { 15 | navigate( `/rollback/${ type }/${ slug }` ); 16 | }; 17 | 18 | return ( 19 | 20 |
21 |

{ __( 'Plugins', 'wp-rollback' ) }

22 |

{ __( 'Select a plugin below to rollback to a previous version.', 'wp-rollback' ) }

23 |
24 | 25 |
26 | 27 |
28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/Routes.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies. 3 | */ 4 | import { Route, Routes as ReactRoutes, Navigate } from 'react-router-dom'; 5 | 6 | /** 7 | * Internal dependencies. 8 | */ 9 | import { Dashboard } from './pages/Dashboard'; 10 | import { PluginList } from './pages/PluginList'; 11 | import { Rollbacks as RollbackPage } from './pages/Rollbacks'; 12 | import { ThemeList } from './pages/ThemeList'; 13 | 14 | /** 15 | * Routes Component - Main router for WP Rollback Free Plugin 16 | * 17 | * @return {JSX.Element} The routes component 18 | */ 19 | const Routes = () => { 20 | return ( 21 | 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | 27 | { /* When no routes match, redirect to dashboard */ } 28 | } /> 29 | 30 | ); 31 | }; 32 | 33 | export default Routes; 34 | -------------------------------------------------------------------------------- /src/Rollbacks/PluginRollback/Actions/AddPluginRollbackLinks.php: -------------------------------------------------------------------------------- 1 | getSlug(), false); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Rollbacks/ThemeRollback/Actions/AddMultisiteThemeRollbackLinks.php: -------------------------------------------------------------------------------- 1 | getSlug(), false); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Rollbacks/ThemeRollback/Views/components/ThemeRollbackButton.jsx: -------------------------------------------------------------------------------- 1 | import { useUIText } from '@wp-rollback/shared-core/context/UITextContext'; 2 | 3 | /** 4 | * Theme rollback button component 5 | * 6 | * @param {Object} props Component properties 7 | * @param {string} props.theme Theme slug 8 | * @param {boolean} props.hasRollback Whether the theme has rollback available 9 | * @return {JSX.Element} The theme rollback button component 10 | */ 11 | const ThemeRollbackButton = ( { theme, hasRollback } ) => { 12 | const { rollbackLabel, notRollbackable } = useUIText(); 13 | 14 | if ( ! hasRollback ) { 15 | return ( 16 | 27 | { notRollbackable } 28 | 29 | ); 30 | } 31 | 32 | return ( 33 | 34 | { rollbackLabel } 35 | 36 | ); 37 | }; 38 | 39 | export default ThemeRollbackButton; 40 | -------------------------------------------------------------------------------- /src/Rollbacks/ThemeRollback/Views/ThemeRollbackButton.php: -------------------------------------------------------------------------------- 1 | isNetworkActivated()) { 44 | return; 45 | } 46 | 47 | $assetsManager = SharedCore::container()->make(AssetsManager::class); 48 | $assetsManager->enqueueScript('themesAdmin', [], false); 49 | } 50 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-rollback", 3 | "version": "3.0.0", 4 | "author": "WP Rollback", 5 | "description": "Rollback (or forward) any WordPress.org plugin, theme, or block like a boss.", 6 | "homepage": "https://wprollback.com", 7 | "license": "GPL-2.0-or-later", 8 | "scripts": { 9 | "build": "wp-scripts build", 10 | "dev-build": "wp-scripts start", 11 | "generate:pot": "php -d xdebug.mode=off -d memory_limit=-1 \"$(which wp)\" i18n make-pot . languages/wp-rollback.pot --include=\"src,wp-rollback.php,build/*.js\" --exclude=\"**/resources\"", 12 | "plugin-zip": "wp-scripts plugin-zip", 13 | "rm-modules": "rm -rf node_modules" 14 | }, 15 | "dependencies": { 16 | "@wordpress/components": "^29.7.0", 17 | "@wordpress/dataviews": "^4.11.1", 18 | "@wordpress/element": "^6.21.0", 19 | "@wordpress/hooks": "^4.17.0", 20 | "@wordpress/html-entities": "^4.16.0", 21 | "@wordpress/i18n": "^5.14.0", 22 | "@wordpress/icons": "^10.14.0", 23 | "@wordpress/plugins": "^7.15.1", 24 | "@wordpress/url": "^3.49.0", 25 | "node-svn-ultimate": "^1.2.1", 26 | "react-router-dom": "^7.1.1", 27 | "@wp-rollback/shared-core": "workspace:*" 28 | }, 29 | "files": [ 30 | "build", 31 | "languages", 32 | "vendor", 33 | "wp-rollback.php", 34 | "readme.txt", 35 | "README.md", 36 | "src" 37 | ], 38 | "devDependencies": { 39 | "@wordpress/browserslist-config": "^6.34.0", 40 | "@wordpress/scripts": "^30.27.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Core/Request.php: -------------------------------------------------------------------------------- 1 | constants = $constants; 38 | } 39 | 40 | /** 41 | * This function is used to check if the request has a valid nonce. 42 | * Overrides the parent method to use the Free plugin's Constants. 43 | * 44 | * @param string $action The action to check the nonce for 45 | * @param string $nonceName Optional custom nonce name 46 | * 47 | * @since 3.0.0 48 | */ 49 | public function hasValidNonce(string $action, string $nonceName = ''): bool 50 | { 51 | if (empty($nonceName)) { 52 | $nonceName = $this->constants->getNonce(); 53 | } 54 | 55 | $requestedData = $this->all(); 56 | return array_key_exists($nonceName, $requestedData) 57 | && wp_verify_nonce($requestedData[$nonceName], $action); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/layout/Header.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { ExternalLink, Button, Icon } from '@wordpress/components'; 3 | import { __ } from '@wordpress/i18n'; 4 | import { starFilled } from '@wordpress/icons'; 5 | import LogoFree from '../components/LogoFree'; 6 | 7 | const Header = () => { 8 | return ( 9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | { __( 'Documentation', 'wp-rollback' ) } 19 | 20 | { __( 'Support', 'wp-rollback' ) } 21 | 30 |
31 |
32 | ); 33 | }; 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /wp-rollback.php: -------------------------------------------------------------------------------- 1 | . 27 | */ 28 | 29 | declare(strict_types=1); 30 | 31 | // Exit if accessed directly. 32 | if (!defined('ABSPATH')) { 33 | exit; 34 | } 35 | 36 | // Load Composer autoloaders 37 | require_once __DIR__ . '/vendor/autoload.php'; 38 | require_once __DIR__ . '/vendor/vendor-prefixed/autoload.php'; 39 | 40 | // Initialize SharedCore - This is lightweight and just marks it as initialized 41 | WpRollback\SharedCore\Core\SharedCore::initialize(); 42 | 43 | // Initialize the plugin 44 | add_action('plugins_loaded', function () { 45 | $pluginSetup = new WpRollback\Free\PluginSetup\PluginSetup(); 46 | $pluginSetup->boot(); 47 | }, 5); -------------------------------------------------------------------------------- /src/Rollbacks/resources/modals/registerTemplates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Register Free Templates 3 | * 4 | * @package 5 | * @since 3.0.0 6 | */ 7 | import { __ } from '@wordpress/i18n'; 8 | import { Dashicon } from '@wordpress/components'; 9 | import FreeCompleteTemplate from './Templates/FreeCompleteTemplate'; 10 | 11 | /** 12 | * Enhances the templates for Free features 13 | * 14 | * @param {Object} templates The template configuration object 15 | * @return {Object} Modified template configuration 16 | */ 17 | const registerFreeTemplates = templates => { 18 | // Add the complete template for free plugin 19 | templates.complete = { 20 | title: __( 'Rollback Complete', 'wp-rollback' ), 21 | icon: , 22 | component: FreeCompleteTemplate, 23 | buttons: { 24 | confirm: { 25 | title: __( 'Return to Screen', 'wp-rollback' ), 26 | onClick: type => { 27 | // Handle both defined and undefined cases 28 | const buttonUrl = 29 | typeof type === 'string' && type === 'theme' 30 | ? `${ window.location.origin }/wp-admin/themes.php` 31 | : `${ window.location.origin }/wp-admin/plugins.php`; 32 | 33 | window.location.href = buttonUrl; 34 | }, 35 | isProcessing: false, 36 | }, 37 | cancel: { 38 | title: __( 'Upgrade to Pro', 'wp-rollback' ), 39 | onClick: () => { 40 | window.location.href = 'https://wprollback.com/'; 41 | }, 42 | }, 43 | }, 44 | }; 45 | 46 | return templates; 47 | }; 48 | 49 | export default registerFreeTemplates; 50 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/pages/RollbackContent.jsx: -------------------------------------------------------------------------------- 1 | import Banner from '@wp-rollback/shared-core/components/Rollbacks/Banner'; 2 | import PluginInfo from '@wp-rollback/shared-core/components/Rollbacks/PluginInfo'; 3 | import MetaInfo from '@wp-rollback/shared-core/components/Rollbacks/MetaInfo'; 4 | import VersionsList from '@wp-rollback/shared-core/components/Rollbacks/VersionsList'; 5 | import RollbackThumbnail from '@wp-rollback/shared-core/components/Rollbacks/RollbackThumbnail'; 6 | import { useRollbackContext } from '@wp-rollback/shared-core/context/RollbackContext'; 7 | 8 | /** 9 | * RollbackContent Component - Free version 10 | * 11 | * Displays rollback information for WordPress.org plugins and themes. 12 | * 13 | * @return {JSX.Element} The rollback content component 14 | */ 15 | const RollbackContent = () => { 16 | const { 17 | type, 18 | rollbackInfo, 19 | currentVersion, 20 | rollbackVersion, 21 | setRollbackVersion, 22 | setIsModalOpen, 23 | setModalTemplate, 24 | } = useRollbackContext(); 25 | 26 | return ( 27 |
28 | 29 |
30 | 31 | 32 | 39 |
40 | 46 |
47 | ); 48 | }; 49 | 50 | export default RollbackContent; 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | # Bug Report 8 | 9 | ## User Story 10 | 11 | As a ________, I want ________ so that ________. 12 | 13 | 14 | 15 | ## Current Behavior 16 | 17 | 18 | ## Expected Behavior 19 | 20 | 21 | ## Bug Type 22 | 23 | - [ ] This bug describes functionality that once worked as expected in version X.X.X. 24 | - [ ] This bug describes functionality that never worked as expected. 25 | - [ ] I am not sure whether this functionality ever worked as expected. 26 | 27 | ## Steps to Reproduce 28 | 29 | 1. 30 | 2. 31 | 3. 32 | 33 | ## Visuals 34 | 35 | 36 | ## Possible Solution 37 | 38 | 39 | ## Related 40 | 41 | 42 | ## Acceptance Criteria 43 | 44 | - [ ] e.g. Something happens when an action is taken. 45 | - [ ] e.g. Something does not happen when an action is taken. 46 | - [ ] e.g. Fixing behavior in Component A does not affect existing behavior in Component B. 47 | 48 | ## Environment 49 | 50 |
51 | Operating System 52 |
    53 |
  • Platform: Mac OS X | Microsoft Windows | Linux | Android | iOS
  • 54 |
  • Version: X.X.X
  • 55 |
56 |
57 | 58 |
59 | Browser 60 |
    61 |
  • Name: Chrome | Firefox | Safari | IE | Edge
  • 62 |
  • Version: X.X.X
  • 63 |
64 |
65 | 66 |
67 | WordPress System Info 68 | 69 |
70 | -------------------------------------------------------------------------------- /src/Rollbacks/PluginRollback.php: -------------------------------------------------------------------------------- 1 | plugin_info = new PluginInfo($pluginSlug); 28 | } 29 | 30 | /** 31 | * Initialize the rollback process 32 | * 33 | * @param string $version Version to rollback to 34 | * @return bool|\WP_Error True on success, WP_Error on failure 35 | */ 36 | public function rollback(string $version) { 37 | if (!PluginUtility::currentUserCanRollback()) { 38 | return new \WP_Error( 39 | 'insufficient_permissions', 40 | __('You do not have permission to perform rollbacks.', 'wp-rollback') 41 | ); 42 | } 43 | 44 | if (!PluginUtility::isValidVersion($version)) { 45 | return new \WP_Error( 46 | 'invalid_version', 47 | __('Invalid version number provided.', 'wp-rollback') 48 | ); 49 | } 50 | 51 | $currentVersion = $this->plugin_info->getCurrentVersion(); 52 | if ($currentVersion === $version) { 53 | return new \WP_Error( 54 | 'same_version', 55 | __('Cannot rollback to the same version.', 'wp-rollback') 56 | ); 57 | } 58 | 59 | $availableVersions = $this->plugin_info->getAvailableVersions(); 60 | if (!in_array($version, $availableVersions, true)) { 61 | return new \WP_Error( 62 | 'version_not_found', 63 | __('The requested version is not available.', 'wp-rollback') 64 | ); 65 | } 66 | 67 | // Perform rollback logic here 68 | return true; 69 | } 70 | } -------------------------------------------------------------------------------- /src/PluginSetup/PluginScripts.php: -------------------------------------------------------------------------------- 1 | make(AssetsManager::class); 44 | 45 | // Determine the correct admin URL based on context 46 | $adminUrl = is_network_admin() 47 | ? network_admin_url('settings.php?page=wp-rollback') 48 | : admin_url('tools.php?page=wp-rollback'); 49 | 50 | $assetsManager->enqueueScript('tools', [ 51 | 'rollback_nonce' => wp_create_nonce('wpr_rollback_nonce'), 52 | 'restApiNonce' => wp_create_nonce('wp_rest'), 53 | 'adminUrl' => $adminUrl, 54 | 'restUrl' => esc_url_raw(rest_url()), 55 | 'rollbackSteps' => $this->getRollbackSteps(), 56 | ]); 57 | } 58 | 59 | /** 60 | * Get rollback steps data for script localization. 61 | * 62 | * @since 3.0.0 63 | * @return array 64 | */ 65 | protected function getRollbackSteps(): array 66 | { 67 | $stepRegisterer = SharedCore::container()->make(RollbackStepRegisterer::class); 68 | $steps = []; 69 | 70 | foreach ($stepRegisterer->getAllRollbackSteps() as $stepClass) { 71 | $steps[] = [ 72 | 'id' => $stepClass::id(), 73 | 'rollbackProcessingMessage' => $stepClass::rollbackProcessingMessage() 74 | ]; 75 | } 76 | 77 | return $steps; 78 | } 79 | } -------------------------------------------------------------------------------- /src/PluginSetup/Language.php: -------------------------------------------------------------------------------- 1 | make(Constants::class); 32 | $pluginRelativePath = self::getRelativePath($constants); 33 | 34 | $locale = is_admin() && function_exists('get_user_locale') ? get_user_locale() : get_locale(); 35 | // Traditional WordPress plugin locale filter. 36 | $locale = apply_filters('plugin_locale', $locale, $constants->getTextDomain()); 37 | 38 | // Setup paths to current locale file. 39 | $moFile = sprintf('%1$s-%2$s.mo', $constants->getTextDomain(), $locale); 40 | $moFileLocal = trailingslashit(WP_PLUGIN_DIR) . $pluginRelativePath . $moFile; 41 | $moFileGlobal = trailingslashit(WP_LANG_DIR) . 'plugins/' . $moFile; 42 | 43 | unload_textdomain($constants->getTextDomain()); 44 | if (file_exists($moFileGlobal)) { 45 | // Look in global /wp-content/languages/plugins folder. 46 | load_textdomain($constants->getTextDomain(), $moFileGlobal); 47 | } elseif (file_exists($moFileLocal)) { 48 | // Look in local /wp-content/plugins/wp-rollback/languages/ folder. 49 | load_textdomain($constants->getTextDomain(), $moFileLocal); 50 | } else { 51 | // Load the default language files. 52 | load_plugin_textdomain($constants->getTextDomain(), false, $pluginRelativePath); 53 | } 54 | } 55 | 56 | /** 57 | * Return the plugin language dir relative path, e.g. "wp-rollback/languages/" 58 | * 59 | * @since 3.0.0 60 | */ 61 | public static function getRelativePath(Constants $constants): string 62 | { 63 | $pluginRelativePath = dirname(plugin_basename($constants->getPluginFile())) . '/languages/'; 64 | $pluginRelativePath = ltrim(apply_filters('wprollback_languages_directory', $pluginRelativePath), '/\\'); 65 | 66 | return trailingslashit($pluginRelativePath); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Core/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | singleton(BaseConstants::class, function () use ($constants) { 44 | return $constants; 45 | }); 46 | 47 | SharedCore::container()->singleton(Constants::class, function () use ($constants) { 48 | return $constants; 49 | }); 50 | 51 | // Register plugin-specific Request implementation 52 | SharedCore::container()->singleton(Request::class, function () use ($constants) { 53 | return new Request($constants); 54 | }); 55 | 56 | // Register plugin-specific Cache implementation 57 | SharedCore::container()->singleton(Cache::class, function () use ($constants) { 58 | return new Cache($constants->getSlug()); 59 | }); 60 | 61 | // Register plugin-specific DebugMode if needed 62 | SharedCore::container()->singleton(DebugMode::class, function () { 63 | return DebugMode::makeWithWpDebugConstant(); 64 | }); 65 | 66 | // Register PluginScripts 67 | SharedCore::container()->singleton(PluginScripts::class); 68 | } 69 | 70 | /** 71 | * This function boots the service provider. 72 | * 73 | * @since 3.0.0 74 | */ 75 | public function boot(): void 76 | { 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/PluginSetup/PluginMeta.php: -------------------------------------------------------------------------------- 1 | make(Constants::class); 30 | 31 | if ($constants->getBasename() !== $pluginFile) { 32 | return $pluginMeta; 33 | } 34 | 35 | $newMetaLinks = [ 36 | sprintf( 37 | '%2$s', 38 | esc_url( 39 | add_query_arg( 40 | [ 41 | 'utm_source' => 'free-plugin', 42 | 'utm_medium' => 'plugin-row', 43 | 'utm_campaign' => 'documentation', 44 | ], 45 | 'https://docs.wprollback.com/' 46 | ) 47 | ), 48 | esc_html__('Documentation', 'wp-rollback') 49 | ), 50 | sprintf( 51 | '%2$s', 52 | esc_url( 53 | add_query_arg( 54 | [ 55 | 'utm_source' => 'free-plugin', 56 | 'utm_medium' => 'plugin-row', 57 | 'utm_campaign' => 'support', 58 | ], 59 | 'https://wprollback.com/support/' 60 | ) 61 | ), 62 | esc_html__('Support', 'wp-rollback') 63 | ), 64 | sprintf( 65 | '%2$s', 66 | esc_url( 67 | add_query_arg( 68 | [ 69 | 'utm_source' => 'free-plugin', 70 | 'utm_medium' => 'plugin-row', 71 | 'utm_campaign' => 'go-pro', 72 | ], 73 | 'https://wprollback.com/pricing/' 74 | ) 75 | ), 76 | esc_html__('Go Pro!', 'wp-rollback') 77 | ), 78 | ]; 79 | 80 | return array_merge($pluginMeta, $newMetaLinks); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | 3 | on: 4 | release: 5 | types: [ released ] 6 | 7 | jobs: 8 | release: 9 | name: New release 10 | runs-on: ubuntu-20.04 11 | environment: live 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: 7.4 20 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, mysql, mysqli, pdo_mysql, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 21 | coverage: none 22 | 23 | # - name: Install composer dependencies 24 | # uses: php-actions/composer@v5 25 | # with: 26 | # php_version: 7.4 27 | # dev: no 28 | 29 | - uses: actions/setup-node@v2 30 | with: 31 | node-version: '12' 32 | 33 | - name: Install npm dependencies & build for translation 34 | run: | 35 | npm install -g npm@7 36 | npm ci 37 | npm run build 38 | 39 | # In order to run this WordPress also needs to be installed 40 | - name: Generate pot file 41 | run: | 42 | curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 43 | chmod +x wp-cli.phar 44 | mv wp-cli.phar /usr/local/bin/wp 45 | php -d xdebug.mode=off "$(which wp)" i18n make-pot ${{github.workspace}} ${{github.workspace}}/languages/wp-rollback.pot --exclude="$(cat .distignore | tr "\n" "," | sed 's/,$/ /' | tr " " "\n"),src/**/*.js,*.js.map" 46 | 47 | - name: Build assets for production 48 | run: npm run build 49 | 50 | - name: WordPress Plugin Deploy 51 | id: deploy 52 | uses: 10up/action-wordpress-plugin-deploy@stable 53 | with: 54 | generate-zip: true 55 | env: 56 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 57 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 58 | SLUG: wp-rollback 59 | 60 | - name: Upload release asset 61 | uses: actions/upload-release-asset@v1 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | upload_url: ${{ github.event.release.upload_url }} 66 | asset_path: ${{github.workspace}}/wp-rollback.zip 67 | asset_name: wp-rollback.zip 68 | asset_content_type: application/zip 69 | -------------------------------------------------------------------------------- /src/Rollbacks/RollbackSteps/UpsellValidatePackage.php: -------------------------------------------------------------------------------- 1 | getType(); 47 | $assetSlug = $rollbackApiRequestDTO->getSlug(); 48 | 49 | // Get the downloaded package from transient to verify it exists 50 | $package = get_transient("wpr_{$assetType}_{$assetSlug}_package"); 51 | 52 | // Basic existence check (free version does minimal validation) 53 | if (empty($package) || !is_string($package) || !file_exists($package)) { 54 | return new RollbackStepResult( 55 | false, 56 | $rollbackApiRequestDTO, 57 | __('Package not found for rollback.', 'wp-rollback') 58 | ); 59 | } 60 | 61 | // For the free version, we skip comprehensive validation and show success 62 | // with an upsell message about the pro version's enhanced security features 63 | $upsellMessage = __( 64 | 'Basic validation complete. 🔒 WP Rollback Pro includes advanced package integrity scanning. Upgrade at wprollback.com/pricing', 65 | 'wp-rollback' 66 | ); 67 | 68 | return new RollbackStepResult( 69 | true, 70 | $rollbackApiRequestDTO, 71 | $upsellMessage, 72 | null, 73 | [ 74 | 'validation_status' => 'basic', 75 | 'upsell_shown' => true, 76 | 'pro_features' => [ 77 | 'advanced_security_scanning', 78 | 'malware_detection', 79 | 'comprehensive_integrity_checks', 80 | 'detailed_validation_reports' 81 | ] 82 | ] 83 | ); 84 | } 85 | 86 | /** 87 | * @inheritdoc 88 | * @since 3.0.0 89 | */ 90 | public static function rollbackProcessingMessage(): string 91 | { 92 | return esc_html__('Validating package integrity…', 'wp-rollback'); 93 | } 94 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![WP Rollback](https://wprollback.com/wp-content/uploads/2024/01/WP-Rollback-GitHub.jpg)](https://wprollback.com) 2 | 3 | # WP Rollback 4 | 5 | Effortlessly rollback (or downgrade, as some may call it) any theme or plugin from WordPress.org to a previous version with WP Rollback. It's as simple as using the plugin updater, but instead, you downgrade to a specific version. Forget the hassle of manual downloads and FTP uploads; this plugin streamlines the process for you. 6 | 7 | ## Important: Usage Disclaimer 8 | 9 | Before performing a rollback, we highly recommend conducting a test on a staging site and creating a full backup of your 10 | WordPress files and database. **Please note: We are not liable for any issues such as data loss, white screens, fatal 11 | errors, or other problems that may arise from using this plugin**. 12 | 13 | ## Download 14 | 15 | For stable releases, visit the [WordPress Repository](https://wordpress.org/plugins/wp-rollback). You can also find them 16 | in your WordPress Dashboard under "Plugins > Add New." For development versions, see the contribution section below on 17 | how to clone this repo and start using the latest updates. 18 | 19 | ## Support 20 | 21 | Have questions or need assistance? Post all your support requests on 22 | the [WordPress Repository support page for WP Rollback](https://wordpress.org/support/plugin/wp-rollback). If you'd like 23 | to report bugs, request features, or contribute, we welcome your input! 24 | 25 | ## Documentation 26 | 27 | Designed for seamless integration with the WordPress interface, WP Rollback is straightforward and setting-free. We're 28 | confident that its functionality will be apparent and intuitive right after activation. 29 | 30 | [Read the WP Rollback Documentation](https://github.com/impress-org/wp-rollback/wiki) 31 | 32 | ## Contributing 33 | 34 | We appreciate contributions from the community! To contribute: 35 | 36 | 1. **Fork the Repository**: Click the 'Fork' button at the top right of this page to create your own copy of this 37 | repository. 38 | 39 | 2. **Clone Your Fork**: Clone your fork to your local machine. This can be done via the command line with 40 | `git clone https://github.com/DevinWalker/wp-rollback.git`. Make sure you clone it to the `wp-content/plugins` 41 | directory of your WordPress installation. 42 | 43 | 3. **Install Dependencies**: Run `bun install` to install all dependencies. 44 | 45 | 4. **Available Scripts**: 46 | 47 | - `bun run build` - Create a production build 48 | - `bun run dev-build` - Start development mode with file watching 49 | - `bun run generate:pot` - Generate translation files 50 | - `bun run plugin-zip` - Create a deployable plugin zip file 51 | - `bun run rm-modules` - Remove node_modules directory 52 | 53 | 5. **Create a New Branch**: Before making your changes, switch to a new branch with `git checkout -b your-branch-name`. 54 | 55 | 6. **Make Your Changes**: Implement your changes, enhancements, or bug fixes. 56 | 57 | 7. **Development**: Run `bun run dev-build` to start the development process. This will watch for changes to the JS and SCSS files and compile them automatically. 58 | 59 | 8. **Testing**: Before submitting, build the plugin with `bun run build` to ensure everything compiles correctly. 60 | 61 | 9. **Commit and Push**: Commit your changes with a clear commit message and push them to your fork with 62 | `git push origin your-branch-name`. 63 | 64 | 10. **Submit a Pull Request (PR)**: Go to the original WP Rollback repository and click 'New pull request'. Choose your 65 | fork and branch, then submit the pull request. Provide a decent PR description explaining the changes you made and 66 | we'll review your PR and merge it if it contributes positively to the project! 67 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { Card, CardBody, Button, Icon } from '@wordpress/components'; 3 | import { __ } from '@wordpress/i18n'; 4 | import { plugins, brush } from '@wordpress/icons'; 5 | import Layout from '../layout/Layout'; 6 | 7 | /** 8 | * Dashboard component that serves as the main landing page for WP Rollback. 9 | * Provides options to rollback plugins or themes. 10 | * 11 | * @return {JSX.Element} The rendered Dashboard component 12 | */ 13 | export const Dashboard = () => { 14 | const navigate = useNavigate(); 15 | 16 | return ( 17 | 18 |
19 |

{ __( 'Rollback a Plugin or Theme', 'wp-rollback' ) }

20 |

21 | { __( 22 | 'With WP Rollback you can go back to a previous WordPress.org plugin or theme version with ease. Which action would you like to perform today?', 23 | 'wp-rollback' 24 | ) } 25 |

26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 |

{ __( 'Plugin Version Rollback', 'wp-rollback' ) }

34 |
35 |

36 | { __( 37 | "Revert any WordPress.org plugin to a previous version with just a few clicks. Choose the plugin and version you'd like to restore.", 38 | 'wp-rollback' 39 | ) } 40 |

41 | 50 |
51 |
52 | 53 | 54 |
55 | 56 |

{ __( 'Theme Version Rollback', 'wp-rollback' ) }

57 |
58 |

59 | { __( 60 | "Revert any WordPress.org plugin to a previous version with just a few clicks. Choose the plugin and version you'd like to restore.", 61 | 'wp-rollback' 62 | ) } 63 |

64 | 73 |
74 |
75 |
76 | 77 | 78 | 79 |

{ __( 'The Safest Way to Rollback Premium Plugins & Themes', 'wp-rollback' ) }

80 |

81 | { __( 82 | 'Get complete control over every plugin on your site with automated backups, rollback notes for your team, and support for premium plugins from any marketplace.', 83 | 'wp-rollback' 84 | ) } 85 |

86 | 89 |
90 |
91 |
92 | ); 93 | }; 94 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/pages/Rollbacks.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | import { useParams, useNavigate } from 'react-router-dom'; 6 | import { Notice } from '@wordpress/components'; 7 | import Loading from '@wp-rollback/shared-core/components/Loading'; 8 | import RollbackModal from '@wp-rollback/shared-core/components/modals/RollbackModal'; 9 | import RollbackHeader from '@wp-rollback/shared-core/components/Rollbacks/RollbackHeader'; 10 | import RollbackActions from '@wp-rollback/shared-core/components/Rollbacks/RollbackActions'; 11 | import { RollbackProvider, useRollbackContext } from '@wp-rollback/shared-core/context/RollbackContext'; 12 | import VersionsList from '@wp-rollback/shared-core/components/Rollbacks/VersionsList'; 13 | import Layout from '../layout/Layout'; 14 | import RollbackContent from './RollbackContent'; 15 | import PremiumRollbackInlineUpsell from '../components/PremiumRollbackInlineUpsell'; 16 | 17 | /** 18 | * Inner component that consumes the context 19 | * 20 | * @return {JSX.Element} The rollback page component content 21 | */ 22 | const RollbacksContent = () => { 23 | const { 24 | isLoading, 25 | error, 26 | rollbackInfo, 27 | isPremiumAsset, 28 | rollbackVersion, 29 | setRollbackVersion, 30 | currentVersion 31 | } = useRollbackContext(); 32 | 33 | if ( isLoading ) { 34 | return ( 35 | 36 | 37 | 38 | ); 39 | } 40 | 41 | // Handle error state 42 | if ( error || rollbackInfo.message ) { 43 | return ( 44 | 45 |
46 |

{ rollbackInfo.code || __( 'Error', 'wp-rollback' ) }

47 |

{ rollbackInfo.message || error }

48 |
49 |
50 | ); 51 | } 52 | 53 | // Show premium upsell for premium assets (in free plugin) 54 | if ( isPremiumAsset ) { 55 | return ( 56 | 57 | {/* Custom header for premium assets */} 58 |
59 |

{ __('Unlock Premium Rollbacks', 'wp-rollback') }

60 |

{ __('This premium asset requires WP Rollback Pro for safe version rollbacks.', 'wp-rollback') }

61 |
62 | 63 |
64 |
65 | 66 |

67 | {rollbackInfo?.name || slug} { __('is not available on WordPress.org and requires WP Rollback Pro for version control.', 'wp-rollback') } 68 |

69 |
70 | 71 | {/* Show available versions if they exist - moved higher */} 72 | { rollbackInfo?.versions && Object.keys( rollbackInfo.versions ).length > 0 && ( 73 |
74 |

{ __( 'Available Versions (Pro Feature)', 'wp-rollback' ) }

75 |

76 | { __( 'These versions would be available for rollback with WP Rollback Pro:', 'wp-rollback' ) } 77 |

78 | 85 |
86 | ) } 87 | 88 | 89 |
90 |
91 |
92 | ); 93 | } 94 | 95 | // Show normal rollback content for wp.org assets 96 | return ( 97 | 98 | 99 |
100 | 101 | 102 |
103 | 104 |
105 | ); 106 | }; 107 | 108 | /** 109 | * RollbackPage component handles the rollback process for plugins and themes 110 | * 111 | * @return {JSX.Element} The rollback page component 112 | */ 113 | export const Rollbacks = () => { 114 | const { type, slug } = useParams(); 115 | const navigate = useNavigate(); 116 | 117 | // Handle navigation back to home 118 | const handleCancel = () => { 119 | navigate( '/' ); 120 | }; 121 | 122 | return ( 123 | 124 | 125 | 126 | ); 127 | }; 128 | 129 | export default Rollbacks; 130 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/components/PremiumRollbackInlineUpsell.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | import { Button, Icon, Flex, FlexItem } from '@wordpress/components'; 6 | import { 7 | starFilled, 8 | shield, 9 | backup, 10 | info, 11 | list 12 | } from '@wordpress/icons'; 13 | import { useRollbackContext } from '@wp-rollback/shared-core/context/RollbackContext'; 14 | 15 | /** 16 | * PremiumRollbackInlineUpsell component provides an inline upsell experience within the rollback view 17 | * 18 | * @return {JSX.Element} The premium rollback inline upsell component 19 | */ 20 | const PremiumRollbackInlineUpsell = () => { 21 | const { handleCancel } = useRollbackContext(); 22 | 23 | const handleUpgrade = () => { 24 | window.open('https://wprollback.com/pricing/?utm_source=free-plugin&utm_medium=rollback-upsell&utm_campaign=premium-rollback', '_blank'); 25 | }; 26 | 27 | const features = [ 28 | { 29 | icon: backup, 30 | title: __('Premium Plugin & Theme Rollbacks', 'wp-rollback'), 31 | description: __('Roll back any premium plugin or theme from any marketplace - not just WordPress.org.', 'wp-rollback') 32 | }, 33 | { 34 | icon: shield, 35 | title: __('Version Preservation', 'wp-rollback'), 36 | description: __('For premium assets, creates a zip archive of the current version and stores the archive.', 'wp-rollback') 37 | }, 38 | { 39 | icon: info, 40 | title: __('Rollback Notes & Documentation', 'wp-rollback'), 41 | description: __('Add detailed notes to each rollback for better team coordination and change tracking.', 'wp-rollback') 42 | }, 43 | { 44 | icon: list, 45 | title: __('Advanced Activity Logging', 'wp-rollback'), 46 | description: __('Complete audit trail of all rollbacks with timestamps, user tracking, and detailed logs.', 'wp-rollback') 47 | } 48 | ]; 49 | 50 | return ( 51 | <> 52 |
53 |

{ __('Why Upgrade to WP Rollback Pro?', 'wp-rollback') }

54 |
55 | {features.map((feature, index) => ( 56 |
57 |
58 | 59 | 60 |
61 | 62 |
63 |
64 | 65 |

66 | {feature.title} 67 |

68 |

69 | {feature.description} 70 |

71 |
72 |
73 |
74 |
75 | ))} 76 |
77 |
78 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | 87 |

{ __('30-Day Money-Back Guarantee', 'wp-rollback') }

88 |

89 | { __('Try WP Rollback Pro risk-free. If you\'re not completely satisfied, get your money back within 30 days.', 'wp-rollback') } 90 |

91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 | 107 | 114 | 121 |
122 | 123 | ); 124 | }; 125 | 126 | export default PremiumRollbackInlineUpsell; -------------------------------------------------------------------------------- /src/PluginSetup/PluginSetup.php: -------------------------------------------------------------------------------- 1 | constants = SharedCore::container()->make(Constants::class); 70 | 71 | Hooks::addAction('plugins_loaded', self::class, 'init'); 72 | 73 | register_activation_hook($this->constants->getPluginFile(), [PluginManager::class, 'activate']); 74 | register_deactivation_hook($this->constants->getPluginFile(), [PluginManager::class, 'deactivate']); 75 | 76 | // Add plugin meta 77 | Hooks::addFilter( 'plugin_row_meta', PluginMeta::class, 'addPluginRowMeta', 10, 2 ); 78 | } 79 | 80 | /** 81 | * Initiate WpRollback when WordPress Initializes plugins. 82 | * 83 | * @since 3.0.0 84 | */ 85 | public function init(): void 86 | { 87 | /** 88 | * Fires before the WpRollback core is initialized. 89 | * 90 | * @since 3.0.0 91 | */ 92 | do_action('before_wpr_init'); 93 | 94 | // Ensure Constants is available 95 | if (null === $this->constants) { 96 | $this->constants = SharedCore::container()->make(Constants::class); 97 | } 98 | 99 | $this->setupLanguage(); 100 | $this->registerLibraries(); 101 | $this->loadServiceProviders(); 102 | 103 | // Initialize scripts after service providers are loaded 104 | $scripts = SharedCore::container()->make(PluginScripts::class); 105 | $scripts->initialize(); 106 | 107 | /** 108 | * Fire the action after WpRollback core loads. 109 | * 110 | * @since 3.0.0 111 | * 112 | * @param self $instance Plugin class instance. 113 | * 114 | */ 115 | do_action('wpr_init', $this); 116 | } 117 | 118 | /** 119 | * This function is used to set up language for application. 120 | * 121 | * @since 3.0.0 122 | */ 123 | protected function setupLanguage(): void 124 | { 125 | Language::load(); 126 | } 127 | 128 | /** 129 | * This function is used to load service providers. 130 | * 131 | * @since 3.0.0 132 | */ 133 | protected function loadServiceProviders(): void 134 | { 135 | if ($this->providersLoaded) { 136 | return; 137 | } 138 | 139 | $providers = []; 140 | 141 | foreach ($this->serviceProviders as $serviceProvider) { 142 | if (! is_subclass_of($serviceProvider, ServiceProvider::class)) { 143 | throw new InvalidArgumentException( 144 | // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped 145 | "$serviceProvider class must implement the ServiceProvider interface" 146 | ); 147 | } 148 | 149 | /** @var ServiceProvider $serviceProvider */ 150 | $serviceProvider = new $serviceProvider(); 151 | 152 | $serviceProvider->register(); 153 | 154 | $providers[] = $serviceProvider; 155 | } 156 | 157 | foreach ($providers as $serviceProvider) { 158 | $serviceProvider->boot(); 159 | } 160 | 161 | $this->providersLoaded = true; 162 | } 163 | 164 | /** 165 | * @since 3.0.0. 166 | */ 167 | protected function registerLibraries(): void 168 | { 169 | // Ensure Constants is available 170 | if (null === $this->constants) { 171 | $this->constants = SharedCore::container()->make(Constants::class); 172 | } 173 | 174 | AdminNotices::initialize( 175 | 'wp-rollback', 176 | $this->constants->getPluginUrl() . '/vendor/vendor-prefixed/stellarwp/admin-notices' 177 | ); 178 | } 179 | 180 | /** 181 | * Get the Constants instance 182 | * 183 | * @since 3.0.0 184 | * 185 | * @return Constants 186 | */ 187 | public function getConstants(): Constants 188 | { 189 | if (null === $this->constants) { 190 | $this->constants = SharedCore::container()->make(Constants::class); 191 | } 192 | 193 | return $this->constants; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Rollbacks/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | singleton(AddPluginRollbackLinks::class, function ($container) { 45 | return new AddPluginRollbackLinks($container->make(Constants::class)); 46 | }); 47 | 48 | // Register PluginScripts 49 | SharedCore::container()->singleton(PluginScripts::class); 50 | 51 | // Override the shared RollbackStepRegisterer to exclude ValidatePackage and add UpsellValidatePackage 52 | // This uses the base steps and modifies them for the free version 53 | SharedCore::container()->singleton(RollbackStepRegisterer::class, function () { 54 | $registerer = new RollbackStepRegisterer(); 55 | 56 | // Register all base steps from shared-core 57 | $registerer->register(RollbackStepRegisterer::getBaseSteps()); 58 | 59 | // Insert the upsell step after BackupAsset (replacing ValidatePackage position) 60 | $registerer->registerAfter( 61 | \WpRollback\Free\Rollbacks\RollbackSteps\UpsellValidatePackage::class, 62 | \WpRollback\SharedCore\Rollbacks\RollbackSteps\BackupAsset::class 63 | ); 64 | 65 | return $registerer; 66 | }); 67 | 68 | // Note: Other shared services (ToolsPage, BackupAsset, BackupService, etc.) 69 | // are provided by the SharedCore\Rollbacks\ServiceProvider loaded before this one 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | * @since 3.0.0 75 | * @throws BindingResolutionException 76 | */ 77 | public function boot(): void 78 | { 79 | $this->bootToolsPage(); 80 | $this->bootPluginRollback(); 81 | $this->bootThemeRollback(); 82 | $this->addMultiSiteSupport(); 83 | 84 | // Initialize PluginScripts 85 | $scripts = SharedCore::container()->make(PluginScripts::class); 86 | $scripts->initialize(); 87 | 88 | // Note: Backup directory setup is now handled by the shared RollbackServiceProvider 89 | } 90 | 91 | 92 | /** 93 | * @since 3.0.0 94 | * @throws BindingResolutionException 95 | */ 96 | private function bootToolsPage(): void 97 | { 98 | Hooks::addAction('admin_menu', RegisterAdminMenu::class); 99 | } 100 | 101 | /** 102 | * @since 3.0.0 103 | * @throws BindingResolutionException 104 | */ 105 | private function bootPluginRollback(): void 106 | { 107 | // Register factory for AddPluginRollbackLinks 108 | Hooks::registerFactory( 109 | AddPluginRollbackLinks::class, 110 | function () { 111 | return SharedCore::container()->make(AddPluginRollbackLinks::class); 112 | } 113 | ); 114 | 115 | Hooks::addFilter('plugin_action_links', AddPluginRollbackLinks::class, '__invoke', 20, 4); 116 | Hooks::addAction('pre_current_active_plugins', PreCurrentActivePlugins::class, '__invoke', 20, 1); 117 | } 118 | 119 | /** 120 | * @since 3.0.0 121 | * @return void 122 | * @throws BindingResolutionException 123 | */ 124 | private function bootThemeRollback(): void 125 | { 126 | // Theme rollback. 127 | Hooks::addAction('admin_enqueue_scripts', ThemeRollbackButton::class); 128 | } 129 | 130 | /** 131 | * @since 3.0.0 132 | * @throws BindingResolutionException 133 | */ 134 | private function addMultiSiteSupport(): void 135 | { 136 | // For multisite support 137 | Hooks::addAction('network_admin_menu', self::class, 'registerMultisiteMenu'); 138 | 139 | Hooks::addFilter('network_admin_plugin_action_links', AddPluginRollbackLinks::class, '__invoke', 20, 4); 140 | 141 | // Register factory for AddMultisiteThemeRollbackLinks 142 | // Note: This only applies in network admin - single sites use ThemeRollbackButton instead 143 | Hooks::registerFactory( 144 | AddMultisiteThemeRollbackLinks::class, 145 | function () { 146 | return SharedCore::container()->make(AddMultisiteThemeRollbackLinks::class); 147 | } 148 | ); 149 | 150 | // Add theme rollback links in network admin themes table 151 | Hooks::addFilter('theme_action_links', AddMultisiteThemeRollbackLinks::class, '__invoke', 20, 3); 152 | } 153 | 154 | /** 155 | * Register multisite menu. 156 | * 157 | * @since 3.0.0 158 | * @throws BindingResolutionException 159 | */ 160 | public static function registerMultisiteMenu(): void 161 | { 162 | $adminMenu = SharedCore::container()->make(RegisterAdminMenu::class); 163 | $adminMenu->registerMultisiteMenu(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/styles/_premium-upsell.scss: -------------------------------------------------------------------------------- 1 | // Premium Upsell Styles 2 | .wpr-premium-rollback-page { 3 | 4 | .wpr-rollback-component-wrap { 5 | border: 0; 6 | } 7 | 8 | // Custom subheader for premium assets 9 | .wpr-subheader { 10 | margin-bottom: 2rem; 11 | 12 | h1 { 13 | font-size: 1.5rem; 14 | font-weight: 600; 15 | margin-bottom: 0.5rem; 16 | color: #1d2327; 17 | } 18 | 19 | p { 20 | color: #646970; 21 | font-size: 1rem; 22 | margin: 0; 23 | line-height: 1.5; 24 | } 25 | } 26 | 27 | .wpr-premium-upsell { 28 | background: #fff; 29 | border: 1px solid #ddd; 30 | padding: 1.5rem; 31 | margin-bottom: 1.5rem; 32 | } 33 | 34 | .wpr-premium-notice { 35 | margin-bottom: 1.5rem; 36 | 37 | .components-notice__content { 38 | margin: 0; 39 | 40 | p { 41 | margin: 0; 42 | font-size: 16px; 43 | } 44 | } 45 | } 46 | 47 | // Available Versions Section (moved higher) 48 | .wpr-available-versions { 49 | background: #f6f7f7; 50 | border: 1px solid #ddd; 51 | padding: 1.25rem; 52 | margin-bottom: 1.5rem; 53 | margin-left: -1.5rem; 54 | margin-right: -1.5rem; 55 | border-right: 0; 56 | border-left: 0; 57 | 58 | h3 { 59 | font-size: 1.1rem; 60 | font-weight: 600; 61 | margin-bottom: 0.5rem; 62 | color: #1d2327; 63 | display: flex; 64 | align-items: center; 65 | gap: 0.5rem; 66 | 67 | &::before { 68 | content: '🔒'; 69 | font-size: 0.9rem; 70 | } 71 | } 72 | 73 | .wpr-versions-container { 74 | min-height: 0; 75 | } 76 | 77 | .wpr-versions-note { 78 | color: #646970; 79 | margin-bottom: 1rem; 80 | font-style: italic; 81 | font-size: 0.9rem; 82 | } 83 | 84 | // Disabled versions list styling 85 | .wpr-version-option { 86 | opacity: 0.6; 87 | pointer-events: none; 88 | position: relative; 89 | 90 | &::after { 91 | content: '🔒 Pro'; 92 | position: absolute; 93 | right: 1rem; 94 | top: 50%; 95 | transform: translateY(-50%); 96 | background: #646970; 97 | color: white; 98 | padding: 0.2rem 0.5rem; 99 | font-size: 0.7rem; 100 | font-weight: 500; 101 | } 102 | } 103 | } 104 | 105 | .wpr-premium-features { 106 | margin-bottom: 1.5rem; 107 | 108 | h3 { 109 | font-size: 1.2rem; 110 | font-weight: 600; 111 | margin-bottom: 1rem; 112 | color: #1d2327; 113 | text-align: center; 114 | } 115 | } 116 | 117 | .wpr-premium-features-grid { 118 | display: grid; 119 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 120 | gap: 0.75rem; 121 | margin-bottom: 1rem; 122 | } 123 | 124 | .wpr-premium-feature-card { 125 | border: 1px solid #ddd; 126 | transition: border-color 0.2s ease; 127 | background: #fff; 128 | 129 | &:hover { 130 | border-color: #135e96; 131 | } 132 | } 133 | 134 | .wpr-premium-feature-card-body { 135 | padding: 1.25rem; 136 | } 137 | 138 | .wpr-premium-feature-icon { 139 | width: 30px; 140 | height: 30px; 141 | background: #f0f6fc; 142 | border: 1px solid #e3f0f9; 143 | border-radius: 100%; 144 | display: flex; 145 | align-items: center; 146 | justify-content: center; 147 | flex-shrink: 0; 148 | 149 | .components-icon { 150 | color: #135e96; 151 | } 152 | } 153 | 154 | .wpr-premium-feature-title { 155 | font-size: 0.95rem; 156 | font-weight: 600; 157 | margin-bottom: 0.5rem; 158 | color: #1d2327; 159 | line-height: 1.3; 160 | } 161 | 162 | .wpr-premium-feature-description { 163 | color: #646970; 164 | line-height: 1.4; 165 | margin: 0; 166 | font-size: 0.85rem; 167 | } 168 | 169 | .wpr-premium-guarantee { 170 | margin-bottom: 1.5rem; 171 | 172 | .wpr-premium-guarantee-card { 173 | background: #f0f6fc; 174 | border: 1px solid #cfe5f6; 175 | color: #1d2327; 176 | } 177 | 178 | .wpr-premium-guarantee-card-body { 179 | padding: 1.25rem; 180 | } 181 | 182 | h4 { 183 | color: #135e96; 184 | font-weight: 600; 185 | margin-bottom: 0.5rem; 186 | font-size: 1rem; 187 | } 188 | 189 | p { 190 | color: #646970; 191 | margin: 0; 192 | line-height: 1.4; 193 | font-size: 0.9rem; 194 | } 195 | 196 | .components-icon { 197 | color: #135e96; 198 | } 199 | } 200 | 201 | .wpr-premium-actions { 202 | text-align: center; 203 | display: flex; 204 | gap: 0.75rem; 205 | justify-content: center; 206 | align-items: center; 207 | flex-wrap: wrap; 208 | } 209 | 210 | // Responsive Design 211 | @media (max-width: 768px) { 212 | .wpr-subheader { 213 | margin-bottom: 1.5rem; 214 | 215 | h1 { 216 | font-size: 1.3rem; 217 | } 218 | 219 | p { 220 | font-size: 0.95rem; 221 | } 222 | } 223 | 224 | .wpr-premium-upsell { 225 | padding: 1rem; 226 | } 227 | 228 | .wpr-premium-features-grid { 229 | grid-template-columns: 1fr; 230 | } 231 | 232 | .wpr-premium-actions { 233 | flex-direction: column; 234 | } 235 | 236 | .wpr-available-versions { 237 | padding: 1rem; 238 | } 239 | } 240 | } -------------------------------------------------------------------------------- /src/Rollbacks/resources/modals/Templates/FreeCompleteTemplate.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Free Rollback Complete Modal. 3 | * Uses RollbackContext for state management. 4 | * 5 | * @param {Object} props Component properties 6 | * @param {Object} props.buttons Button configuration for the template 7 | * @return {JSX.Element} Complete template content 8 | */ 9 | 10 | import { ExternalLink, Icon, Button, Notice } from '@wordpress/components'; 11 | import { __, sprintf } from '@wordpress/i18n'; 12 | import { decodeEntities } from '@wordpress/html-entities'; 13 | import { useRollbackContext } from '@wp-rollback/shared-core/context/RollbackContext'; 14 | import { useNavigate } from 'react-router-dom'; 15 | import { useEffect } from '@wordpress/element'; 16 | import { check, starFilled, shield, backup, list, help } from '@wordpress/icons'; 17 | import RollbackButtons from '@wp-rollback/shared-core/components/modals/RollbackButtons'; 18 | 19 | const FreeCompleteTemplate = ( { buttons } ) => { 20 | const { rollbackInfo, rollbackVersion, setCurrentVersion } = useRollbackContext(); 21 | const navigate = useNavigate(); 22 | 23 | // Update the current version to the rolled-back version. 24 | useEffect( () => { 25 | if ( rollbackVersion ) { 26 | setCurrentVersion( rollbackVersion ); 27 | } 28 | }, [ rollbackVersion, setCurrentVersion ] ); 29 | 30 | // Don't render until we have the required data 31 | if ( ! rollbackInfo || ! rollbackVersion ) { 32 | return null; 33 | } 34 | 35 | const successMessage = sprintf( 36 | /* translators: 1: Asset name 2: Asset version */ 37 | __( '%1$s has been successfully rolled back to version %2$s.', 'wp-rollback' ), 38 | `${ decodeEntities( rollbackInfo.name ) }`, 39 | `${ rollbackVersion }` 40 | ); 41 | 42 | const proFeatures = [ 43 | { 44 | icon: list, 45 | title: __( 'Detailed Activity Logs', 'wp-rollback' ), 46 | description: __( 'Track every rollback with comprehensive logs and notes', 'wp-rollback' ), 47 | }, 48 | { 49 | icon: backup, 50 | title: __( 'Version Preservation', 'wp-rollback' ), 51 | description: __( 'Preserve current versions of premium assets before updates', 'wp-rollback' ), 52 | }, 53 | { 54 | icon: shield, 55 | title: __( 'Priority Support', 'wp-rollback' ), 56 | description: __( 'Get expert help when you need it most', 'wp-rollback' ), 57 | }, 58 | ]; 59 | 60 | return ( 61 | <> 62 | {/* Success Message */} 63 | 64 |
65 | 66 |
67 |
68 | 69 | 70 |
71 | {/* What's Next Section */} 72 |
73 |

74 | 75 | { __( "What's next?", 'wp-rollback' ) } 76 |

77 |
    78 |
  1. 79 | { __( 80 | 'Check your website to verify the rollback resolved any visual or functional issues', 81 | 'wp-rollback' 82 | ) } 83 |
  2. 84 |
  3. 85 | { __( 86 | "If you rolled back due to an error message, review your error logs to confirm it's resolved", 87 | 'wp-rollback' 88 | ) } 89 |
  4. 90 |
  5. 91 | { __( 92 | 'Test key functionality on your site to ensure everything works as expected', 93 | 'wp-rollback' 94 | ) } 95 |
  6. 96 |
97 |
98 | 99 | {/* Pro Features Upgrade Card */} 100 |
101 |
102 |
103 |
104 |

105 | { __( 'Upgrade to WP Rollback Pro', 'wp-rollback' ) } 106 |

107 |
108 | 109 |

110 | { __( 'Take your rollback management to the next level with professional features designed for serious WordPress sites.', 'wp-rollback' ) } 111 |

112 | 113 |
114 | { proFeatures.map( ( feature, index ) => ( 115 |
116 | 117 |
118 |
{ feature.title }
119 |

{ feature.description }

120 |
121 |
122 | ) ) } 123 |
124 | 125 |
126 | 134 | 135 | { __( 'Learn more', 'wp-rollback' ) } 136 | 137 |
138 |
139 |
140 | 141 | {/* Help Section */} 142 |
143 |

144 | { __( 'Need help with your rollback?', 'wp-rollback' ) } 145 |

146 | 147 | { __( 'View our troubleshooting guide', 'wp-rollback' ) } 148 | 149 |
150 | 151 | 152 |
153 | 154 | ); 155 | }; 156 | 157 | export default FreeCompleteTemplate; 158 | -------------------------------------------------------------------------------- /src/Rollbacks/resources/styles/_free-complete-template.scss: -------------------------------------------------------------------------------- 1 | // Free Complete Template Styles 2 | @use "@wp-rollback/shared-core/styles/variables" as *; 3 | 4 | // Success Notice Styling 5 | .wpr-success-notice { 6 | margin-bottom: 20px !important; 7 | border-left: 4px solid #15803d !important; 8 | 9 | .components-notice__content { 10 | margin: 0 !important; 11 | } 12 | 13 | &__content { 14 | display: flex; 15 | align-items: center; 16 | gap: 8px; 17 | 18 | .components-icon { 19 | color: #15803d; 20 | } 21 | } 22 | } 23 | 24 | // What's Next Section 25 | .wpr-next-steps { 26 | margin-bottom: 24px; 27 | 28 | &__heading { 29 | display: flex; 30 | align-items: center; 31 | gap: 8px; 32 | margin: 0 0 16px 0; 33 | font-size: 16px; 34 | font-weight: 600; 35 | color: #1d2327; 36 | border-bottom: 1px solid #e0e0e0; 37 | padding-bottom: 8px; 38 | } 39 | 40 | &__list { 41 | margin-bottom: 24px; 42 | line-height: 1.6; 43 | 44 | li { 45 | margin-bottom: 8px; 46 | color: #3c434a; 47 | 48 | &:last-child { 49 | margin-bottom: 0 !important; 50 | } 51 | } 52 | } 53 | } 54 | 55 | // Pro Upgrade Card - Modern Design 56 | .wpr-pro-upgrade-card { 57 | background: #fff; 58 | border: 1px solid #e5e7eb; 59 | border-left: 4px solid #8b5cf6; 60 | margin-bottom: 20px; 61 | border-radius: 0; 62 | transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; 63 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); 64 | 65 | &__body { 66 | padding: 24px; 67 | color: #1f2937; 68 | position: relative; 69 | 70 | // Subtle gradient overlay 71 | &::before { 72 | content: ''; 73 | position: absolute; 74 | top: 0; 75 | right: 0; 76 | width: 100px; 77 | height: 100px; 78 | background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(59, 130, 246, 0.05) 100%); 79 | border-radius: 50%; 80 | transform: translate(30px, -30px); 81 | pointer-events: none; 82 | } 83 | } 84 | 85 | &:hover { 86 | transform: translateY(-2px); 87 | box-shadow: 0 10px 25px rgba(139, 92, 246, 0.15), 0 4px 6px rgba(0, 0, 0, 0.05) !important; 88 | border-left-color: #7c3aed; 89 | } 90 | 91 | &__header { 92 | display: flex; 93 | align-items: center; 94 | gap: 12px; 95 | margin-bottom: 16px; 96 | 97 | h3 { 98 | margin: 0; 99 | font-size: 18px; 100 | line-height: 1.2; 101 | font-weight: 600; 102 | color: #1f2937; 103 | background: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 100%); 104 | -webkit-background-clip: text; 105 | -webkit-text-fill-color: transparent; 106 | background-clip: text; 107 | } 108 | } 109 | 110 | &__description { 111 | margin: 0 0 20px 0; 112 | font-size: 14px; 113 | color: #6b7280; 114 | line-height: 1.5; 115 | } 116 | 117 | &__features { 118 | display: grid; 119 | gap: 16px; 120 | margin-bottom: 24px; 121 | } 122 | 123 | &__feature { 124 | display: flex; 125 | align-items: flex-start; 126 | gap: 12px; 127 | padding: 12px; 128 | background: linear-gradient(135deg, rgba(139, 92, 246, 0.03) 0%, rgba(59, 130, 246, 0.03) 100%); 129 | border-radius: 8px; 130 | border: 1px solid rgba(139, 92, 246, 0.1); 131 | transition: background-color 0.2s ease, border-color 0.2s ease; 132 | 133 | &:hover { 134 | background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(59, 130, 246, 0.05) 100%); 135 | border-color: rgba(139, 92, 246, 0.2); 136 | } 137 | 138 | .components-icon { 139 | color: #8b5cf6; 140 | margin-top: 2px; 141 | flex-shrink: 0; 142 | animation: none; 143 | filter: drop-shadow(0 1px 2px rgba(139, 92, 246, 0.2)); 144 | } 145 | 146 | &-content { 147 | h5 { 148 | margin: 0 0 4px 0; 149 | font-size: 14px; 150 | font-weight: 600; 151 | color: #1f2937; 152 | } 153 | 154 | p { 155 | margin: 0; 156 | font-size: 13px; 157 | color: #6b7280; 158 | line-height: 1.4; 159 | } 160 | } 161 | } 162 | 163 | &__actions { 164 | display: flex; 165 | gap: 12px; 166 | align-items: center; 167 | 168 | .components-button { 169 | background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%) !important; 170 | color: #fff !important; 171 | border: none !important; 172 | padding: 10px 20px; 173 | font-size: 14px; 174 | transition: all 0.2s ease; 175 | box-shadow: 0 2px 4px rgba(139, 92, 246, 0.2); 176 | 177 | &:hover { 178 | transform: translateY(-1px); 179 | box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3); 180 | background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%) !important; 181 | } 182 | 183 | &:active { 184 | transform: translateY(0); 185 | } 186 | } 187 | 188 | .components-external-link { 189 | color: #8b5cf6 !important; 190 | text-decoration: none; 191 | font-size: 14px; 192 | font-weight: 500; 193 | padding: 8px 12px; 194 | border-radius: 6px; 195 | transition: background-color 0.2s ease; 196 | 197 | &:hover { 198 | background: rgba(139, 92, 246, 0.1); 199 | text-decoration: underline; 200 | } 201 | } 202 | } 203 | } 204 | 205 | // Help Section 206 | .wpr-help-section { 207 | padding: 16px; 208 | background: #f8fafc; 209 | border-radius: 8px; 210 | text-align: center; 211 | margin-bottom: 20px; 212 | border: 1px solid #e2e8f0; 213 | transition: background-color 0.2s ease; 214 | 215 | &:hover { 216 | background: #f1f5f9 !important; 217 | } 218 | 219 | &__text { 220 | margin: 0 0 8px 0; 221 | font-size: 14px; 222 | color: #64748b; 223 | } 224 | 225 | .components-external-link { 226 | color: #3858e9 !important; 227 | text-decoration: none; 228 | font-size: 14px; 229 | font-weight: 500; 230 | } 231 | } 232 | 233 | // Modal content enhancements 234 | .wpr-modal { 235 | // Increase modal width for better layout 236 | max-width: 650px !important; 237 | 238 | .wpr-modal-content { 239 | // Add some breathing room 240 | padding: 0 4px; 241 | } 242 | } 243 | 244 | // Animations 245 | @keyframes pulse-star { 246 | 0% { transform: scale(1); } 247 | 50% { transform: scale(1.1); } 248 | 100% { transform: scale(1); } 249 | } 250 | 251 | // Responsive improvements 252 | @media (max-width: 600px) { 253 | .wpr-pro-upgrade-card { 254 | .components-card__body { 255 | padding: 16px !important; 256 | } 257 | 258 | &__header h3 { 259 | font-size: 16px !important; 260 | } 261 | 262 | &__features { 263 | grid-template-columns: 1fr !important; 264 | } 265 | } 266 | 267 | .wpr-next-steps { 268 | &__list { 269 | padding-left: 16px !important; 270 | } 271 | } 272 | } -------------------------------------------------------------------------------- /src/Rollbacks/resources/components/LogoFree.jsx: -------------------------------------------------------------------------------- 1 | const LogoFree = ( { className, ...props } ) => { 2 | return ( 3 | 4 | 11 | 18 | 25 | 32 | 39 | 46 | 53 | 60 | 67 | 74 | 75 | { /* The text/logo portion remains filled */ } 76 | 80 | 81 | { /* Gradients */ } 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 121 | 122 | 123 | 124 | 125 | 133 | 134 | 135 | 136 | 137 | 145 | 146 | 147 | 148 | 149 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | ); 164 | }; 165 | 166 | export default LogoFree; 167 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WP Rollback - Rollback Plugins and Themes === 2 | Contributors: dlocc, drrobotnik, webdevmattcrom 3 | Tags: rollback, revert, downgrade, version, plugins 4 | Requires at least: 6.5 5 | Donate Link: https://wprollback.com/ 6 | Tested up to: 6.9 7 | Requires PHP: 7.4 8 | Stable tag: 3.0.10 9 | License: GPLv3 10 | License URI: http://www.gnu.org/licenses/gpl-3.0.html 11 | 12 | Rollback (or forward) any WordPress.org plugin, theme, or block like a boss. 13 | 14 | == Description == 15 | 16 | Quickly and easily rollback any theme or plugin from WordPress.org to any previous (or newer) version without any of the manual fuss. Works just like the plugin updater, except you're rolling back (or forward) to a specific version. No need for manually downloading and FTPing the files or learning Subversion. This plugin takes care of the trouble for you. 17 | 18 | = 🔙 Rollback WordPress.org Plugins and Themes = 19 | 20 | While it's considered best practice to always keep your WordPress plugins and themes updated, we understand there are times you may need to quickly revert to a previous version. This plugin makes that process as easy as a few mouse clicks. Simply select the version of the plugin or theme that you'd like to rollback to, confirm, and in a few moments you'll be using the version requested. No more fumbling to find the version, downloading, unzipping, FTPing, learning Subversion or hair pulling. 21 | 22 | For advanced features like premium plugin/theme support (Envato, Kadence Pro, Astra Pro, etc.), comprehensive activity logging, multisite network support, and priority support, consider upgrading to [WP Rollback Pro](https://wprollback.com/). 23 | 24 | = Muy Importante (Very Important): Always Test and Backup = 25 | 26 | **Important Disclaimer:** This plugin is not intended to be used without first taking the proper precautions to ensure zero data loss or site downtime. Always be sure you have first tested the rollback on a staging or development site prior to using WP Rollback on a live site. 27 | 28 | We provide no (zero) assurances, guarantees, or warranties that the plugin, theme, or WordPress version you are downgrading to will work as you expect. Use this plugin at your own risk. 29 | 30 | = Translation Ready = 31 | 32 | Do you speak another language? Want to contribute in a meaningful way to WP Rollback? There's no better way than to help us translate the plugin. This plugin is translation ready. Simply header over to the WP Rollback [translation project](https://translate.wordpress.org/projects/wp-plugins/wp-rollback/) that's powered by WordPress.org volunteer translators. There you can contribute to the translation of the plugin into your language. 33 | 34 | = Support and Documentation = 35 | 36 | We answer all free user support requests [on the WordPress.org support forum](https://wordpress.org/support/plugin/wp-rollback). For pro users, please submit your questions to [WP Rollback Pro support](https://wprollback.com/?utm_campaign=free-plugin&utm_medium=free-plugin&utm_source=readme). 37 | 38 | WP Rollback was created to be as intuitive to the natural WordPress experience as possible. We believe that once you activate WP Rollback, you'll quickly discover exactly how it works without question. 39 | 40 | **BUT!!** 41 | 42 | We do have documentation on our website. See [WP Rollback Documentation](https://docs.wprollback.com/?utm_source=free-plugin&utm_medium=readme&utm_campaign=documentation). 43 | 44 | == Installation == 45 | 46 | = Minimum Requirements = 47 | 48 | * WordPress 6.5 or greater 49 | * PHP version 7.4 or greater 50 | * MySQL version 5.0 or greater 51 | 52 | = Automatic installation = 53 | 54 | Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't need to leave your web browser. To do an automatic install of WP Rollback, log in to your WordPress dashboard, navigate to the Plugins menu and click Add New. 55 | 56 | In the search field type "WP Rollback" and click Search Plugins. Once you have found the plugin you can view details about it such as the point release, rating and description. Most importantly of course, you can install it by simply clicking "Install Now". 57 | 58 | = Manual installation = 59 | 60 | The manual installation method involves downloading our donation plugin and uploading it to your server via your favorite FTP application. The WordPress codex contains [instructions on how to do this here](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation). 61 | 62 | = Updating = 63 | 64 | Automatic updates should work like a charm; as always though, ensure you backup your site just in case. 65 | 66 | == Frequently Asked Questions == 67 | 68 | = Is this plugin safe to use? = 69 | Short answer = Yes. Longer answer = It depends on how you use it. 70 | 71 | WP Rollback is completely safe because all it does is take publicly available versions of the plugins you already have on your site and install the version that you designate. There is no other kinds of trickery or fancy offsite calls or anything. BUT!!! 72 | 73 | Safety largely depends on you. The WordPress website admin. We absolutely do NOT recommend rolling back any plugins or themes on a live site. Test the rollback locally first, have backups, use all the best practice tools available to you. This is intended to make rolling back easier, that's all. 74 | 75 | = Why isn't there a rollback button next to X plugin or theme? = 76 | 77 | WP Rollback only works with plugins or themes installed from the WordPress Repository. If you don't see the rollback link, then most likely that plugin or theme is not found on WordPress.org. This plugin does not support plugins from GitHub, ThemeForest, or other sources other than the WordPress.org Repo. 78 | 79 | = I rolled my [insert plugin name] back to version X.X and now my site is broken. This is your fault. = 80 | 81 | Nope. We warned you in **bold** print several times in many places. And our plugin delivered exactly what it said it would do. May the Gods of the internet pity your broken site's soul. 82 | 83 | = Where is the complete documentation located? = 84 | 85 | The documentation for this plugin is located on our the [WP Rollback site](https://docs.wprollback.com/?utm_source=free-plugin&utm_medium=readme&utm_campaign=documentation). This is where we make regular updates. 86 | 87 | = What's the difference between WP Rollback Free and Pro? = 88 | 89 | WP Rollback Free provides essential rollback functionality for WordPress.org plugins and themes. WP Rollback Pro adds powerful features including premium plugin/theme support (Gravity Forms, Elementor, Kadence Pro, Astra Pro, Divi, etc.), comprehensive activity logging, multisite network support, and priority support. [Learn more about Pro features](https://wprollback.com/). 90 | 91 | = Can this plugin be translated? = 92 | 93 | Yes! All strings are internationalized and ready to be translated. You can either use your favorite translation plugin, or [help translate the plugin on WordPress.org](https://translate.wordpress.org/projects/wp-plugins/wp-rollback/). 94 | 95 | == Screenshots == 96 | 97 | 1. Click the Rollback link on the Plugins page to begin a plugin rollback. 98 | 99 | 2. Select the version you would like to switch to on the version selection page. 100 | 101 | 3. Confirm you would like to proceed with the rollback. 102 | 103 | 4. The plugin will update to the selected version. 104 | 105 | 5. Click the Rollback button on the Theme details screen to begin a theme rollback. 106 | 107 | 5. The theme Rollback version selection page works exactly like the plugins page. 108 | 109 | == Upgrade Notice == 110 | 111 | This is the first version of this plugin. It is a tool for your convenience. Rollback at your own risk! 112 | 113 | == Changelog == 114 | 115 | = 3.0.10 = 116 | * Fix: Resolved fatal error when Visual Composer page builder is active. The plugin now properly validates screen IDs to handle cases where page builders return non-standard screen ID values. 117 | 118 | = 3.0.9 = 119 | * New: Re-added "Trunk" as an available rollback version option for testing development versions. 120 | * New: Added support for pre-release versions including beta, alpha, and RC versions (e.g., 15.1-beta.2, 15.2-a.7, 2.5.0-RC1). 121 | * Enhancement: Improved version sorting algorithm to properly order pre-release versions alongside stable releases. 122 | * Tweak: Added smooth rotating animation to loading indicators for better visual feedback. 123 | * Tweak: Updated compatibility to WordPress 6.9. 124 | 125 | = 3.0.8 = 126 | * Fix: Changed the filesystem type in BackupService from WP_Filesystem_Direct to WP_Filesystem_Base to allow for broader compatibility with different filesystem implementations. 127 | 128 | = 3.0.7 = 129 | * Fix: Updated the WP_Filesystem call in BackupService to set $allow_relaxed_file_ownership to true, enabling support for Group/World writable files. This change aims to prevent potential issues with file permissions during backup operations. Thanks to @hanno from WP.org support forums. 130 | 131 | = 3.0.6 = 132 | * Enhancement: Premium plugin and theme archives are no longer recreated if an archive already exists for the current version. This improves performance during updates and rollbacks by skipping unnecessary backup operations. 133 | * Fix: Resolved critical issue where WooCommerce and other plugins with autoloaders would cause fatal errors during rollback. The plugin is now properly deactivated before deletion to prevent PHP errors when files are removed. 134 | 135 | = 3.0.5 = 136 | * Fix: Resolved conflict where maintenance mode was interfering with external monitoring tools like Nagios, WP-CLI, and automated WordPress update checks. The maintenance page is now only shown to regular site visitors, allowing monitoring tools and admin processes to function normally. 137 | * Fix: Maintenance mode now only activates when rolling back active plugins or themes. Inactive plugins and themes no longer trigger maintenance mode unnecessarily. 138 | * Fix: Corrected an issue where the maintenance mode step wasn't displaying in the Free version's rollback progress UI due to incorrect service provider load order. 139 | * Improvement: Refactored rollback step registration for better code maintainability across Free and Pro versions, ensuring consistent behavior. 140 | 141 | = 3.0.4 = 142 | * New: Added maintenance mode support during rollback operations to prevent site access while files are being replaced, following WordPress Core update patterns. 143 | * Improvement: Enhanced rollback safety with automatic maintenance mode cleanup that ensures your site never gets stuck in maintenance mode, even if a rollback fails. 144 | * Fix: Removed overly restrictive package validation that required plugin main files to match the plugin slug. This fix allows plugins like Visual Composer (with main file "plugin-wordpress.php") and other legitimate plugins with non-standard main file names to be rolled back successfully. 145 | * Fix: Resolved fatal error when using WP CLI bulk updates (`wp plugin update --all`) due to missing string type check. The backup service now properly handles cases where the package parameter is boolean instead of a string during bulk operations. 146 | * Fix: WordPress Multisite network admin pages now properly load rollback scripts and styles. 147 | * Fix: Resolved package validation errors on multisite installations where ZIP files were incorrectly flagged as invalid. 148 | * Fix: Fixed multisite upload size restrictions that prevented rollbacks due to the default 1MB limit. 149 | 150 | = 3.0.3 = 151 | * Fix: Resolved fatal error when attempting to rollback plugins that return boolean false for requires_php field instead of a string value. This fix ensures proper type validation for WordPress requirement fields. 152 | * Fix: Plugin and theme names containing HTML entities (like &, <, etc.) now display correctly in rollback modals instead of showing raw HTML characters. 153 | 154 | = 3.0.2 = 155 | * Improvement: Simplified theme rollback button display functionality - all themes now display rollback buttons without checking WordPress.org availability. 156 | * Improvement: Consolidated theme rollback JavaScript handlers between free and pro versions for better code maintainability. 157 | * Improvement: Removed visual distinction between WordPress.org and premium plugin rollback links for a more consistent UI. 158 | * Fix: Resolved fatal error on themes.php page caused by incorrect namespace references. 159 | 160 | = 3.0.1 = 161 | * Fix: Resolved an error with JetPack Sync and potentially other plugins that modify plugin data and return null. 162 | 163 | = 3.0.0 = 164 | * New: Added additional "WP Rollback" menu item under WP-Admin > Tools. 165 | * New: Added new "Plugin" and "Themes" list views to select a rollback more easily. 166 | * New: WP Rollback now stores premium assets locally on your server for easy future access. 167 | * New: Added upsells to the new [WP Rollback Pro](https://wprollback.com/). 168 | * New: Updated plugin to support PHP versions 7.4 - 8.4. 169 | 170 | = 2.0.7 = 171 | * Fix: Resolved a bug with plain permalink websites which caused a `rest_no_route` error when trying to rollback a plugin or theme. Thanks, @afizesan for helping pinpoint the issue. 172 | * Fix: Update the way the React app is loaded to suppress React 18+ warnings. 173 | * Tweak: Bumped the plugin's minimum required WordPress version to 6.0+ for best compatibility with new React components in UI. 174 | 175 | = 2.0.6 = 176 | Fix: The release corrects the paths used in plugin file includes and requires. The unnecessary forward slashes at the start of each file path have been removed. This change ensures proper file inclusion and requirement, avoiding potential issues with file not found errors. 177 | 178 | = 2.0.5 = 179 | * New: In this version we've brought back the "trunk" option to rollback to. This allows plugin or theme developers who use trunk for beta testing to rollback to the latest trunk version. Thanks, @megamenu for suggesting this be brought back. 180 | * Fix: Refactored how plugin avatar images are checked so that all available image types and sizes are checked. This resolves an issue where some plugins would not display an avatar image. 181 | * Fix: On the final rollback confirmation screen, the plugin name field was outputting raw HTML. This has been fixed to properly display the plugin name, even if it contains some html characters. 182 | 183 | = 2.0.4 = 184 | * Fix: Resolved issue REST route not including proper permission callback which created a PHP notice. Thanks, @rom1our for submitting the issue. 185 | * Fix: Resolve issue with REST API and multisite installs not being able to properly communicate with the endpoint. 186 | 187 | = 2.0.3 = 188 | * Fix: A few additional strings in JavaScript needed to be internationalized. Thanks, @pedro-mendonca for contributing the fix. 189 | 190 | = 2.0.2 = 191 | * Fix: Resolves an issue with WP Rollback not being able to communicate to its REST API on WordPress subdirectory installs. Thanks, @emaralive for reporting the issue. 192 | 193 | = 2.0.1 = 194 | * Fix: Resolved an issue with the POT file not properly being generated at release. This resolves the issue with the new UI not being able to be translated. 195 | 196 | = 2.0.0 = 197 | * New: Introducing version 2.0! In this new version the UI is now better looking and snappier than ever. The branding has also been updated to look and feel more modern. 198 | 199 | = 1.7.3 = 200 | * Fix: Resolved an issue with plugin rollbacks not correctly setting a filepath for the plugin being rolled back. Props to WP.org user @itmesteren for the fix. 201 | 202 | = 1.7.2 = 203 | * Fix: Ensure that the "Rollback" button displays properly when a WordPress site only has a single theme installed. Thanks [@eldertech](https://wordpress.org/support/users/eldertech/) for your help uncovering this bug. 204 | * Fix: Minor CSS fixes for the Rollback page. 205 | * Tweak: Update the WordPress.org readme.txt file to have better instructions for translating the plugin. We also fixed a few typos. 206 | 207 | = 1.7.1 = 208 | * Fix: Prevent PHP notice when rolling back a plugin or theme on PHP 7.4. 209 | 210 | = 1.7.0 = 211 | * Tweak: Removed the WP Time Capsule staging button and banner. 212 | 213 | = 1.6.0 = 214 | * New: You now have the ability to rollback to the trunk for plugins. This is useful for beta testing releases and more. Thanks to [karpstrucking](https://github.com/karpstrucking) for making this happen. [#45](https://github.com/impress-org/wp-rollback/issues/45) 215 | * New: Add actions "wpr_plugin_success", "wpr_plugin_failure", "wpr_theme_success", and "wpr_theme_failure" for developers. 216 | * New: If a plugin or theme does not have any tagged releases to select from then then an informative notice appears rather than empty space for a better user experience. [#42](https://github.com/impress-org/wp-rollback/issues/42) 217 | * Tweak: Use the WP.org API to retrieve plugin release version information for more reliable results. [#35](https://github.com/impress-org/wp-rollback/issues/35) 218 | 219 | = 1.5.1 = 220 | * Tweak: Added additional information about the importance of Staging and Backups and links to our preferred plugin. 221 | 222 | = 1.5 = 223 | * New: You can now view plugin changelogs within the rollback screen. [#7](https://github.com/impress-org/wp-rollback/issues/7) 224 | * New: Added support for WordPress Multisite rollbacks for themes and plugins. [#22](https://github.com/impress-org/wp-rollback/issues/22) 225 | * New: Rollback button is fixed to the bottom of the page now to prevent long scrolls for rollbacks with many versions. [#23](https://github.com/impress-org/wp-rollback/issues/23) 226 | * New: Updated the WP.org plugin header graphic. [#37](https://github.com/impress-org/wp-rollback/issues/37) 227 | 228 | = 1.4 = 229 | * New: Updated plugin's text domain to the plugin's slug of 'wp-rollback' to support WordPress' GlotPress translations. [#28](https://github.com/impress-org/wp-rollback/issues/28) 230 | * New: Gulp automated POT file generation and text domain checker. [#28](https://github.com/impress-org/wp-rollback/issues/28) 231 | * Fix: Check the WP install's themes transient is present, if not fetch it to see if a theme can be rolled back. Allows rollbacks for new WP installs or in a case where the transient is not set properly.[#27](https://github.com/impress-org/wp-rollback/issues/27) 232 | 233 | = 1.3 = 234 | * Tested compatibility with WordPress 4.4 and verified as working; bumped up compatibility 235 | * Fix: Trying to get property of non-object warning. [#20](https://github.com/impress-org/wp-rollback/issues/20) 236 | * Improvement: Better version sorting now using usort & version_compare. [#16](https://github.com/impress-org/wp-rollback/issues/16) 237 | 238 | = 1.2.4 = 239 | * New: Portuguese translations added. 240 | * Fix: Limit HTTP requests to Plugin page only. [Report 1](https://wordpress.org/support/topic/great-plugin-but-small-issue?replies=5) [Report 2](https://wordpress.org/support/topic/great-plugin-but-small-issue?replies=1#post-7234287) 241 | 242 | = 1.2.3 = 243 | * Fixed: XSS hardening. Thanks @secupress 244 | * Fixed: CSRF patch regarding missing nonces. Thanks @secupress 245 | * Improvement: escape all of the things. 246 | 247 | = 1.2.2 = 248 | * New: Russian translations from @Flector - thanks! 249 | * Fix: Replaced use of wp_json_encode to support older WordPress versions. [Report](https://wordpress.org/support/topic/wordpress-requirement-issue-with-wp_json_encode) 250 | 251 | = 1.2.1 = 252 | * Fix: Rollback link appears on non wp.org plugins - thanks @scottopolis. [#14](https://github.com/impress-org/wp-rollback/issues/14) 253 | * Removed unnecessary WP_ROLLBACK_VERSION constant. 254 | 255 | = 1.2 = 256 | * New: Swedish translation files - Thanks @WPDailyThemes. 257 | 258 | = 1.1 = 259 | * Fixed "Cancel" button which was falsely submitting the form. 260 | 261 | = 1.0 = 262 | * Initial plugin release. Yippee! 263 | * Adds "Rollback" link to all plugins from the WordPress repo on the plugin screen. 264 | * Adds "Rollback" link to all themes from the WordPress repo inside the modal details screen. 265 | * The "Rollback" page allows you to choose which version you want to rollback to. 266 | --------------------------------------------------------------------------------