config()->get('lock_bypage')) { $key .= '_' . md5($itemkey ?? ''); } // Add user-specific identifier if ($this->config()->get('lock_byuserip') && Controller::has_curr()) { $ip = Controller::curr()->getRequest()->getIP(); $key .= '_' . md5($ip ?? ''); } return $key; } public function getContent($key, $callback) { // Bypass rate limiting if flushing, or timeout isn't set $timeout = $this->config()->get('lock_timeout'); if (isset($_GET['flush']) || !$timeout) { return parent::getContent($key, $callback); } // Generate result with rate limiting enabled $limitKey = $this->getCacheKey($key); $cache = $this->getCache(); if ($lockedUntil = $cache->get($limitKey)) { if (time() < $lockedUntil) { // Politely inform visitor of limit $response = new HTTPResponse_Exception('Too Many Requests.', 429); $response->getResponse()->addHeader('Retry-After', 1 + $lockedUntil - time()); throw $response; } } $lifetime = Config::inst()->get(ContentFilter::class, 'cache_lifetime') ?: null; // Apply rate limit $cache->set($limitKey, time() + $timeout, $lifetime); // Generate results $result = parent::getContent($key, $callback); // Reset rate limit with optional cooldown if ($cooldown = $this->config()->get('lock_cooldown')) { // Set cooldown on successful query execution $cache->set($limitKey, time() + $cooldown, $lifetime); } else { // Without cooldown simply disable lock $cache->delete($limitKey); } return $result; } }