Elgg  Version 1.10
CacheHandler.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
3 
11 class CacheHandler {
12 
13  protected $config;
14 
20  public function __construct($config) {
21  $this->config = $config;
22  }
23 
31  public function handleRequest($get_vars, $server_vars) {
32  if (empty($get_vars['request'])) {
33  $this->send403();
34  }
35  $request = $this->parseRequestVar($get_vars['request']);
36  if (!$request) {
37  $this->send403();
38  }
39  $ts = $request['ts'];
40  $view = $request['view'];
41  $viewtype = $request['viewtype'];
42 
43  $this->sendContentType($view);
44 
45  // this may/may not have to connect to the DB
46  $this->setupSimplecache();
47 
48  if (!$this->config->simplecache_enabled) {
49  $this->loadEngine();
51  $this->send403();
52  } else {
53  echo $this->renderView($view, $viewtype);
54  }
55  exit;
56  }
57 
58  $etag = "\"$ts\"";
59  // If is the same ETag, content didn't change.
60  if (isset($server_vars['HTTP_IF_NONE_MATCH']) && trim($server_vars['HTTP_IF_NONE_MATCH']) === $etag) {
61  header("HTTP/1.1 304 Not Modified");
62  exit;
63  }
64 
65  $filename = $this->config->dataroot . 'views_simplecache/' . md5("$viewtype|$view");
66  if (file_exists($filename)) {
67  $this->sendCacheHeaders($etag);
68  readfile($filename);
69  exit;
70  }
71 
72  $this->loadEngine();
73 
76  $this->send403();
77  }
78 
79  $cache_timestamp = (int)_elgg_services()->config->get('lastcache');
80 
81  if ($cache_timestamp == $ts) {
82  $this->sendCacheHeaders($etag);
83 
84  $content = $this->getProcessedView($view, $viewtype);
85 
86  $dir_name = $this->config->dataroot . 'views_simplecache/';
87  if (!is_dir($dir_name)) {
88  mkdir($dir_name, 0700);
89  }
90 
91  file_put_contents($filename, $content);
92  } else {
93  // if wrong timestamp, don't send HTTP cache
94  $content = $this->renderView($view, $viewtype);
95  }
96 
97  echo $content;
98  exit;
99  }
100 
107  public function parseRequestVar($request_var) {
108  // no '..'
109  if (false !== strpos($request_var, '..')) {
110  return array();
111  }
112  // only alphanumeric characters plus /, ., -, and _
113  if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $request_var)) {
114  return array();
115  }
116 
117  // testing showed regex to be marginally faster than array / string functions over 100000 reps
118  // it won't make a difference in real life and regex is easier to read.
119  // <ts>/<viewtype>/<name/of/view.and.dots>.<type>
120  if (!preg_match('#^/?([0-9]+)/([^/]+)/(.+)$#', $request_var, $matches)) {
121  return array();
122  }
123 
124  return array(
125  'ts' => $matches[1],
126  'viewtype' => $matches[2],
127  'view' => $matches[3],
128  );
129  }
130 
136  protected function setupSimplecache() {
137  if (!empty($this->config->dataroot) && isset($this->config->simplecache_enabled)) {
138  return;
139  }
140 
141  $dblink = mysql_connect($this->config->dbhost, $this->config->dbuser, $this->config->dbpass, true);
142  if (!$dblink) {
143  $this->send403('Cache error: unable to connect to database server');
144  }
145 
146  if (!mysql_select_db($this->config->dbname, $dblink)) {
147  $this->send403('Cache error: unable to connect to Elgg database');
148  }
149 
150  $query = "SELECT `name`, `value` FROM {$this->config->dbprefix}datalists
151  WHERE `name` IN ('dataroot', 'simplecache_enabled')";
152 
153  $result = mysql_query($query, $dblink);
154  if ($result) {
155  while ($row = mysql_fetch_object($result)) {
156  $this->config->{$row->name} = $row->value;
157  }
158  mysql_free_result($result);
159  }
160  mysql_close($dblink);
161 
162  if (!$result || !isset($this->config->dataroot)) {
163  $this->send403('Cache error: unable to get the data root');
164  }
165  }
166 
173  protected function sendCacheHeaders($etag) {
174  header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+6 months")), true);
175  header("Pragma: public", true);
176  header("Cache-Control: public", true);
177  header("ETag: $etag");
178  }
179 
186  protected function sendContentType($view) {
187  $segments = explode('/', $view, 2);
188  switch ($segments[0]) {
189  case 'css':
190  header("Content-Type: text/css", true);
191  break;
192  case 'js':
193  header('Content-Type: text/javascript', true);
194  break;
195  }
196  }
197 
206  protected function getProcessedView($view, $viewtype) {
207  $content = $this->renderView($view, $viewtype);
208 
209  $hook_type = _elgg_get_view_filetype($view);
210  $hook_params = array(
211  'view' => $view,
212  'viewtype' => $viewtype,
213  'view_content' => $content,
214  );
215  return _elgg_services()->hooks->trigger('simplecache:generate', $hook_type, $hook_params, $content);
216  }
217 
225  protected function renderView($view, $viewtype) {
227 
228  if (!elgg_view_exists($view)) {
229  $this->send403();
230  }
231 
232  // disable error reporting so we don't cache problems
233  _elgg_services()->config->set('debug', null);
234 
235  // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and
236  // triggers an event if it's not. Calling elgg_view() here breaks submenus
237  // (at least) because the page setup hook is called before any
238  // contexts can be correctly set (since this is called before page_handler()).
239  // To avoid this, lie about $CONFIG->pagehandlerdone to force
240  // the trigger correctly when the first view is actually being output.
241  _elgg_services()->config->set('pagesetupdone', true);
242 
243  return elgg_view($view);
244  }
245 
251  protected function loadEngine() {
252  require_once dirname(dirname(dirname(__FILE__))) . "/start.php";
253  }
254 
261  protected function send403($msg = 'Cache error: bad request') {
262  header('HTTP/1.1 403 Forbidden');
263  echo $msg;
264  exit;
265  }
266 }
267 
getProcessedView($view, $viewtype)
Get the contents of a view for caching.
$view
Definition: crop.php:68
handleRequest($get_vars, $server_vars)
Handle a request for a cached view.
elgg_view_exists($view, $viewtype= '', $recurse=true)
Returns whether the specified view exists.
Definition: views.php:318
parseRequestVar($request_var)
Parse a request.
if(!$autoload_available) _elgg_services()
Definition: autoloader.php:20
exit
Definition: reorder.php:12
setupSimplecache()
Do a minimal engine load.
_elgg_get_view_filetype($view)
Returns the type of output expected from the view.
Definition: cache.php:148
__construct($config)
Constructor.
Save menu items.
elgg_set_viewtype($viewtype="")
Manually set the viewtype.
Definition: views.php:70
renderView($view, $viewtype)
Render a view for caching.
_elgg_is_view_cacheable($view)
Check whether a view is registered as cacheable.
Definition: views.php:253
sendCacheHeaders($etag)
Send cache headers.
elgg echo
Translates a string.
Definition: languages.js:43
elgg_view($view, $vars=array(), $bypass=false, $ignored=false, $viewtype= '')
Return a parsed view.
Definition: views.php:354
send403($msg= 'Cache error:bad request')
Send an error message to requestor.
$content
Set robots.txt action.
Definition: set_robots.php:6
sendContentType($view)
Send content type.
$filename
Definition: crop.php:23
$row
clearfix elgg elgg elgg elgg page header
Definition: admin.php:127
$request
$viewtype
Definition: start.php:76
loadEngine()
Load the complete Elgg engine.