1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits of supporting
4: developers from this source code or any supporting source code which is considered
5: copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: /**
13: * Near drop-in replacement for PHP's setcookie()
14: *
15: * @copyright Copyright 2021 The XOOPS Project https://xoops.org
16: * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
17: * @author Richard Griffith <richard@geekwright.com>
18: *
19: * This exists to bring samesite support to php versions before 7.3, and
20: * it treats the default as samesite=strict
21: *
22: * It supports both of the two declared signatures:
23: * - setcookie ( string $name , string $value = "" , int $expires = 0 , string $path = "" , string $domain = "" , bool $secure = false , bool $httponly = false ) : bool
24: * - setcookie ( string $name , string $value = "" , array $options = [] ) : bool
25: */
26: function xoops_setcookie()
27: {
28: if (headers_sent()) {
29: return false;
30: }
31: $argNames = array('name', 'value', 'expires', 'path', 'domain', 'secure', 'httponly');
32: //$argDefaults = array(null, '', 0, '', '', false, false);
33: //$optionsKeys = array('expires', 'path', 'domain', 'secure', 'httponly', 'samesite');
34: $rawArgs = func_get_args();
35: $args = array();
36: foreach ($rawArgs as $key => $value) {
37: if (2 === $key && \is_array($value)) {
38: // modern call
39: $args['options'] = array();
40: foreach ($value as $optionKey => $optionValue) {
41: $args['options'][strtolower($optionKey)] = $optionValue;
42: }
43: break;
44: }
45: if ($key>1) {
46: if (null !== $value) {
47: $args['options'][$argNames[$key]] = $value;
48: }
49: } else {
50: $args[$argNames[$key]] = $value;
51: }
52: }
53:
54: // make samesite=strict the default
55: $args['options']['samesite'] = isset($args['options']['samesite']) ? $args['options']['samesite'] : 'strict';
56: if (!isset($args['value'])){
57: $args['value'] = '';
58: }
59: // after php 7.3 we just let php do it
60: if (PHP_VERSION_ID >= 70300) {
61: return setcookie($args['name'], (string)$args['value'], $args['options']);
62: }
63: // render and send our own headers below php 7.3
64: header(xoops_buildCookieHeader($args), false);
65: return true;
66: }
67:
68: /**
69: * @param array $args 'name', 'value' and 'options' corresponding to php 7.3 arguments to setcookie()
70: *
71: * @return string
72: */
73: function xoops_buildCookieHeader($args)
74: {
75: //$optionsKeys = array('expires', 'path', 'domain', 'secure', 'httponly', 'samesite');
76: $options = $args['options'];
77:
78: $header = 'Set-Cookie: ' . $args['name'] . '=' . rawurlencode($args['value']) . ' ';
79:
80: if (isset($options['expires']) && 0 !== $options['expires']) {
81: $dateTime = new DateTime();
82: if (time() >= $options['expires']) {
83: $dateTime->setTimestamp(0);
84: $header = 'Set-Cookie: ' . $args['name'] . '=deleted ; expires=' . $dateTime->format(DateTime::COOKIE) . ' ; Max-Age=0 ';
85: } else {
86: $dateTime->setTimestamp($options['expires']);
87: $header .= '; expires=' . $dateTime->format(DateTime::COOKIE) . ' ';
88: }
89: }
90:
91: if (isset($options['path']) && '' !== $options['path']) {
92: $header .= '; path=' . $options['path'] . ' ';
93: }
94:
95: if (isset($options['domain']) && '' !== $options['domain']) {
96: $header .= '; domain=' . $options['domain'] . ' ';
97: }
98:
99: if (isset($options['secure']) && true === (bool) $options['secure']) {
100: $header .= '; Secure ';
101: }
102:
103: if (isset($options['httponly']) && true === (bool) $options['httponly']) {
104: $header .= '; HttpOnly ';
105: }
106:
107: if (isset($options['samesite']) && '' !== $options['samesite']) {
108: $header .= '; samesite=' . $options['samesite'] . ' ';
109: }
110:
111: return $header;
112: }
113: