getConf($key)); if ($value !== '') return $value; } return $fallback; } public function getDefaultPolicy(): string { $mode = strtolower(trim((string)$this->getConf('default_policy'))); if (in_array($mode, ['allow', 'block', 'force_allow', 'force_block'], true)) { return $mode; } return 'allow'; } private function hasMacro(string $content, string $macro): bool { return (bool)preg_match('/~~\s*' . preg_quote($macro, '/') . '\s*~~/i', $content); } public function parseRuleList($raw): array { $parts = preg_split('/[\r\n,;]+/', (string)$raw); $rules = []; foreach ($parts as $part) { $rule = trim((string)$part); if ($rule !== '') $rules[] = $rule; } return $rules; } public function idMatchesRule(string $id, string $rule): bool { $rule = trim($rule); if ($rule === '') return false; if (strlen($rule) > 1 && $rule[0] === '/' && substr($rule, -1) === '/') { return (bool)@preg_match($rule, $id); } if (substr($rule, -1) === ':') { return strpos($id, $rule) === 0; } if (strpos($rule, '*') !== false) { $regex = '/^' . str_replace('\\*', '.*', preg_quote($rule, '/')) . '$/'; return (bool)preg_match($regex, $id); } return $id === $rule; } public function matchesConfiguredList(string $id, string $confKey): bool { $rules = $this->parseRuleList($this->getConf($confKey)); foreach ($rules as $rule) { if ($this->idMatchesRule($id, $rule)) return true; } return false; } public function hasConfiguredList(string $confKey): bool { return !empty($this->parseRuleList($this->getConf($confKey))); } private function hasFilter(): bool { if ($this->hasConfiguredList('filter_list')) return true; return trim((string)$this->getConf('filter_regex')) !== ''; } private function isInFilter(string $id): bool { // filter_list: exact pages, namespace prefixes and wildcards only — no regex entries foreach ($this->parseRuleList($this->getConf('filter_list')) as $rule) { $isRegex = strlen($rule) > 1 && $rule[0] === '/' && substr($rule, -1) === '/'; if (!$isRegex && $this->idMatchesRule($id, $rule)) return true; } // filter_regex: dedicated regex field $regex = trim((string)$this->getConf('filter_regex')); if ($regex !== '' && @preg_match($regex, $id)) return true; return false; } public function getRequestMatchKey(): string { return $this->getConfiguredString(['request_match_key', 'server_ip_key'], 'REMOTE_ADDR'); } public function getExtranetMatchList(): string { return $this->getConfiguredString(['extranet_match_list', 'extranet_ip_list']); } public function getExtranetMatchRegex(): string { return $this->getConfiguredString(['extranet_match_regex', 'extranet_ip_regex']); } /** * Build candidate values from the configured request marker. * * A proxy header may contain a single marker ("extranet"), a hostname, * or a comma-separated list such as X-Forwarded-For. We accept both the * full raw value and each comma-separated token. * * @return string[] */ private function getRequestMatchCandidates(): array { $requestKey = $this->getRequestMatchKey(); $rawValue = trim((string)($_SERVER[$requestKey] ?? '')); if ($rawValue === '') return []; $candidates = [$rawValue]; if (strpos($rawValue, ',') !== false) { foreach (explode(',', $rawValue) as $part) { $part = trim((string)$part); if ($part !== '') $candidates[] = $part; } } return array_values(array_unique($candidates)); } public function isExtranetRequest(): bool { $candidates = $this->getRequestMatchCandidates(); if ($candidates === []) return false; $matchList = $this->getExtranetMatchList(); if ($matchList !== '') { foreach (explode(',', $matchList) as $configuredValue) { $configuredValue = trim((string)$configuredValue); if ($configuredValue !== '' && in_array($configuredValue, $candidates, true)) { return true; } } } $matchRegex = $this->getExtranetMatchRegex(); if ($matchRegex !== '') { foreach ($candidates as $candidate) { if ((bool)@preg_match($matchRegex, $candidate)) return true; } } return false; } public function isClientFromExtranet(): bool { return $this->isExtranetRequest(); } public function isMediaVisibleFromExtranet(string $mediaID): bool { if (!$this->hasFilter()) return true; $inFilter = $this->isInFilter($mediaID); $defaultPolicy = $this->getDefaultPolicy(); if ($defaultPolicy === 'force_allow' || $defaultPolicy === 'allow') { return !$inFilter; } return $inFilter; } public function isPageVisibleFromExtranet(string $id, ?string $content = null): bool { if ($content === null) { $content = rawWiki($id); } $content = (string)$content; $hasFilter = $this->hasFilter(); $inFilter = $hasFilter && $this->isInFilter($id); $defaultPolicy = $this->getDefaultPolicy(); if ($defaultPolicy === 'force_allow') { return !$inFilter; } if ($defaultPolicy === 'force_block') { return $inFilter; } $hasExtranet = $this->hasMacro($content, 'EXTRANET'); $hasNoExtranet = $this->hasMacro($content, 'NOEXTRANET'); if ($defaultPolicy === 'allow') { return !$inFilter && !$hasNoExtranet; } // block return $inFilter || $hasExtranet; } public function isPageAllowed(string $id, ?string $content = null): bool { if (!$this->isExtranetRequest()) return true; return $this->isPageVisibleFromExtranet($id, $content); } public function isMediaAllowed(string $mediaID): bool { if (!$this->isExtranetRequest()) return true; return $this->isMediaVisibleFromExtranet($mediaID); } }