13f108b37SAndreas Gohr<?php 23f108b37SAndreas Gohr 33f108b37SAndreas Gohrnamespace dokuwiki; 43f108b37SAndreas Gohr 53f108b37SAndreas Gohr/** 63f108b37SAndreas Gohr * The preference cookie is used to store small user preference data 73f108b37SAndreas Gohr * 83f108b37SAndreas Gohr * The cookie is written from PHP (using this class) and from JavaScript (using the DokuCookie object). 93f108b37SAndreas Gohr * 103f108b37SAndreas Gohr * Data is stored as key#value#key#value string, with all keys and values being urlencoded 113f108b37SAndreas Gohr */ 123f108b37SAndreas Gohrclass PrefCookie 133f108b37SAndreas Gohr{ 143f108b37SAndreas Gohr const COOKIENAME = 'DOKU_PREFS'; 153f108b37SAndreas Gohr 163f108b37SAndreas Gohr /** @var string[] */ 173f108b37SAndreas Gohr protected array $data = []; 183f108b37SAndreas Gohr 193f108b37SAndreas Gohr /** 203f108b37SAndreas Gohr * Initialize the class from the cookie data 213f108b37SAndreas Gohr */ 223f108b37SAndreas Gohr public function __construct() 233f108b37SAndreas Gohr { 243f108b37SAndreas Gohr $this->data = $this->decodeData($_COOKIE[self::COOKIENAME] ?? ''); 253f108b37SAndreas Gohr } 263f108b37SAndreas Gohr 273f108b37SAndreas Gohr /** 283f108b37SAndreas Gohr * Get a preference from the cookie 293f108b37SAndreas Gohr * 303f108b37SAndreas Gohr * @param string $pref The preference to read 313f108b37SAndreas Gohr * @param mixed $default The default to return if no preference is set 323f108b37SAndreas Gohr * @return mixed 333f108b37SAndreas Gohr */ 343f108b37SAndreas Gohr public function get(string $pref, $default = null) 353f108b37SAndreas Gohr { 363f108b37SAndreas Gohr return $this->data[$pref] ?? $default; 373f108b37SAndreas Gohr } 383f108b37SAndreas Gohr 393f108b37SAndreas Gohr /** 403f108b37SAndreas Gohr * Set a preference 413f108b37SAndreas Gohr * 423f108b37SAndreas Gohr * This will trigger a setCookie header and needs to be called before any output is sent 433f108b37SAndreas Gohr * 443f108b37SAndreas Gohr * @param string $pref The preference to set 453f108b37SAndreas Gohr * @param string|null $value The value to set. Null to delete a value 463f108b37SAndreas Gohr * @return void 473f108b37SAndreas Gohr */ 483f108b37SAndreas Gohr public function set(string $pref, ?string $value): void 493f108b37SAndreas Gohr { 503f108b37SAndreas Gohr if ($value === null) { 513f108b37SAndreas Gohr if (isset($this->data[$pref])) { 523f108b37SAndreas Gohr unset($this->data[$pref]); 533f108b37SAndreas Gohr } 543f108b37SAndreas Gohr } else { 553f108b37SAndreas Gohr $this->data[$pref] = $value; 563f108b37SAndreas Gohr } 573f108b37SAndreas Gohr 583f108b37SAndreas Gohr $this->sendCookie(); 593f108b37SAndreas Gohr } 603f108b37SAndreas Gohr 613f108b37SAndreas Gohr /** 623f108b37SAndreas Gohr * Set the cookie header 633f108b37SAndreas Gohr * 643f108b37SAndreas Gohr * @return void 653f108b37SAndreas Gohr */ 663f108b37SAndreas Gohr protected function sendCookie(): void 673f108b37SAndreas Gohr { 683f108b37SAndreas Gohr global $conf; 693f108b37SAndreas Gohr 703f108b37SAndreas Gohr ksort($this->data); // sort by key 713f108b37SAndreas Gohr $olddata = $_COOKIE[self::COOKIENAME] ?? ''; 723f108b37SAndreas Gohr $newdata = self::encodeData($this->data); 733f108b37SAndreas Gohr 743f108b37SAndreas Gohr // no need to set a cookie when it's the same as before 753f108b37SAndreas Gohr if ($olddata == $newdata) return; 763f108b37SAndreas Gohr 773f108b37SAndreas Gohr // update the cookie data for the current request 783f108b37SAndreas Gohr $_COOKIE[self::COOKIENAME] = $newdata; 793f108b37SAndreas Gohr 803f108b37SAndreas Gohr // no cookies to set when running on CLI 813f108b37SAndreas Gohr if (PHP_SAPI === 'cli') return; 823f108b37SAndreas Gohr 833f108b37SAndreas Gohr // set the cookie header 843f108b37SAndreas Gohr setcookie(self::COOKIENAME, $newdata, [ 853f108b37SAndreas Gohr 'expires' => time() + 365 * 24 * 3600, 863f108b37SAndreas Gohr 'path' => empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'], 873f108b37SAndreas Gohr 'secure' => ($conf['securecookie'] && Ip::isSsl()), 883f108b37SAndreas Gohr 'samesite' => 'Lax' 893f108b37SAndreas Gohr ]); 903f108b37SAndreas Gohr } 913f108b37SAndreas Gohr 923f108b37SAndreas Gohr /** 933f108b37SAndreas Gohr * Decode the cookie data (if any) 943f108b37SAndreas Gohr * 953f108b37SAndreas Gohr * @return array the cookie data as associative array 963f108b37SAndreas Gohr */ 973f108b37SAndreas Gohr protected function decodeData(string $rawdata): array 983f108b37SAndreas Gohr { 993f108b37SAndreas Gohr $data = []; 1003f108b37SAndreas Gohr if ($rawdata === '') return $data; 1013f108b37SAndreas Gohr $parts = explode('#', $rawdata); 1023f108b37SAndreas Gohr $count = count($parts); 1033f108b37SAndreas Gohr 1043f108b37SAndreas Gohr for ($i = 0; $i < $count; $i += 2) { 1053f108b37SAndreas Gohr if (!isset($parts[$i + 1])) { 1063f108b37SAndreas Gohr Logger::error('Odd entries in user\'s pref cookie', $rawdata); 1073f108b37SAndreas Gohr continue; 1083f108b37SAndreas Gohr } 1093f108b37SAndreas Gohr 1103f108b37SAndreas Gohr // if the entry was duplicated, it will be overwritten. Takes care of #2721 1113f108b37SAndreas Gohr $data[urldecode($parts[$i])] = urldecode($parts[$i + 1]); 1123f108b37SAndreas Gohr } 1133f108b37SAndreas Gohr 1143f108b37SAndreas Gohr return $data; 1153f108b37SAndreas Gohr } 1163f108b37SAndreas Gohr 1173f108b37SAndreas Gohr /** 1183f108b37SAndreas Gohr * Encode the given cookie data 1193f108b37SAndreas Gohr * 1203f108b37SAndreas Gohr * @param array $data the cookie data as associative array 1213f108b37SAndreas Gohr * @return string the raw string to save in the cookie 1223f108b37SAndreas Gohr */ 1233f108b37SAndreas Gohr protected function encodeData(array $data): string 1243f108b37SAndreas Gohr { 1253f108b37SAndreas Gohr $parts = []; 1263f108b37SAndreas Gohr 1273f108b37SAndreas Gohr foreach ($data as $key => $val) { 1283f108b37SAndreas Gohr $val = (string)$val; // we only store strings 129*9399c87eSsplitbrain $parts[] = implode('#', [rawurlencode($key), rawurlencode($val)]); 1303f108b37SAndreas Gohr } 1313f108b37SAndreas Gohr 132*9399c87eSsplitbrain return implode('#', $parts); 1333f108b37SAndreas Gohr } 1343f108b37SAndreas Gohr} 135