summaryrefslogtreecommitdiffstats
path: root/boost.api.inc
blob: 09afb94f75eb07e615046f7fa52be7abd98fd2e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<?php
// $Id$

/**
 * @file
 * Implements the Boost API for static page caching.
 */

//////////////////////////////////////////////////////////////////////////////
// BOOST API

/**
 * Determines whether a given Drupal page can be cached or not.
 *
 * To avoid potentially troublesome situations, the user login page is never
 * cached, nor are any admin pages. At present, we also refuse to cache any
 * RSS feeds provided by Drupal, since they would require special handling
 * in the mod_rewrite ruleset as they shouldn't be sent out using the
 * text/html content type.
 */
function boost_is_cacheable($path) {
  $alias = drupal_get_path_alias($path);
  $path = drupal_get_normal_path($path); // normalize path

  // Never cache the basic user login/registration pages or any administration pages
  if ($path == 'user' || preg_match('!^user/(login|register|password)!', $path) || preg_match('!^admin!', $path))
    return FALSE;

  // At present, RSS feeds are not cacheable due to content type restrictions
  if ($path == 'rss.xml' || preg_match('!/feed$!', $path))
    return FALSE;

  // Don't cache comment reply pages
  if (preg_match('!^comment/reply!', $path))
    return FALSE;

  // Match the user's cacheability settings against the path
  if (BOOST_CACHEABILITY_OPTION == 2) {
    $result = drupal_eval(BOOST_CACHEABILITY_PAGES);
    return !empty($result);
  }
  $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote(BOOST_CACHEABILITY_PAGES, '/')) .')$/';
  return !(BOOST_CACHEABILITY_OPTION xor preg_match($regexp, $alias));
}

/**
 * Determines whether a given Drupal page is currently cached or not.
 */
function boost_is_cached($path) {
  $path = (empty($path) ? BOOST_FRONTPAGE : $path);
  $alias = drupal_get_path_alias($path);
  $path = drupal_get_normal_path($path); // normalize path

  // TODO: also determine if alias/symlink exists?
  return file_exists(boost_file_path($path));
}

/**
 * Deletes all static files currently in the cache.
 */
function boost_cache_clear_all() {
  clearstatcache();
  if (($cache_dir = boost_cache_directory()) && file_exists($cache_dir)) {
    return _boost_rmdir_rf($cache_dir);
  }
}

/**
 * Deletes all expired static files currently in the cache.
 */
function boost_cache_expire_all() {
  clearstatcache();
  if (($cache_dir = boost_cache_directory()) && file_exists($cache_dir)) {
    _boost_rmdir_rf($cache_dir, 'boost_file_is_expired');
  }
  return TRUE;
}

/**
 * Expires the static file cache for a given page, or multiple pages
 * matching a wildcard.
 */
function boost_cache_expire($path, $wildcard = FALSE) {
  // TODO: handle wildcard.

  $alias = drupal_get_path_alias($path);
  $path = drupal_get_normal_path($path); // normalize path

  if (($filename = boost_file_path($path)) && file_exists($filename)) {
    @unlink($filename);
  }

  if ($alias != $path && ($symlink = boost_file_path($alias)) && is_link($symlink)) {
    @unlink($symlink);
  }

  return TRUE;
}

/**
 * Returns the cached contents of the specified page, if available.
 */
function boost_cache_get($path) {
  $path = drupal_get_normal_path($path); // normalize path

  if (($filename = boost_file_path($path))) {
    if (file_exists($filename) && is_readable($filename)) {
      return file_get_contents($filename);
    }
  }

  return NULL;
}


/**
 * Replaces the cached contents of the specified page, if stale.
 */
function boost_cache_set($path, $data = '') {
  // Append the Boost footer with the relevant timestamps
  $time = time();
  $cached_at = date('Y-m-d H:i:s', $time);
  $expires_at = date('Y-m-d H:i:s', $time + variable_get('cache_lifetime', 600));
  $data = rtrim($data) . "\n" . str_replace(array('%cached_at', '%expires_at'), array($cached_at, $expires_at), BOOST_BANNER);

  // Execute the pre-process function if one has been defined
  if (function_exists(BOOST_PRE_PROCESS_FUNCTION))
    $data = call_user_func(BOOST_PRE_PROCESS_FUNCTION, $data);

  $alias = drupal_get_path_alias($path);
  $path = drupal_get_normal_path($path); // normalize path

  // Create or update the static file as needed
  if (($filename = boost_file_path($path))) {
    _boost_mkdir_p(dirname($filename));
    if (!file_exists($filename) || boost_file_is_expired($filename)) {
      if (file_put_contents($filename, $data) === FALSE) {
        watchdog('boost', t('Unable to write file: %file', array('%file' => $filename)), WATCHDOG_WARNING);
      }
    }

    // If a URL alias is defined, create that as a symlink to the actual file
    if ($alias != $path && ($symlink = boost_file_path($alias))) {
      _boost_mkdir_p(dirname($symlink));
      if (!is_link($symlink) || realpath(readlink($symlink)) != realpath($filename)) {
        if (file_exists($symlink)) {
          @unlink($symlink);
        }
        if (!_boost_symlink($filename, $symlink)) {
          watchdog('boost', t('Unable to create symlink: %link to %target', array('%link' => $symlink, '%target' => $filename)), WATCHDOG_WARNING);
        }
      }
    }
  }

  return TRUE;
}

/**
 * Returns the full directory path to the static file cache directory.
 */
function boost_cache_directory($user_id = 0, $host = NULL) {
  global $user, $base_url;
  $user_id = 0; //(!is_null($user_id) ? $user_id : BOOST_USER_ID);
  $parts = parse_url($base_url);
  $host = (!empty($host) ? $host : $parts['host']);

  // FIXME: correctly handle Drupal subdirectory installations.
  return implode('/', array(getcwd(), BOOST_FILE_PATH, $host, $user_id));
}

/**
 * Returns the static file path for a Drupal page.
 */
function boost_file_path($path) {
  if (empty($path) || $path == BOOST_FRONTPAGE) {
    $path = 'index'; // special handling for Drupal's front page
  }

  // Under no circumstances should the incoming path contain '..' or null
  // bytes; we also limit the maximum directory nesting depth of the path
  if (strpos($path, '..') !== FALSE || strpos($path, "\0") !== FALSE ||
      count(explode('/', $path)) > BOOST_MAX_PATH_DEPTH) {
    return FALSE;
  }

  // Convert any other undesirable characters in the path to underscores
  $path = preg_replace('@[^/a-z0-9_-]@i', '_', $path);

  return boost_cache_directory() . '/' . $path . BOOST_FILE_EXTENSION;
}

/**
 * Returns the age of a cached file, measured in seconds since it was last
 * updated.
 */
function boost_file_get_age($filename) {
  return time() - filemtime($filename);
}

/**
 * Determines whether a cached file has expired, i.e. whether its age exceeds
 * the maximum cache lifetime as defined by Drupal's system settings.
 */
function boost_file_is_expired($filename) {
  if (is_link($filename))
    return FALSE;
  return boost_file_get_age($filename) > variable_get('cache_lifetime', 600);
}

//////////////////////////////////////////////////////////////////////////////