[TikiWiki-commits] [Git][tikiwiki/tiki][29.x] 2 commits: [ENH] Add pref cookie_consent_disable_builtin_categories. Allows disabling a...
Benoit Grégoire (@benoitg) via TikiWiki-cvs <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <69c539c26ea4f_3caf42b826816@gitlab-sidekiq-low-urgency-cpu-bound-v2-6dcfbc9b5f-wbw56.mail> |
Benoit Grégoire pushed to branch 29.x at Tiki Wiki CMS Groupware / Tiki
Commits:
7647f82a by Benoit Grégoire at 2026-03-26T13:42:02+00:00
[ENH] Add pref cookie_consent_disable_builtin_categories. Allows disabling a built-in consent category. Tiki will not show the consent at all for that category, and will act as if the user refused consent for that category. This pref will be removed once tiki collects what categories were actually requested by calling tiki code, and will only ask for those that were requested.
- - - - -
e3922ccb by Benoit Grégoire at 2026-03-26T13:42:02+00:00
[FIX] Validate and restore basic cookie functionnality. Among other things, it was impossible to change consent from the the tiki-user_preferences.php (and likely anywhere else). Make sure consent lookup is all done from the same code, and spot check that is is obeyed
- - - - -
14 changed files:
- comments.php
- installer/installlib.php
- lib/CookieConsent/CookieConsentLib.php
- lib/modules/modlib.php
- lib/prefs/cookie.php
- lib/prefslib.php
- lib/setup/cookies.php
- lib/setup/javascript.php
- lib/smarty_tiki/FunctionHandler/WikiDiff.php
- lib/wiki-plugins/wikiplugin_cookieconsent.php
- lib/wiki-plugins/wikiplugin_googleanalytics.php
- templates/admin/include_login.tpl
- tiki-setup.php
- tiki-user_preferences.php
Changes:
=====================================
comments.php
=====================================
@@ -90,7 +90,7 @@ if (isset($_REQUEST['comzone'])) {
}
if ($comzone_state == 'hide' || $comzone_state == 'c') {
if ((! isset($_COOKIE['comzone']) || $_COOKIE['comzone'] == 'o') && CookieConsentLib::isCategoryAllowed(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_FUNCTIONAL)) {
- setcookie('comzone', 'c');
+ CookieConsentLib::tikiSetCookie('comzone', 'c', CookieConsentLib::BUILTIN_COOKIE_CATEGORY_FUNCTIONAL);
}
}
} else {
=====================================
installer/installlib.php
=====================================
@@ -64,6 +64,7 @@ function write_local_php($host_tiki, $user_tiki, $pass_tiki, $dbs_tiki, $client_
$filetowrite .= "// If your php installation does not not have pdo extension\n";
$filetowrite .= "// Want configurations managed at the system level or restrict some preferences? http://doc.tiki.org/System+Configuration\n";
$filetowrite .= "// \$system_configuration_file = '/etc/tiki.ini.php';\n";
+ $filetowrite .= "// If \$system_configuration_identifier is present, it MUST match one of the top sections in your ini file. That section will be the active configuration.\n";
$filetowrite .= "// \$system_configuration_identifier = 'example.com';\n\n";
fwrite($fw, $filetowrite);
fclose($fw);
=====================================
lib/CookieConsent/CookieConsentLib.php
=====================================
@@ -6,6 +6,8 @@
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
namespace Tiki\Lib\CookieConsent;
+use InvalidArgumentException;
+
class CookieConsentLib
{
// Define constants for cookie categories
@@ -46,6 +48,39 @@ class CookieConsentLib
];
}
+ private static function getDisabledCookieCategoryKeys(): array
+ {
+ $disabledCategoriesInfo = \TikiLib::lib('prefs')->getPreference('cookie_consent_disable_builtin_categories');
+ return $disabledCategoriesInfo['value'];
+ }
+
+ /**
+ * Stub function to minimize code churn. In the future this will return all consent categories that were requested
+ * by tiki code for the current request.
+ *
+ * Right now, it will return the categories not included in the temporary cookie_consent_disable_builtin_categories prefs
+ *
+ * @return array Cookie categories requested by tiki code in the current request.
+ */
+ public static function getRequestedCookieCategories(): array
+ {
+
+ $requestedCategories = self::getCookieCategories();
+
+ $disabledCategories = self::getDisabledCookieCategoryKeys();
+ if ($disabledCategories) {
+ //Strip out the disabled categories
+ foreach ($disabledCategories as $disabledCategoryKey) {
+ if (isset($requestedCategories[$disabledCategoryKey])) {
+ unset($requestedCategories[$disabledCategoryKey]);
+ } else {
+ \Feedback::error(tr("Category %0 from pref cookie_consent_disable_builtin_categories not found in: %1", $disabledCategoryKey, implode(', ', array_keys($requestedCategories))));
+ }
+ }
+ }
+ return $requestedCategories;
+ }
+
/**
* Initialize the consent preferences based on existing cookies.
*
@@ -55,21 +90,28 @@ class CookieConsentLib
{
global $tikilib, $user, $prefs;
- $consentPreferences = [
+ $defaultConsentPreferences = [
'action' => 'customized',
'consentGiven' => false, // Helps determine if the user has given consent or not
'categories' => array_map(fn() => false, array_keys(self::getCookieCategories())) // Default to false for all categories
];
+ $consentPreferences = null;
+ /* FIXME: This is incomplete. There are at least the following cases to deal with:
+ 1- The browser cookie is absent, user not logged in.
+ * Right now we ask consent systematically (can't improve until we collect the functions that request consent)
+ * The consent form is modal, and you can't do anything unless you clear it. Which you can't. This is stupid, and I can't remember another website that does this.
+ 2- The browser cookie is present, user not logged in. No need to re-ask consent, unless new categories requested (which isn't working now, but since we ask for all of them, and it's a bit unlikely to have new categories added beyond the BUILTIN ones, it may be tolerable for the time being)
+ 3- The browser cookie is absent, user logs in. Consent needs to be written back.
+ * This scenario is impossible now (the consent interface being modal) But it looks like it would work with the 2026-03-24 fixes..
+ 4- The browser cookie is present, user logs in and had no prefs set. Consent needs to be migrated over. This does not work now as far as I can tell
+ 5- The browser cookie is present, user logs in and had inconsistent prefs set. This isn't dealt with at all.
+ 6- User is or isn't logged in, consent is updated manually. This wasn't working if the user was logged-in. Fixed with the 2026-03-24 fixes for logged in users.
+ * I cannot find where an anonymous user is supposed to do this.
- // First, try to read the consent cookie from browser cookie
- $rawConsentCookie = self::getCookie(self::COOKIE_CONSENT_NAME);
- $consentCookie = $rawConsentCookie !== null ? urldecode($rawConsentCookie) : null;
+ benoitg - 2026-03-24
+ */
- if ($consentCookie) {
- $consentPreferences = json_decode(urldecode($consentCookie), true);
- return $consentPreferences;
- }
- // If the user is logged in, try to load their stored preference
+ // First, if the user is logged in, try to load their stored preference
if ($user) {
$userPreferences = $tikilib->get_user_preference($user, 'cookie_consent_user_pref', '');
if ($userPreferences) {
@@ -77,10 +119,26 @@ class CookieConsentLib
if (is_array($consentPreferences)) {
// Sync the cookie with the user preference
self::setConsentPreferences($consentPreferences);
- return $consentPreferences;
}
}
}
+
+ // Second, try to read the consent cookie from browser cookie
+ if (! $consentPreferences) {
+ $rawConsentCookie = self::getCookie(self::COOKIE_CONSENT_NAME);
+ $consentCookie = $rawConsentCookie !== null ? urldecode($rawConsentCookie) : null;
+
+ if ($consentCookie) {
+ $consentPreferences = json_decode(urldecode($consentCookie), true);
+ return $consentPreferences;
+ }
+ }
+
+ // Third, fallback to defaults
+ if (! $consentPreferences) {
+ $consentPreferences = $defaultConsentPreferences;
+ }
+
return $consentPreferences;
}
@@ -146,7 +204,7 @@ class CookieConsentLib
* @param string $category The category to check (e.g., 'analytics').
* @return bool True if the category is explicitly allowed (true), false otherwise.
*/
- public static function isCategoryAllowed(string $category)
+ public static function isCategoryAllowed(string $category): bool
{
if (! array_key_exists($category, self::getCookieCategories())) {
throw new \InvalidArgumentException('Invalid category.');
@@ -155,26 +213,28 @@ class CookieConsentLib
if (empty($category)) {
throw new \InvalidArgumentException('Category not provided.');
}
+ $disabledCategories = self::getDisabledCookieCategoryKeys();
+ if (isset($disabledCategories[$category])) {
+ //The category is disabled, act as if the user refused consent.
+ return false;
+ }
+
+ //Uncomment to debug effective cookies consent. The js variable in jqueryTiki isn't really readable from page source.
+ //echo "Cookies consent before first check: \n";var_dump(self::getConsentPreferences()); die;
+
// Retrieve the stored consent for the given category.
// getConsentPreferences($category) should return
// true (allowed), false (refused) or null (not answered).
- return self::getConsentPreferences()['categories'][$category] ?? false;
- }
+ $preference = self::getConsentPreferences()['categories'][$category];
- /**
- * Public wrapper to check if a specific category is allowed.
- *
- * @param string $category The category to check (e.g., 'analytics').
- * @return bool True if the category is allowed, false otherwise.
- */
- public static function checkAllowedCookieCategory(string $category)
- {
- return self::isCategoryAllowed($category);
+ return $preference ?? false;
}
/**
* Set a cookie at runtime with the specified category, if allowed.
*
+ * TODO: Write a simple setCookie() function to mirror getCookie(). This function's signature is what it is only for legacy reasons - benoitg - 2026-03-25
+ *
* @return bool Returns true if the cookie was set, false if consent was not given.
*/
public static function tikiSetCookie(
@@ -188,10 +248,9 @@ class CookieConsentLib
bool $secure = false,
bool $httpOnly = false
): bool {
- // Retrieve the stored consent preferences
- $preferences = self::initializeConsentPreferences();
// Check if the category is allowed
- if (isset($preferences['categories'][$category]) && $preferences['categories'][$category] === true) {
+
+ if (self::isCategoryAllowed($category)) {
self::setCookieSection(
$name,
$value,
@@ -208,7 +267,7 @@ class CookieConsentLib
private static function setCookieSection($name, $value, $section = '', $expire = 0, $path = '', $domain = '', $secure = '')
{
- global $feature_no_cookie;
+ global $feature_no_cookie; //This global is undocumented, but I can only find one place that assigns it, and it's not a global there! benoitg - 2026-06-25
if (TIKI_API) {
return;
@@ -230,6 +289,7 @@ class CookieConsentLib
}
} else {
if ($feature_no_cookie) {
+ // See note at the begining of fucntion. As far as I can tell, this is unreachable code - benoitg - 2026-06-25
$_SESSION['tiki_cookie_jar'][$name] = $value;
} else {
setcookie($name, $value, $expire, $path, $domain, $secure);
=====================================
lib/modules/modlib.php
=====================================
@@ -656,7 +656,7 @@ class ModLib extends TikiLib
if ($prefs['cookie_consent_feature'] == 'y' && $prefs['cookie_consent_disable'] !== 'y') { // check if consent required to show
if (! empty($params['cookie_consent']) && $params['cookie_consent'] === 'y') {
- if (! CookieConsentLib::checkAllowedCookieCategory(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL)) {
+ if (! CookieConsentLib::isCategoryAllowed(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL)) {
return false;
}
}
=====================================
lib/prefs/cookie.php
=====================================
@@ -4,8 +4,19 @@
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+use Tiki\Lib\CookieConsent\CookieConsentLib;
+
function prefs_cookie_list()
{
+
+ $consentCategories = CookieConsentLib::getCookieCategories();
+ $keyToName = function (array $categoryInfo): string {
+ return $categoryInfo['name'];
+ };
+ $cookieConsentDisableBuiltinCategoriesOptions = array_map($keyToName, $consentCategories);
+ unset($cookieConsentDisableBuiltinCategoriesOptions[CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL]);
+
return [
'cookie_name' => [
'name' => tra('Cookie name'),
@@ -31,7 +42,7 @@ function prefs_cookie_list()
'type' => 'text',
'size' => 35,
'perspective' => false,
- 'default' => isset($GLOBALS['tikiroot']) ? $GLOBALS['tikiroot'] : '' ,
+ 'default' => isset($GLOBALS['tikiroot']) ? $GLOBALS['tikiroot'] : '',
],
'cookie_consent_feature' => [
'name' => tra('Cookie Consent'),
@@ -103,6 +114,17 @@ function prefs_cookie_list()
'cookie_consent_feature',
],
],
+ 'cookie_consent_disable_builtin_categories' => [
+ 'name' => tra('Deactivate specific cookie categories'),
+ 'description' => tra('Interim pref. Allows disabling a built-in consent category. Tiki will not show the consent at all for that category, and will act as if the user refused consent for that category. This pref will be removed once tiki collects what categories were actually requested by calling tiki code, and will only ask for those that were requested.'),
+ 'type' => 'multilist',
+ 'options' => $cookieConsentDisableBuiltinCategoriesOptions,
+ 'default' => [],
+ 'tags' => ['experimental'],
+ 'dependencies' => [
+ 'cookie_consent_feature',
+ ],
+ ],
'cookie_refresh_rememberme' => [
'name' => tr('Refresh the remember-me cookie expiration'),
'description' => tr('Each time a user is logged in with a cookie set in a previous session, the cookie expiration date is updated.'),
=====================================
lib/prefslib.php
=====================================
@@ -107,6 +107,22 @@ class PreferencesLib
return '';
}
+ /**
+ * Clearly this was meant as the main accessor for preferences information (would be more adequately called getPreferenceInfo).
+ * It is the only place I could find that lists all the possible values
+ * for the keys contained in the preference definition (although it doesn't document them)
+ *
+ * I do not know why (historically) a true refactoring from the global $pref variable was
+ * never started.
+ *
+ * There is a TikiLib::get_preference() to directly return the value, but it has questionable fallbacks, and is independent of this code:
+ * It does not read the type, so can't be relied upon unless you know in advance it returns an array.
+ * @param [type] $name
+ * @param boolean $deps Process dependencies (does not currently enforce them, just returns information)
+ * @param ?array $source Is set, will look for the preference value in this variable instead of the global $pref array.
+ * @param boolean $get_pages If true, will return the result of $this->getPreferenceLocations($name) in the 'pages' key.
+ * @return false on failure or an array of information on the preference. That actual value is in the 'value' key.
+ */
public function getPreference($name, $deps = true, $source = null, $get_pages = false)
{
global $prefs, $systemConfiguration;
@@ -117,7 +133,7 @@ class PreferencesLib
return false;
}
$defaults = [
- 'type' => '',
+ 'type' => '', //Possible values appear to be 'flag', 'list' as in selection from a list as opposed to array, 'multilist' (as in array), 'text', 'textarea', 'password', 'multicheckbox', 'group'
'helpurl' => '',
'help' => '',
'adminurl' => 'tiki-admin.php?lm_criteria=' . urlencode($name) . '&exact',
=====================================
lib/setup/cookies.php
=====================================
@@ -30,6 +30,10 @@ if (isset($_SESSION['tiki_cookie_jar'])) {
$smarty->assign_by_ref('cookie', $_COOKIE);
+/**
+ * This seems to be the mirror function of CookieConsentLib::setCookieSection(), but
+ * I am not 100% sure since CookieConsentLib has it's own setCookie() method - benoitg - 2026-03-26
+ */
function getCookie($name, $section = null, $default = null)
{
global $feature_no_cookie, $jitCookie;
=====================================
lib/setup/javascript.php
=====================================
@@ -240,7 +240,7 @@ $jqueryTiki['cookie_consent_dom_id'] = $prefs['cookie_consent_dom_id'];
$jqueryTiki['cookie_consent_mode'] = $prefs['cookie_consent_mode'];
$jqueryTiki['cookie_consent_expires'] = $prefs['cookie_consent_expires'];
$jqueryTiki['cookie_consent_name'] = CookieConsentLib::COOKIE_CONSENT_NAME;
-$jqueryTiki['cookie_consent_categories'] = json_encode(array_keys(CookieConsentLib::getCookieCategories()));
+$jqueryTiki['cookie_consent_categories'] = json_encode(array_keys(CookieConsentLib::getRequestedCookieCategories()));
$jqueryTiki['cookie_consent_value'] = json_encode(CookieConsentLib::getConsentPreferences(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$jqueryTiki['BUILTIN_COOKIE_CATEGORY_ESSENTIAL'] = json_encode(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL);
$jqueryTiki['wiki_url_scheme'] = $prefs['wiki_url_scheme'];
=====================================
lib/smarty_tiki/FunctionHandler/WikiDiff.php
=====================================
@@ -75,7 +75,7 @@ class WikiDiff extends Base
$html = $smarty->fetch('pagehistory.tpl');
return $html;
- } else if ($params['object_type'] === 'direct') {
+ } elseif ($params['object_type'] === 'direct') {
require_once('lib/Diff/difflib.php');
$diff = diff2($params['oldver'], $params['newver'], $params['diff_style']);
$result = '';
=====================================
lib/wiki-plugins/wikiplugin_cookieconsent.php
=====================================
@@ -66,7 +66,7 @@ function wikiplugin_cookieconsent($body, $params)
$class = $params['element_class'];
- if (! CookieConsentLib::checkAllowedCookieCategory(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL)) {
+ if (! CookieConsentLib::isCategoryAllowed(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ESSENTIAL)) {
$body = '';
$class .= ($class ? ' ' : '') . $params['no_consent_class'];
}
=====================================
lib/wiki-plugins/wikiplugin_googleanalytics.php
=====================================
@@ -55,7 +55,7 @@ function wikiplugin_googleanalytics($data, $params)
if (empty($params['account'])) {
return tra('Missing parameter');
}
- if (! CookieConsentLib::checkAllowedCookieCategory(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ANALYTICS)) {
+ if (! CookieConsentLib::isCategoryAllowed(CookieConsentLib::BUILTIN_COOKIE_CATEGORY_ANALYTICS)) {
return;
}
$account = htmlspecialchars($params['account'], ENT_QUOTES);
=====================================
templates/admin/include_login.tpl
=====================================
@@ -189,6 +189,7 @@
{preference name=cookie_consent_description}
{preference name=cookie_consent_mode}
{preference name=cookie_consent_dom_id}
+ {preference name=cookie_consent_disable_builtin_categories}
{preference name=cookie_consent_disable}
</div>
</fieldset>
=====================================
tiki-setup.php
=====================================
@@ -246,6 +246,7 @@ if (! TIKI_API) {
$prefs['site_closed'] === 'y'
) {
// js disabled, so we need to set the cookies server-side
+ // DRY, this appears to be the default value of initializeConsentPreferences why do we have another copy here? benoitg - 2026-03-25
$consent = [
'consentGiven' => true,
'categories' => [
@@ -256,11 +257,16 @@ if (! TIKI_API) {
],
];
CookieConsentLib::setConsentPreferences($consent);
- $feature_no_cookie = false;
+ $feature_no_cookie = false; //This isn't a global here, so this set will have no effect! - benoitg - 2026-03-25
}
- // Retrieve the full consent object (This check first the browser and then user cookie consent prefs)
+ // Retrieve the full consent object
$consent_preferences = CookieConsentLib::initializeConsentPreferences();
+ /*
+ I don't understand why CookieConsentLib doesn't deal with this at init.
+ Especially since it's not clearing the cookies for a category if one withdraws consent from
+ tiki-user_preferences.php, so tiki essentially lies in that situation - benoitg - 2026-03-25
+ */
if (! $consent_preferences['consentGiven'] || $jitRequest->offsetExists('cookie_consent')) {
if (! $jitRequest->offsetExists('cookie_consent')) {
foreach ($_COOKIE as $k => $v) {
@@ -270,8 +276,7 @@ if (! TIKI_API) {
}
}
// Get cookie categories
- $cookie_categories = CookieConsentLib::getCookieCategories();
- $smarty->assign('cookie_categories', $cookie_categories);
+ $smarty->assign('cookie_categories', CookieConsentLib::getRequestedCookieCategories());
$cookie_consent_html = $smarty->fetch('cookie_consent.tpl');
}
}
=====================================
tiki-user_preferences.php
=====================================
@@ -81,7 +81,7 @@ $perspectivelib = TikiLib::lib('perspective');
// Get cookie consent user preferences
$cookieConsentPrefs = CookieConsentLib::getConsentPreferences();
// Get all the existing cookie categories
-$cookieCategories = CookieConsentLib::getCookieCategories();
+$cookieCategories = CookieConsentLib::getRequestedCookieCategories();
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use PragmaRX\Google2FA\Google2FA;
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/compare/2e8f76b41e7124cecf530c18d76306988440792b...e3922ccbcbaece2bb27c31796235940fabfc945b
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/compare/2e8f76b41e7124cecf530c18d76306988440792b...e3922ccbcbaece2bb27c31796235940fabfc945b
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs