1
0
Эх сурвалжийг харах

Merge branch 'master' of https://github.com/unacms/UNA

AntonLV 2 сар өмнө
parent
commit
ee0931e678
44 өөрчлөгдсөн 2792 нэмэгдсэн , 214 устгасан
  1. 6 1
      SECURITY.md
  2. 3 1
      inc/classes/BxDolAlerts.php
  3. 1 0
      inc/classes/BxDolPage.php
  4. 14 17
      inc/classes/BxDolService.php
  5. 112 0
      inc/classes/BxDolServiceCallCodec.php
  6. 72 5
      index.php
  7. 7 4
      install/classes/BxDolInstallController.php
  8. 14 2
      install/classes/BxDolInstallLang.php
  9. 5 0
      install/classes/BxDolInstallView.php
  10. 642 50
      install/css/styles.css
  11. 57 16
      install/img/logo.svg
  12. 32 51
      install/templates/_page.php
  13. 13 0
      install/templates/audit.php
  14. 1 6
      install/templates/finish.php
  15. 4 4
      install/templates/initial.php
  16. 0 2
      install/templates/initial_fail.php
  17. 45 5
      install/templates/lang_swither.php
  18. 3 0
      install/templates/permissions.php
  19. 1 1
      install/templates/remove_install.php
  20. 21 0
      modules/boonex/cidaas_connect/classes/BxCidaasConAlerts.php
  21. 54 0
      modules/boonex/cidaas_connect/classes/BxCidaasConConfig.php
  22. 20 0
      modules/boonex/cidaas_connect/classes/BxCidaasConDb.php
  23. 235 0
      modules/boonex/cidaas_connect/classes/BxCidaasConModule.php
  24. 104 0
      modules/boonex/cidaas_connect/classes/BxCidaasConProvider.php
  25. 31 0
      modules/boonex/cidaas_connect/classes/BxCidaasConStudioPage.php
  26. 20 0
      modules/boonex/cidaas_connect/classes/BxCidaasConTemplate.php
  27. 13 0
      modules/boonex/cidaas_connect/composer.json
  28. 785 0
      modules/boonex/cidaas_connect/composer.lock
  29. 68 0
      modules/boonex/cidaas_connect/install/config.php
  30. 20 0
      modules/boonex/cidaas_connect/install/installer.php
  31. 34 0
      modules/boonex/cidaas_connect/install/langs/en.xml
  32. 20 0
      modules/boonex/cidaas_connect/install/sql/disable.sql
  33. 45 0
      modules/boonex/cidaas_connect/install/sql/enable.sql
  34. 21 0
      modules/boonex/cidaas_connect/install/sql/install.sql
  35. 8 0
      modules/boonex/cidaas_connect/install/sql/uninstall.sql
  36. 18 0
      modules/boonex/cidaas_connect/request.php
  37. 75 0
      modules/boonex/cidaas_connect/template/images/icons/std-icon.svg
  38. 25 17
      modules/boonex/english/data/langs/system/en.xml
  39. 25 17
      modules/boonex/russian/data/langs/system/ru.xml
  40. 1 1
      studio/classes/BxDolStudioTools.php
  41. 31 9
      studio/classes/BxDolStudioToolsAudit.php
  42. 4 2
      studio/classes/BxDolStudioWidgets.php
  43. 1 3
      template/scripts/BxBaseMenuSetAclLevel.php
  44. 81 0
      tests/units/inc/classes/BxDolServiceCallCodecTest.php

+ 6 - 1
SECURITY.md

@@ -10,4 +10,9 @@
 
 ## Reporting a Vulnerability
 
-Please, email us at team@unacms.com to report any security, privacy or performance issues.
+Please report security issues privately. Do not open a public GitHub issue.
+
+1. Prefer [GitHub private vulnerability reporting](https://github.com/unacms/UNA/security/advisories/new) on this repository (creates a draft GHSA the maintainers can publish after a fix).
+2. Or email [team@unacms.com](mailto:team@unacms.com).
+
+We will acknowledge the report and follow up on a fix. Reporters who want credit can be named on the advisory when it is published.

+ 3 - 1
inc/classes/BxDolAlerts.php

@@ -121,7 +121,9 @@ class BxDolAlerts extends BxDol
                     $oHandler = new $aHandler['class']();
                     $oHandler->response($this);
                 } else if(!empty($aHandler['service_call']) && BxDolService::isSerializedService($aHandler['service_call'])) {
-                    $aService = unserialize($aHandler['service_call']);
+                    $aService = BxDolService::decodeServiceCall($aHandler['service_call']);
+                    if (false === $aService)
+                        continue;
 
                     $aParams = array($this);
                     if(isset($aService['params']) && is_array($aService['params']))

+ 1 - 0
inc/classes/BxDolPage.php

@@ -496,6 +496,7 @@ class BxDolPage extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
         $aSeoParams = array('id', 'profile_id'); // supported SEO params which are transformed to SEO strings
         $sSeoParamName = '';
         $sSeoParamValue = '';
+        $sSeoTitle = '';
         if ('cmts-view' != $sPageUri) {
             foreach ($aSeoParams as $k) {
                 if (empty($aQueryParams[$k]) && empty($aParams[$k]))

+ 14 - 17
inc/classes/BxDolService.php

@@ -72,8 +72,8 @@ class BxDolService extends BxDol
      */
     public static function callSerialized($s, $aMarkers = array(), $sReplaceIn = 'params')
     {
-        $a = @unserialize($s);
-        if (false === $a || !is_array($a))
+        $a = self::decodeServiceCall($s);
+        if (false === $a)
             return '';
 
         if (isset($a[$sReplaceIn]) && $aMarkers)
@@ -134,30 +134,27 @@ class BxDolService extends BxDol
     }
 
     /**
-     * Check if string is serialized array
+     * Check if string is a stored service call (JSON or historic PHP serialize).
      */
     public static function isSerializedService($s)
     {
-        return preg_match('/^a:[\d+]:\{/', $s);
+        return BxDolServiceCallCodec::isEncoded($s);
     }
 
     /**
-     * Serialized service call array
+     * Decode a stored service-call blob. Arrays/scalars only; objects are rejected.
      */
-    public static function getSerializedService($mixedModule, $sMethod, $aParams = array(), $sClass = '')
+    public static function decodeServiceCall($s)
     {
-		$aService = array(
-			'module' => $mixedModule,
-			'method' => $sMethod,
-		);
-
-		if(!empty($aParams))
-			$aService['params'] = $aParams;
-
-		if(!empty($sClass))
-			$aService['class'] = $sClass;
+        return BxDolServiceCallCodec::decode($s);
+    }
 
-		return serialize($aService);
+    /**
+     * Encode a service call for storage. New writes are JSON, not PHP serialize.
+     */
+    public static function getSerializedService($mixedModule, $sMethod, $aParams = array(), $sClass = '')
+    {
+        return BxDolServiceCallCodec::encode($mixedModule, $sMethod, $aParams, $sClass);
     }
 
     protected static function getModule($mixed)

+ 112 - 0
inc/classes/BxDolServiceCallCodec.php

@@ -0,0 +1,112 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * Encode/decode stored service-call blobs.
+ * New writes are JSON. Old PHP-serialized arrays still decode.
+ * Objects are never instantiated (allowed_classes => false + plain-data check).
+ *
+ * Writers of these blobs (inventory, 18 Aug 2026):
+ * - BxDolService::getSerializedService — used by cron transient jobs, background jobs,
+ *   live updates (comments/timeline), search-extended field values, ads form values_src.
+ * - DB tables: sys_cron_jobs.service_call, background jobs.service_call,
+ *   sys_alerts_handlers.service_call, studio widget cnt_notices, live-update service_call.
+ * - Studio/admin paths write alerts + widgets. A lower-privilege INSERT into those
+ *   columns is still "call any module method". This codec blocks object injection,
+ *   not an authorized service call.
+ */
+
+class BxDolServiceCallCodec
+{
+    /**
+     * JSON-encode a service-call array for new writes.
+     */
+    public static function encode($mixedModule, $sMethod, $aParams = array(), $sClass = '')
+    {
+        $aService = array(
+            'module' => $mixedModule,
+            'method' => $sMethod,
+        );
+        if (!empty($aParams))
+            $aService['params'] = $aParams;
+        if (!empty($sClass))
+            $aService['class'] = $sClass;
+
+        return json_encode($aService, JSON_UNESCAPED_UNICODE);
+    }
+
+    /**
+     * True for a PHP-serialized array service call or a JSON object with module+method.
+     */
+    public static function isEncoded($s)
+    {
+        return false !== self::decode($s);
+    }
+
+    /**
+     * Decode a stored blob. Returns an array or false.
+     * Never instantiates PHP objects from the payload.
+     */
+    public static function decode($s)
+    {
+        if (!is_string($s) || $s === '')
+            return false;
+
+        $s = ltrim($s);
+        if ($s === '')
+            return false;
+
+        if ($s[0] === '{') {
+            $a = json_decode($s, true);
+            return self::isServiceArray($a) ? $a : false;
+        }
+
+        // Historic PHP serialize of an array: a:N:{...}
+        if (!preg_match('/^a:\d+:\{/', $s))
+            return false;
+
+        $a = @unserialize($s, array('allowed_classes' => false));
+        return self::isServiceArray($a) ? $a : false;
+    }
+
+    /**
+     * Same decode rules as BxBaseMenuSetAclLevel::getCode() AJAX profile_id.
+     * Numeric string stays a string/number; otherwise json_decode. Never unserialize.
+     */
+    public static function decodeAclProfileId($mixedProfileId)
+    {
+        if (!is_string($mixedProfileId) && !is_numeric($mixedProfileId))
+            return $mixedProfileId;
+
+        $mixedProfileId = urldecode((string)$mixedProfileId);
+        if (!is_numeric($mixedProfileId))
+            $mixedProfileId = json_decode($mixedProfileId, true);
+
+        return $mixedProfileId;
+    }
+
+    protected static function isServiceArray($a)
+    {
+        if (!is_array($a) || !isset($a['module'], $a['method']))
+            return false;
+        if (!is_scalar($a['module']) || !is_scalar($a['method']))
+            return false;
+        return self::isPlainData($a);
+    }
+
+    protected static function isPlainData($v)
+    {
+        if (is_scalar($v) || $v === null)
+            return true;
+        if (!is_array($v))
+            return false;
+        foreach ($v as $k => $item) {
+            if (!is_int($k) && !is_string($k))
+                return false;
+            if (!self::isPlainData($item))
+                return false;
+        }
+        return true;
+    }
+}

+ 72 - 5
index.php

@@ -15,11 +15,78 @@ if (!file_exists("./inc/header.inc.php")) {
     header("Cache-Control: no-cache, must-revalidate");
     header("Pragma: no-cache");
 
-    echo "It seems to be script is <b>not</b> installed.<br />\n";
-    if ( file_exists( "install/index.php" ) ) {
-        echo "Please, wait. Redirecting you to installation form...<br />\n";
-        echo "<script language=\"javascript\">location.href = 'install/index.php';</script>\n";
-    }
+    $bHasInstaller = file_exists("install/index.php");
+    $sTitle = $bHasInstaller ? 'Initiating Installation' : 'Installation files are missing';
+    header('Content-Type: text/html; charset=utf-8');
+    ?>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <title><?php echo htmlspecialchars($sTitle, ENT_QUOTES, 'UTF-8'); ?></title>
+<?php if ($bHasInstaller): ?>
+    <meta http-equiv="refresh" content="2;url=install/index.php" />
+<?php endif; ?>
+    <style>
+        :root {
+            --bx-flash-bg: #f4f5f7;
+            --bx-flash-text: #111827;
+            --bx-flash-muted: #6b7280;
+            --bx-flash-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+        }
+        html, body {
+            margin: 0;
+            height: 100%;
+        }
+        body {
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background: var(--bx-flash-bg);
+            font-family: var(--bx-flash-font);
+            color: var(--bx-flash-text);
+            -webkit-font-smoothing: antialiased;
+        }
+        .bx-install-flash {
+            text-align: center;
+            padding: 24px;
+        }
+        .bx-install-flash img {
+            display: block;
+            width: 64px;
+            height: 64px;
+            margin: 0 auto 20px;
+            border-radius: 16px;
+        }
+        .bx-install-flash p {
+            margin: 0;
+            font-size: 16px;
+            font-weight: 550;
+            letter-spacing: -0.01em;
+            color: var(--bx-flash-muted);
+        }
+        .bx-install-flash-pulse {
+            animation: bx-install-flash-pulse 1.4s ease-in-out infinite;
+        }
+        @keyframes bx-install-flash-pulse {
+            0%, 100% { opacity: 0.35; }
+            50% { opacity: 1; }
+        }
+    </style>
+</head>
+<body>
+    <div class="bx-install-flash">
+        <img src="install/img/logo.svg" alt="UNA" />
+        <p class="bx-install-flash-pulse"><?php echo htmlspecialchars($sTitle, ENT_QUOTES, 'UTF-8'); ?></p>
+    </div>
+<?php if ($bHasInstaller): ?>
+    <script>setTimeout(function () { location.href = 'install/index.php'; }, 1400);</script>
+<?php endif; ?>
+</body>
+</html>
+    <?php
     exit;
 }
 

+ 7 - 4
install/classes/BxDolInstallController.php

@@ -36,8 +36,11 @@ class BxDolInstallController
 
         $oAudit = new BxDolStudioToolsAudit();
         $sAuditOutput = $oAudit->generate();
+        $iAuditPassed = preg_match_all('/class="ok"/', $sAuditOutput);
+        $iAuditWarnings = preg_match_all('/class="warn"/', $sAuditOutput);
+        $iAuditFailed = preg_match_all('/class="fail"/', $sAuditOutput);
 
-        $this->_oView->out('audit.php', compact('sAuditOutput'));
+        $this->_oView->out('audit.php', compact('sAuditOutput', 'iAuditPassed', 'iAuditWarnings', 'iAuditFailed'));
 
         $this->_oView->pageEnd($this->_getTitle());
     }
@@ -76,7 +79,7 @@ class BxDolInstallController
 
         $this->_oView->out('permissions.php', compact('sPermissionsStyles', 'sPermissionsTable', 'bPermissionsOk'));
 
-        $this->_oView->setToolbarItem('question', 'https://github.com/unaio/una/wiki/Installation', _t('_sys_inst_help_permissions'), '_blank');
+        $this->_oView->setToolbarItem('question', 'https://unacms.com/wiki/installation-overview', _t('_sys_inst_help_permissions'), '_blank');
 
         $this->_oView->pageEnd($this->_getTitle());
     }
@@ -90,7 +93,7 @@ class BxDolInstallController
 
         $this->_oView->out('site_config.php', compact('sForm'));
 
-        $this->_oView->setToolbarItem('question', 'https://github.com/unaio/una/wiki/Installation', _t('_sys_inst_help_site_config'), '_blank');
+        $this->_oView->setToolbarItem('question', 'https://unacms.com/wiki/installation-overview', _t('_sys_inst_help_site_config'), '_blank');
 
         $this->_oView->pageEnd($this->_getTitle());
     }
@@ -116,7 +119,7 @@ class BxDolInstallController
 
         $this->_oView->out('finish.php', compact('sPathToPhp'));
 
-        $this->_oView->setToolbarItem('question', 'https://github.com/unaio/una/wiki/Installation', _t('_sys_inst_help_finish'), '_blank');
+        $this->_oView->setToolbarItem('question', 'https://unacms.com/wiki/installation-overview', _t('_sys_inst_help_finish'), '_blank');
 
         $this->_oView->pageEnd($this->_getTitle());
 

+ 14 - 2
install/classes/BxDolInstallLang.php

@@ -53,16 +53,28 @@ class BxDolInstallLang
         return $sKey;
     }
 
+    public function getLang()
+    {
+        return $this->_sLang;
+    }
+
     public function getAvailableLanguages ()
     {
         $aRet = array();
         $aModules = $this->_oModulesTools->getModules('language');
-        foreach ($aModules as $aModuleConfig)
+        foreach ($aModules as $aModuleConfig) {
+            $sStudioTitle = $aModuleConfig['title'];
+            $sTitle = $sStudioTitle;
+            $aOwnStrings = $this->_oModulesTools->readLanguage($aModuleConfig);
+            if (!empty($aOwnStrings['__' . $sStudioTitle]))
+                $sTitle = $aOwnStrings['__' . $sStudioTitle];
+
             $aRet[$aModuleConfig['home_uri']] = array(
                 'code' => $aModuleConfig['home_uri'],
-                'title' => $aModuleConfig['title'],
+                'title' => $sTitle,
                 'icon' => BX_INSTALL_URL_MODULES . $aModuleConfig['home_dir'] . 'template/images/icons/' . (file_exists(BX_INSTALL_DIR_MODULES .  $aModuleConfig['home_dir'] . 'template/images/icons/std-pi.png') ? 'std-pi.png' : 'std-icon.svg'),
             );
+        }
         return $aRet;
     }
 

+ 5 - 0
install/classes/BxDolInstallView.php

@@ -57,6 +57,11 @@ class BxDolInstallView
 
     function pageEnd ($sTitle)
     {
+        $oLang = BxDolInstallLang::getInstance();
+        $aLangs = $oLang->getAvailableLanguages();
+        $sLang = $oLang->getLang();
+        $sLangTitle = (isset($aLangs[$sLang]) && !empty($aLangs[$sLang]['title'])) ? $aLangs[$sLang]['title'] : $sLang;
+
         $aToolbarItem = $this->_aToolbarItem;
         $sInlineCSS = $this->_getInlineCSS();
         $sFilesCSS = $this->_getFilesCSS();

+ 642 - 50
install/css/styles.css

@@ -1,89 +1,681 @@
+/* Install wizard — modern minimal shell (una-g). Same flow; visual refresh only. */
 
+:root {
+    --bx-inst-bg: #f4f5f7;
+    --bx-inst-surface: #ffffff;
+    --bx-inst-text: #111827;
+    --bx-inst-muted: #6b7280;
+    --bx-inst-border: #e5e7eb;
+    --bx-inst-accent: #2563eb;
+    --bx-inst-accent-hover: #1d4ed8;
+    --bx-inst-ok: #059669;
+    --bx-inst-ok-bg: #ecfdf5;
+    --bx-inst-bad: #dc2626;
+    --bx-inst-bad-bg: #fef2f2;
+    --bx-inst-warn: #d97706;
+    --bx-inst-warn-bg: #fffbeb;
+    --bx-inst-radius: 12px;
+    --bx-inst-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 24px rgba(16, 24, 40, 0.06);
+    --bx-inst-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+}
 
 html, body {
     margin: 0;
-	height: 100%;
+    min-height: 100%;
+}
+
+body.bx-install-body,
+.bx-install-body {
+    font-family: var(--bx-inst-font) !important;
+    color: var(--bx-inst-text);
+    background: var(--bx-inst-bg) !important;
+    -webkit-font-smoothing: antialiased;
+}
+
+.bx-install-shell {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+}
+
+.bx-install-topbar {
+    background: var(--bx-inst-surface);
+    border-bottom: 1px solid var(--bx-inst-border);
+    position: sticky;
+    top: 0;
+    z-index: 20;
+}
+
+.bx-install-topbar-inner {
+    position: relative;
+    max-width: 720px;
+    margin: 0 auto;
+    padding: 10px 20px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    min-height: 52px;
+    box-sizing: border-box;
+}
+.bx-install-header-logo {
+    position: absolute;
+    left: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 28px;
+    height: 28px;
+    border-radius: 7px;
+    z-index: 1;
+}
+
+.bx-install-brand-title {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 14px;
+    font-weight: 600;
+    letter-spacing: -0.01em;
+    color: var(--bx-inst-text);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: calc(100% - 160px);
+    text-align: center;
+    pointer-events: none;
+}
+
+.bx-install-help {
+    position: relative;
+    z-index: 1;
+    font-size: 13px;
+    font-weight: 500;
+    color: var(--bx-inst-accent);
+    text-decoration: none;
+}
+.bx-install-help:hover {
+    text-decoration: underline;
+}
+
+.bx-install-main {
+    flex: 1 0 auto;
+    width: 100%;
+    max-width: 720px;
+    margin: 0 auto;
+    padding: 32px 20px 48px;
+    box-sizing: border-box;
+}
+
+.bx-install-card {
+    background: var(--bx-inst-surface);
+    border: 1px solid var(--bx-inst-border);
+    border-radius: var(--bx-inst-radius);
+    box-shadow: var(--bx-inst-shadow);
+    padding: 28px 28px 32px;
+}
+
+.bx-install-footer {
+    flex: 0 0 auto;
+    display: grid;
+    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+    align-items: center;
+    gap: 12px;
+    padding: 16px 20px;
+    font-size: 12px;
+    color: var(--bx-inst-muted);
+}
+.bx-install-footer-lang {
+    justify-self: start;
+    position: relative;
+}
+.bx-install-footer-links {
+    justify-self: center;
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    justify-content: center;
+}
+.bx-install-footer-spacer {
+    justify-self: end;
+}
+.bx-install-footer-links a {
+    color: var(--bx-inst-muted);
+    text-decoration: none;
+}
+.bx-install-footer-links a:hover {
+    color: var(--bx-inst-text);
+    text-decoration: underline;
+}
+.bx-install-footer-sep {
+    display: inline-block;
+    width: 1px;
+    height: 12px;
+    margin: 0 10px;
+    background: var(--bx-inst-border);
+    vertical-align: middle;
+}
+
+.bx-install-lang {
+    position: relative;
+}
+.bx-install-lang-trigger {
+    list-style: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 4px 2px;
+    cursor: pointer;
+    color: var(--bx-inst-muted);
+    font-size: 13px;
+    font-weight: 500;
+    user-select: none;
+}
+.bx-install-lang-trigger::-webkit-details-marker {
+    display: none;
+}
+.bx-install-lang-trigger::marker {
+    content: "";
+}
+.bx-install-lang[open] .bx-install-lang-trigger,
+.bx-install-lang-trigger:hover {
+    color: var(--bx-inst-accent);
+    text-decoration: underline;
+}
+.bx-install-lang-current {
+    line-height: 1.2;
+}
+.bx-install-lang-icon {
+    display: block;
+    flex: 0 0 auto;
+}
+.bx-install-lang-menu {
+    position: absolute;
+    left: 0;
+    bottom: calc(100% + 8px);
+    min-width: 220px;
+    padding: 6px;
+    background: #fff;
+    border: 1px solid var(--bx-inst-border);
+    border-radius: 12px;
+    box-shadow: var(--bx-inst-shadow);
+    z-index: 30;
+}
+.bx-install-lang-item {
+    display: flex !important;
+    align-items: center;
+    gap: 10px;
+    padding: 8px 10px;
+    border-radius: 8px;
+    color: var(--bx-inst-text) !important;
+    font-size: 13px;
+    text-decoration: none !important;
+}
+.bx-install-lang-item:hover {
+    background: #f3f4f6;
+}
+.bx-install-lang-item.is-selected {
+    background: #eff6ff;
+    color: var(--bx-inst-accent) !important;
+}
+.bx-install-lang-badge {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 28px;
+    height: 28px;
+    border-radius: 999px;
+    background: #f3f4f6;
+    color: var(--bx-inst-text);
+    flex: 0 0 auto;
+}
+.bx-install-lang-item.is-selected .bx-install-lang-badge {
+    background: #dbeafe;
+    color: var(--bx-inst-accent);
+}
+
+/* Kill the old teal header if any leftover classes still render */
+.bx-header.bx-def-color-bg-header,
+#bx-toolbar {
+    background: transparent !important;
+    box-shadow: none !important;
+}
+
+h1.bx-def-font-h1,
+.bx-install-card h1 {
+    font-family: var(--bx-inst-font) !important;
+    font-size: 22px !important;
+    font-weight: 650 !important;
+    letter-spacing: -0.02em;
+    margin: 0 0 20px !important;
+    color: var(--bx-inst-text) !important;
+}
+
+.bx-install-card p,
+.bx-install-card li,
+.bx-install-card .bx-def-font-large {
+    font-family: var(--bx-inst-font) !important;
+    color: var(--bx-inst-text);
+    line-height: 1.55;
+}
+
+/* Buttons */
+.bx-btn,
+a.bx-btn,
+button.bx-btn,
+.bx-install-site-config .bx-form-input-wrapper-submit button,
+.bx-install-site-config .bx-form-input-wrapper-submit input[type="submit"] {
+    display: inline-flex !important;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    float: none !important;
+    margin: 0 8px 8px 0 !important;
+    padding: 0 20px !important;
+    height: 48px !important;
+    min-height: 48px !important;
+    max-height: 48px !important;
+    box-sizing: border-box !important;
+    border-radius: 12px !important;
+    border: 1px solid var(--bx-inst-border) !important;
+    background: var(--bx-inst-surface) !important;
+    color: var(--bx-inst-text) !important;
+    font-family: var(--bx-inst-font) !important;
+    font-size: 14px !important;
+    font-weight: 550 !important;
+    line-height: 1.2 !important;
+    text-decoration: none !important;
+    box-shadow: none !important;
+    cursor: pointer;
+    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
+}
+
+.bx-btn:hover,
+a.bx-btn:hover,
+button.bx-btn:hover {
+    background: #f9fafb !important;
+    border-color: #d1d5db !important;
 }
 
-.bx-btn-primary {
-    background-color: #3b8686;
+.bx-btn-primary,
+a.bx-btn-primary,
+button.bx-btn-primary,
+.bx-btn.bx-btn-primary {
+    background: var(--bx-inst-accent) !important;
+    border-color: var(--bx-inst-accent) !important;
+    color: #fff !important;
 }
 
-.bx-install-permissions .bx-btn,
-.bx-install-initial .bx-btn {
-    display:inline;
-    display:inline-block;
-    float:none;
+.bx-btn-primary:hover,
+a.bx-btn-primary:hover {
+    background: var(--bx-inst-accent-hover) !important;
+    border-color: var(--bx-inst-accent-hover) !important;
+    color: #fff !important;
 }
 
+.bx-btn-disabled,
+a.bx-btn-disabled,
+.bx-btn.bx-btn-disabled {
+    opacity: 0.45 !important;
+    pointer-events: none !important;
+    cursor: not-allowed !important;
+}
+
+.bx-install-permissions-buttons,
 .bx-install-initial,
 .bx-install-initial-langs,
-.bx-install-permissions-buttons,
 .bx-install-site-config .bx-form-input-wrapper-submit {
-    text-align:center;
+    text-align: center;
 }
+
 .bx-install-initial > a,
 .bx-install-initial-langs > a,
 .bx-install-permissions-buttons > a,
 .bx-install-site-config .bx-form-input-wrapper-submit > a {
-    display:inline-block;
+    display: inline-flex;
 }
 
-.bx-install-error-message {
-    border:1px solid #966;
-    background-color:#c99;
-    color:#000;
-    font-weight:bold;
+/* Initial / welcome */
+.bx-install-initial img.bx-install-img {
+    display: block;
+    margin: 8px auto 24px;
+    width: 64px;
+    height: 64px;
+    max-width: 64px;
+    border-radius: 16px;
 }
 
-.bx-install-page {
-	min-height: 100%;
-	height: auto !important;
-	height: 100%;
-	margin: 0 auto -60px; /* the bottom margin is the negative value of the footer's height */
+.bx-install-initial-langs {
+    margin-bottom: 20px !important;
 }
-.bx-install-footer-push,
-.bx-install-footer {
-    height: 60px;
-    line-height: 60px;
+.bx-install-initial-langs img {
+    width: 28px;
+    height: 28px;
+    border-radius: 6px;
+    border: 1px solid var(--bx-inst-border);
+}
+.bx-install-initial-langs a {
+    margin: 0 4px;
 }
 
-.bx-install-initial img.bx-install-img {
-    display:block;
-    margin-left:auto;
-    margin-right:auto;
-    width:400px;
-    max-width:90%;
+.bx-install-initial-waring,
+.bx-install-initial-fail {
+    margin-top: 20px;
+    padding: 12px 14px;
+    border-radius: 8px;
+    background: var(--bx-inst-warn-bg);
+    border: 1px solid #fde68a;
+    color: #92400e;
+    font-size: 14px;
+    text-align: left;
 }
 
-.bx-install-initial-langs img {
-    width:32px;
-    height:32px;
+.bx-install-error-message,
+.bx-install-initial-fail ul {
+    border: 1px solid #fecaca;
+    background: var(--bx-inst-bad-bg);
+    color: #991b1b;
+    border-radius: 8px;
+    padding: 12px 14px;
+    font-weight: 500;
+}
+
+/* Permissions table → modern list */
+.bx-install-permissions-table-wrap {
+    overflow-x: auto;
+}
+.bx-permissions-table {
+    width: 100% !important;
+    table-layout: auto;
+    border-collapse: separate !important;
+    border-spacing: 0 !important;
+    border: 1px solid var(--bx-inst-border);
+    border-radius: 10px;
+    overflow: hidden;
+    font-size: 13px;
+}
+
+.bx-permissions-table thead td,
+.bx-permissions-table thead th {
+    font-weight: 600 !important;
+    font-size: 12px !important;
+    text-transform: uppercase;
+    letter-spacing: 0.04em;
+    color: var(--bx-inst-muted) !important;
+    background: #fafafa;
+    border-bottom: 1px solid var(--bx-inst-border);
+    text-align: left !important;
+    padding: 10px 12px !important;
+}
+
+.bx-permissions-table td {
+    padding: 10px 12px !important;
+    border-bottom: 1px solid var(--bx-inst-border);
+    vertical-align: middle;
+    font-family: var(--bx-inst-font) !important;
+}
+td.bx-permissions-ok,
+td.bx-permissions-wrong {
+    display: table-cell !important;
+    background: transparent !important;
+    border-radius: 0 !important;
+    padding: 10px 12px !important;
+    font-size: inherit !important;
+    font-weight: inherit !important;
+    line-height: inherit !important;
+}
+
+.bx-permissions-table tbody tr:last-child td {
+    border-bottom: none;
+}
+
+.bx-permissions-table tbody tr.bx-def-color-bg-hl-even,
+.bx-permissions-table tbody tr {
+    background: #fff !important;
+}
+
+.bx-permissions-table td:first-child {
+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
+    font-size: 12px;
+    color: #374151;
+    overflow-wrap: anywhere;
+    word-break: break-word;
+}
+.bx-permissions-table td:nth-child(2),
+.bx-permissions-table td:nth-child(3) {
+    white-space: nowrap;
+    width: 1%;
+}
+
+.bx-permissions-table td:not(:first-child) {
+    text-align: left !important;
+}
+
+.bx-install-chip,
+span.bx-permissions-ok,
+span.bx-permissions-wrong,
+.bx-install-audit b.ok,
+.bx-install-audit b.fail,
+.bx-install-audit b.warn,
+.bx-install-audit b.undef {
+    display: inline-flex !important;
+    align-items: center;
+    justify-content: center;
+    gap: 0;
+    padding: 2px 8px !important;
+    border-radius: 999px !important;
+    font-size: 11px !important;
+    font-weight: 500 !important;
+    line-height: 16px !important;
+    font-style: normal !important;
+    white-space: nowrap;
+}
+
+span.bx-permissions-ok,
+.bx-install-chip-ok,
+.bx-install-audit b.ok {
+    color: var(--bx-inst-ok) !important;
+    background: var(--bx-inst-ok-bg) !important;
+}
+
+span.bx-permissions-wrong,
+.bx-install-chip-bad,
+.bx-install-audit b.fail {
+    color: var(--bx-inst-bad) !important;
+    background: var(--bx-inst-bad-bg) !important;
+}
+
+.bx-install-chip-warn,
+.bx-install-audit b.warn {
+    color: var(--bx-inst-warn) !important;
+    background: var(--bx-inst-warn-bg) !important;
+}
+
+.bx-install-audit b.undef {
+    color: var(--bx-inst-muted) !important;
+    background: #f3f4f6 !important;
 }
 
-.bx-install-site-config .bx-form-input-wrapper-submit button {
-    display:inline-block;
-} 
+.bx-install-permissions-buttons {
+    margin-top: 24px !important;
+}
+
+.bx-install-permissions-buttons .bx-def-margin-bottom {
+    margin-bottom: 16px !important;
+    padding: 12px 14px;
+    border-radius: 8px;
+    background: var(--bx-inst-warn-bg);
+    border: 1px solid #fde68a;
+    color: #92400e;
+    font-size: 14px;
+    text-align: left;
+}
+
+/* Forms (site config) */
+.bx-install-site-config fieldset.bx-form-section {
+    background: transparent !important;
+    border: none !important;
+    border-radius: 0 !important;
+    margin: 0 0 18px !important;
+    padding: 0 !important;
+}
+
+.bx-install-site-config fieldset.bx-form-section .bx-form-section-content {
+    background: transparent !important;
+}
+
+.bx-install-site-config .bx-form-section-title,
+.bx-install-site-config legend {
+    font-size: 13px !important;
+    font-weight: 650 !important;
+    color: var(--bx-inst-muted) !important;
+    text-transform: uppercase;
+    letter-spacing: 0.04em;
+    margin-bottom: 10px !important;
+}
+
+.bx-install-site-config input[type="text"],
+.bx-install-site-config input[type="password"],
+.bx-install-site-config input[type="email"],
+.bx-install-site-config select,
+.bx-install-site-config textarea,
+.bx-install-finish textarea,
+.bx-form-input-text,
+.bx-form-input-password,
+.bx-form-input-select,
+.bx-form-input-textarea {
+    width: 100%;
+    max-width: 100%;
+    box-sizing: border-box;
+    border: 1px solid var(--bx-inst-border) !important;
+    border-radius: 8px !important;
+    padding: 10px 12px !important;
+    font-family: var(--bx-inst-font) !important;
+    font-size: 14px !important;
+    background: #fff !important;
+    color: var(--bx-inst-text) !important;
+    box-shadow: none !important;
+}
+
+.bx-install-site-config input:focus,
+.bx-install-site-config select:focus,
+.bx-install-site-config textarea:focus,
+.bx-install-finish textarea:focus {
+    outline: none !important;
+    border-color: var(--bx-inst-accent) !important;
+    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15) !important;
+}
 
 .bx-install-finish textarea {
-    display:block;
-    width:70%;
-    height:60px;
+    display: block;
+    width: 100% !important;
+    height: 88px;
+    margin-top: 8px;
+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
+    font-size: 12px !important;
+}
+
+.bx-install-finish ol {
+    padding-left: 20px;
+    margin: 0;
 }
 
+.bx-install-finish li {
+    margin-bottom: 16px;
+}
 
-.bx-header.bx-def-color-bg-header {
-    background-color: #3b8686;
+/* Audit report card */
+.bx-install-audit {
+    font-size: 13px;
+}
+.bx-install-audit-summary {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin: 0 0 20px;
+}
+.bx-install-audit-body {
+    overflow-x: auto;
+}
+.bx-install-audit-body > h1 {
+    font-family: var(--bx-inst-font) !important;
+    font-size: 13px !important;
+    font-weight: 650 !important;
+    letter-spacing: 0.04em;
+    text-transform: uppercase;
+    color: var(--bx-inst-muted) !important;
+    margin: 22px 0 10px !important;
+}
+.bx-install-audit-body > h1:first-child {
+    margin-top: 0 !important;
+}
+.bx-install-audit-body .bx-audit-section-head {
+    display: block;
+    font-size: 14px;
+    font-weight: 600;
+    text-transform: none;
+    color: var(--bx-inst-text);
+    margin: 16px 0 8px;
+}
+.bx-install-audit-body .bx-audit-section-head b {
+    display: inline;
+    font-weight: 600;
+}
+.bx-install-audit-body ul {
+    list-style: none;
+    margin: 0 0 8px;
+    padding: 0;
+    border: 1px solid var(--bx-inst-border);
+    border-radius: 10px;
+    overflow: hidden;
+    background: #fff;
+}
+.bx-install-audit-body li {
+    padding: 10px 12px;
+    border-bottom: 1px solid var(--bx-inst-border);
+    line-height: 1.45;
+    color: #374151;
+}
+.bx-install-audit-body li:last-child {
+    border-bottom: none;
+}
+.bx-install-audit-body li b.ok,
+.bx-install-audit-body li b.fail,
+.bx-install-audit-body li b.warn,
+.bx-install-audit-body li b.undef {
+    float: right;
+    margin-left: 8px;
+}
+.bx-install-audit-body table {
+    width: 100%;
+    border-collapse: collapse;
+    border: 1px solid var(--bx-inst-border);
+    border-radius: 10px;
+    overflow: hidden;
+}
+.bx-install-audit-body td,
+.bx-install-audit-body th {
+    padding: 8px 10px;
+    border-bottom: 1px solid var(--bx-inst-border);
+    text-align: left;
 }
 
-fieldset.bx-form-section {
-    background-color:transparent !important;
-    border-left:none;
-    border-right:none;
-    border-bottom:none;
-    border-radius:0 !important;
+@media (max-width: 640px) {
+    .bx-install-card {
+        padding: 20px 16px 24px;
+    }
+    .bx-install-main {
+        padding: 20px 12px 36px;
+    }
 }
 
-fieldset.bx-form-section .bx-form-section-content {
-    background-color:transparent !important;
+.bx-install-step-lead {
+    margin: -8px 0 20px !important;
+    color: var(--bx-inst-muted) !important;
+    font-size: 14px !important;
+}
+.bx-install-actions {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    gap: 4px;
 }

+ 57 - 16
install/img/logo.svg

@@ -1,18 +1,59 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 520 520" style="enable-background:new 0 0 520 520;" xml:space="preserve">
-<style type="text/css">
-	.inner .p1 {fill:#4990E2;}
-	.inner .p2 {fill:#2AB191;}
-	.outer .p1, .outer .p2 {fill:#FF805F;}
-</style>
-<g class="colored">
-<g class="inner">
-<path class="p1" d="M152,260C152,260,152,260,152,260c0-59.8,48.4-108.4,108.1-108.4c43.3,0,80.9,25.6,97.9,62.4V107.3c-28-18.3-61.9-28.9-98.1-28.9C159.8,78.4,78.6,159.9,78.6,260c0,59.5,28.4,112.4,73.4,145.6V260z"/>
-<path class="p2" d="M368,114.4V260c0,59.8-48.4,108.4-108.1,108.4c-43.3,0-80.9-25.6-97.9-62.4v106.7c28,18.3,61.9,28.9,98.1,28.9c100.1,0,181.3-81.4,181.3-181.6C441.4,200.5,413,147.6,368,114.4z"/>
-</g>
-<g class="outer">
-<path class="p1" d="M146.9,106.7c-8.1-15-36.9-29.9-68.9-27.3v124.2C90,164.3,114.6,130.5,146.9,106.7z"/>
-<path class="p2" d="M373.1,413.3c8.1,15,36.7,29.9,68.9,27.3V316.4C429.8,355.7,405.4,389.5,373.1,413.3z"/>
-</g>
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="48" height="48" rx="12" fill="url(#paint0_linear_201_173)"/>
+<path d="M23.9997 6.66663C33.5725 6.66663 41.3335 14.4268 41.3337 23.9996C41.3337 33.5726 33.5726 41.3336 23.9997 41.3336C14.4269 41.3334 6.66669 33.5725 6.66669 23.9996C6.66686 14.427 14.427 6.6668 23.9997 6.66663ZM23.9997 17.3336C20.3181 17.3338 17.3339 20.318 17.3337 23.9996C17.3337 24.7903 17.4717 25.5488 17.7243 26.2526C19.6509 26.8298 21.1693 28.3485 21.7468 30.275C22.4506 30.5278 23.2089 30.6666 23.9997 30.6666C27.6816 30.6666 30.6667 27.6815 30.6667 23.9996C30.6666 23.2088 30.5279 22.4505 30.2751 21.7467C28.3485 21.1692 26.8298 19.6509 26.2526 17.7242C25.5489 17.4716 24.7904 17.3336 23.9997 17.3336Z" fill="black" fill-opacity="0.1"/>
+<circle cx="24" cy="24" r="22" stroke="white" stroke-opacity="0.1" stroke-width="0.5" stroke-linecap="round" stroke-dasharray="1 2"/>
+<circle cx="24" cy="24" r="20" stroke="white" stroke-opacity="0.1" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="0.9 1.8"/>
+<g clip-path="url(#clip0_201_173)">
+<path d="M20.1429 33.6428C20.1429 36.4831 17.8404 38.7856 15 38.7856C12.1596 38.7856 9.85717 36.4831 9.85717 33.6428C9.85717 30.8025 12.1596 28.4999 15 28.4999C17.8404 28.4999 20.1429 30.8025 20.1429 33.6428Z" fill="black"/>
+<path d="M38.1429 15.6428C38.1429 18.4831 35.8404 20.7856 33 20.7856C30.1596 20.7856 27.8572 18.4831 27.8572 15.6428C27.8572 12.8025 30.1596 10.4999 33 10.4999C35.8404 10.4999 38.1429 12.8025 38.1429 15.6428Z" fill="black"/>
+<path d="M17.011 17.6053C18.8039 15.8246 21.2735 14.7244 24.0001 14.7244C24.285 14.7244 24.567 14.7364 24.8459 14.76C25.1147 14.7827 25.3599 14.5987 25.4188 14.3354C25.9821 11.8137 27.8256 9.77508 30.2316 8.9373C30.4562 8.85907 30.4793 8.53355 30.2574 8.44775C28.3162 7.69721 26.2062 7.28564 24.0001 7.28564C21.4493 7.28564 19.0271 7.83587 16.8456 8.82406C16.6712 8.90306 16.5613 9.07797 16.5613 9.26942V17.4266C16.5613 17.655 16.8489 17.7662 17.011 17.6053Z" fill="black"/>
+<path d="M34.3045 38.6115C34.1433 38.7306 33.9184 38.614 33.9184 38.4134V24.6428C33.9184 24.3579 33.9065 24.0758 33.8829 23.797C33.8601 23.5282 34.0441 23.2829 34.3074 23.2241C36.8292 22.6608 38.8677 20.8173 39.7055 18.4113C39.7836 18.1866 40.1092 18.1636 40.195 18.3855C40.9456 20.3267 41.3572 22.4367 41.3572 24.6428C41.3572 30.3706 38.5827 35.4503 34.3045 38.6115Z" fill="black"/>
+<path d="M8.29454 30.8742C8.21631 31.0989 7.89079 31.122 7.805 30.9C7.05445 28.9589 6.64288 26.8489 6.64288 24.6428C6.64288 18.915 9.41731 13.8353 13.6956 10.674C13.8568 10.5549 14.0817 10.6717 14.0817 10.8721V24.6428C14.0817 24.9277 14.0937 25.2098 14.1172 25.4886C14.1399 25.7574 13.956 26.0027 13.6927 26.0615C11.171 26.6248 9.13232 28.4683 8.29454 30.8742Z" fill="black"/>
+<path d="M31.1543 40.4616C31.3289 40.3825 31.4387 40.2077 31.4387 40.0163V31.8589C31.4387 31.6305 31.1511 31.5194 30.9889 31.6804C29.1962 33.461 26.7265 34.5612 23.9999 34.5612C23.715 34.5612 23.433 34.5493 23.1542 34.5257C22.8853 34.5029 22.6401 34.6869 22.5813 34.9502C22.018 37.472 20.1744 39.5104 17.7684 40.3483C17.5437 40.4266 17.5207 40.752 17.7426 40.8378C19.6839 41.5883 21.7938 41.9999 23.9999 41.9999C26.5507 41.9999 28.973 41.4497 31.1543 40.4616Z" fill="black"/>
+<path d="M20.1429 33C20.1429 35.8404 17.8405 38.1429 15 38.1429C12.1596 38.1429 9.85718 35.8404 9.85718 33C9.85718 30.1597 12.1596 27.8572 15 27.8572C17.8405 27.8572 20.1429 30.1597 20.1429 33Z" fill="url(#paint1_linear_201_173)"/>
+<path d="M38.1429 15C38.1429 17.8403 35.8404 20.1429 33 20.1429C30.1596 20.1429 27.8572 17.8403 27.8572 15C27.8572 12.1597 30.1596 9.85718 33 9.85718C35.8404 9.85718 38.1429 12.1597 38.1429 15Z" fill="url(#paint2_linear_201_173)"/>
+<path d="M17.011 16.9624C18.8039 15.1818 21.2735 14.0816 24.0001 14.0816C24.285 14.0816 24.567 14.0936 24.8459 14.1171C25.1147 14.1398 25.3599 13.9559 25.4188 13.6926C25.9821 11.1709 27.8256 9.13226 30.2316 8.29448C30.4562 8.21625 30.4793 7.89073 30.2574 7.80493C28.3162 7.05438 26.2062 6.64282 24.0001 6.64282C21.4493 6.64282 19.0271 7.19304 16.8456 8.18123C16.6712 8.26023 16.5613 8.43514 16.5613 8.62659V16.7838C16.5613 17.0122 16.8489 17.1234 17.011 16.9624Z" fill="url(#paint3_linear_201_173)" fill-opacity="0.8"/>
+<path d="M34.3045 37.9687C34.1433 38.0877 33.9184 37.9712 33.9184 37.7706V23.9999C33.9184 23.7151 33.9065 23.433 33.8829 23.1542C33.8601 22.8853 34.0441 22.6401 34.3074 22.5813C36.8292 22.018 38.8677 20.1745 39.7055 17.7684C39.7836 17.5438 40.1092 17.5208 40.195 17.7426C40.9456 19.6839 41.3572 21.7939 41.3572 23.9999C41.3572 29.7278 38.5827 34.8075 34.3045 37.9687Z" fill="url(#paint4_linear_201_173)" fill-opacity="0.8"/>
+<path d="M8.29454 30.2314C8.21631 30.4561 7.89079 30.4791 7.805 30.2572C7.05445 28.316 6.64288 26.2061 6.64288 24C6.64288 18.2722 9.41731 13.1924 13.6956 10.0312C13.8568 9.91208 14.0817 10.0288 14.0817 10.2293V24C14.0817 24.2849 14.0937 24.567 14.1172 24.8458C14.1399 25.1146 13.956 25.3599 13.6927 25.4187C11.171 25.982 9.13232 27.8255 8.29454 30.2314Z" fill="url(#paint5_linear_201_173)" fill-opacity="0.8"/>
+<path d="M31.1543 39.8188C31.3289 39.7397 31.4387 39.5649 31.4387 39.3734V31.2161C31.4387 30.9877 31.1511 30.8766 30.9889 31.0375C29.1962 32.8181 26.7265 33.9183 23.9999 33.9183C23.715 33.9183 23.433 33.9064 23.1542 33.8829C22.8853 33.8601 22.6401 34.0441 22.5813 34.3074C22.018 36.8291 20.1744 38.8676 17.7684 39.7055C17.5437 39.7838 17.5207 40.1091 17.7426 40.1949C19.6839 40.9455 21.7938 41.3571 23.9999 41.3571C26.5507 41.3571 28.973 40.8069 31.1543 39.8188Z" fill="url(#paint6_linear_201_173)" fill-opacity="0.8"/>
+<path d="M20.1429 33.6428C20.1429 36.4831 17.8404 38.7856 15 38.7856C12.1596 38.7856 9.85717 36.4831 9.85717 33.6428C9.85717 30.8025 12.1596 28.4999 15 28.4999C17.8404 28.4999 20.1429 30.8025 20.1429 33.6428Z" fill="black" fill-opacity="0.1"/>
+<path d="M38.1429 15.6428C38.1429 18.4831 35.8404 20.7856 33 20.7856C30.1596 20.7856 27.8572 18.4831 27.8572 15.6428C27.8572 12.8025 30.1596 10.4999 33 10.4999C35.8404 10.4999 38.1429 12.8025 38.1429 15.6428Z" fill="black" fill-opacity="0.1"/>
+<path d="M17.011 17.6053C18.8039 15.8246 21.2735 14.7244 24.0001 14.7244C24.285 14.7244 24.567 14.7364 24.8459 14.76C25.1147 14.7827 25.3599 14.5987 25.4188 14.3354C25.9821 11.8137 27.8256 9.77508 30.2316 8.9373C30.4562 8.85907 30.4793 8.53355 30.2574 8.44775C28.3162 7.69721 26.2062 7.28564 24.0001 7.28564C21.4493 7.28564 19.0271 7.83587 16.8456 8.82406C16.6712 8.90306 16.5613 9.07797 16.5613 9.26942V17.4266C16.5613 17.655 16.8489 17.7662 17.011 17.6053Z" fill="black" fill-opacity="0.1"/>
+<path d="M34.3045 38.6115C34.1433 38.7306 33.9184 38.614 33.9184 38.4134V24.6428C33.9184 24.3579 33.9065 24.0758 33.8829 23.797C33.8601 23.5282 34.0441 23.2829 34.3074 23.2241C36.8292 22.6608 38.8677 20.8173 39.7055 18.4113C39.7836 18.1866 40.1092 18.1636 40.195 18.3855C40.9456 20.3267 41.3572 22.4367 41.3572 24.6428C41.3572 30.3706 38.5827 35.4503 34.3045 38.6115Z" fill="black" fill-opacity="0.1"/>
+<path d="M8.29454 30.8742C8.21631 31.0989 7.89079 31.122 7.805 30.9C7.05445 28.9589 6.64288 26.8489 6.64288 24.6428C6.64288 18.915 9.41731 13.8353 13.6956 10.674C13.8568 10.5549 14.0817 10.6717 14.0817 10.8721V24.6428C14.0817 24.9277 14.0937 25.2098 14.1172 25.4886C14.1399 25.7574 13.956 26.0027 13.6927 26.0615C11.171 26.6248 9.13232 28.4683 8.29454 30.8742Z" fill="black" fill-opacity="0.1"/>
+<path d="M31.1543 40.4616C31.3289 40.3825 31.4387 40.2077 31.4387 40.0163V31.8589C31.4387 31.6305 31.1511 31.5194 30.9889 31.6804C29.1962 33.461 26.7265 34.5612 23.9999 34.5612C23.715 34.5612 23.433 34.5493 23.1542 34.5257C22.8853 34.5029 22.6401 34.6869 22.5813 34.9502C22.018 37.472 20.1744 39.5104 17.7684 40.3483C17.5437 40.4266 17.5207 40.752 17.7426 40.8378C19.6839 41.5883 21.7938 41.9999 23.9999 41.9999C26.5507 41.9999 28.973 41.4497 31.1543 40.4616Z" fill="black" fill-opacity="0.1"/>
 </g>
+<defs>
+<linearGradient id="paint0_linear_201_173" x1="24" y1="0" x2="24" y2="48" gradientUnits="userSpaceOnUse">
+<stop stop-color="#232120"/>
+<stop offset="1" stop-color="#131211"/>
+</linearGradient>
+<linearGradient id="paint1_linear_201_173" x1="15" y1="27.8572" x2="15" y2="38.1429" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<linearGradient id="paint2_linear_201_173" x1="33" y1="9.85718" x2="33" y2="20.1429" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<linearGradient id="paint3_linear_201_173" x1="24" y1="6.64282" x2="24" y2="41.3571" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<linearGradient id="paint4_linear_201_173" x1="24" y1="6.64282" x2="24" y2="41.3571" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<linearGradient id="paint5_linear_201_173" x1="24" y1="6.64282" x2="24" y2="41.3571" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<linearGradient id="paint6_linear_201_173" x1="24" y1="6.64282" x2="24" y2="41.3571" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0.8"/>
+</linearGradient>
+<clipPath id="clip0_201_173">
+<path d="M6 20.4C6 15.3595 6 12.8393 6.98094 10.9141C7.8438 9.22063 9.22063 7.8438 10.9141 6.98094C12.8393 6 15.3595 6 20.4 6H27.6C32.6405 6 35.1607 6 37.0859 6.98094C38.7794 7.8438 40.1562 9.22063 41.0191 10.9141C42 12.8393 42 15.3595 42 20.4V27.6C42 32.6405 42 35.1607 41.0191 37.0859C40.1562 38.7794 38.7794 40.1562 37.0859 41.0191C35.1607 42 32.6405 42 27.6 42H20.4C15.3595 42 12.8393 42 10.9141 41.0191C9.22063 40.1562 7.8438 38.7794 6.98094 37.0859C6 35.1607 6 32.6405 6 27.6V20.4Z" fill="white"/>
+</clipPath>
+</defs>
 </svg>

+ 32 - 51
install/templates/_page.php

@@ -1,69 +1,50 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="<?=bx_html_attribute($sLang); ?>">
 <head>
     <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
     <title><?=$sTitle; ?></title>
-
     <style>
-
 <?=$sInlineCSS; ?>
-
     </style>
-    <link rel="stylesheet" href="css/styles.css" />
-
 <?=$sFilesCSS; ?>
-
+    <link rel="stylesheet" href="css/styles.css" />
 <?=$sFilesJS; ?>
-
-    <script>
-        $(document).ready(function () {
-            $('#bx-toolbar').css({opacity: 0.9});
-        });
-    </script>
 </head>
-<body class="bx-def-font bx-def-color-bg-page">
-
-<div class="bx-install-page bx-main">
-
-    <div id="bx-toolbar" class="bx-header bx-def-color-bg-header bx-def-z-index-nav bx-def-shadow bx-toolbar-anim headroom--top headroom--not-bottom">
-        <div class="bx-def-centered bx-def-padding-sec-leftright bx-def-page-width bx-def-box-sizing">
-                <div id="bx-toolbar-content" class="bx-toolbar-content bx-clearfix">
-                    <div id="bx-menu-toolbar-1-container" class="bx-toolbar-item bx-ti-left"></div>
-                    <div id="bx-logo-container" class="bx-toolbar-item bx-ti-logo bx-ti-stretcher"><a class="bx-def-font-contrasted"><span><?=$sTitle; ?></span></a></div>
-                    <div id="bx-menu-toolbar-2-container" class="bx-toolbar-item bx-ti-right">
-                        <?php if (!empty($aToolbarItem)): ?>
-                        <ul class="bx-menu-toolbar bx-menu-object-sys_toolbar_member">
-                            <li class="bx-def-color-bg-hl-hover ">
-                                <a title="<?=$aToolbarItem['title']; ?>" href="<?=$aToolbarItem['link']; ?>" target="<?=$aToolbarItem['target']; ?>" onclick="" class="bx-def-margin-sec-left bx-def-font-contrasted">
-                                    <i class="sys-icon <?=$aToolbarItem['icon']; ?> bx-def-margin-sec-leftright"></i>
-                                </a>
-                            </li>
-                        </ul>
-                        <?php endif; ?>
-                    </div>
-                </div>
-
+<body class="bx-install-body bx-def-font">
+
+<div class="bx-install-shell">
+    <header class="bx-install-topbar">
+        <div class="bx-install-topbar-inner">
+            <img class="bx-install-header-logo" src="img/logo.svg" alt="UNA" />
+            <span class="bx-install-brand-title"><?=$sTitle; ?></span>
+            <?php if (!empty($aToolbarItem)): ?>
+            <a class="bx-install-help" title="<?=$aToolbarItem['title']; ?>" href="<?=$aToolbarItem['link']; ?>" target="<?=$aToolbarItem['target']; ?>">
+                <?php echo _t('_sys_inst_help'); ?>
+            </a>
+            <?php endif; ?>
         </div>
-    </div>
+    </header>
 
-    <div id="bx-menu-main-bar-wrapper"></div>
-
-    <div class="bx-page-wrapper bx-menu-toolbar-padding bx-def-centered bx-def-padding-sec-left bx-def-padding-sec-right bx-def-page-width">
-        <div id="bx-content-container" class="bx-def-padding-sec-left bx-def-padding-sec-right">
-            <div id="bx-content-main" class="bx-def-margin-top">
-                <?=$sCode; ?>
-            </div>
+    <main class="bx-install-main">
+        <div class="bx-install-card">
+            <?=$sCode; ?>
         </div>
-    </div>
-    <div class="bx-install-footer-push"></div>
-</div>
+    </main>
 
-<div class="bx-install-footer">
-    <div class="bx-def-centered bx-def-padding-sec-left bx-def-padding-sec-right bx-def-page-width">
-        <div id="bx-content-container" class="bx-def-padding-sec-left bx-def-padding-sec-right bx-clearfix">
-            &nbsp;
+    <footer class="bx-install-footer">
+        <div class="bx-install-footer-lang">
+            <?php include('lang_swither.php'); ?>
+        </div>
+        <div class="bx-install-footer-links">
+            <a href="https://unacms.com" target="_blank" rel="noopener"><?php echo _t('_sys_inst_footer_product'); ?></a>
+            <span class="bx-install-footer-sep" aria-hidden="true"></span>
+            <a href="https://unacms.com/docs" target="_blank" rel="noopener"><?php echo _t('_sys_inst_footer_docs'); ?></a>
+            <span class="bx-install-footer-sep" aria-hidden="true"></span>
+            <a href="https://unacms.com/contact" target="_blank" rel="noopener"><?php echo _t('_sys_inst_footer_contact'); ?></a>
         </div>
-    </div>
+        <div class="bx-install-footer-spacer" aria-hidden="true"></div>
+    </footer>
 </div>
 
 </body>

+ 13 - 0
install/templates/audit.php

@@ -1,3 +1,16 @@
 <div class="bx-install-audit">
+
+    <h1 class="bx-def-font-h1"><?php echo _t('_sys_inst_server_audit'); ?></h1>
+    <p class="bx-install-step-lead"><?php echo _t('_sys_inst_audit_lead'); ?></p>
+
+    <div class="bx-install-audit-summary">
+        <span class="bx-install-chip bx-install-chip-ok"><?php echo _t('_sys_inst_audit_passed', (int)$iAuditPassed); ?></span>
+        <span class="bx-install-chip bx-install-chip-warn"><?php echo _t('_sys_inst_audit_warnings', (int)$iAuditWarnings); ?></span>
+        <span class="bx-install-chip bx-install-chip-bad"><?php echo _t('_sys_inst_audit_failed', (int)$iAuditFailed); ?></span>
+    </div>
+
+    <div class="bx-install-audit-body">
    <?=$sAuditOutput; ?>
+    </div>
+
 </div>

+ 1 - 6
install/templates/finish.php

@@ -2,14 +2,9 @@
 
     <h1 class="bx-def-font-h1"><?php echo _t('_sys_inst_finish'); ?></h1>
 
-    <p class="bx-def-font-large" style="font-weight:bold;"><?php echo _t('_sys_inst_msg_finish'); ?></p>
+    <p class="bx-install-step-lead"><?php echo _t('_sys_inst_msg_finish'); ?></p>
 
     <ol>
-        <!--
-        <li>
-            change permissions back
-        <li>
-        -->
         <li>
             <?php echo _t('_sys_inst_msg_finish_cron_jobs'); ?>
             <textarea class="bx-form-input-textarea bx-def-font-inputs bx-def-margin-sec-top">

+ 4 - 4
install/templates/initial.php

@@ -2,10 +2,10 @@
 
     <img class="bx-def-margin-bottom bx-install-img" src="img/logo.svg" alt="UNA" />
 
-    <?php include('lang_swither.php'); ?>
-
-    <a href="?action=permissions" class="bx-btn bx-btn-primary"><?php echo _t('_sys_inst_start_installation'); ?></a>
-    <a href="?action=audit" class="bx-btn"><?php echo _t('_sys_inst_server_audit'); ?></a>
+    <div class="bx-install-actions">
+        <a href="?action=permissions" class="bx-btn bx-btn-primary"><?php echo _t('_sys_inst_start_installation'); ?></a>
+        <a href="?action=audit" class="bx-btn"><?php echo _t('_sys_inst_server_audit'); ?></a>
+    </div>
 
     <?php if ($aWarnings): ?>
     <div class="bx-install-initial-waring bx-def-margin-top">

+ 0 - 2
install/templates/initial_fail.php

@@ -1,7 +1,5 @@
 <div class="bx-install-initial-fail">
 
-    <?php include('lang_swither.php'); ?>
-
     <?php echo _t('_sys_inst_hosting_fails'); ?>
     <ul>
     <?php foreach ($aErrors as $sMsg): ?>

+ 45 - 5
install/templates/lang_swither.php

@@ -1,8 +1,48 @@
+<?php
+/**
+ * Footer language switcher. Hidden when fewer than two language modules exist.
+ */
+if (empty($aLangs) || count($aLangs) < 2)
+    return;
 
-    <div class="bx-install-initial-langs bx-def-margin-bottom">
-    <?php foreach ($aLangs as $aLang): ?>
-        <a title="<?php echo bx_html_attribute($aLang['title']); ?>" href="?lang=<?=$aLang['code']; ?>">
-            <img src="<?php echo bx_html_attribute($aLang['icon']); ?>" alt="<?php echo bx_html_attribute($aLang['title']); ?>" />
+if (!function_exists('bx_install_lang_url')) {
+    function bx_install_lang_url($sCode)
+    {
+        $a = array();
+        if (!empty($_GET['action']))
+            $a['action'] = $_GET['action'];
+        $a['lang'] = $sCode;
+        return '?' . http_build_query($a);
+    }
+}
+
+$sTranslateSvg = '<svg class="bx-install-lang-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 8 6 6"/><path d="m4 14 6-6 2-3"/><path d="M2 5h12"/><path d="M7 2h1"/><path d="m22 22-5-10-5 10"/><path d="M14 18h6"/></svg>';
+?>
+<details class="bx-install-lang">
+    <summary class="bx-install-lang-trigger">
+        <?php echo $sTranslateSvg; ?>
+        <span class="bx-install-lang-current"><?php echo bx_html_attribute($sLangTitle); ?></span>
+    </summary>
+    <div class="bx-install-lang-menu" role="menu">
+        <?php foreach ($aLangs as $aLang): ?>
+        <a role="menuitem" class="bx-install-lang-item<?php echo ($aLang['code'] === $sLang) ? ' is-selected' : ''; ?>" href="<?php echo bx_html_attribute(bx_install_lang_url($aLang['code'])); ?>">
+            <span class="bx-install-lang-badge"><?php echo $sTranslateSvg; ?></span>
+            <span><?php echo bx_html_attribute($aLang['title']); ?></span>
         </a>
-    <?php endforeach; ?>
+        <?php endforeach; ?>
     </div>
+</details>
+<script>
+(function () {
+    var d = document.querySelector('.bx-install-lang');
+    if (!d) return;
+    document.addEventListener('click', function (e) {
+        if (!d.contains(e.target))
+            d.removeAttribute('open');
+    });
+    document.addEventListener('keydown', function (e) {
+        if (e.key === 'Escape')
+            d.removeAttribute('open');
+    });
+})();
+</script>

+ 3 - 0
install/templates/permissions.php

@@ -1,9 +1,12 @@
 <div class="bx-install-permissions">
 
     <h1 class="bx-def-font-h1"><?php echo _t('_adm_admtools_Permissions'); ?></h1>
+    <p class="bx-install-step-lead"><?php echo _t('_sys_inst_msg_permissions_lead'); ?></p>
 
    <?=$sPermissionsStyles; ?>
+   <div class="bx-install-permissions-table-wrap">
    <?=$sPermissionsTable; ?>
+   </div>
 
     <div class="bx-install-permissions-buttons bx-def-margin-top">
 

+ 1 - 1
install/templates/remove_install.php

@@ -1,5 +1,5 @@
 <div class="bx-install-remove-install">
 
-    <p class="bx-def-font-large" style="font-weight:bold;"><?php echo _t('_sys_inst_msg_remove_install'); ?></p>
+    <p class="bx-install-step-lead"><?php echo _t('_sys_inst_msg_remove_install'); ?></p>
 
 </div>

+ 21 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConAlerts.php

@@ -0,0 +1,21 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConAlerts extends BxBaseModConnectAlerts
+{
+    function __construct()
+    {
+        parent::__construct();
+        $this -> oModule = BxDolModule::getInstance('bx_cidaascon');
+    }
+}
+
+/** @} */

+ 54 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConConfig.php

@@ -0,0 +1,54 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConConfig extends BxBaseModConnectConfig
+{
+    public $sBaseUrl;
+    public $sClientID;
+    public $sSecret;
+    public $sScope = 'openid profile email offline_access';
+    public $bPkce = false;
+    public $bDebug = false;
+
+    public $sPageStart;
+    public $sPageHandle;
+
+    public $sSessionCodeVerifier;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->sBaseUrl = rtrim(getParam('bx_cidaascon_base_url'), '/');
+        $this->sClientID = getParam('bx_cidaascon_client_id');
+        $this->sSecret = getParam('bx_cidaascon_secret');
+        $this->sScope = getParam('bx_cidaascon_scope') ? getParam('bx_cidaascon_scope') : $this->sScope;
+        $this->bPkce = (bool)getParam('bx_cidaascon_pkce');
+        $this->bDebug = (bool)getParam('bx_cidaascon_debug');
+
+        $this->sSessionUid = 'cidaascon_session';
+        $this->sSessionProfile = 'cidaascon_session_profile';
+        $this->sSessionCodeVerifier = 'cidaascon_code_verifier';
+
+        $this->sEmailTemplatePasswordGenerated = 'bx_cidaascon_password_generated';
+        $this->sDefaultTitleLangKey = '_bx_cidaascon';
+
+        $this->sRedirectPage = getParam('bx_cidaascon_redirect_page');
+        $this->sProfilesModule = getParam('bx_cidaascon_module');
+        $this->isAlwaysConfirmEmail = (bool)getParam('bx_cidaascon_confirm_email');
+        $this->isAlwaysAutoApprove = (bool)getParam('bx_cidaascon_approve');
+
+        $this->sPageStart = BX_DOL_URL_ROOT . $this->getBaseUri() . 'start';
+        $this->sPageHandle = BX_DOL_URL_ROOT . $this->getBaseUri() . 'handle';
+    }
+}
+
+/** @} */

+ 20 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConDb.php

@@ -0,0 +1,20 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConDb extends BxBaseModConnectDb
+{
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+}
+
+/** @} */

+ 235 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConModule.php

@@ -0,0 +1,235 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_MODULES . 'boonex/cidaas_connect/vendor/autoload.php');
+
+use Cidaas\OAuth2\Client\Provider\GrantType;
+use GuzzleHttp\Exception\ClientException;
+
+/**
+ * Login via the official Cidaas PHP SDK hosted login page.
+ * @see https://github.com/cidaas/cidaas-sdk-php
+ */
+class BxCidaasConModule extends BxBaseModConnectModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    /**
+     * Redirect to Cidaas hosted login page.
+     *
+     * @return n/a - redirect or HTML page in case of error
+     */
+    function actionStart()
+    {
+        if (isset($_GET['error'])) {
+            $this->_oTemplate->getPage(_t($this->_oConfig->sDefaultTitleLangKey), DesignBoxContent(_t($this->_oConfig->sDefaultTitleLangKey), MsgBox(bx_get('error'))));
+            exit;
+        }
+
+        if (isLogged()) {
+            $this->_redirect($this->_oConfig->sDefaultRedirectUrl);
+        }
+
+        if (!$this->_oConfig->sBaseUrl || !$this->_oConfig->sClientID || (!$this->_oConfig->bPkce && !$this->_oConfig->sSecret)) {
+            require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+            bx_import('BxDolLanguages');
+            $sCode = MsgBox(_t('_bx_cidaascon_profile_error_api_keys'));
+            $this->_oTemplate->getPage(_t('_bx_cidaascon'), $sCode);
+            return;
+        }
+
+        try {
+            $this->_getProvider()->loginWithBrowser($this->_oConfig->sScope, [
+                'state' => $this->_genToken(),
+            ], $this->_oConfig->bPkce);
+        } catch (Exception $oException) {
+            require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
+            return;
+        }
+
+        // SDK stores the PKCE verifier in PHP $_SESSION; persist it in UNA session for the callback.
+        if ($this->_oConfig->bPkce && !empty($_SESSION['code-verifier']))
+            BxDolSession::getInstance()->setValue($this->_oConfig->sSessionCodeVerifier, $_SESSION['code-verifier']);
+
+        exit;
+    }
+
+    function actionHandle()
+    {
+        require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+
+        try {
+            $oProvider = $this->_getProvider();
+        } catch (Exception $oException) {
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
+            return;
+        }
+
+        $aParameters = $oProvider->loginCallback();
+
+        if ($this->_getToken() != ($aParameters['state'] ?? '')) {
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox(_t('_sys_connect_state_invalid')));
+            return;
+        }
+
+        $sCode = $aParameters['code'] ?? '';
+        if (!$sCode || !empty($aParameters['error'])) {
+            $sErrorDescription = !empty($aParameters['error_description']) ? $aParameters['error_description'] : (!empty($aParameters['error']) ? $aParameters['error'] : _t('_error occured'));
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox($sErrorDescription));
+            return;
+        }
+
+        if ($this->_oConfig->bPkce) {
+            $sCodeVerifier = BxDolSession::getInstance()->getUnsetValue($this->_oConfig->sSessionCodeVerifier);
+            if ($sCodeVerifier)
+                $_SESSION['code-verifier'] = $sCodeVerifier;
+        }
+
+        try {
+            $aAuthData = $oProvider->getAccessToken(GrantType::AuthorizationCode, $sCode, '', $this->_oConfig->bPkce)->wait();
+            if (empty($aAuthData['access_token'])) {
+                $sErrorDescription = isset($aAuthData['error_description']) ? $aAuthData['error_description'] : _t('_error occured');
+                $this->_oTemplate->getPage(_t('_Error'), MsgBox($sErrorDescription));
+                return;
+            }
+
+            $aRemoteProfileInfo = $oProvider->getUserProfile($aAuthData['access_token'])->wait();
+        } catch (Exception $oException) {
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox($this->_getExceptionMessage($oException)));
+            return;
+        }
+
+        if (isset($aRemoteProfileInfo['data']) && is_array($aRemoteProfileInfo['data']))
+            $aRemoteProfileInfo = $aRemoteProfileInfo['data'];
+
+        if (empty($aRemoteProfileInfo['id']) && !empty($aRemoteProfileInfo['sub']))
+            $aRemoteProfileInfo['id'] = $aRemoteProfileInfo['sub'];
+
+        if ($aRemoteProfileInfo && !empty($aRemoteProfileInfo['id'])) {
+            $iLocalProfileId = $this->_oDb->getProfileId($aRemoteProfileInfo['id']);
+
+            if ($iLocalProfileId && $oProfile = BxDolProfile::getInstance($iLocalProfileId)) {
+                $this->setLogged($oProfile->id(), '', true, true);
+            }
+            else {
+                $this->_createProfile($aRemoteProfileInfo);
+            }
+        }
+        else {
+            $this->_oTemplate->getPage(_t('_Error'), MsgBox(_t('_sys_connect_profile_error_info')));
+        }
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array_merge(parent::serviceGetSafeServices(), [
+            'Handle' => '',
+        ]);
+    }
+
+    public function serviceHandle($aRemoteProfileInfo = [])
+    {
+        if (!$this->_bIsApi)
+            return;
+
+        if (is_string($aRemoteProfileInfo))
+            $aRemoteProfileInfo = bx_api_get_browse_params($aRemoteProfileInfo);
+
+        if (empty($aRemoteProfileInfo) || !is_array($aRemoteProfileInfo))
+            return [
+                bx_api_get_msg(_t('_sys_connect_profile_error_info'))
+            ];
+
+        if (empty($aRemoteProfileInfo['id']) && !empty($aRemoteProfileInfo['sub']))
+            $aRemoteProfileInfo['id'] = $aRemoteProfileInfo['sub'];
+
+        $iProfileId = $this->_oDb->getProfileId($aRemoteProfileInfo['id']);
+        if ($iProfileId && $oProfile = BxDolProfile::getInstance($iProfileId))
+            return $this->setLogged($oProfile->id());
+        else
+            return $this->_createProfile($aRemoteProfileInfo);
+    }
+
+    /**
+     * @param $aProfileInfo - remote profile info
+     * @param $sAlternativeName - suffix to add to NickName to make it unique
+     * @return profile array info, ready for the local database
+     */
+    protected function _convertRemoteFields($aProfileInfo, $sAlternativeName = '')
+    {
+        $aProfileFields = $aProfileInfo;
+
+        $sEmail = !empty($aProfileInfo['email']) ? $aProfileInfo['email'] : '';
+        if (!$sEmail && !empty($aProfileInfo['identities'][0]['email']))
+            $sEmail = $aProfileInfo['identities'][0]['email'];
+
+        $sName = !empty($aProfileInfo['preferred_username']) ? $aProfileInfo['preferred_username'] : '';
+        if (!$sName && !empty($aProfileInfo['name']))
+            $sName = $aProfileInfo['name'];
+        if (!$sName && !empty($aProfileInfo['given_name']))
+            $sName = $aProfileInfo['given_name'];
+        if (!$sName && $sEmail)
+            $sName = explode('@', $sEmail)[0];
+        if (!$sName)
+            $sName = !empty($aProfileInfo['sub']) ? $aProfileInfo['sub'] : '';
+
+        $sFullname = !empty($aProfileInfo['name']) ? $aProfileInfo['name'] : trim((!empty($aProfileInfo['given_name']) ? $aProfileInfo['given_name'] : '') . ' ' . (!empty($aProfileInfo['family_name']) ? $aProfileInfo['family_name'] : ''));
+        if (!$sFullname)
+            $sFullname = $sName;
+
+        $aProfileFields['name'] = $sName;
+        $aProfileFields['fullname'] = $sFullname;
+        $aProfileFields['last_name'] = !empty($aProfileInfo['family_name']) ? $aProfileInfo['family_name'] : '';
+        $aProfileFields['email'] = $sEmail;
+        $aProfileFields['picture'] = !empty($aProfileInfo['picture']) ? $aProfileInfo['picture'] : '';
+        $aProfileFields['allow_view_to'] = getParam('bx_cidaascon_privacy');
+
+        return $aProfileFields;
+    }
+
+    /**
+     * @return BxCidaasConProvider
+     */
+    protected function _getProvider()
+    {
+        bx_import('Provider', $this->_aModule);
+        return new BxCidaasConProvider(
+            $this->_oConfig->sBaseUrl,
+            $this->_oConfig->sClientID,
+            $this->_oConfig->sSecret ? $this->_oConfig->sSecret : '',
+            $this->_oConfig->sPageHandle,
+            null,
+            $this->_oConfig->bDebug
+        );
+    }
+
+    protected function _getExceptionMessage($oException)
+    {
+        if ($oException instanceof ClientException && $oException->hasResponse()) {
+            $aBody = json_decode((string)$oException->getResponse()->getBody(), true);
+            if (!empty($aBody['error']['error_description']))
+                return $aBody['error']['error_description'];
+            if (!empty($aBody['error_description']))
+                return $aBody['error_description'];
+            if (!empty($aBody['error']) && is_string($aBody['error']))
+                return $aBody['error'];
+        }
+
+        $sMessage = $oException->getMessage();
+        return $sMessage ? $sMessage : _t('_error occured');
+    }
+}
+
+/** @} */

+ 104 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConProvider.php

@@ -0,0 +1,104 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+use Cidaas\OAuth2\Client\Provider\Cidaas;
+use Cidaas\OAuth2\Client\Provider\GrantType;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Promise\PromiseInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Cidaas SDK wrapper that allows PKCE without a client secret.
+ * The upstream constructor and token request always require client_secret.
+ */
+class BxCidaasConProvider extends Cidaas
+{
+    protected $sBaseUrl;
+    protected $sClientId;
+    protected $sClientSecret;
+    protected $sRedirectUri;
+    protected $bDebug;
+
+    public function __construct(string $baseUrl, string $clientId, string $clientSecret, string $redirectUri, ?HandlerStack $handler = null, bool $debug = false)
+    {
+        $this->sBaseUrl = rtrim($baseUrl, '/');
+        $this->sClientId = $clientId;
+        $this->sClientSecret = $clientSecret;
+        $this->sRedirectUri = $redirectUri;
+        $this->bDebug = $debug;
+
+        // Upstream validate() rejects an empty secret; pass a placeholder then clear it.
+        parent::__construct($baseUrl, $clientId, $clientSecret !== '' ? $clientSecret : '.', $redirectUri, $handler, $debug);
+
+        if ($clientSecret === '') {
+            $oProp = new ReflectionProperty(Cidaas::class, 'clientSecret');
+            $oProp->setValue($this, '');
+        }
+    }
+
+    public function getAccessToken(string $grantType, string $code = '', string $refreshToken = '', bool $pkceEnabled = false): PromiseInterface
+    {
+        if ($grantType === GrantType::AuthorizationCode) {
+            if (empty($code))
+                throw new InvalidArgumentException('code must not be empty in authorization_code flow');
+
+            $aParams = [
+                'client_id' => $this->sClientId,
+                'redirect_uri' => $this->sRedirectUri,
+                'grant_type' => 'authorization_code',
+                'code' => $code,
+            ];
+
+            if ($this->sClientSecret !== '')
+                $aParams['client_secret'] = $this->sClientSecret;
+
+            if ($pkceEnabled)
+                $aParams['code_verifier'] = $_SESSION['code-verifier'] ?? '';
+        }
+        else if ($grantType === GrantType::RefreshToken) {
+            if (empty($refreshToken))
+                throw new InvalidArgumentException('refreshToken must not be empty in refresh_token flow');
+
+            $aParams = [
+                'client_id' => $this->sClientId,
+                'grant_type' => 'refresh_token',
+                'refresh_token' => $refreshToken,
+            ];
+
+            if ($this->sClientSecret !== '')
+                $aParams['client_secret'] = $this->sClientSecret;
+        }
+        else if ($grantType === GrantType::ClientCredentials) {
+            if ($this->sClientSecret === '')
+                throw new InvalidArgumentException('client secret is required for client_credentials flow');
+
+            $aParams = [
+                'client_id' => $this->sClientId,
+                'client_secret' => $this->sClientSecret,
+                'grant_type' => 'client_credentials',
+            ];
+        }
+        else {
+            throw new InvalidArgumentException('invalid grant type');
+        }
+
+        $oCreateClient = new ReflectionMethod(Cidaas::class, 'createClient');
+        $oClient = $oCreateClient->invoke($this);
+
+        $oParseJson = new ReflectionMethod(Cidaas::class, 'parseJson');
+
+        return $oClient->requestAsync('POST', $this->sBaseUrl . '/token-srv/token', ['form_params' => $aParams])->then(function (ResponseInterface $oResponse) use ($oParseJson) {
+            return $oParseJson->invoke($this, $oResponse->getBody());
+        });
+    }
+}
+
+/** @} */

+ 31 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConStudioPage.php

@@ -0,0 +1,31 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConStudioPage extends BxTemplStudioModule
+{
+    function __construct($sModule, $mixedPageName, $sPage = "")
+    {
+        parent::__construct($sModule, $mixedPageName, $sPage);
+
+        $this->aMenuItems = array(
+            'settings' => array('name' => 'settings', 'icon' => 'cogs', 'title' => '_adm_lmi_cpt_settings'),
+            'help' => array('name' => 'help', 'icon' => 'question', 'title' => '_sys_connect_information'),
+        );
+    }
+    
+    function getHelp ()
+    {
+        $oModule = BxDolModule::getInstance('bx_cidaascon'); 
+        return _t('_bx_cidaascon_information_block', BX_DOL_URL_ROOT . $oModule -> _oConfig -> getBaseUri() . 'handle');
+    }
+}
+
+/** @} */

+ 20 - 0
modules/boonex/cidaas_connect/classes/BxCidaasConTemplate.php

@@ -0,0 +1,20 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConTemplate extends BxBaseModConnectTemplate
+{
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+    }
+}
+
+/** @} */

+ 13 - 0
modules/boonex/cidaas_connect/composer.json

@@ -0,0 +1,13 @@
+{
+    "name": "unacms/cidaas-connect",
+    "repositories": [
+        {
+            "type": "vcs",
+            "url": "https://github.com/Cidaas/cidaas-sdk-php.git"
+        }
+    ],
+    "require": {
+        "php": ">=8.2",
+        "cidaas/oauth2-cidaas": "dev-master"
+    }
+}

+ 785 - 0
modules/boonex/cidaas_connect/composer.lock

@@ -0,0 +1,785 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "dd258f24d7ab52d620921241237b9cf8",
+    "packages": [
+        {
+            "name": "cidaas/oauth2-cidaas",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cidaas/cidaas-sdk-php.git",
+                "reference": "738105b141115cb51d7262a02868062215c71e3d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cidaas/cidaas-sdk-php/zipball/738105b141115cb51d7262a02868062215c71e3d",
+                "reference": "738105b141115cb51d7262a02868062215c71e3d",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^7.2.0",
+                "php": "^8.2"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.10",
+                "phpunit/php-code-coverage": "^10.1",
+                "phpunit/phpunit": "^10.5",
+                "roave/security-advisories": "dev-master",
+                "vlucas/phpdotenv": "^5.3.0"
+            },
+            "default-branch": true,
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Cidaas\\OAuth2\\Client\\Provider\\": "src/"
+                }
+            },
+            "autoload-dev": {
+                "psr-4": {
+                    "Cidaas\\OAuth2\\Client\\Test\\Provider\\": "tests/"
+                }
+            },
+            "scripts": {
+                "test": [
+                    "phpunit"
+                ],
+                "phpstan": [
+                    "phpstan analyse -c phpstan.neon"
+                ]
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "cidaas by Widas ID GmbH",
+                    "email": "developer@widas.de",
+                    "homepage": "https://www.cidaas.com"
+                }
+            ],
+            "description": "Cidaas OAuth 2.0 and OpenID connect Client SDK",
+            "keywords": [
+                "authorisation",
+                "authorization",
+                "cidaas",
+                "client",
+                "oauth",
+                "oauth2",
+                "openid connect"
+            ],
+            "support": {
+                "source": "https://github.com/cidaas/cidaas-sdk-php/tree/v2.0.1",
+                "issues": "https://github.com/cidaas/cidaas-sdk-php/issues"
+            },
+            "time": "2026-06-19T11:04:36+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.15.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc",
+                "reference": "ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^2.5.2",
+                "guzzlehttp/psr7": "^2.13",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/polyfill-php80": "^1.25"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-curl": "*",
+                "guzzle/client-integration-tests": "3.0.3",
+                "guzzlehttp/test-server": "^0.7",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.15.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-05T19:48:21+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "2823687acff28b2dbe67b2508a6b300e2c3fa4ce"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/2823687acff28b2dbe67b2508a6b300e2c3fa4ce",
+                "reference": "2823687acff28b2dbe67b2508a6b300e2c3fa4ce",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-08-05T19:30:54+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.13.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4",
+                "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/polyfill-php80": "^1.25"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "1.1.0",
+                "jshttp/mime-db": "1.54.0.1",
+                "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.13.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-07-16T22:23:49+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-client"
+            },
+            "time": "2023-09-23T14:17:50+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-factory"
+            },
+            "time": "2024-04-15T12:06:14+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/2.0"
+            },
+            "time": "2023-04-04T09:54:51+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.7-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-06-05T06:23:12+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.37.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-04-10T16:19:22+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "cidaas/oauth2-cidaas": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=8.2"
+    },
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
+}

+ 68 - 0
modules/boonex/cidaas_connect/install/config.php

@@ -0,0 +1,68 @@
+<?php 
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_cidaascon',
+    'title' => 'Cidaas Connect',
+    'note' => 'Join the site using Cidaas account.',
+    'version' => '15.0.0.DEV',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.x'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/cidaas_connect/',
+    'home_uri' => 'cidaascon',
+
+    'db_prefix' => 'bx_cidaascon_',
+    'class_prefix' => 'BxCidaasCon',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Cidaas Connect',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+);
+
+/** @} */

+ 20 - 0
modules/boonex/cidaas_connect/install/installer.php

@@ -0,0 +1,20 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCidaasConInstaller extends BxDolStudioInstaller
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}
+
+/** @} */

+ 34 - 0
modules/boonex/cidaas_connect/install/langs/en.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string name="_sys_module_cidaascon"><![CDATA[Cidaas Connect]]></string>
+	<string name="_bx_cidaascon"><![CDATA[Cidaas Connect]]></string>
+	<string name="_bx_cidaascon_settings"><![CDATA[Cidaas Connect]]></string>
+	<string name="_bx_cidaascon_information_block"><![CDATA[
+        <p>This module logs users in through the Cidaas hosted login page using the official <a target="_blank" href="https://github.com/cidaas/cidaas-sdk-php">Cidaas PHP SDK</a>.</p>
+        <p>Create an application in your Cidaas tenant and set:</p>
+        <ul>
+            <li>Redirect URL: <strong>{0}</strong></li>
+            <li>Base URL: your Cidaas instance, e.g. https://yourtenant.cidaas.de</li>
+            <li>Client ID issued for that application</li>
+            <li>Client Secret — required unless PKCE is enabled for a public client</li>
+        </ul>
+        <p>Enable PKCE in module settings if the Cidaas app is configured for the PKCE authorization code flow. With PKCE, Client Secret can be left empty. For more info see <a target="_blank" href="https://docs.cidaas.com/">Cidaas documentation</a>.</p>
+        ]]></string>
+	<string name="_bx_cidaascon_profile_error_api_keys"><![CDATA[Please configure the module in admin panel (Base URL and Client ID are required; Client Secret is required unless PKCE is enabled)]]></string>
+	<string name="_bx_cidaascon_auth_title"><![CDATA[Login with Cidaas]]></string>
+
+    <string name="_bx_cidaascon_adm_stg_cpt_type"><![CDATA[Cidaas Connect]]></string>
+    <string name="_bx_cidaascon_option_base_url"><![CDATA[Base URL]]></string>
+    <string name="_bx_cidaascon_option_client_id"><![CDATA[Client ID]]></string>
+    <string name="_bx_cidaascon_option_secret"><![CDATA[Client Secret]]></string>
+    <string name="_bx_cidaascon_option_scope"><![CDATA[Scope]]></string>
+    <string name="_bx_cidaascon_option_pkce"><![CDATA[Enable PKCE flow]]></string>
+    <string name="_bx_cidaascon_option_debug"><![CDATA[Debug mode]]></string>
+
+	<string name="_bx_cidaascon_et_password_generated"><![CDATA[Cidaas connect temporary password]]></string>
+	<string name="_bx_cidaascon_et_password_generated_subject"><![CDATA[Temporary password]]></string>
+	<string name="_bx_cidaascon_et_password_generated_body"><![CDATA[{email_header}
+<p>Temporary password - <b>{password}</b></p>
+<p>Change it after logging-in.</p>
+{email_footer}]]></string>
+</resources>

+ 20 - 0
modules/boonex/cidaas_connect/install/sql/disable.sql

@@ -0,0 +1,20 @@
+
+-- Email template
+
+DELETE FROM `sys_email_templates` WHERE `Name` = 'bx_cidaascon_password_generated';
+
+-- Auth objects
+
+DELETE FROM `sys_objects_auths` WHERE `Name` = 'bx_cidaascon';
+
+-- Alerts
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name`  =  'bx_cidaascon');
+
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandlerId;
+DELETE FROM `sys_alerts` WHERE `handler_id` =  @iHandlerId;
+
+-- Options
+
+DELETE FROM `top`, `toc`, `to` USING `sys_options_types` AS `top` LEFT JOIN `sys_options_categories` AS `toc` ON `top`.`id`=`toc`.`type_id` LEFT JOIN `sys_options` AS `to` ON `toc`.`id`=`to`.`category_id` WHERE `top`.`name` = 'bx_cidaascon';
+

+ 45 - 0
modules/boonex/cidaas_connect/install/sql/enable.sql

@@ -0,0 +1,45 @@
+
+-- Email template
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_cidaascon', '_bx_cidaascon_et_password_generated', 'bx_cidaascon_password_generated', '_bx_cidaascon_et_password_generated_subject', '_bx_cidaascon_et_password_generated_body');
+
+-- Auth objects
+
+INSERT INTO `sys_objects_auths` (`Name`, `Title`, `Link`, `Icon`, `Style`) VALUES
+('bx_cidaascon', '_bx_cidaascon_auth_title', 'modules/?r=cidaascon/start', 'fas fingerprint', 'a:1:{s:7:".bx-btn";a:1:{s:10:"background";s:18:"#00a0b0 !important";}}');
+
+-- Alerts
+
+INSERT INTO `sys_alerts_handlers` SET `name` = 'bx_cidaascon', `class` = 'BxCidaasConAlerts', `file` = 'modules/boonex/cidaas_connect/classes/BxCidaasConAlerts.php';
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name` = 'bx_cidaascon');
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'logout', @iHandlerId),
+('profile', 'delete', @iHandlerId),
+('profile', 'add', @iHandlerId);
+
+-- Options
+
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types` (`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules', 'bx_cidaascon', '_bx_cidaascon_adm_stg_cpt_type', 'bx_cidaascon@modules/boonex/cidaas_connect/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_cidaascon_general', '_sys_connect_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_cidaascon_base_url', '', @iCategId, '_bx_cidaascon_option_base_url', 'digit', '', '', 20, ''),
+('bx_cidaascon_client_id', '', @iCategId, '_bx_cidaascon_option_client_id', 'digit', '', '', 22, ''),
+('bx_cidaascon_secret', '', @iCategId, '_bx_cidaascon_option_secret', 'secret', '', '', 24, ''),
+('bx_cidaascon_scope', 'openid profile email offline_access', @iCategId, '_bx_cidaascon_option_scope', 'digit', '', '', 26, ''),
+('bx_cidaascon_pkce', '', @iCategId, '_bx_cidaascon_option_pkce', 'checkbox', '', '', 28, ''),
+('bx_cidaascon_redirect_page', 'index', @iCategId, '_sys_connect_option_redirect', 'select', '', '', 40, 'join,settings,dashboard,index'),
+('bx_cidaascon_module', 'bx_persons', @iCategId, '_sys_connect_option_module', 'select', '', '', 50, 'a:2:{s:6:"module";s:12:"bx_cidaascon";s:6:"method";s:20:"get_profiles_modules";}'),
+('bx_cidaascon_privacy', '3', @iCategId, '_sys_connect_option_privacy', 'select', '', '', 54, 'a:2:{s:6:"module";s:12:"bx_cidaascon";s:6:"method";s:18:"get_privacy_groups";}'),
+('bx_cidaascon_confirm_email', 'on', @iCategId, '_sys_connect_option_confirm_email', 'checkbox', '', '', 70, ''),
+('bx_cidaascon_approve', '', @iCategId, '_sys_connect_option_approve', 'checkbox', '', '', 80, ''),
+('bx_cidaascon_debug', '', @iCategId, '_bx_cidaascon_option_debug', 'checkbox', '', '', 90, '');

+ 21 - 0
modules/boonex/cidaas_connect/install/sql/install.sql

@@ -0,0 +1,21 @@
+
+CREATE TABLE `bx_cidaascon_accounts` (
+  `local_profile` int(10) unsigned NOT NULL,
+  `remote_profile` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+  PRIMARY KEY (`local_profile`),
+  KEY `remote_profile` (`remote_profile`)
+);
+
+-- Studio page and widget
+
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, 'bx_cidaascon', '_bx_cidaascon', '_bx_cidaascon', 'bx_cidaascon@modules/boonex/cidaas_connect/|std-icon.svg');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name` = 'home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id` = @iParentPageId);
+INSERT INTO `sys_std_widgets` (`page_id`, `module`, `type`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, 'bx_cidaascon', 'integrations', '{url_studio}module.php?name=bx_cidaascon', '', 'bx_cidaascon@modules/boonex/cidaas_connect/|std-icon.svg', '_bx_cidaascon', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+INSERT INTO `sys_std_pages_widgets` (`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(ISNULL(@iParentPageOrder), 1, @iParentPageOrder + 1));
+

+ 8 - 0
modules/boonex/cidaas_connect/install/sql/uninstall.sql

@@ -0,0 +1,8 @@
+
+DROP TABLE IF EXISTS `bx_cidaascon_accounts`;
+
+
+-- Studio page and widget
+DELETE FROM `tp`, `tw`, `twb`, `tpw` 
+USING `sys_std_pages` AS `tp` LEFT JOIN `sys_std_widgets` AS `tw` ON `tp`.`id` = `tw`.`page_id` LEFT JOIN `sys_std_widgets_bookmarks` AS `twb` ON `tw`.`id` = `twb`.`widget_id` LEFT JOIN `sys_std_pages_widgets` AS `tpw` ON `tw`.`id` = `tpw`.`widget_id`
+WHERE  `tp`.`name` = 'bx_cidaascon';

+ 18 - 0
modules/boonex/cidaas_connect/request.php

@@ -0,0 +1,18 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    CidaasConnect Cidaas Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . 'profiles.inc.php');
+
+check_logged();
+
+BxDolRequest::processAsAction($GLOBALS['aModule'], $GLOBALS['aRequest']);
+
+/** @} */

+ 75 - 0
modules/boonex/cidaas_connect/template/images/icons/std-icon.svg

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 288 286.9" style="enable-background:new 0 0 288 286.9;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F04923;}
+	.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#F9AE1C;}
+	.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F89521;}
+	.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#F47422;}
+	.st4{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
+	.st5{fill:#565656;}
+</style>
+<g>
+	<g>
+		<g id="Rounded_Rectangle_1">
+			<g>
+				<path class="st0" d="M179.3,38.3c-20-20-52.4-20-72.4,0L7.7,137.5l99.1,99.2c20,20,52.4,20,72.4,0l99.2-99.1L179.3,38.3z"/>
+			</g>
+		</g>
+		<g id="Rounded_Rectangle_1_copy">
+			<g>
+				<path class="st1" d="M223.1,82.2L88.5,218.4l18.3,18.3c20,20,52.4,20,72.4,0l99.2-99.1L223.1,82.2z"/>
+			</g>
+		</g>
+		<g id="Rounded_Rectangle_1_copy_2">
+			<g>
+				<path class="st2" d="M224.5,83.6l-45.2-45.2c-20-20-52.4-20-72.4,0L7.7,137.5l96.7,96.8C211,193,223.2,107.6,224.5,83.6z"/>
+			</g>
+		</g>
+		<g id="Rounded_Rectangle_1_copy_4">
+			<g>
+				<path class="st3" d="M190.3,49.4l-11-11c-20-20-52.4-20-72.4,0L7.7,137.5l65.9,65.9C88.5,198.3,180.1,162.8,190.3,49.4z"/>
+			</g>
+		</g>
+		<g id="Rounded_Rectangle_1_copy_3">
+			<g>
+				<path class="st0" d="M151.1,24c-15.6-2.5-32.2,2.3-44.2,14.4L7.7,137.5l29.9,30C50.1,163.4,138.8,131.3,151.1,24z"/>
+			</g>
+		</g>
+		<g id="Ellipse_1">
+			<g>
+				<path class="st4" d="M148.5,189.4c-25.1-4-44.3-25.5-44.3-51.5c0-25.6,18.7-46.9,43.3-51.3c2.6-4,5.5-7.8,8.7-11.2
+					c-34.7,1.9-62.3,29-62.3,62.1c0,33.3,27.8,60.5,62.8,62.2C153.7,196.5,151,193,148.5,189.4z"/>
+			</g>
+		</g>
+		<g id="Rectangle_1">
+			<g>
+				<path class="st4" d="M133.3,137.9v-0.8c0-12.4,10-22.4,22.4-22.4h16.5V105h-17.3c-17.2,0-31.1,13.9-31.1,31.1v3.5
+					c0,17.2,13.9,31.1,31.1,31.1h20v-10.3h-19.2C143.4,160.3,133.3,150.3,133.3,137.9z"/>
+			</g>
+		</g>
+		<g id="Ellipse_2">
+			<g>
+				<path class="st4" d="M172.5,75.2c-10.9,0-19.7,8.8-19.7,19.7c0,1.4,0.1,2.7,0.4,4.1h10.6c-0.5-1.1-0.7-2.4-0.7-3.6
+					c0-5.3,4.3-9.6,9.6-9.6s9.6,4.3,9.6,9.6c0,4.9-3.7,8.9-8.4,9.5v0.1h-18.3c3.4,5.8,9.7,9.7,17,9.7c10.9,0,19.7-8.8,19.7-19.7
+					C192.2,84.1,183.4,75.2,172.5,75.2z"/>
+			</g>
+		</g>
+		<g id="Ellipse_2_copy">
+			<g>
+				<path class="st4" d="M175.6,160.3h-6.3c-6.2,1-11.4,4.9-14.2,10.3h16.6v0c0.3,0,0.6,0,0.9,0c5.3,0,9.6,4.3,9.6,9.6
+					s-4.3,9.6-9.6,9.6s-9.6-4.3-9.6-9.6c0-1.2,0.2-2.3,0.6-3.4H153c-0.1,1-0.2,2-0.2,3c0,10.9,8.8,19.7,19.7,19.7
+					c10.9,0,19.7-8.8,19.7-19.7C192.2,169.9,185,161.8,175.6,160.3z"/>
+			</g>
+		</g>
+	</g>
+	<g>
+		<path class="st5" d="M278.7,242.3c0,5.6-4.4,10-10.1,10c-5.7,0-10.2-4.4-10.2-10c0-5.5,4.5-9.9,10.2-9.9
+			C274.3,232.4,278.7,236.8,278.7,242.3z M260.9,242.3c0,4.4,3.3,7.9,7.7,7.9c4.3,0,7.5-3.5,7.5-7.8c0-4.4-3.2-7.9-7.6-7.9
+			C264.2,234.4,260.9,238,260.9,242.3z M267,247.5h-2.3v-9.9c0.9-0.2,2.2-0.3,3.8-0.3c1.9,0,2.7,0.3,3.4,0.7c0.5,0.4,1,1.2,1,2.2
+			c0,1.1-0.8,1.9-2,2.3v0.1c1,0.4,1.5,1.1,1.8,2.4c0.3,1.5,0.5,2.1,0.7,2.5h-2.5c-0.3-0.4-0.5-1.3-0.8-2.4c-0.2-1.1-0.8-1.6-2-1.6
+			H267V247.5z M267.1,241.9h1.1c1.3,0,2.3-0.4,2.3-1.4c0-0.9-0.7-1.5-2.1-1.5c-0.6,0-1,0.1-1.3,0.1V241.9z"/>
+	</g>
+</g>
+</svg>

+ 25 - 17
modules/boonex/english/data/langs/system/en.xml

@@ -464,7 +464,7 @@
 	<string name="_adm_admtools_Executable"><![CDATA[Executable]]></string>
 	<string name="_adm_admtools_Non_Executable"><![CDATA[Not executable]]></string>
 	<string name="_adm_admtools_Non_Writable"><![CDATA[Not writable]]></string>
-	<string name="_adm_admtools_Not_Exists"><![CDATA[Not exists]]></string>
+	<string name="_adm_admtools_Not_Exists"><![CDATA[Missing]]></string>
 	<string name="_adm_admtools_Path"><![CDATA[Path]]></string>
 	<string name="_adm_admtools_Permissions"><![CDATA[Permissions]]></string>
 	<string name="_adm_admtools_Writable"><![CDATA[Writable]]></string>
@@ -2625,13 +2625,12 @@
 	<string name="_sys_audit_msg_optimization_warn"><![CDATA[installing PHP accelerator will speed-up file cache]]></string>
 	<string name="_sys_audit_msg_x_based_cache_engine"><![CDATA[ ({0} based cache engine)]]></string>
 	<string name="_sys_audit_msg_ffmpeg"><![CDATA[<pre class="code">{0}</pre>if you don't know if output is correct then <a href="#manual_audit">manual server audit</a> may be required.]]></string>
-	<string name="_sys_audit_msg_hardware_requirements"><![CDATA[Hardware requirements can not be determined automatically - <a href="#manual_audit">manual server audit</a> may be required.]]></string>
+	<string name="_sys_audit_msg_hardware_requirements"><![CDATA[Hardware cannot be checked automatically. A <a href="#manual_audit">manual server audit</a> may be needed.]]></string>
 	<string name="_sys_audit_msg_mail_send_failed"><![CDATA[mail send failed]]></string>
 	<string name="_sys_audit_msg_mail_sending"><![CDATA[<span id="bx-sys-adm-audit-test-email"><a href="javascript:void(0);" onclick="bx_sys_adm_audit_test_email()">click here</a> to send test email</span>]]></string>
 	<string name="_sys_audit_msg_mail_sent"><![CDATA[test mail was send, please check {0} mailbox]]></string>
 	<string name="_sys_audit_msg_manual_audit"><![CDATA[<p>
-Some things can not be determined automatically, manual server audit is required to check it. 
-If you don't know how to do it by yourself you can submit <a target="_blank" href="mailto:team@unacms.com?subject=Server Audit Request">Server Audit Request</a>.
+Some checks have to be done by hand. If you want help, <a target="_blank" href="mailto:team@unacms.com?subject=Server Audit Request">submit a server audit request</a>.
 </p>]]></string>
 	<string name="_sys_audit_msg_must_be"><![CDATA[must be {0} {1}]]></string>
 	<string name="_sys_audit_msg_optimization_apache_module"><![CDATA[To apply this optimization you need to have <b>{0}</b> - {1}]]></string>
@@ -2639,12 +2638,12 @@ If you don't know how to do it by yourself you can submit <a target="_blank" hre
 	<string name="_sys_audit_msg_php_accelerator_missing"><![CDATA[The script can be much faster if you install some PHP accelerator]]></string>
 	<string name="_sys_audit_msg_php_setup_inefficient"><![CDATA[your PHP setup maybe very inefficient, <a href="javascript:void(0)" onclick="bx_sys_adm_audit_phpinfo()">please check it for sure</a> and try to switch to mod_php, apache dso module or FastCGI]]></string>
 	<string name="_sys_audit_msg_serverside_compression"><![CDATA[
-can be checked <a href="#manual_audit">manually</a> or in "Page Speed" tool build-in into browser.<br />
-If it is not enabled then please consider implement this optimization, since it improve perceived site speed and save the bandwidth.<br />{0}]]></string>
+can be checked <a href="#manual_audit">manually</a> or in the PageSpeed tool built into the browser.<br />
+If it is not enabled then please consider implementing this optimization, since it improves perceived site speed and saves bandwidth.<br />{0}]]></string>
 	<string name="_sys_audit_msg_should_be"><![CDATA[should be {0} {1}]]></string>
 	<string name="_sys_audit_msg_userside_caching"><![CDATA[
-<a href="{0}">click here to check it in Google Page Speed</a><br />
-If it is not enabled then please consider implement this optimization, since it improve perceived site speed and save the bandwidth.<br />{1}]]></string>
+<a href="{0}">click here to check it in Google PageSpeed</a><br />
+If it is not enabled then please consider implementing this optimization, since it improves perceived site speed and saves bandwidth.<br />{1}]]></string>
 	<string name="_sys_audit_msg_value_checking_failed"><![CDATA[value checking failed]]></string>
 	<string name="_sys_audit_msg_version_is_incompatible"><![CDATA[your version is incompatible with UNA, must be at least {0}]]></string>
 	<string name="_sys_audit_msg_version_is_outdated"><![CDATA[your version is outdated, upgrade to the latest {0} maybe required]]></string>
@@ -2655,10 +2654,10 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_audit_section_os"><![CDATA[OS]]></string>
 	<string name="_sys_audit_section_webserver"><![CDATA[Web-server]]></string>
 	<string name="_sys_audit_serverside_compression"><![CDATA[Server-side content compression]]></string>
-	<string name="_sys_audit_title_fail"><![CDATA[FAIL]]></string>
-	<string name="_sys_audit_title_ok"><![CDATA[OK]]></string>
-	<string name="_sys_audit_title_undef"><![CDATA[UNDEFINED]]></string>
-	<string name="_sys_audit_title_warn"><![CDATA[WARNING]]></string>
+	<string name="_sys_audit_title_fail"><![CDATA[Fail]]></string>
+	<string name="_sys_audit_title_ok"><![CDATA[Pass]]></string>
+	<string name="_sys_audit_title_undef"><![CDATA[Unknown]]></string>
+	<string name="_sys_audit_title_warn"><![CDATA[Warning]]></string>
 	<string name="_sys_audit_userside_caching"><![CDATA[User-side caching for static content]]></string>
 	<string name="_sys_audit_version"><![CDATA[version]]></string>
 	<string name="_sys_audit_version_script"><![CDATA[UNA version]]></string>
@@ -3275,6 +3274,10 @@ If it is not enabled then please consider implement this optimization, since it
   <string name="_sys_grid_search"><![CDATA[Search...]]></string>
   <string name="_sys_grid_total_count"><![CDATA[Сount: {0}]]></string>
   <string name="_sys_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+	<string name="_sys_inst_audit_failed"><![CDATA[{0} failed]]></string>
+	<string name="_sys_inst_audit_lead"><![CDATA[A check of PHP, extensions, and paths on this host.]]></string>
+	<string name="_sys_inst_audit_passed"><![CDATA[{0} passed]]></string>
+	<string name="_sys_inst_audit_warnings"><![CDATA[{0} warnings]]></string>
 	<string name="_sys_inst_conf_desc"><![CDATA[{0}{1}<br />Example: {2}]]></string>
 	<string name="_sys_inst_conf_desc_admin_email"><![CDATA[Admin user e-mail address.]]></string>
 	<string name="_sys_inst_conf_desc_admin_pwd"><![CDATA[Admin user password here.]]></string>
@@ -3316,9 +3319,9 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_inst_conf_field_site_title"><![CDATA[Site title]]></string>
 	<string name="_sys_inst_conf_field_site_url"><![CDATA[Site URL]]></string>
 	<string name="_sys_inst_conf_field_template"><![CDATA[Template]]></string>
-	<string name="_sys_inst_conf_found"><![CDATA[<span style="color:green">Found</span>]]></string>
+	<string name="_sys_inst_conf_found"><![CDATA[<span class="bx-install-chip bx-install-chip-ok">Found</span>]]></string>
 	<string name="_sys_inst_conf_no_example"><![CDATA[no example]]></string>
-	<string name="_sys_inst_conf_not_found"><![CDATA[<span style="color:red">Not found</span>]]></string>
+	<string name="_sys_inst_conf_not_found"><![CDATA[<span class="bx-install-chip bx-install-chip-bad">Not found</span>]]></string>
 	<string name="_sys_inst_conf_section_db_config"><![CDATA[DB config]]></string>
 	<string name="_sys_inst_conf_section_link_with_market"><![CDATA[Link with UNA market]]></string>
 	<string name="_sys_inst_conf_section_modules"><![CDATA[Modules]]></string>
@@ -3326,11 +3329,15 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_inst_conf_section_site_info"><![CDATA[Site info]]></string>
 	<string name="_sys_inst_continue"><![CDATA[Continue]]></string>
 	<string name="_sys_inst_finish"><![CDATA[Installation has been successfully completed]]></string>
+	<string name="_sys_inst_footer_contact"><![CDATA[Contact UNA Team]]></string>
+	<string name="_sys_inst_footer_docs"><![CDATA[Documentation]]></string>
+	<string name="_sys_inst_footer_product"><![CDATA[UNA Community Management System]]></string>
+	<string name="_sys_inst_help"><![CDATA[Help]]></string>
 	<string name="_sys_inst_help_finish"><![CDATA[Help with cron jobs]]></string>
 	<string name="_sys_inst_help_permissions"><![CDATA[Setting permissions help]]></string>
 	<string name="_sys_inst_help_site_config"><![CDATA[Creating database help]]></string>
-	<string name="_sys_inst_hosting_fails"><![CDATA[Your hosting doesn't meet minimal requirements:]]></string>
-	<string name="_sys_inst_hosting_warnings"><![CDATA[Your hosting setup has some warnings, for the complete report go to <a href="?action=audit">server audit page</a>.]]></string>
+	<string name="_sys_inst_hosting_fails"><![CDATA[This host does not meet the minimum requirements:]]></string>
+	<string name="_sys_inst_hosting_warnings"><![CDATA[This host has a few warnings. See the full report on the <a href="?action=audit">server audit page</a>.]]></string>
 	<string name="_sys_inst_msg_admin_account_not_found"><![CDATA[Admin account wasn't found by email - {0}]]></string>
 	<string name="_sys_inst_msg_db_err_connect"><![CDATA[Database connect failed]]></string>
 	<string name="_sys_inst_msg_db_err_select"><![CDATA[Database select failed]]></string>
@@ -3342,7 +3349,8 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_inst_msg_finish_goto_studio"><![CDATA[<a href="{0}studio/">Go to Studio</a> to add additional modules and customize the site.]]></string>
 	<string name="_sys_inst_msg_form_error"><![CDATA[Please fix form errors.]]></string>
 	<string name="_sys_inst_msg_module_error"><![CDATA[{0} - {1}]]></string>
-	<string name="_sys_inst_msg_permissions_wrong"><![CDATA[Some files or folders rights aren't correct, please set necessary permissions then click "Continue".]]></string>
+	<string name="_sys_inst_msg_permissions_lead"><![CDATA[These paths need to be writable, or executable, before you continue.]]></string>
+	<string name="_sys_inst_msg_permissions_wrong"><![CDATA[Some paths need attention. Fix them, then continue.]]></string>
 	<string name="_sys_inst_msg_remove_install"><![CDATA[Please, remove INSTALL directory from your server and reload this page to activate your site.<br />After deletion you can safely go <a href="../studio/">to the site Studio</a> to add additional modules and customize the site.]]></string>
 	<string name="_sys_inst_msg_script_isnt_installed"><![CDATA[Script isn't installed]]></string>
 	<string name="_sys_inst_refresh"><![CDATA[Refresh]]></string>

+ 25 - 17
modules/boonex/russian/data/langs/system/ru.xml

@@ -462,7 +462,7 @@
 	<string name="_adm_admtools_Executable"><![CDATA[Исполняемый]]></string>
 	<string name="_adm_admtools_Non_Executable"><![CDATA[Неисполняемый]]></string>
 	<string name="_adm_admtools_Non_Writable"><![CDATA[Незаписываемый]]></string>
-	<string name="_adm_admtools_Not_Exists"><![CDATA[Несуществующий]]></string>
+	<string name="_adm_admtools_Not_Exists"><![CDATA[Отсутствует]]></string>
 	<string name="_adm_admtools_Path"><![CDATA[Путь]]></string>
 	<string name="_adm_admtools_Permissions"><![CDATA[Разрешения]]></string>
 	<string name="_adm_admtools_Writable"><![CDATA[Записываемый]]></string>
@@ -2598,13 +2598,12 @@
 	<string name="_sys_audit_msg_optimization_warn"><![CDATA[установка PHP-акселератора ускорит кэширование файлов]]></string>
 	<string name="_sys_audit_msg_x_based_cache_engine"><![CDATA[ ({0} кэш-движок)]]></string>
 	<string name="_sys_audit_msg_ffmpeg"><![CDATA[<pre class="code">{0}</pre>если Вы не уверены, корректен ли вывод, может понадобиться <a href="#manual_audit">ручной аудит сервера</a>.]]></string>
-	<string name="_sys_audit_msg_hardware_requirements"><![CDATA[Требования к "железу" невозможно установить автоматически - может быть необходим <a href="#manual_audit">ручной аудит сервера</a>.]]></string>
+	<string name="_sys_audit_msg_hardware_requirements"><![CDATA[Оборудование нельзя проверить автоматически. Может понадобиться <a href="#manual_audit">ручной аудит сервера</a>.]]></string>
 	<string name="_sys_audit_msg_mail_send_failed"><![CDATA[не удалось отослать письмо]]></string>
 	<string name="_sys_audit_msg_mail_sending"><![CDATA[<span id="bx-sys-adm-audit-test-email"><a href="javascript:void(0);" onclick="bx_sys_adm_audit_test_email()">нажмите здесь</a>, чтобы отослать тестовое сообщение</span>]]></string>
 	<string name="_sys_audit_msg_mail_sent"><![CDATA[тестовое сообщение отослано, пожалуйста, проверьте {0} эл. ящик]]></string>
 	<string name="_sys_audit_msg_manual_audit"><![CDATA[<p>
-Некоторые пункты не могут быть автоматически определены, ручной аудит сервера необходим для проверки. 
-Если Вы не знаете, как провести его самостоятельно, Вы можете запросить <a target="_blank" href="mailto:team@unacms.com?subject=Ручной аудит сервера">ручной аудит сервера</a>.
+Некоторые проверки нужно сделать вручную. Если нужна помощь, <a target="_blank" href="mailto:team@unacms.com?subject=Ручной аудит сервера">отправьте запрос на аудит сервера</a>.
 </p>]]></string>
 	<string name="_sys_audit_msg_must_be"><![CDATA[нужно {0} {1}]]></string>
 	<string name="_sys_audit_msg_optimization_apache_module"><![CDATA[Чтобы применить эту оптимизацию, Вам нужно иметь <b>{0}</b> - {1}]]></string>
@@ -2612,12 +2611,12 @@
 	<string name="_sys_audit_msg_php_accelerator_missing"><![CDATA[Можно значительно ускорить работу скрипта, если установить PHP-акселератор]]></string>
 	<string name="_sys_audit_msg_php_setup_inefficient"><![CDATA[Ваши PHP-настройки могут быть очень неэффективными, <a href="javascript:void(0)" onclick="bx_sys_adm_audit_phpinfo()">проверьте их</a> и попробуйте переключиться на mod_php, apache dso module или FastCGI]]></string>
 	<string name="_sys_audit_msg_serverside_compression"><![CDATA[
-может быть проверено <a href="#manual_audit">вручную</a> или с помощью интсрумента "Page Speed", встроенного в браузер.<br />
-Если он не задействован, тогда попробуйте применить эту оптимизацию, так как она улучшает скорость сайта и сохраняет трафик.<br />{0}]]></string>
+может быть проверено <a href="#manual_audit">вручную</a> или с помощью инструмента PageSpeed, встроенного в браузер.<br />
+Если он не задействован, тогда попробуйте применить эту оптимизацию, так как она улучшает скорость сайта и экономит трафик.<br />{0}]]></string>
 	<string name="_sys_audit_msg_should_be"><![CDATA[хорошо бы {0} {1}]]></string>
 	<string name="_sys_audit_msg_userside_caching"><![CDATA[
-<a href="{0}">нажмите здесь для проверки в Google Page Speed</a><br />
-Если он не задействован, тогда попробуйте применить эту оптимизацию, так как она улучшает скорость сайта и сохраняет трафик.<br />{1}]]></string>
+<a href="{0}">нажмите здесь для проверки в Google PageSpeed</a><br />
+Если он не задействован, тогда попробуйте применить эту оптимизацию, так как она улучшает скорость сайта и экономит трафик.<br />{1}]]></string>
 	<string name="_sys_audit_msg_value_checking_failed"><![CDATA[проверка значения не удалась]]></string>
 	<string name="_sys_audit_msg_version_is_incompatible"><![CDATA[Ваша версия несовместима с UNA, нужно как минимум {0}]]></string>
 	<string name="_sys_audit_msg_version_is_outdated"><![CDATA[Ваша версия устарела, может потребоваться обновление до последней версии {0}]]></string>
@@ -2628,10 +2627,10 @@
 	<string name="_sys_audit_section_os"><![CDATA[ОС]]></string>
 	<string name="_sys_audit_section_webserver"><![CDATA[Web-сервер]]></string>
 	<string name="_sys_audit_serverside_compression"><![CDATA[Сжатие содержимого на стороне сервера]]></string>
-	<string name="_sys_audit_title_fail"><![CDATA[ПРОВАЛ]]></string>
-	<string name="_sys_audit_title_ok"><![CDATA[ПОРЯДОК]]></string>
-	<string name="_sys_audit_title_undef"><![CDATA[НЕ ОПРЕДЕЛЕНО]]></string>
-	<string name="_sys_audit_title_warn"><![CDATA[ПРЕДУПРЕЖДЕНИЕ]]></string>
+	<string name="_sys_audit_title_fail"><![CDATA[Ошибка]]></string>
+	<string name="_sys_audit_title_ok"><![CDATA[Пройдено]]></string>
+	<string name="_sys_audit_title_undef"><![CDATA[Неизвестно]]></string>
+	<string name="_sys_audit_title_warn"><![CDATA[Предупреждение]]></string>
 	<string name="_sys_audit_userside_caching"><![CDATA[Кэширование статичного содержимого на стороне пользователя]]></string>
 	<string name="_sys_audit_version"><![CDATA[версия]]></string>
 	<string name="_sys_audit_version_script"><![CDATA[Версия UNA]]></string>
@@ -3248,6 +3247,10 @@
 	<string name="_sys_grid_search"><![CDATA[Поиск...]]></string>
   <string name="_sys_grid_total_count"><![CDATA[Всего записей: {0}]]></string>
   <string name="_sys_grid_txt_account_manager"><![CDATA[Перенаправить в Управление аккаунтом.]]></string>
+	<string name="_sys_inst_audit_failed"><![CDATA[{0} ошибок]]></string>
+	<string name="_sys_inst_audit_lead"><![CDATA[Проверка PHP, расширений и путей на этом хосте.]]></string>
+	<string name="_sys_inst_audit_passed"><![CDATA[{0} пройдено]]></string>
+	<string name="_sys_inst_audit_warnings"><![CDATA[{0} предупреждений]]></string>
 	<string name="_sys_inst_conf_desc"><![CDATA[{0}{1}<br />Пример: {2}]]></string>
 	<string name="_sys_inst_conf_desc_admin_email"><![CDATA[Администратор - email.]]></string>
 	<string name="_sys_inst_conf_desc_admin_pwd"><![CDATA[Администратор - пароль.]]></string>
@@ -3289,9 +3292,9 @@
 	<string name="_sys_inst_conf_field_site_title"><![CDATA[Заголовок сайта]]></string>
 	<string name="_sys_inst_conf_field_site_url"><![CDATA[URL сайта]]></string>
 	<string name="_sys_inst_conf_field_template"><![CDATA[Тема]]></string>
-	<string name="_sys_inst_conf_found"><![CDATA[<span style="color:green">Найдено</span>]]></string>
+	<string name="_sys_inst_conf_found"><![CDATA[<span class="bx-install-chip bx-install-chip-ok">Найдено</span>]]></string>
 	<string name="_sys_inst_conf_no_example"><![CDATA[нет примера]]></string>
-	<string name="_sys_inst_conf_not_found"><![CDATA[<span style="color:red">Не найдено</span>]]></string>
+	<string name="_sys_inst_conf_not_found"><![CDATA[<span class="bx-install-chip bx-install-chip-bad">Не найдено</span>]]></string>
 	<string name="_sys_inst_conf_section_db_config"><![CDATA[БД - конфигурация]]></string>
 	<string name="_sys_inst_conf_section_link_with_market"><![CDATA[Связать с UNA Market]]></string>
 	<string name="_sys_inst_conf_section_modules"><![CDATA[Модули]]></string>
@@ -3299,11 +3302,15 @@
 	<string name="_sys_inst_conf_section_site_info"><![CDATA[Инфо сайта]]></string>
 	<string name="_sys_inst_continue"><![CDATA[Продолжить]]></string>
 	<string name="_sys_inst_finish"><![CDATA[Установка удачно завершена]]></string>
+	<string name="_sys_inst_footer_contact"><![CDATA[Связаться с командой UNA]]></string>
+	<string name="_sys_inst_footer_docs"><![CDATA[Документация]]></string>
+	<string name="_sys_inst_footer_product"><![CDATA[UNA Community Management System]]></string>
+	<string name="_sys_inst_help"><![CDATA[Помощь]]></string>
 	<string name="_sys_inst_help_finish"><![CDATA[Помощь по планировщику заданий]]></string>
 	<string name="_sys_inst_help_permissions"><![CDATA[Помощь по установке разрешений]]></string>
 	<string name="_sys_inst_help_site_config"><![CDATA[Помощь по созданию базы данных]]></string>
-	<string name="_sys_inst_hosting_fails"><![CDATA[Ваш хостинг не соответствует минимальным требованиям:]]></string>
-	<string name="_sys_inst_hosting_warnings"><![CDATA[Есть некоторые замечания к настройкам сервера, можно перейти к <a href="?action=audit">аудиту сервера</a> для более подробной информации.]]></string>
+	<string name="_sys_inst_hosting_fails"><![CDATA[Этот хост не соответствует минимальным требованиям:]]></string>
+	<string name="_sys_inst_hosting_warnings"><![CDATA[На этом хосте есть предупреждения. Полный отчёт на <a href="?action=audit">странице аудита сервера</a>.]]></string>
 	<string name="_sys_inst_msg_admin_account_not_found"><![CDATA[Аккаунт админа не был найден по эл. адресу - {0}]]></string>
 	<string name="_sys_inst_msg_db_err_connect"><![CDATA[Ошибка соединения с базой]]></string>
 	<string name="_sys_inst_msg_db_err_select"><![CDATA[Ошибка выбора базы]]></string>
@@ -3315,7 +3322,8 @@
 	<string name="_sys_inst_msg_finish_goto_studio"><![CDATA[<a href="{0}studio/">Перейти в Студию</a>, чтобы добавить дополнительные модули и модифицировать сайт.]]></string>
 	<string name="_sys_inst_msg_form_error"><![CDATA[Пожалуйста, исправьте ошибки формы.]]></string>
 	<string name="_sys_inst_msg_module_error"><![CDATA[{0} - {1}]]></string>
-	<string name="_sys_inst_msg_permissions_wrong"><![CDATA[Неправильные разрешения для некоторых файлов и папок, установите правильные и нажмите "Продолжить".]]></string>
+	<string name="_sys_inst_msg_permissions_lead"><![CDATA[Эти пути должны быть доступны для записи или исполняемые, прежде чем продолжить.]]></string>
+	<string name="_sys_inst_msg_permissions_wrong"><![CDATA[Некоторым путям нужно внимание. Исправьте их, затем продолжите.]]></string>
 	<string name="_sys_inst_msg_remove_install"><![CDATA[Пожалуйста, удалите директорию INSTALL с сервера и перегрузите страницу, чтобы активировать сайт.<br />После удаления, Вы можете перейти по ссылке <a href="../studio/">в Студию</a>, чтобы добавить дополнительные модули и модифицировать сайт.]]></string>
 	<string name="_sys_inst_msg_script_isnt_installed"><![CDATA[Скрипт не установлен]]></string>
 	<string name="_sys_inst_refresh"><![CDATA[Обновить]]></string>

+ 1 - 1
studio/classes/BxDolStudioTools.php

@@ -216,7 +216,7 @@ EOF;
         return <<<EOF
 <tr class="bx-def-color-bg-hl-even">
     <td class="bx-def-padding-thd">{$s}</td>
-    <td class="bx-def-padding-thd {$sClass}">{$sResultPerm}</td>
+    <td class="bx-def-padding-thd"><span class="{$sClass}">{$sResultPerm}</span></td>
     <td class="bx-def-padding-thd">{$sAwaitedPerm}</td>
 </tr>
 EOF;

+ 31 - 9
studio/classes/BxDolStudioToolsAudit.php

@@ -209,9 +209,20 @@ class BxDolStudioToolsAudit extends BxDol
 
         $this->$sFunc(false, $aMessages);
 
-        foreach ($aMessages as $sName => $r)
-            if ($sType == $r['type'])
-                $aRet[] = "$sName = " . $this->format_output($r['params']['real_val'], isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : '') . " - " . $this->getMsgHTML($sName, $r);        
+        foreach ($aMessages as $sName => $r) {
+            if ($sType != $r['type'])
+                continue;
+
+            $aSetting = isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : array();
+            $sLabel = (!empty($aSetting['op']) && $aSetting['op'] === 'module') ? $aSetting['val'] : $sName;
+            $sValue = (!empty($aSetting['op']) && $aSetting['op'] === 'module') ? '' : $this->format_output($r['params']['real_val'], $aSetting);
+            $s = $sLabel;
+            if ($sValue !== '' && $sValue !== null)
+                $s .= ' = ' . $sValue;
+            if ($s !== '')
+                $s .= ' ';
+            $aRet[] = trim($s . $this->getMsgHTML($sName, $r));
+        }
 
         $this->restoreErrorReporting();
 
@@ -273,7 +284,10 @@ class BxDolStudioToolsAudit extends BxDol
         if ($bEcho) {
             $s = '';
             foreach ($aMessages as $sName => $r) {
-                $s .= $this->getBlock($sName, $this->format_output($r['params']['real_val'], isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : ''), $this->getMsgHTML($sName, $r));
+                $aSetting = isset($this->aPhpSettings[$sName]) ? $this->aPhpSettings[$sName] : array();
+                $sLabel = (!empty($aSetting['op']) && $aSetting['op'] === 'module') ? $aSetting['val'] : $sName;
+                $sValue = (!empty($aSetting['op']) && $aSetting['op'] === 'module') ? '' : $this->format_output($r['params']['real_val'], $aSetting);
+                $s .= $this->getBlock($sLabel, $sValue, $this->getMsgHTML($sName, $r));
             }
             echo $this->getSection('PHP', '', $s);
         }
@@ -662,7 +676,12 @@ class BxDolStudioToolsAudit extends BxDol
 
     protected function getSection($sTitle, $sTitleAddon, $sContent)
     {
-        $s = '<b>' . $sTitle . '</b>: ' . $sTitleAddon;
+        $s = '<div class="bx-audit-section-head">';
+        if ($sTitle !== '')
+            $s .= '<b>' . $sTitle . '</b>';
+        if ($sTitleAddon !== '')
+            $s .= ($sTitle !== '' ? ': ' : '') . $sTitleAddon;
+        $s .= '</div>';
         $s .= '<ul>';
         $s .= $sContent;
         $s .= '</ul>';
@@ -673,11 +692,14 @@ class BxDolStudioToolsAudit extends BxDol
     {
         $s = $bWrapAsListItem ? '<li>'  : '';
         if ($sName !== '')
-            $s .= "$sName ";
+            $s .= $sName;
         if ($sValue !== '')
-            $s .= " = $sValue ";
-        if ($sMsg)
-                $s .= ($s ? " - " : '') . $sMsg;
+            $s .= ' = ' . $sValue;
+        if ($sMsg) {
+            if ($sName !== '' || $sValue !== '')
+                $s .= ' ';
+            $s .= $sMsg;
+        }
         return $s . ($bWrapAsListItem ? '</li>' : '') . "\n";
     }
 

+ 4 - 2
studio/classes/BxDolStudioWidgets.php

@@ -63,9 +63,11 @@ class BxDolStudioWidgets extends BxTemplStudioPage
 
     	$aResult = array();
     	foreach($aWidgets as $aWidget) {
+            $sNotices = '';
             if(BxDolService::isSerializedService($aWidget['cnt_notices'])) {
-                $aService = unserialize($aWidget['cnt_notices']);
-                $sNotices = BxDolService::call($aService['module'], $aService['method'], array_merge(array($aWidget), $aService['params']), $aService['class']);
+                $aService = BxDolService::decodeServiceCall($aWidget['cnt_notices']);
+                if ($aService)
+                    $sNotices = BxDolService::call($aService['module'], $aService['method'], array_merge(array($aWidget), isset($aService['params']) ? $aService['params'] : array()), isset($aService['class']) ? $aService['class'] : 'Module');
             }
 
             $aResult[$aWidget['id']] = $sNotices;

+ 1 - 3
template/scripts/BxBaseMenuSetAclLevel.php

@@ -47,9 +47,7 @@ class BxBaseMenuSetAclLevel extends BxTemplMenu
     	$this->mixedProfileId = $mixedProfileId;
 
         if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && ($mixedProfileId = bx_get('profile_id', 'post')) && ($iAclLevelId = bx_get('level_id', 'post'))) {
-            $mixedProfileId = urldecode($mixedProfileId);
-            if(!is_numeric($mixedProfileId))
-                $mixedProfileId = json_decode($mixedProfileId, true);
+            $mixedProfileId = BxDolServiceCallCodec::decodeAclProfileId($mixedProfileId);
 
             echoJson($this->setMembership($mixedProfileId, $iAclLevelId, bx_get('duration', 'post') !== false ? (int)bx_get('duration', 'post') : 0, (int)bx_get('card', 'post') > 0));
             exit;

+ 81 - 0
tests/units/inc/classes/BxDolServiceCallCodecTest.php

@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Service-call codec: JSON writes, safe decode of old PHP serialize, no object injection.
+ * Loads the codec file directly so it can run without a full UNA install.
+ */
+class BxDolServiceCallCodecTest extends \PHPUnit\Framework\TestCase
+{
+    public static function setUpBeforeClass(): void
+    {
+        if (!defined('BX_DOL'))
+            define('BX_DOL', 1);
+        if (!class_exists('BxDolServiceCallCodec', false)) {
+            require_once dirname(__DIR__, 4) . '/inc/classes/BxDolServiceCallCodec.php';
+        }
+    }
+
+    public function testEncodeIsJsonWithModuleAndMethod()
+    {
+        $s = BxDolServiceCallCodec::encode('system', 'test', array('a' => 1), 'Module');
+        $this->assertSame('{', $s[0]);
+        $a = json_decode($s, true);
+        $this->assertSame('system', $a['module']);
+        $this->assertSame('test', $a['method']);
+        $this->assertSame(array('a' => 1), $a['params']);
+        $this->assertTrue(BxDolServiceCallCodec::isEncoded($s));
+        $this->assertSame($a, BxDolServiceCallCodec::decode($s));
+    }
+
+    public function testDecodeLegacySerializedArray()
+    {
+        $s = serialize(array(
+            'module' => 'system',
+            'method' => 'test',
+            'params' => array(1, 2),
+        ));
+        $this->assertTrue(BxDolServiceCallCodec::isEncoded($s));
+        $a = BxDolServiceCallCodec::decode($s);
+        $this->assertSame('system', $a['module']);
+        $this->assertSame('test', $a['method']);
+        $this->assertSame(array(1, 2), $a['params']);
+    }
+
+    public function testRejectSerializedObject()
+    {
+        $s = serialize(new stdClass());
+        $this->assertFalse(BxDolServiceCallCodec::isEncoded($s));
+        $this->assertFalse(BxDolServiceCallCodec::decode($s));
+    }
+
+    public function testRejectSerializedArrayContainingObject()
+    {
+        $s = serialize(array(
+            'module' => 'system',
+            'method' => 'test',
+            'params' => array(new stdClass()),
+        ));
+        $this->assertFalse(BxDolServiceCallCodec::decode($s));
+    }
+
+    public function testRejectGarbageAndEmpty()
+    {
+        $this->assertFalse(BxDolServiceCallCodec::decode(''));
+        $this->assertFalse(BxDolServiceCallCodec::decode('not a blob'));
+        $this->assertFalse(BxDolServiceCallCodec::decode('{"foo":1}'));
+        $this->assertFalse(BxDolServiceCallCodec::decode(null));
+    }
+
+    /**
+     * Lock CVE-2025-32101 / CVE-2025-66571: posted profile_id is json_decode, never unserialize.
+     */
+    public function testAclPostedProfileIdDoesNotUnserialize()
+    {
+        $this->assertSame('12', BxDolServiceCallCodec::decodeAclProfileId('12'));
+        $this->assertSame(array(1, 2), BxDolServiceCallCodec::decodeAclProfileId(urlencode(json_encode(array(1, 2)))));
+
+        $serializedObject = serialize(new stdClass());
+        $this->assertNull(BxDolServiceCallCodec::decodeAclProfileId($serializedObject));
+        $this->assertNull(BxDolServiceCallCodec::decodeAclProfileId(urlencode($serializedObject)));
+    }
+}