Completed work (we hope) on CacheManager admin page
authorDan
Wed, 09 Jul 2008 17:47:57 -0400
changeset 613 c08670a77871
parent 612 3e73e4996d64
child 614 78d1e71dc720
Completed work (we hope) on CacheManager admin page
includes/clientside/jsres.php
includes/common.php
includes/functions.php
includes/lang.php
includes/paths.php
includes/plugins.php
includes/rijndael.php
language/english/admin.json
plugins/admin/CacheManager.php
--- a/includes/clientside/jsres.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/clientside/jsres.php	Wed Jul 09 17:47:57 2008 -0400
@@ -69,6 +69,10 @@
 
 chdir(ENANO_ROOT);
 
+// fetch only the site config
+define('ENANO_EXIT_AFTER_CONFIG', 1);
+require('includes/common.php');
+
 // CONFIG
 
 // Files safe to run full (aggressive) compression on
@@ -97,8 +101,6 @@
 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
 $compress_unsafe = array('SpryEffects.js', 'json.js', 'fat.js', 'admin-menu.js', 'autofill.js');
 
-require('includes/functions.php');
-require('includes/json2.php');
 require('includes/js-compressor.php');
 
 // try to gzip the output
@@ -279,7 +281,7 @@
       }
     }
   }
-  if ( !$loaded_cache )
+  if ( !$loaded_cache && getConfig('cache_thumbs') == '1' )
   {
     // Try to open the cache file and write to it. If we can't do that, just don't compress the code.
     $handle = @fopen($cache_path, 'w');
--- a/includes/common.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/common.php	Wed Jul 09 17:47:57 2008 -0400
@@ -252,6 +252,11 @@
 
 profiler_log('Config fetched');
 
+if ( defined('ENANO_EXIT_AFTER_CONFIG') )
+{
+  return true;
+}
+
 // Now that we have the config, check the Enano version.
 if ( enano_version(false, true) != $version && !defined('IN_ENANO_UPGRADE') )
 {
--- a/includes/functions.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/functions.php	Wed Jul 09 17:47:57 2008 -0400
@@ -4565,6 +4565,8 @@
     return false;
   fwrite($fh, $ranks_exported);
   fclose($fh);
+  
+  return true;
 }
 
 /**
@@ -4589,13 +4591,16 @@
 
 function purge_all_caches()
 {
+  global $cache;
   if ( $dh = opendir(ENANO_ROOT . '/cache') )
   {
+    $cache->purge('page_meta');
+    $cache->purge('anon_sidebar');
+    $cache->purge('plugins');
+    
     $data_files = array(
         'aes_decrypt.php',
-        'cache_anon_sidebar.php',
-        'cache_page_meta.php',
-        'cache_plugins.php',
+        // ranks cache is stored using a custom engine (not enano's default cache)
         'cache_ranks.php'
       );
     while ( $file = @readdir($dh) )
@@ -4618,7 +4623,7 @@
       else if ( preg_match('/^tiny_mce_(?:[a-f0-9]+)\.gz$/', $file) )
         unlink($fullpath);
       // language files
-      else if ( preg_match('/^lang_json_(?:[a-f0-9]+?)\.php$/', $file) || preg_match('/^lang_(?:[0-9]+?)\.php$/', $file) )
+      else if ( preg_match('/^lang_json_(?:[a-f0-9]+?)\.php$/', $file) || preg_match('/^(?:cache_)?lang_(?:[0-9]+?)\.php$/', $file) )
         unlink($fullpath);
     }
     return true;
--- a/includes/lang.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/lang.php	Wed Jul 09 17:47:57 2008 -0400
@@ -554,6 +554,8 @@
     $q = $db->sql_query('UPDATE ' . table_prefix . 'language SET last_changed = ' . time() . ' WHERE lang_id = ' . $this->lang_id . ';');
     if ( !$q )
       $db->_die('lang.php - updating timestamp on language');
+    
+    return true;
   }
   
   /**
--- a/includes/paths.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/paths.php	Wed Jul 09 17:47:57 2008 -0400
@@ -89,24 +89,24 @@
     $session->register_acl_type('edit_acl',               AUTH_DISALLOW, 'perm_edit_acl',               Array());
     
     // DO NOT add new admin pages here! Use a plugin to call $paths->addAdminNode();
-    $this->addAdminNode('adm_cat_general',    'adm_page_general_config', 'GeneralConfig', scriptPath . '/images/icons/applets/generalconfig.png');
-    $this->addAdminNode('adm_cat_general',    'adm_page_file_uploads',   'UploadConfig', scriptPath . '/images/icons/applets/uploadconfig.png');
-    $this->addAdminNode('adm_cat_general',    'adm_page_file_types',     'UploadAllowedMimeTypes', scriptPath . '/images/icons/applets/uploadallowedmimetypes.png');
-    $this->addAdminNode('adm_cat_content',    'adm_page_manager',        'PageManager', scriptPath . '/images/icons/applets/pagemanager.png');
-    $this->addAdminNode('adm_cat_content',    'adm_page_editor',         'PageEditor', scriptPath . '/images/icons/applets/pageeditor.png');
-    $this->addAdminNode('adm_cat_content',    'adm_page_pg_groups',      'PageGroups', scriptPath . '/images/icons/applets/pagegroups.png');
-    $this->addAdminNode('adm_cat_appearance', 'adm_page_themes',         'ThemeManager', scriptPath . '/images/icons/applets/thememanager.png');
-    $this->addAdminNode('adm_cat_appearance', 'adm_page_plugins',        'PluginManager', scriptPath . '/images/icons/applets/pluginmanager.png');
-    $this->addAdminNode('adm_cat_appearance', 'adm_page_db_backup',      'DBBackup', scriptPath . '/images/icons/applets/dbbackup.png');
-    $this->addAdminNode('adm_cat_appearance', 'adm_page_lang_manager',   'LangManager', scriptPath . '/images/icons/applets/langmanager.png');
-    $this->addAdminNode('adm_cat_appearance', 'adm_page_cache_manager',  'CacheManager', scriptPath . '/images/icons/applets/cachemanager.png');
-    $this->addAdminNode('adm_cat_users',      'adm_page_users',          'UserManager', scriptPath . '/images/icons/applets/usermanager.png');
-    $this->addAdminNode('adm_cat_users',      'adm_page_user_groups',    'GroupManager', scriptPath . '/images/icons/applets/groupmanager.png');
-    $this->addAdminNode('adm_cat_users',      'adm_page_coppa',          'COPPA', scriptPath . '/images/icons/applets/coppa.png');
-    $this->addAdminNode('adm_cat_users',      'adm_page_mass_email',     'MassEmail', scriptPath . '/images/icons/applets/massemail.png');
-    $this->addAdminNode('adm_cat_users',      'adm_page_user_ranks',     'UserRanks', scriptPath . '/images/icons/applets/userranks.png');
-    $this->addAdminNode('adm_cat_security',   'adm_page_security_log',   'SecurityLog', scriptPath . '/images/icons/applets/securitylog.png');
-    $this->addAdminNode('adm_cat_security',   'adm_page_ban_control',    'BanControl', scriptPath . '/images/icons/applets/bancontrol.png');
+    $this->addAdminNode('adm_cat_general',    'adm_page_general_config', 'GeneralConfig',          array(2, 2));
+    $this->addAdminNode('adm_cat_general',    'adm_page_file_uploads',   'UploadConfig',           array(2, 5));
+    $this->addAdminNode('adm_cat_general',    'adm_page_file_types',     'UploadAllowedMimeTypes', array(1, 5));
+    $this->addAdminNode('adm_cat_content',    'adm_page_manager',        'PageManager',            array(1, 4));
+    $this->addAdminNode('adm_cat_content',    'adm_page_editor',         'PageEditor',             array(3, 3));
+    $this->addAdminNode('adm_cat_content',    'adm_page_pg_groups',      'PageGroups',             array(4, 3));
+    $this->addAdminNode('adm_cat_appearance', 'adm_page_themes',         'ThemeManager',           array(4, 4));
+    $this->addAdminNode('adm_cat_appearance', 'adm_page_plugins',        'PluginManager',          array(2, 4));
+    $this->addAdminNode('adm_cat_appearance', 'adm_page_db_backup',      'DBBackup',               array(1, 2));
+    $this->addAdminNode('adm_cat_appearance', 'adm_page_lang_manager',   'LangManager',            array(1, 3));
+    $this->addAdminNode('adm_cat_appearance', 'adm_page_cache_manager',  'CacheManager',           array(3, 1));
+    $this->addAdminNode('adm_cat_users',      'adm_page_users',          'UserManager',            array(3, 5));
+    $this->addAdminNode('adm_cat_users',      'adm_page_user_groups',    'GroupManager',           array(3, 2));
+    $this->addAdminNode('adm_cat_users',      'adm_page_coppa',          'COPPA',                  array(4, 1));
+    $this->addAdminNode('adm_cat_users',      'adm_page_mass_email',     'MassEmail',              array(2, 3));
+    $this->addAdminNode('adm_cat_users',      'adm_page_user_ranks',     'UserRanks',              array(4, 5));
+    $this->addAdminNode('adm_cat_security',   'adm_page_security_log',   'SecurityLog',            array(3, 4));
+    $this->addAdminNode('adm_cat_security',   'adm_page_ban_control',    'BanControl',             array(2, 1));
     
     $code = $plugins->setHook('acl_rule_init');
     foreach ( $code as $cmd )
@@ -463,7 +463,7 @@
     $k = array_keys($this->admin_tree);
     $i = 0;
     $ret = '';
-    $icon = "<img alt=\"\" src=\"" . scriptPath . "/images/icons/applets/home.png\" style=\"border-width: 0; margin-right: 3px;\" /> ";
+    $icon = $this->make_sprite_icon(4, 2);
     $icon = addslashes($icon);
     $ret .= "var TREE_ITEMS = [\n  ['$icon" . $lang->get('adm_btn_home') . "', 'javascript:ajaxPage(\'".$this->nslist['Admin']."Home\');',\n    ";
     foreach($k as $key)
@@ -477,7 +477,16 @@
         $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($c['name']) : $c['name'];
         if ( $c['icon'] && $c['icon'] != scriptPath . '/images/spacer.gif' )
         {
-          $icon = "<img alt=\"\" src=\"{$c['icon']}\" style=\"border-width: 0; margin-right: 3px;\" /> ";
+          if ( is_array($c['icon']) )
+          {
+            // this is a sprite reference
+            list($ix, $iy) = $c['icon'];
+            $icon = $this->make_sprite_icon($ix, $iy);
+          }
+          else
+          {
+            $icon = "<img alt=\"\" src=\"{$c['icon']}\" style=\"border-width: 0; margin-right: 3px;\" /> ";
+          }
         }
         else
         {
@@ -488,7 +497,7 @@
       }
       $ret .= "      ],\n";
     }
-    $icon = "<img alt=\"\" src=\"" . scriptPath . "/images/icons/applets/adminlogout.png\" style=\"border-width: 0; margin-right: 3px;\" /> ";
+    $icon = $this->make_sprite_icon(1, 1);
     $icon = addslashes($icon);
     $ret .= "    ['$icon" . $lang->get('adm_btn_logout') . "', 'javascript:ajaxPage(\\'".$this->nslist['Admin']."AdminLogout\\');'],\n";
     $ret .= "    ['<span id=\\'keepalivestat\\'>" . $lang->get('adm_btn_keepalive_loading') . "</span>', 'javascript:ajaxToggleKeepalive();', 
@@ -501,6 +510,20 @@
   }
   
   /**
+   * Internal function to generate HTML code for an icon in the admin panel tree which is sprited.
+   * @param int X index of icon
+   * @param int Y index of icon
+   * @return string
+   */
+  
+  function make_sprite_icon($ix, $iy)
+  {
+    $xpos = 16 * ( $ix - 1 );
+    $ypos = 16 * ( $iy - 1 );
+    return "<img alt=\"\" src=\"" . scriptPath . "/images/spacer.gif\" class=\"adminiconsprite\" style=\"border-width: 0; margin-right: 3px; background-position: -{$xpos}px -{$ypos}px;\" /> ";
+  }
+  
+  /**
    * Creates a new entry in the administration panel's navigation tree.
    * @param string Section name - if this is a language string identifier, it will be sent through $lang->get()
    * @param string The title of the page, also may be a language string identifier
--- a/includes/plugins.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/plugins.php	Wed Jul 09 17:47:57 2008 -0400
@@ -446,6 +446,8 @@
     
     $this->update_plugins_cache($plugin_info);
     $GLOBALS['plugins_cache'] = $plugin_info;
+    
+    return true;
   }
   
   /**
--- a/includes/rijndael.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/includes/rijndael.php	Wed Jul 09 17:47:57 2008 -0400
@@ -1689,6 +1689,9 @@
 
 function aes_decrypt_cache_store($encrypted, $decrypted, $key)
 {
+  if ( getConfig('cache_thumbs') != '1' )
+    return false;
+  
   $cache_file = ENANO_ROOT . '/cache/aes_decrypt.php';
   // only cache if $decrypted is long enough to actually warrant caching
   if ( strlen($decrypted) < 32 )
--- a/language/english/admin.json	Wed Jul 09 17:38:26 2008 -0400
+++ b/language/english/admin.json	Wed Jul 09 17:47:57 2008 -0400
@@ -544,6 +544,7 @@
     acpcm: {
       heading_main: 'Performance and caching settings',
       intro: 'From this page you can control what information on your site is stored in cache files. Caching speeds up Enano performance by allowing data to be retrieved from the disk instead of querying the database. Sometimes the cache isn\'t updated immediately when the database is. From this page you can control what is cached, and you can clear specific caches to force them to refresh.',
+      msg_refresh_warning: 'Some of the caches on this page will automatically refresh immediately even if you click Clear. This is because any use of the component will invoke the cache. To disable this behavior, you must disable caching site-wide.',
       table_header: 'Cache settings',
       lbl_enable_cache: 'Enable the cache (recommended)',
       hint_enable_cache: 'To use the cache, the folder "cache" in the Enano root directory needs to be writable by the server. You can usually accomplish this by using your FTP client\'s CHMOD feature to set the permissions on this folder to 777. Learn more <a href="http://en.wikipedia.org/wiki/Chmod" onclick="window.open(this.href); return false;">about UNIX permissions</a>.',
@@ -551,6 +552,32 @@
       hint_clear_all: 'To force all caches (except image thumbnails) on the site to empty, click this button. Certain caches, such as language data, must be regenerated in order to fully refresh the cache. It is recommended that you use "%this.acpcm_btn_refresh_all%" below to clear all caches and then regenerate them.',
       btn_refresh_all: 'Refresh all caches',
       hint_refresh_all: 'This will clear all caches (except image thumbnails) on the site to empty and then regenerate, except templates, which are cached on demand.',
+      
+      th_individual_caches: 'Individual caches',
+      btn_clear: 'Clear',
+      btn_refresh: 'Refresh',
+      
+      cache_page_desc_title: 'Page metadata',
+      cache_page_desc_body: 'Information about pages on the site is stored in this cache. This cache is updated when a page is created, renamed or deleted, and it expires every 20 minutes.',
+      cache_ranks_desc_title: 'User ranks',
+      cache_ranks_desc_body: 'Since user ranks take a long time to calculate, fully computed rank data is cached to speed loading of comments and user pages. This cache is updated when any user information is changed, and its expiry time is 15 minutes.',
+      cache_sidebar_desc_title: 'Sidebar for guest users',
+      cache_sidebar_desc_body: 'Rendering the sidebar is a CPU-intensive process because of the number of templates that must be parsed and the custom logic in sidebars. As a result, the fully rendered sidebar is cached for guests. This cache is updated when the sidebar editor is used and expires every 10 minutes.',
+      cache_plugins_desc_title: 'Plugin metadata',
+      cache_plugins_desc_body: 'Plugin files contain special information in the headers that allow Enano to read information about the plugin. Since it takes time to read and parse this information, metadata about plugins is cached. This cache is updated when any change is detected to the first 10 lines of a PHP file in the plugins/ directory.',
+      cache_template_desc_title: 'Template files',
+      cache_template_desc_body: 'Page templates have a lot of logic that takes time to process. They are compiled and then cached on disk to speed loading. This cache is cleared whenever a template file is changed. This cachs cannot be refreshed, as refreshes take place incrementally.',
+      cache_aes_desc_title: 'Encrypted session keys',
+      cache_aes_desc_body: 'Enano encrypts session keys using the AES encryption cipher. Decrypted session keys are cached on the server in a way that is inaccessible to users browsing the site. This cache is dynamically updated whenever a string is sent to the AES decryption module. Clearing it may cause a temporary increase in the load on your site and should only be done if disk space is a concern. This cache is limited to 5,000 keys; when this limit is reached, the oldest 2,500 keys are deleted.',
+      cache_lang_desc_title: 'Language strings',
+      cache_lang_desc_body: 'Language strings, or the text that makes up the Enano user interface, are cached on the disk because they take up a large amount of space in the database. Caching this information allows Enano to minimize bandwidth used when communicating with the database server.',
+      cache_js_desc_title: 'Compressed Javascript runtimes',
+      cache_js_desc_body: 'The on-page tools that Enano provides require a significant amount of Javascript code, which is compressed and cached for better load times. This cache is updated whenever any of the Javascript files are changed, but it is an incremental cache, meaning that it cannot be refreshed manually (only cleared).',
+      cache_thumbs_desc_title: 'Image thumbnails',
+      cache_thumbs_desc_body: 'Thumbnails (preview-size versions of uploaded images) can take up to 2 seconds to generate, usually increasing load times and potentially causing increased server load. This cache is refreshed on demand. Clearing this cache may cause increased loads on your server.',
+      
+      msg_action_success: 'The action you requested was successful.',
+      err_action_failed: 'There was an error during the requested action.'
     },
     acpdb: {
       err_not_supported_title: 'Not supported',
--- a/plugins/admin/CacheManager.php	Wed Jul 09 17:38:26 2008 -0400
+++ b/plugins/admin/CacheManager.php	Wed Jul 09 17:47:57 2008 -0400
@@ -18,6 +18,7 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $lang;
+  global $cache;
   if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
   {
     $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
@@ -26,9 +27,185 @@
     return;
   }
   
+  // validation/actions
+  if ( isset($_POST['refresh']) || isset($_POST['clear']) )
+  {
+    $success = false;
+    
+    $target = ( isset($_POST['refresh']) ) ? $_POST['refresh'] : $_POST['clear'];
+    $do_refresh = isset($_POST['refresh']);
+    switch ( $target )
+    {
+      case 'page':
+        $success = $cache->purge('page_meta');
+        if ( $do_refresh && $success )
+          $success = $paths->update_metadata_cache();
+        break;
+      case 'ranks':
+        $success = $cache->purge('ranks');
+        if ( $do_refresh && $success )
+          $success = generate_cache_userranks();
+        break;
+      case 'sidebar':
+        $success = $cache->purge('anon_sidebar');
+        break;
+      case 'plugins':
+        $success = $cache->purge('plugins');
+        if ( $do_refresh && $success )
+          $success = $plugins->generate_plugins_cache();
+        break;
+      case 'template':
+        if ( $dh = opendir(ENANO_ROOT . '/cache') )
+        {
+          while ( $file = @readdir($dh) )
+          {
+            $fullpath = ENANO_ROOT . "/cache/$file";
+            // we don't want to mess with directories
+            if ( !is_file($fullpath) )
+              continue;
+            
+            if ( preg_match('/\.(?:tpl|css)\.php$/', $file) )
+            {
+              unlink($fullpath);
+            }
+          }
+          $success = true;
+        }
+        break;
+      case 'aes':
+        $success = @unlink(ENANO_ROOT . '/cache/aes_decrypt.php');
+        break;
+      case 'lang':
+        if ( $dh = opendir(ENANO_ROOT . '/cache') )
+        {
+          while ( $file = @readdir($dh) )
+          {
+            $fullpath = ENANO_ROOT . "/cache/$file";
+            // we don't want to mess with directories
+            if ( !is_file($fullpath) )
+              continue;
+            
+            if ( preg_match('/^lang_json_(?:[a-f0-9]+?)\.php$/', $file) || preg_match('/^(?:cache_)?lang_(?:[0-9]+?)\.php$/', $file) )
+              unlink($fullpath);
+          }
+          $success = true;
+        }
+        if ( $do_refresh && $success )
+        {
+          // for each language in the database, call regen_caches()
+          $q = $db->sql_query('SELECT lang_id FROM ' . table_prefix . 'language;');
+          if ( !$q )
+            $db->_die();
+          while ( $row = $db->fetchrow($q) )
+          {
+            $lang_local = ( $row['lang_id'] == $lang->lang_id ) ? $lang : new Language($row['lang_id']);
+            $success = $lang_local->regen_caches();
+            if ( !$success )
+              break 2;
+          }
+        }
+        break;
+      case 'js':
+        if ( $dh = opendir(ENANO_ROOT . '/cache') )
+        {
+          while ( $file = @readdir($dh) )
+          {
+            $fullpath = ENANO_ROOT . "/cache/$file";
+            // we don't want to mess with directories
+            if ( !is_file($fullpath) )
+              continue;
+            
+            // compressed javascript
+            if ( preg_match('/^jsres_(?:[A-z0-9_-]+)\.js\.json$/', $file) )
+              unlink($fullpath);
+            // tinymce stuff
+            else if ( preg_match('/^tiny_mce_(?:[a-f0-9]+)\.gz$/', $file) )
+              unlink($fullpath);
+          }
+          $success = true;
+        }
+        break;
+      case 'thumbs':
+        if ( $dh = opendir(ENANO_ROOT . '/cache') )
+        {
+          while ( $file = @readdir($dh) )
+          {
+            $fullpath = ENANO_ROOT . "/cache/$file";
+            // we don't want to mess with directories
+            if ( !is_file($fullpath) )
+              continue;
+            
+            if ( preg_match('/^(?:[a-z0-9\._,-]+)-(?:[0-9]{10})-[0-9]+x[0-9]+\.([a-z0-9_-]+)$/i', $file) )
+              unlink($fullpath);
+          }
+          $success = true;
+        }
+        break;
+      case 'all':
+        $success = purge_all_caches();
+        if ( $do_refresh )
+        {
+          //
+          // refresh all static (non-incremental) caches
+          //
+          
+          // pages
+          $success = $paths->update_metadata_cache();
+          if ( !$success )
+            break;
+          
+          // user ranks
+          $success = generate_cache_userranks();
+          if ( !$success )
+            break;
+          
+          // plugins
+          $success = $plugins->generate_plugins_cache();
+          if ( !$success )
+            break;
+          
+          // languages
+          $q = $db->sql_query('SELECT lang_id FROM ' . table_prefix . 'language;');
+          if ( !$q )
+            $db->_die();
+          while ( $row = $db->fetchrow($q) )
+          {
+            $lang_local = ( $row['lang_id'] == $lang->lang_id ) ? $lang : new Language($row['lang_id']);
+            $success = $lang_local->regen_caches();
+            if ( !$success )
+              break 2;
+          }
+        }
+        break;
+      default:
+        $code = $plugins->setHook('acp_cache_manager_action');
+        foreach ( $code as $cmd )
+        {
+          eval($cmd);
+        }
+        break;
+    }
+    if ( $success )
+    {
+      echo '<div class="info-box">' . $lang->get('acpcm_msg_action_success') . '</div>';
+    }
+    else
+    {
+      echo '<div class="error-box">' . $lang->get('acpcm_err_action_failed') . '</div>';
+    }
+  }
+  else if ( isset($_POST['save']) )
+  {
+    $config_value = ( isset($_POST['cache_thumbs']) ) ? '1' : '0';
+    setConfig('cache_thumbs', $config_value);
+    echo '<div class="info-box">' . $lang->get('acpcm_msg_action_success') . '</div>';
+  }
+  
   echo '<h3><img alt=" " src="' . scriptPath . '/images/icons/applets/cachemanager.png" />&nbsp;&nbsp;&nbsp;' . $lang->get('acpcm_heading_main') . '</h3>';
   echo '<p>' . $lang->get('acpcm_intro') . '</p>';
   
+  echo '<div class="warning-box">' . $lang->get('acpcm_msg_refresh_warning') . '</div>';
+  
   acp_start_form();
   ?>
   <div class="tblholder">
@@ -56,24 +233,68 @@
       
       <!-- CLEAR ALL -->
       <tr>
-        <td class="row2">
-          <input type="submit" name="clear_all" value="<?php echo $lang->get('acpcm_btn_clear_all'); ?>" />
+      <td class="row2" style="width: 120px; text-align: center;">
+          <button name="clear" value="all"><?php echo $lang->get('acpcm_btn_clear_all'); ?></button>
         </td>
         <td class="row2">
           <?php echo $lang->get('acpcm_hint_clear_all'); ?>
         </td>
       </tr>
       
+      <?php
+      // if caching is disabled, might as well break off here
+      if ( getConfig('cache_thumbs') == '1' ):
+      ?>
+      
       <!-- REFRESH ALL -->
       <tr>
-        <td class="row1">
-          <input type="submit" name="refresh_all" value="<?php echo $lang->get('acpcm_btn_refresh_all'); ?>" />
+        <td class="row1" style="text-align: center;">
+          <button name="refresh" value="all"><?php echo $lang->get('acpcm_btn_refresh_all'); ?></button>
         </td>
         <td class="row1">
           <?php echo $lang->get('acpcm_hint_refresh_all'); ?>
         </td>
       </tr>
       
+      <!-- INDIVIDUAL CACHES -->
+      <tr>
+        <th class="subhead" colspan="2">
+          <?php echo $lang->get('acpcm_th_individual_caches'); ?>
+        </th>
+      </tr>
+      
+      <?php
+      $class = 'row2';
+      $cache_list = array('page', 'ranks', 'sidebar', 'plugins', 'template', 'aes', 'lang', 'js', 'thumbs');
+      $code = $plugins->setHook('acp_cache_manager_list_caches');
+      foreach ( $code as $cmd )
+      {
+        eval($cmd);
+      }
+      foreach ( $cache_list as $target )
+      {
+        $class = ( $class == 'row1' ) ? 'row2' : 'row1';
+        ?><tr>
+        <td class="<?php echo $class; ?>" style="text-align: center;">
+          <button name="refresh" value="<?php echo $target; ?>"<?php if ( in_array($target, array('template', 'sidebar', 'aes', 'js', 'thumbs')) ) echo ' disabled="disabled"'; ?>>
+            <?php echo $lang->get('acpcm_btn_refresh'); ?>
+          </button>
+          <button name="clear" value="<?php echo $target; ?>">
+            <?php echo $lang->get('acpcm_btn_clear'); ?>
+          </button>
+        </td>
+        <td class="<?php echo $class; ?>">
+          <b><?php echo $lang->get("acpcm_cache_{$target}_desc_title"); ?></b> &ndash;
+          <?php echo $lang->get("acpcm_cache_{$target}_desc_body"); ?>
+        </td>
+        </tr>
+      <?php
+      }
+      
+      // getConfig('cache_thumbs') == '1'
+      endif;
+      ?>
+      
       <!-- SAVE CHANGES -->
       <tr>
         <th colspan="2" class="subhead">