11ab40613Stracker-user<?php 21ab40613Stracker-user 31ab40613Stracker-user/** 41ab40613Stracker-user * User Settings plugin — action component. 51ab40613Stracker-user * 61ab40613Stracker-user * Provides three things: 71ab40613Stracker-user * 81ab40613Stracker-user * 1. A "Preferences" item in the user menu, placed just before "Update 91ab40613Stracker-user * Profile" (via the MENU_ITEMS_ASSEMBLY event — template-independent). 101ab40613Stracker-user * 111ab40613Stracker-user * 2. A custom action, do=usersettings, claimed in ACTION_ACT_PREPROCESS and 121ab40613Stracker-user * rendered in TPL_ACT_UNKNOWN. This is the documented way for a plugin to 131ab40613Stracker-user * own a do= value: preventing the preprocess default makes DokuWiki route 141ab40613Stracker-user * the action through dokuwiki\Action\Plugin, which fires TPL_ACT_UNKNOWN. 151ab40613Stracker-user * 161ab40613Stracker-user * 3. The settings page itself: a plain HTML form of every registered toggle, 171ab40613Stracker-user * with Post/Redirect/Get handling that saves through the helper. 181ab40613Stracker-user */ 191ab40613Stracker-user 201ab40613Stracker-user// must be run within DokuWiki 211ab40613Stracker-userif (!defined('DOKU_INC')) die(); 221ab40613Stracker-user 231ab40613Stracker-userclass action_plugin_usersettings extends DokuWiki_Action_Plugin 241ab40613Stracker-user{ 251ab40613Stracker-user /** the do= value this plugin owns */ 261ab40613Stracker-user const ACTION = 'usersettings'; 271ab40613Stracker-user 281ab40613Stracker-user /** 291ab40613Stracker-user * Register event handlers. 301ab40613Stracker-user */ 311ab40613Stracker-user public function register(Doku_Event_Handler $controller) 321ab40613Stracker-user { 331ab40613Stracker-user $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handleMenuAssembly'); 341ab40613Stracker-user $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handlePreprocess'); 351ab40613Stracker-user $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleUnknown'); 36*cc98f4d1Stracker-user 37*cc98f4d1Stracker-user // Register the built-in interface language toggle. 38*cc98f4d1Stracker-user $controller->register_hook( 39*cc98f4d1Stracker-user helper_plugin_usersettings::REGISTER_EVENT, 40*cc98f4d1Stracker-user 'BEFORE', 41*cc98f4d1Stracker-user $this, 42*cc98f4d1Stracker-user 'registerLangToggle' 43*cc98f4d1Stracker-user ); 44*cc98f4d1Stracker-user 45*cc98f4d1Stracker-user // Apply the user's language choice as early as possible so that all 46*cc98f4d1Stracker-user // DokuWiki rendering — including TPL_ hooks further down the chain — 47*cc98f4d1Stracker-user // uses the right language strings. ACTION_ACT_PREPROCESS fires before 48*cc98f4d1Stracker-user // any output is produced and before template rendering begins. 49*cc98f4d1Stracker-user $controller->register_hook( 50*cc98f4d1Stracker-user 'ACTION_ACT_PREPROCESS', 51*cc98f4d1Stracker-user 'BEFORE', 52*cc98f4d1Stracker-user $this, 53*cc98f4d1Stracker-user 'applyUserLang', 54*cc98f4d1Stracker-user null, 55*cc98f4d1Stracker-user // run at priority -10 so we fire before handlePreprocess (0) and 56*cc98f4d1Stracker-user // before anything else that might read $conf['lang'] 57*cc98f4d1Stracker-user -10 58*cc98f4d1Stracker-user ); 591ab40613Stracker-user } 601ab40613Stracker-user 611ab40613Stracker-user /** 621ab40613Stracker-user * Load the storage/registration helper. 631ab40613Stracker-user * 641ab40613Stracker-user * @return helper_plugin_usersettings|null 651ab40613Stracker-user */ 661ab40613Stracker-user protected function getHelper() 671ab40613Stracker-user { 681ab40613Stracker-user /** @var helper_plugin_usersettings|null $helper */ 691ab40613Stracker-user $helper = plugin_load('helper', 'usersettings'); 701ab40613Stracker-user return $helper; 711ab40613Stracker-user } 721ab40613Stracker-user 731ab40613Stracker-user // --------------------------------------------------------------------- 741ab40613Stracker-user // 1. The user-menu item 751ab40613Stracker-user // --------------------------------------------------------------------- 761ab40613Stracker-user 771ab40613Stracker-user /** 781ab40613Stracker-user * Insert the "Preferences" item into the user menu, just before the 791ab40613Stracker-user * "Update Profile" item. 801ab40613Stracker-user * 811ab40613Stracker-user * @param Doku_Event $event MENU_ITEMS_ASSEMBLY 821ab40613Stracker-user * @param mixed $param 831ab40613Stracker-user */ 841ab40613Stracker-user public function handleMenuAssembly(Doku_Event $event, $param) 851ab40613Stracker-user { 861ab40613Stracker-user if (!is_array($event->data) || ($event->data['view'] ?? '') !== 'user') { 871ab40613Stracker-user return; 881ab40613Stracker-user } 891ab40613Stracker-user 901ab40613Stracker-user try { 911ab40613Stracker-user $item = new \dokuwiki\plugin\usersettings\MenuItem(); 921ab40613Stracker-user } catch (\RuntimeException $e) { 931ab40613Stracker-user // anonymous visitor, or the action is disabled — no menu item 941ab40613Stracker-user return; 951ab40613Stracker-user } 961ab40613Stracker-user 971ab40613Stracker-user if (!isset($event->data['items']) || !is_array($event->data['items'])) { 981ab40613Stracker-user return; 991ab40613Stracker-user } 1001ab40613Stracker-user $items =& $event->data['items']; 1011ab40613Stracker-user 1021ab40613Stracker-user // find the Profile item; default to appending if it is not present 1031ab40613Stracker-user $pos = count($items); 1041ab40613Stracker-user foreach ($items as $i => $existing) { 1051ab40613Stracker-user if ($existing instanceof \dokuwiki\Menu\Item\Profile) { 1061ab40613Stracker-user $pos = $i; 1071ab40613Stracker-user break; 1081ab40613Stracker-user } 1091ab40613Stracker-user } 1101ab40613Stracker-user array_splice($items, $pos, 0, [$item]); 1111ab40613Stracker-user } 1121ab40613Stracker-user 1131ab40613Stracker-user // --------------------------------------------------------------------- 1141ab40613Stracker-user // 2. Claiming the custom action + handling the save 1151ab40613Stracker-user // --------------------------------------------------------------------- 1161ab40613Stracker-user 1171ab40613Stracker-user /** 1181ab40613Stracker-user * Claim do=usersettings and, on a form submission, save and redirect. 1191ab40613Stracker-user * 1201ab40613Stracker-user * @param Doku_Event $event ACTION_ACT_PREPROCESS 1211ab40613Stracker-user * @param mixed $param 1221ab40613Stracker-user */ 1231ab40613Stracker-user public function handlePreprocess(Doku_Event $event, $param) 1241ab40613Stracker-user { 1251ab40613Stracker-user if ($event->data !== self::ACTION) { 1261ab40613Stracker-user return; 1271ab40613Stracker-user } 1281ab40613Stracker-user 1291ab40613Stracker-user // Preventing the default makes DokuWiki keep the action and route it 1301ab40613Stracker-user // through dokuwiki\Action\Plugin, which will fire TPL_ACT_UNKNOWN. 1311ab40613Stracker-user $event->preventDefault(); 1321ab40613Stracker-user $event->stopPropagation(); 1331ab40613Stracker-user 1341ab40613Stracker-user global $INPUT, $ID; 1351ab40613Stracker-user 1361ab40613Stracker-user $user = $INPUT->server->str('REMOTE_USER'); 1371ab40613Stracker-user if ($user === '') { 1381ab40613Stracker-user return; // anonymous — the rendered page shows a login notice 1391ab40613Stracker-user } 1401ab40613Stracker-user 1411ab40613Stracker-user // not a save submission — nothing to do, the page will just render 1421ab40613Stracker-user if (!$INPUT->post->bool('usersettings_save')) { 1431ab40613Stracker-user return; 1441ab40613Stracker-user } 1451ab40613Stracker-user 1461ab40613Stracker-user // CSRF protection; checkSecurityToken() shows its own error on failure 1471ab40613Stracker-user if (!checkSecurityToken()) { 1481ab40613Stracker-user return; 1491ab40613Stracker-user } 1501ab40613Stracker-user 1511ab40613Stracker-user $ok = $this->saveSubmittedPreferences($user); 1521ab40613Stracker-user msg($this->getLang($ok ? 'saved' : 'savefail'), $ok ? 1 : -1); 1531ab40613Stracker-user 1541ab40613Stracker-user // Post/Redirect/Get: a refresh must not re-submit the form 1551ab40613Stracker-user send_redirect(wl($ID, ['do' => self::ACTION], true, '&')); 1561ab40613Stracker-user } 1571ab40613Stracker-user 1581ab40613Stracker-user /** 1591ab40613Stracker-user * Read the submitted toggle values for every registered toggle and store 1601ab40613Stracker-user * them for the given user. 1611ab40613Stracker-user * 1621ab40613Stracker-user * Kept separate from handlePreprocess() so it carries no redirect and can 1631ab40613Stracker-user * be exercised directly by tests. Checkboxes that are unchecked do not 1641ab40613Stracker-user * appear in the POST data, so every registered toggle is read explicitly 1651ab40613Stracker-user * rather than iterating whatever was submitted. 1661ab40613Stracker-user * 1671ab40613Stracker-user * @param string $user whose preferences are being written 1681ab40613Stracker-user * @param string|null $actor who is making the change; defaults to $user 1691ab40613Stracker-user * (the admin component passes the admin here) 1701ab40613Stracker-user * @return bool 1711ab40613Stracker-user */ 1721ab40613Stracker-user public function saveSubmittedPreferences($user, $actor = null) 1731ab40613Stracker-user { 1741ab40613Stracker-user global $INPUT; 1751ab40613Stracker-user 1761ab40613Stracker-user if ($actor === null) { 1771ab40613Stracker-user $actor = $user; 1781ab40613Stracker-user } 1791ab40613Stracker-user 1801ab40613Stracker-user $helper = $this->getHelper(); 1811ab40613Stracker-user if ($helper === null) { 1821ab40613Stracker-user return false; 1831ab40613Stracker-user } 1841ab40613Stracker-user 1851ab40613Stracker-user $values = []; 1861ab40613Stracker-user foreach ($helper->getRegisteredToggles() as $key => $def) { 1871ab40613Stracker-user if ($def['type'] === 'checkbox') { 1881ab40613Stracker-user $values[$key] = $INPUT->post->bool($key) ? 1 : 0; 1891ab40613Stracker-user } else { 1901ab40613Stracker-user $values[$key] = $INPUT->post->str($key); 1911ab40613Stracker-user } 1921ab40613Stracker-user } 1931ab40613Stracker-user 1941ab40613Stracker-user return $helper->setPreferences($values, $user, $actor); 1951ab40613Stracker-user } 1961ab40613Stracker-user 1971ab40613Stracker-user // --------------------------------------------------------------------- 1981ab40613Stracker-user // 3. Rendering the settings page 1991ab40613Stracker-user // --------------------------------------------------------------------- 2001ab40613Stracker-user 2011ab40613Stracker-user /** 2021ab40613Stracker-user * Render the settings page for do=usersettings. 2031ab40613Stracker-user * 2041ab40613Stracker-user * @param Doku_Event $event TPL_ACT_UNKNOWN 2051ab40613Stracker-user * @param mixed $param 2061ab40613Stracker-user */ 2071ab40613Stracker-user public function handleUnknown(Doku_Event $event, $param) 2081ab40613Stracker-user { 2091ab40613Stracker-user if ($event->data !== self::ACTION) { 2101ab40613Stracker-user return; 2111ab40613Stracker-user } 2121ab40613Stracker-user $event->preventDefault(); 2131ab40613Stracker-user $event->stopPropagation(); 2141ab40613Stracker-user 2151ab40613Stracker-user echo $this->renderSettingsPage(); 2161ab40613Stracker-user } 2171ab40613Stracker-user 2181ab40613Stracker-user /** 2191ab40613Stracker-user * Build the HTML of the settings page. 2201ab40613Stracker-user * 2211ab40613Stracker-user * @return string 2221ab40613Stracker-user */ 2231ab40613Stracker-user public function renderSettingsPage() 2241ab40613Stracker-user { 2251ab40613Stracker-user global $INPUT, $ID; 2261ab40613Stracker-user 2271ab40613Stracker-user $user = $INPUT->server->str('REMOTE_USER'); 2281ab40613Stracker-user 2291ab40613Stracker-user $html = '<div class="plugin_usersettings">'; 2301ab40613Stracker-user $html .= '<h1>' . hsc($this->getLang('heading')) . '</h1>'; 2311ab40613Stracker-user 2321ab40613Stracker-user if ($user === '') { 2331ab40613Stracker-user $html .= '<p>' . hsc($this->getLang('nologin')) . '</p>'; 2341ab40613Stracker-user return $html . '</div>'; 2351ab40613Stracker-user } 2361ab40613Stracker-user 2371ab40613Stracker-user $helper = $this->getHelper(); 2381ab40613Stracker-user $toggles = $helper ? $helper->getRegisteredToggles() : []; 2391ab40613Stracker-user 2401ab40613Stracker-user if (empty($toggles)) { 2411ab40613Stracker-user $html .= '<p>' . hsc($this->getLang('notoggles')) . '</p>'; 2421ab40613Stracker-user return $html . '</div>'; 2431ab40613Stracker-user } 2441ab40613Stracker-user 2451ab40613Stracker-user $html .= '<p class="us-intro">' . hsc($this->getLang('intro')) . '</p>'; 2461ab40613Stracker-user 2471ab40613Stracker-user $action = wl($ID, ['do' => self::ACTION], false, '&'); 2481ab40613Stracker-user $html .= '<form method="post" action="' . $action . '" class="us-form">'; 2491ab40613Stracker-user $html .= formSecurityToken(false); 2501ab40613Stracker-user 2511ab40613Stracker-user foreach ($toggles as $key => $def) { 2521ab40613Stracker-user $html .= $this->renderToggleRow($def, $helper->getPreference($key, $user)); 2531ab40613Stracker-user } 2541ab40613Stracker-user 2551ab40613Stracker-user $html .= '<div class="us-actions">'; 2561ab40613Stracker-user $html .= '<button type="submit" name="usersettings_save" value="1" class="button">' 2571ab40613Stracker-user . hsc($this->getLang('save')) . '</button>'; 2581ab40613Stracker-user $html .= '</div>'; 2591ab40613Stracker-user $html .= '</form>'; 2601ab40613Stracker-user 2611ab40613Stracker-user return $html . '</div>'; 2621ab40613Stracker-user } 2631ab40613Stracker-user 264*cc98f4d1Stracker-user // --------------------------------------------------------------------- 265*cc98f4d1Stracker-user // Built-in: interface language toggle 266*cc98f4d1Stracker-user // --------------------------------------------------------------------- 267*cc98f4d1Stracker-user 268*cc98f4d1Stracker-user /** 269*cc98f4d1Stracker-user * Contribute the "Interface language" select to the usersettings registry. 270*cc98f4d1Stracker-user * 271*cc98f4d1Stracker-user * The option list is built by scanning DOKU_INC/inc/lang/ for sub- 272*cc98f4d1Stracker-user * directories that contain a lang.php file — the same source the 273*cc98f4d1Stracker-user * Configuration Manager uses for its own language drop-down. The scan 274*cc98f4d1Stracker-user * result is sorted alphabetically by language code; the site default is 275*cc98f4d1Stracker-user * used as the toggle's default value so the toggle appears pre-selected 276*cc98f4d1Stracker-user * correctly for users who have never changed it. 277*cc98f4d1Stracker-user * 278*cc98f4d1Stracker-user * @param Doku_Event $event PLUGIN_USERSETTINGS_REGISTER 279*cc98f4d1Stracker-user * @param mixed $param 280*cc98f4d1Stracker-user */ 281*cc98f4d1Stracker-user public function registerLangToggle(Doku_Event $event, $param) 282*cc98f4d1Stracker-user { 283*cc98f4d1Stracker-user global $conf; 284*cc98f4d1Stracker-user 285*cc98f4d1Stracker-user $options = $this->getAvailableLanguages(); 286*cc98f4d1Stracker-user if (empty($options)) { 287*cc98f4d1Stracker-user return; // nothing to register if we cannot list languages 288*cc98f4d1Stracker-user } 289*cc98f4d1Stracker-user 290*cc98f4d1Stracker-user $siteDefault = $conf['lang'] ?? 'en'; 291*cc98f4d1Stracker-user if (!array_key_exists($siteDefault, $options)) { 292*cc98f4d1Stracker-user $siteDefault = array_key_first($options); 293*cc98f4d1Stracker-user } 294*cc98f4d1Stracker-user 295*cc98f4d1Stracker-user $event->data[] = [ 296*cc98f4d1Stracker-user 'key' => 'lang', 297*cc98f4d1Stracker-user 'label' => $this->getLang('lang_label'), 298*cc98f4d1Stracker-user 'desc' => $this->getLang('lang_desc'), 299*cc98f4d1Stracker-user 'type' => 'select', 300*cc98f4d1Stracker-user 'options' => $options, 301*cc98f4d1Stracker-user 'default' => $siteDefault, 302*cc98f4d1Stracker-user 'plugin' => 'usersettings', 303*cc98f4d1Stracker-user ]; 304*cc98f4d1Stracker-user } 305*cc98f4d1Stracker-user 306*cc98f4d1Stracker-user /** 307*cc98f4d1Stracker-user * Build the [code => display name] map of all installed DokuWiki interface 308*cc98f4d1Stracker-user * languages by scanning inc/lang/. The display name is the language code 309*cc98f4d1Stracker-user * itself (e.g. "en", "de", "fr") — consistent with how the Configuration 310*cc98f4d1Stracker-user * Manager presents the option. 311*cc98f4d1Stracker-user * 312*cc98f4d1Stracker-user * @return array [langCode => langCode] sorted by language code 313*cc98f4d1Stracker-user */ 314*cc98f4d1Stracker-user protected function getAvailableLanguages() 315*cc98f4d1Stracker-user { 316*cc98f4d1Stracker-user $pattern = DOKU_INC . 'inc/lang/*/lang.php'; 317*cc98f4d1Stracker-user $files = glob($pattern); 318*cc98f4d1Stracker-user if ($files === false || empty($files)) { 319*cc98f4d1Stracker-user return []; 320*cc98f4d1Stracker-user } 321*cc98f4d1Stracker-user 322*cc98f4d1Stracker-user $langs = []; 323*cc98f4d1Stracker-user foreach ($files as $file) { 324*cc98f4d1Stracker-user $code = basename(dirname($file)); // e.g. "de" from ".../inc/lang/de/lang.php" 325*cc98f4d1Stracker-user if ($code === '' || $code === '.' || $code === '..') { 326*cc98f4d1Stracker-user continue; 327*cc98f4d1Stracker-user } 328*cc98f4d1Stracker-user $langs[$code] = $code; 329*cc98f4d1Stracker-user } 330*cc98f4d1Stracker-user 331*cc98f4d1Stracker-user ksort($langs, SORT_STRING); 332*cc98f4d1Stracker-user return $langs; 333*cc98f4d1Stracker-user } 334*cc98f4d1Stracker-user 335*cc98f4d1Stracker-user /** 336*cc98f4d1Stracker-user * Apply the logged-in user's preferred interface language, overriding the 337*cc98f4d1Stracker-user * site-wide $conf['lang'] before any rendering takes place. 338*cc98f4d1Stracker-user * 339*cc98f4d1Stracker-user * DokuWiki loads language strings lazily (via getLang() / $lang global 340*cc98f4d1Stracker-user * reloads triggered by calls to init_lang()), so changing $conf['lang'] 341*cc98f4d1Stracker-user * here — in the earliest ACTION_ACT_PREPROCESS handler — is sufficient 342*cc98f4d1Stracker-user * to affect all subsequent output. 343*cc98f4d1Stracker-user * 344*cc98f4d1Stracker-user * No-op for anonymous visitors or when the user has not chosen a language 345*cc98f4d1Stracker-user * that differs from the site default. 346*cc98f4d1Stracker-user * 347*cc98f4d1Stracker-user * @param Doku_Event $event ACTION_ACT_PREPROCESS 348*cc98f4d1Stracker-user * @param mixed $param 349*cc98f4d1Stracker-user */ 350*cc98f4d1Stracker-user public function applyUserLang(Doku_Event $event, $param) 351*cc98f4d1Stracker-user { 352*cc98f4d1Stracker-user global $conf, $INPUT; 353*cc98f4d1Stracker-user 354*cc98f4d1Stracker-user $user = $INPUT->server->str('REMOTE_USER'); 355*cc98f4d1Stracker-user if ($user === '') { 356*cc98f4d1Stracker-user return; // anonymous — use the site default 357*cc98f4d1Stracker-user } 358*cc98f4d1Stracker-user 359*cc98f4d1Stracker-user $helper = $this->getHelper(); 360*cc98f4d1Stracker-user if ($helper === null) { 361*cc98f4d1Stracker-user return; 362*cc98f4d1Stracker-user } 363*cc98f4d1Stracker-user 364*cc98f4d1Stracker-user $preferred = $helper->getPreference('lang', $user); 365*cc98f4d1Stracker-user if ($preferred === null || $preferred === '' || $preferred === $conf['lang']) { 366*cc98f4d1Stracker-user return; // no preference stored or already correct 367*cc98f4d1Stracker-user } 368*cc98f4d1Stracker-user 369*cc98f4d1Stracker-user // Validate: only apply if the directory actually exists to avoid a 370*cc98f4d1Stracker-user // broken page when someone stores a stale language code. 371*cc98f4d1Stracker-user $langDir = DOKU_INC . 'inc/lang/' . $preferred; 372*cc98f4d1Stracker-user if (!is_dir($langDir)) { 373*cc98f4d1Stracker-user return; 374*cc98f4d1Stracker-user } 375*cc98f4d1Stracker-user 376*cc98f4d1Stracker-user $conf['lang'] = $preferred; 377*cc98f4d1Stracker-user 378*cc98f4d1Stracker-user // Re-initialise the global $lang array so immediately-following 379*cc98f4d1Stracker-user // getLang() calls within this request pick up the new language. 380*cc98f4d1Stracker-user init_lang($preferred); 381*cc98f4d1Stracker-user } 382*cc98f4d1Stracker-user 383*cc98f4d1Stracker-user // --------------------------------------------------------------------- 384*cc98f4d1Stracker-user // Form rendering (shared between action and admin) 385*cc98f4d1Stracker-user // --------------------------------------------------------------------- 386*cc98f4d1Stracker-user 3871ab40613Stracker-user /** 3881ab40613Stracker-user * Render one toggle as a form row. Public so the admin component can 3891ab40613Stracker-user * reuse it for its per-user edit form. 3901ab40613Stracker-user * 3911ab40613Stracker-user * @param array $def a normalised toggle definition 3921ab40613Stracker-user * @param mixed $value the user's effective value for this toggle 3931ab40613Stracker-user * @return string 3941ab40613Stracker-user */ 3951ab40613Stracker-user public function renderToggleRow(array $def, $value) 3961ab40613Stracker-user { 3971ab40613Stracker-user $key = hsc($def['key']); 3981ab40613Stracker-user 3991ab40613Stracker-user if ($def['type'] === 'select') { 4001ab40613Stracker-user $id = 'us__' . $key; 4011ab40613Stracker-user $html = '<div class="us-row us-row-select">'; 4021ab40613Stracker-user $html .= '<label class="us-label" for="' . $id . '">'; 4031ab40613Stracker-user $html .= '<span class="us-name">' . hsc($def['label']) . '</span>'; 4041ab40613Stracker-user $html .= '<select name="' . $key . '" id="' . $id . '">'; 4051ab40613Stracker-user foreach ($def['options'] as $optValue => $optLabel) { 4061ab40613Stracker-user $selected = ((string) $optValue === (string) $value) ? ' selected="selected"' : ''; 4071ab40613Stracker-user $html .= '<option value="' . hsc((string) $optValue) . '"' . $selected . '>' 4081ab40613Stracker-user . hsc((string) $optLabel) . '</option>'; 4091ab40613Stracker-user } 4101ab40613Stracker-user $html .= '</select>'; 4111ab40613Stracker-user $html .= '</label>'; 4121ab40613Stracker-user } else { 4131ab40613Stracker-user $checked = empty($value) ? '' : ' checked="checked"'; 4141ab40613Stracker-user $html = '<div class="us-row us-row-checkbox">'; 4151ab40613Stracker-user $html .= '<label class="us-label">'; 4161ab40613Stracker-user $html .= '<input type="checkbox" name="' . $key . '" value="1"' . $checked . ' />'; 4171ab40613Stracker-user $html .= '<span class="us-name">' . hsc($def['label']) . '</span>'; 4181ab40613Stracker-user $html .= '</label>'; 4191ab40613Stracker-user } 4201ab40613Stracker-user 4211ab40613Stracker-user if ($def['desc'] !== '') { 4221ab40613Stracker-user $html .= '<div class="us-desc">' . hsc($def['desc']) . '</div>'; 4231ab40613Stracker-user } 4241ab40613Stracker-user 4251ab40613Stracker-user return $html . '</div>'; 4261ab40613Stracker-user } 4271ab40613Stracker-user} 428