Elgg  Version 1.11
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  $db_config = new Database\Config($this->config);
142  $db = new Database($db_config, new Logger(new PluginHooksService()));
143 
144  try {
145  $rows = $db->getData("
146  SELECT `name`, `value`
147  FROM {$db->getTablePrefix()}datalists
148  WHERE `name` IN ('dataroot', 'simplecache_enabled')
149  ");
150  if (!$rows) {
151  $this->send403('Cache error: unable to get the data root');
152  }
153  } catch (\DatabaseException $e) {
154  if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
155  $this->send403('Cache error: unable to connect to database server');
156  } else {
157  $this->send403('Cache error: unable to connect to Elgg database');
158  }
159  exit; // unnecessary, but helps PhpStorm understand
160  }
161 
162  foreach ($rows as $row) {
163  $this->config->{$row->name} = $row->value;
164  }
165 
166  if (empty($this->config->dataroot)) {
167  $this->send403('Cache error: unable to get the data root');
168  }
169  }
170 
177  protected function sendCacheHeaders($etag) {
178  header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+6 months")), true);
179  header("Pragma: public", true);
180  header("Cache-Control: public", true);
181  header("ETag: $etag");
182  }
183 
190  protected function sendContentType($view) {
191  $segments = explode('/', $view, 2);
192  switch ($segments[0]) {
193  case 'css':
194  header("Content-Type: text/css", true);
195  break;
196  case 'js':
197  header('Content-Type: text/javascript', true);
198  break;
199  }
200  }
201 
210  protected function getProcessedView($view, $viewtype) {
211  $content = $this->renderView($view, $viewtype);
212 
213  $hook_type = _elgg_get_view_filetype($view);
214  $hook_params = array(
215  'view' => $view,
216  'viewtype' => $viewtype,
217  'view_content' => $content,
218  );
219  return _elgg_services()->hooks->trigger('simplecache:generate', $hook_type, $hook_params, $content);
220  }
221 
229  protected function renderView($view, $viewtype) {
231 
232  if (!elgg_view_exists($view)) {
233  $this->send403();
234  }
235 
236  // disable error reporting so we don't cache problems
237  _elgg_services()->config->set('debug', null);
238 
239  // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and
240  // triggers an event if it's not. Calling elgg_view() here breaks submenus
241  // (at least) because the page setup hook is called before any
242  // contexts can be correctly set (since this is called before page_handler()).
243  // To avoid this, lie about $CONFIG->pagehandlerdone to force
244  // the trigger correctly when the first view is actually being output.
245  _elgg_services()->config->set('pagesetupdone', true);
246 
247  return elgg_view($view);
248  }
249 
255  protected function loadEngine() {
256  require_once dirname(dirname(dirname(__FILE__))) . "/start.php";
257  }
258 
265  protected function send403($msg = 'Cache error: bad request') {
266  header('HTTP/1.1 403 Forbidden');
267  echo $msg;
268  exit;
269  }
270 }
271 
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.
$e
Definition: metadata.php:12
elgg_view_exists($view, $viewtype= '', $recurse=true)
Returns whether the specified view exists.
Definition: views.php:318
parseRequestVar($request_var)
Parse a request.
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_services()
Definition: autoloader.php:14
_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
$rows
clearfix elgg elgg elgg elgg page header
Definition: admin.php:127
$request
$viewtype
Definition: start.php:76
loadEngine()
Load the complete Elgg engine.