From f0ded94d8497ca5e7fd60e25bf6818ff0c8aa2e1 Mon Sep 17 00:00:00 2001 From: JasWSInc Date: Tue, 5 Aug 2014 12:52:09 -0800 Subject: [PATCH 1/2] Adding Uninstall Routine; see websharks/quick-cache#261 --- quick-cache-pro/includes/menu-pages.php | 12 +-- quick-cache-pro/quick-cache-pro.inc.php | 113 +++++++++++++++++++++--- quick-cache-pro/quick-cache-pro.php | 2 +- quick-cache-pro/uninstall.php | 43 +++++++++ 4 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 quick-cache-pro/uninstall.php diff --git a/quick-cache-pro/includes/menu-pages.php b/quick-cache-pro/includes/menu-pages.php index f591c8e7..c0c55d5d 100644 --- a/quick-cache-pro/includes/menu-pages.php +++ b/quick-cache-pro/includes/menu-pages.php @@ -136,16 +136,16 @@ public function options() echo '
'."\n"; echo '
'."\n"; - echo ' '.__('Deactivation Safeguards', $this->plugin->text_domain)."\n"; + echo ' '.__('Plugin Deletion Safeguards', $this->plugin->text_domain)."\n"; echo '
'."\n"; echo '
'."\n"; echo ' '."\n"; - echo '

'.__('Uninstall on Deactivation; or Safeguard Options?', $this->plugin->text_domain).'

'."\n"; - echo '

'.__('Tip: By default, if you deactivate Quick Cache from the plugins menu in WordPress; nothing is lost. However, if you want to uninstall Quick Cache you should set this to Yes and THEN deactivate it from the plugins menu in WordPress. This way Quick Cache will erase your options for the plugin, clear the cache, remove the advanced-cache.php file, terminate CRON jobs, etc. It erases itself from existence completely.', $this->plugin->text_domain).'

'."\n"; - echo '

'."\n"; + echo ' '."\n"; + echo ' '."\n"; echo '

'."\n"; echo '
'."\n"; diff --git a/quick-cache-pro/quick-cache-pro.inc.php b/quick-cache-pro/quick-cache-pro.inc.php index dfcfb8a9..6d77c4e2 100644 --- a/quick-cache-pro/quick-cache-pro.inc.php +++ b/quick-cache-pro/quick-cache-pro.inc.php @@ -83,6 +83,16 @@ class plugin extends share */ public $network_cap = ''; + /** + * Uninstall capability requirement. + * + * @since 14xxxx Adding uninstall handler. + * + * @var string WordPress capability required to + * completely uninstall/delete QC. + */ + public $uninstall_cap = ''; + /** * Cache directory. * @@ -119,16 +129,31 @@ class plugin extends share */ public $cache = array(); + /** + * Used by the plugin's uninstall handler. + * + * @since 14xxxx Adding uninstall handler. + * + * @var boolean If FALSE, run without any hooks. + */ + public $enable_hooks = TRUE; + /** * Quick Cache plugin constructor. * + * @param boolean $enable_hooks Defaults to a TRUE value. + * If FALSE, setup runs but without adding any hooks. + * * @since 140422 First documented version. */ - public function __construct() + public function __construct($enable_hooks = TRUE) { parent::__construct(); // Shared constructor. - $this->file = preg_replace('/\.inc\.php$/', '.php', __FILE__); + $this->enable_hooks = (boolean)$enable_hooks; + $this->file = preg_replace('/\.inc\.php$/', '.php', __FILE__); + + if(!$this->enable_hooks) return; // All done in this case. add_action('after_setup_theme', array($this, 'setup')); register_activation_hook($this->file, array($this, 'activate')); @@ -142,7 +167,8 @@ public function __construct() */ public function setup() { - do_action('before__'.__METHOD__, get_defined_vars()); + if($this->enable_hooks) // Hooks enabled? + do_action('before__'.__METHOD__, get_defined_vars()); load_plugin_textdomain($this->text_domain); @@ -195,7 +221,7 @@ public function setup() 'htmlc_compress_html_code' => '1', // `0|1`. 'change_notifications_enable' => '1', // `0|1`. - 'uninstall_on_deactivation' => '0', // `0|1`. + 'uninstall_on_deletion' => '0', // `0|1`. 'auto_cache_enable' => '0', // `0|1`. 'auto_cache_sitemap_url' => 'sitemap.xml', // Relative to `site_url()`. @@ -250,9 +276,12 @@ public function setup() if(!$this->options['base_dir']) // Security enhancement; NEVER allow this to be empty. $this->options['base_dir'] = $this->default_options['base_dir']; - $this->cap = apply_filters(__METHOD__.'__cap', 'activate_plugins'); - $this->network_cap = apply_filters(__METHOD__.'__network_cap', 'manage_network_plugins'); - $this->update_cap = apply_filters(__METHOD__.'__update_cap', 'update_plugins'); + $this->cap = apply_filters(__METHOD__.'__cap', 'activate_plugins'); + $this->update_cap = apply_filters(__METHOD__.'__update_cap', 'update_plugins'); + $this->network_cap = apply_filters(__METHOD__.'__network_cap', 'manage_network_plugins'); + $this->uninstall_cap = apply_filters(__METHOD__.'__uninstall_cap', 'delete_plugins'); + + if(!$this->enable_hooks) return; // Stop here; setup without hooks. add_action('init', array($this, 'check_advanced_cache')); add_action('init', array($this, 'check_blog_paths')); @@ -418,11 +447,35 @@ public function deactivate() $this->remove_wp_cache_from_wp_config(); $this->remove_advanced_cache(); $this->clear_cache(); + } + + /** + * Plugin uninstall hook. + * + * @since 14xxxx Adding uninstall handler. + * + * @attaches-to {@link \register_uninstall_hook()} ~ via {@link uninstall()} + */ + public function uninstall() + { + if(!current_user_can($this->uninstall_cap)) + return; // Extra layer of security. - if(!$this->options['uninstall_on_deactivation']) + if(!class_exists('\\'.__NAMESPACE__.'\\uninstall')) + return; // Expecting the uninstall class. + + if(!defined('WP_UNINSTALL_PLUGIN')) + return; // Disallow. + + $this->remove_wp_cache_from_wp_config(); + $this->remove_advanced_cache(); + $this->wipe_cache(); + + if(!$this->options['uninstall_on_deletion']) return; // Nothing to do here. $this->delete_advanced_cache(); + $this->remove_base_dir(); delete_option(__NAMESPACE__.'_options'); if(is_multisite()) // Delete network options too. @@ -2600,6 +2653,44 @@ public function update_blog_paths($enable_live_network_counts = NULL) } return $value; // Pass through untouched (always). } + + /** + * Removes the entire base directory. + * + * @since 14xxx First documented version. + * + * @return integer Total files removed by this routine (if any). + * + * @throws \exception If a wipe failure occurs. + */ + public function remove_base_dir() + { + $counter = 0; // Initialize. + + // @TODO When set_time_limit() is disabled by PHP configuration, display a warning message to users upon plugin activation. + @set_time_limit(1800); // In case of HUGE sites w/ a very large directory. Errors are ignored in case `set_time_limit()` is disabled. + + $base_dir = $this->wp_content_dir_to(''); // Simply the base directory. + + /** @var $_dir_file \RecursiveDirectoryIterator For IDEs. */ + if($base_dir && is_dir($base_dir)) foreach($this->dir_regex_iteration($base_dir, '/.+/') as $_dir_file) + { + if(($_dir_file->isFile() || $_dir_file->isLink())) + if(!unlink($_dir_file->getPathname())) // Throw exception if unable to delete. + throw new \exception(sprintf(__('Unable to remove file: `%1$s`.', $this->text_domain), $_dir_file->getPathname())); + else $counter++; // Increment counter for each file we wipe. + + else if($_dir_file->isDir()) + if(!rmdir($_dir_file->getPathname())) // Throw exception if unable to delete. + throw new \exception(sprintf(__('Unable to remove dir: `%1$s`.', $this->text_domain), $_dir_file->getPathname())); + } + unset($_dir_file); // Just a little housekeeping. + + if(is_dir($base_dir) && !rmdir($base_dir)) // Throw exception if unable to delete. + throw new \exception(sprintf(__('Unable to remove base dir: `%1$s`.', $this->text_domain), $base_dir)); + + return $counter; // Total removals. + } } /** @@ -2620,15 +2711,15 @@ function plugin() // Easy reference. * * @since 140422 First documented version. * - * @var plugin $GLOBALS [__NAMESPACE__] + * @var plugin Main plugin class. */ - $GLOBALS[__NAMESPACE__] = new plugin(); // New plugin instance. + $GLOBALS[__NAMESPACE__] = new plugin(!class_exists('\\'.__NAMESPACE__.'\\uninstall')); /* * API class inclusion; depends on {@link $GLOBALS[__NAMESPACE__]}. */ require_once dirname(__FILE__).'/includes/api-class.php'; } - else add_action('all_admin_notices', function () // Do NOT load in this case. + else if(!class_exists('\\'.__NAMESPACE__.'\\uninstall')) add_action('all_admin_notices', function () { echo '

'. // Running multiple versions of this plugin at same time. __('Please disable the LITE version of Quick Cache before you activate the PRO version.', diff --git a/quick-cache-pro/quick-cache-pro.php b/quick-cache-pro/quick-cache-pro.php index 3387fe2e..1c7c7925 100644 --- a/quick-cache-pro/quick-cache-pro.php +++ b/quick-cache-pro/quick-cache-pro.php @@ -18,4 +18,4 @@ if(require(dirname(__FILE__).'/includes/wp-php53.php')) // TRUE if running PHP v5.3+. require_once dirname(__FILE__).'/quick-cache-pro.inc.php'; -else wp_php53_notice('Quick Cache Pro'); +else wp_php53_notice('Quick Cache Pro'); \ No newline at end of file diff --git a/quick-cache-pro/uninstall.php b/quick-cache-pro/uninstall.php new file mode 100644 index 00000000..29656c40 --- /dev/null +++ b/quick-cache-pro/uninstall.php @@ -0,0 +1,43 @@ + + * @license GNU General Public License, version 2 + */ +namespace quick_cache +{ + if(!defined('WPINC')) // MUST have WordPress. + exit('Do NOT access this file directly: '.basename(__FILE__)); + + require_once dirname(__FILE__).'/quick-cache-pro.inc.php'; + + if(!class_exists('\\'.__NAMESPACE__.'\\uninstall')) + { + class uninstall // Uninstall handler. + { + /** + * @since 14xxxx Adding uninstaller. + * + * @var plugin Primary plugin class instance. + */ + protected $plugin; // Set by constructor. + + /** + * Uninstall constructor. + * + * @since 14xxxx Adding uninstall handler. + */ + public function __construct() + { + $this->plugin = plugin( /* Without hooks. */); + $this->plugin->setup( /* Without hooks. */); + $this->plugin->uninstall(); + } + } + + new uninstall(); // Run the uninstaller. + } +} \ No newline at end of file From 6f33ef44f9733c6a5c5c02e853a0bb12d3a59c09 Mon Sep 17 00:00:00 2001 From: JasWSInc Date: Tue, 5 Aug 2014 12:59:02 -0800 Subject: [PATCH 2/2] Plugin Deletion Safeguards; see websharks/quick-cache#261 --- quick-cache-pro/includes/menu-pages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick-cache-pro/includes/menu-pages.php b/quick-cache-pro/includes/menu-pages.php index c0c55d5d..4d6b0702 100644 --- a/quick-cache-pro/includes/menu-pages.php +++ b/quick-cache-pro/includes/menu-pages.php @@ -145,7 +145,7 @@ public function options() echo '

'.__('Tip: By default, if you delete Quick Cache using the plugins menu in WordPress, nothing is lost. However, if you want to completely uninstall Quick Cache you should set this to Yes and THEN deactivate & delete Quick Cache from the plugins menu in WordPress. This way Quick Cache will erase your options for the plugin, erase directories/files created by the plugin, remove the advanced-cache.php file, terminate CRON jobs, etc. It erases itself from existence completely.', $this->plugin->text_domain).'

'."\n"; echo '

'."\n"; echo '
'."\n";