/[drupal]/contributions/modules/boost/boost.module
ViewVC logotype

Contents of /contributions/modules/boost/boost.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.4 - (show annotations) (download) (as text)
Wed Dec 6 20:08:04 2006 UTC (2 years, 11 months ago) by arto
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +5 -2 lines
File MIME type: text/x-php
Only update cached copy if the request's query string is empty (fixes #101530).
1 <?php
2 // $Id: boost.module,v 1.3 2006/11/27 16:34:49 arto Exp $
3
4 /**
5 * @file
6 * Provides static page caching for Drupal.
7 */
8
9 //////////////////////////////////////////////////////////////////////////////
10 // BOOST SETTINGS
11
12 define('BOOST_PATH', dirname(__FILE__));
13 define('BOOST_FRONTPAGE', drupal_get_normal_path(variable_get('site_frontpage', 'node')));
14
15 define('BOOST_ENABLED', variable_get('boost', CACHE_DISABLED));
16 define('BOOST_FILE_PATH', variable_get('boost_file_path', 'cache'));
17 define('BOOST_FILE_EXTENSION', variable_get('boost_file_extension', '.html'));
18 define('BOOST_CACHEABILITY_OPTION', variable_get('boost_cacheability_option', 0));
19 define('BOOST_CACHEABILITY_PAGES', variable_get('boost_cacheability_pages', ''));
20 define('BOOST_FETCH_METHOD', variable_get('boost_fetch_method', 'php'));
21 define('BOOST_PRE_PROCESS_FUNCTION', variable_get('boost_pre_process_function', ''));
22 define('BOOST_POST_UPDATE_COMMAND', variable_get('boost_post_update_command', ''));
23 define('BOOST_CRON_LIMIT', variable_get('boost_cron_limit', 100));
24
25 // This cookie is set for all logged-in users, so that they can be excluded
26 // from caching (or, in the future, get a user-specific cached page):
27 define('BOOST_COOKIE', variable_get('boost_cookie', 'DRUPAL_UID'));
28
29 // This line is appended to the generated static files; it is very useful
30 // for troubleshooting (e.g. determining whether one got the dynamic or
31 // static version):
32 define('BOOST_BANNER', variable_get('boost_banner', "<!-- Page cached by Boost at %date -->\n"));
33
34 // This is needed since the $user object is already destructed in _boost_ob_handler():
35 define('BOOST_USER_ID', $GLOBALS['user']->uid);
36
37 //////////////////////////////////////////////////////////////////////////////
38 // BOOST INCLUDES
39
40 require_once BOOST_PATH . '/boost.helpers.inc';
41 require_once BOOST_PATH . '/boost.api.inc';
42
43 //////////////////////////////////////////////////////////////////////////////
44 // DRUPAL API HOOKS
45
46 /**
47 * Implementation of hook_help(). Provides online user help.
48 */
49 function boost_help($section) {
50 switch ($section) {
51 case 'admin/modules#name':
52 return t('boost');
53 case 'admin/modules#description':
54 return t('Provides a performance and scalability boost through caching Drupal pages as static HTML files.');
55 case 'admin/help#boost':
56 $file = drupal_get_path('module', 'boost') . '/README.txt';
57 if (file_exists($file))
58 return '<pre>' . implode("\n", array_slice(explode("\n", @file_get_contents($file)), 2)) . '</pre>';
59 break;
60 case 'admin/settings/boost':
61 return '<p>' . '</p>'; // TODO: add help text.
62 }
63 }
64
65 /**
66 * Implementation of hook_perm(). Defines user permissions.
67 */
68 function boost_perm() {
69 return array('administer cache');
70 }
71
72 /**
73 * Implementation of hook_menu(). Defines menu items and page callbacks.
74 */
75 function boost_menu($may_cache) {
76 $access = user_access('administer cache');
77 $items = array();
78 if ($may_cache) {
79 // TODO: define menu actions for cache administration.
80 }
81 return $items;
82 }
83
84 /**
85 * Implementation of hook_init(). Performs page setup tasks.
86 */
87 function boost_init() {
88 // Stop right here unless we're being called for an ordinary page request
89 if (strpos($_SERVER['PHP_SELF'], 'index.php') === FALSE)
90 return;
91
92 // TODO: check interaction with other modules that use ob_start(); this
93 // may have to be moved to an earlier stage of the page request.
94 if (!variable_get('cache', CACHE_DISABLED) && BOOST_ENABLED) {
95 // We only support GET requests by anonymous visitors:
96 global $user;
97 if (empty($user->uid) && $_SERVER['REQUEST_METHOD'] == 'GET') {
98 // Make sure no query string (in addition to ?q=) was set, and that
99 // the page is cacheable according to our current configuration:
100 if (count($_GET) == 1 && boost_is_cacheable($_GET['q']))
101 ob_start('_boost_ob_handler');
102 }
103 }
104
105 // Executed when saving Drupal's settings:
106 if (!empty($_POST['edit']) && $_GET['q'] == 'admin/settings') {
107 // Forcibly disable Drupal's built-in SQL caching to prevent any conflicts of interest:
108 variable_set('cache', CACHE_DISABLED);
109
110 // TODO: handle 'offline' site maintenance settings.
111
112 $old = variable_get('boost', '');
113 if (!empty($_POST['edit']['boost'])) {
114 // Ensure the cache directory exists or can be created
115 file_check_directory($_POST['edit']['boost_file_path'], FILE_CREATE_DIRECTORY, 'boost_file_path');
116 }
117 else if (!empty($old)) { // the cache was previously enabled
118 if (boost_cache_expire_all())
119 drupal_set_message('Static cache files deleted.');
120 }
121 }
122 }
123
124 /**
125 * Implementation of hook_exit(). Performs cleanup tasks.
126 *
127 * For POST requests by anonymous visitors, this adds a dummy query string
128 * to any URL being redirected to using drupal_goto().
129 *
130 * This is pretty much a hack that assumes a bit too much familiarity with
131 * what happens under the hood of the Drupal core function drupal_goto().
132 *
133 * It's necessary, though, in order for any session messages set on form
134 * submission to actually show up on the next page if that page has been
135 * cached by Boost.
136 */
137 function boost_exit($destination = NULL) {
138 // Check that hook_exit() was invoked by drupal_goto() for a POST request:
139 if (!empty($destination) && $_SERVER['REQUEST_METHOD'] == 'POST') {
140
141 // Check that we're dealing with an anonymous visitor. and that some
142 // session messages have actually been set during this page request:
143 global $user;
144 if (empty($user->uid) && ($messages = drupal_set_message())) {
145
146 // Check that the page we're redirecting to has been cached by Boost
147 // and really necessitates special handling:
148 extract(parse_url($destination));
149 $path = ($path == base_path() ? '' : substr($path, strlen(base_path())));
150 if (boost_is_cached($path) && empty($query)) {
151 // FIXME: call any remaining exit hooks since we're about to terminate.
152
153 // Add a query string to ensure we don't serve a static copy of
154 // the page we're redirecting to, which would prevent the session
155 // messages from showing up:
156 $destination = url($path, 't=' . time(), $fragment, TRUE);
157
158 // Do what drupal_goto() would do if we were to return to it:
159 exit(header('Location: ' . $destination));
160 }
161 }
162 }
163 }
164
165 /**
166 * Implementation of hook_form_alter(). Performs alterations before a form
167 * is rendered.
168 */
169 function boost_form_alter($form_id, &$form) {
170 // Alter Drupal's settings form by hiding the default cache enabled/disabled control (which will now always default to CACHE_DISABLED), and add our own control instead.
171 if ($form_id == 'system_settings_form') {
172 require_once BOOST_PATH . '/boost.admin.inc';
173 $form['cache'] = boost_system_settings_form($form['cache']);
174 }
175 }
176
177 /**
178 * Implementation of hook_cron(). Performs periodic actions.
179 */
180 function boost_cron() {
181 if (!BOOST_ENABLED) return;
182
183 if (boost_cache_expire_all()) {
184 watchdog('boost', t('Expired stale files from static page cache.'), WATCHDOG_NOTICE);
185 }
186 }
187
188 /**
189 * Implementation of hook_comment(). Acts on comment modification.
190 */
191 function boost_comment($comment, $op) {
192 if (!BOOST_ENABLED) return;
193
194 switch ($op) {
195 case 'insert':
196 case 'update':
197 // Expire the relevant node page from the static page cache to prevent serving stale content:
198 if (!empty($comment['nid']))
199 boost_cache_expire('node/' . $comment['nid'], TRUE);
200 break;
201 }
202 }
203
204 /**
205 * Implementation of hook_nodeapi(). Acts on nodes defined by other modules.
206 */
207 function boost_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
208 if (!BOOST_ENABLED) return;
209
210 switch ($op) {
211 case 'insert':
212 case 'update':
213 case 'delete':
214 // Expire all relevant node pages from the static page cache to prevent serving stale content:
215 if (!empty($node->nid))
216 boost_cache_expire('node/' . $node->nid, TRUE);
217 break;
218 }
219 }
220
221 /**
222 * Implementation of hook_taxonomy(). Acts on taxonomy changes.
223 */
224 function boost_taxonomy($op, $type, $term = NULL) {
225 if (!BOOST_ENABLED) return;
226
227 switch ($op) {
228 case 'insert':
229 case 'update':
230 case 'delete':
231 // TODO: Expire all relevant taxonomy pages from the static page cache to prevent serving stale content.
232 break;
233 }
234 }
235
236 /**
237 * Implementation of hook_user(). Acts on user account actions.
238 */
239 function boost_user($op, &$edit, &$account, $category = NULL) {
240 if (!BOOST_ENABLED) return;
241
242 global $user;
243 switch ($op) {
244 case 'login':
245 // Set special cookie to prevent logged-in users getting served pages from the static page cache.
246 $expires = ini_get('session.cookie_lifetime');
247 $expires = (!empty($expires) && is_numeric($expires) ? time() + (int)$expires : 0);
248 setcookie(BOOST_COOKIE, $user->uid, $expires, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
249 break;
250 case 'logout':
251 setcookie(BOOST_COOKIE, FALSE, time() - 86400, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure') == '1');
252 break;
253 case 'insert':
254 // TODO: create user-specific cache directory.
255 break;
256 case 'delete':
257 // Expire the relevant user page from the static page cache to prevent serving stale content:
258 if (!empty($account->uid))
259 boost_cache_expire('user/' . $account->uid);
260 // TODO: recursively delete user-specific cache directory.
261 break;
262 }
263 }
264
265 /**
266 * Implementation of hook_settings(). Declares administrative settings for a module.
267 *
268 * @deprecated in Drupal 5.0.
269 */
270 function boost_settings() {
271 require_once BOOST_PATH . '/boost.admin.inc';
272 return boost_settings_form();
273 }
274
275 //////////////////////////////////////////////////////////////////////////////
276 // OUTPUT BUFFERING CALLBACK
277
278 /**
279 * PHP output buffering callback.
280 *
281 * NOTE: objects have already been destructed so $user is not available.
282 */
283 function _boost_ob_handler($buffer) {
284 // Ensure we're in the correct working directory, since some web servers (e.g. Apache) mess this up here.
285 chdir(dirname($_SERVER['SCRIPT_FILENAME']));
286
287 // Check the currently set content type; at present we can't deal with anything else than HTML.
288 if (_boost_get_content_type() == 'text/html') {
289 if (strlen($buffer) > 0) { // Sanity check
290 boost_cache_set($_GET['q'], $buffer);
291 }
292 }
293
294 // Allow the page request to finish up normally
295 return $buffer;
296 }
297
298 /**
299 * Determines the MIME content type of the current page response based on
300 * the currently set Content-Type HTTP header.
301 *
302 * This should normally return the string 'text/html' unless another module
303 * has overridden the content type.
304 */
305 function _boost_get_content_type($default = NULL) {
306 static $regex = '/^Content-Type:\s*([\w\d\/\-]+)/i';
307
308 // The last Content-Type header is the one that counts:
309 $headers = preg_grep($regex, explode("\n", drupal_set_header()));
310 if (!empty($headers) && preg_match($regex, array_pop($headers), $matches))
311 return $matches[1]; // found it
312
313 return $default;
314 }
315
316 //////////////////////////////////////////////////////////////////////////////

  ViewVC Help
Powered by ViewVC 1.1.2