1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\Modules\CoreWebclient;
9:
10: use Aurora\Api;
11: use Aurora\System\Application;
12: use Aurora\System\Module\Decorator;
13: use Aurora\System\Router;
14:
15: /**
16: * System module that provides Web application core functionality and UI framework.
17: *
18: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
19: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
20: * @copyright Copyright (c) 2023, Afterlogic Corp.
21: *
22: * @property Settings $oModuleSettings
23: *
24: * @package Modules
25: */
26: class Module extends \Aurora\System\Module\AbstractWebclientModule
27: {
28: /**
29: * @return Module
30: */
31: public static function getInstance()
32: {
33: return parent::getInstance();
34: }
35:
36: /**
37: * @return Module
38: */
39: public static function Decorator()
40: {
41: return parent::Decorator();
42: }
43:
44: /**
45: * @return Settings
46: */
47: public function getModuleSettings()
48: {
49: return $this->oModuleSettings;
50: }
51:
52: /***** private functions *****/
53: /**
54: * Initializes CoreWebclient Module.
55: *
56: * @ignore
57: */
58: public function init()
59: {
60: \Aurora\System\Router::getInstance()->registerArray(
61: self::GetName(),
62: [
63: 'default' => [$this, 'EntryDefault'],
64: 'error' => [$this, 'EntryDefault'],
65: 'debugmode' => [$this, 'EntryDefault'],
66: 'xdebug_session_start' => [$this, 'EntryDefault'],
67: 'install' => [$this, 'EntryCompatibility']
68: ]
69: );
70:
71: $this->subscribeEvent('Core::UpdateSettings::after', array($this, 'onAfterUpdateSettings'));
72: $this->subscribeEvent('System::RunEntry::after', array($this, 'onAfterRunEntry'));
73:
74: $this->denyMethodsCallByWebApi([
75: 'SetHtmlOutputHeaders',
76: ]);
77: }
78:
79: /**
80: *
81: * @param array $aSystemList
82: * @return array
83: */
84: private function getLanguageList($aSystemList)
85: {
86: $aResultList = [];
87: $aLanguageNames = $this->oModuleSettings->LanguageNames;
88: foreach ($aSystemList as $sLanguage) {
89: if (isset($aLanguageNames[$sLanguage])) {
90: $aResultList[] = [
91: 'name' => json_decode('"' . $aLanguageNames[$sLanguage] . '"'),
92: 'value' => $sLanguage
93: ];
94: } else {
95: $aResultList[] = [
96: 'name' => $sLanguage,
97: 'value' => $sLanguage
98: ];
99: }
100: }
101: return $aResultList;
102: }
103: /***** private functions *****/
104:
105: /***** public functions *****/
106: /**
107: *
108: * @return array
109: */
110: public function GetSettings()
111: {
112: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
113:
114: $oUser = \Aurora\System\Api::getAuthenticatedUser();
115: $oIntegrator = \Aurora\Modules\Core\Module::getInstance()->getIntegratorManager();
116:
117: return array(
118: 'AllowChangeSettings' => $this->oModuleSettings->AllowChangeSettings,
119: 'AllowClientDebug' => $this->oModuleSettings->AllowClientDebug,
120: 'AllowDesktopNotifications' => $oUser && null !== $oUser->getExtendedProp(self::GetName() . '::AllowDesktopNotifications') ? $oUser->getExtendedProp(self::GetName() . '::AllowDesktopNotifications') : $this->oModuleSettings->AllowDesktopNotifications,
121: 'AllowMobile' => $this->oModuleSettings->AllowMobile,
122: 'AllowPrefetch' => $this->oModuleSettings->AllowPrefetch,
123: 'AttachmentSizeLimit' => $this->oModuleSettings->AttachmentSizeLimit,
124: 'AutoRefreshIntervalMinutes' => $oUser && null !== $oUser->getExtendedProp(self::GetName() . '::AutoRefreshIntervalMinutes') ? $oUser->getExtendedProp(self::GetName() . '::AutoRefreshIntervalMinutes') : $this->oModuleSettings->AutoRefreshIntervalMinutes,
125: 'CustomLogoutUrl' => $this->oModuleSettings->CustomLogoutUrl,
126: 'DefaultAnonymScreenHash' => $this->oModuleSettings->DefaultAnonymScreenHash,
127: 'DefaultUserScreenHash' => $this->oModuleSettings->DefaultUserScreenHash,
128: 'GoogleAnalyticsAccount' => $this->oModuleSettings->GoogleAnalyticsAccount,
129: 'HeaderModulesOrder' => $this->oModuleSettings->HeaderModulesOrder,
130: 'IsDemo' => $this->oModuleSettings->IsDemo,
131: 'IsMobile' => $oIntegrator->isMobile(),
132: 'LanguageListWithNames' => $this->getLanguageList($oIntegrator->getLanguageList()),
133: 'MultipleFilesUploadLimit' => $this->oModuleSettings->MultipleFilesUploadLimit,
134: 'ShowQuotaBar' => $this->oModuleSettings->ShowQuotaBar,
135: 'ShowQuotaBarTextAsTooltip' => $this->oModuleSettings->ShowQuotaBarTextAsTooltip,
136: 'QuotaWarningPerc' => $this->oModuleSettings->QuotaWarningPerc,
137: 'Theme' => $oUser && null !== $oUser->getExtendedProp(self::GetName() . '::Theme') ? $oUser->getExtendedProp(self::GetName() . '::Theme') : $this->oModuleSettings->Theme,
138: 'ThemeList' => $this->oModuleSettings->ThemeList,
139: 'HideLogout' => $this->oModuleSettings->HideLogout,
140: 'BaseUrl' => Application::getBaseUrl(),
141: );
142: }
143:
144: /**
145: *
146: * @param array $Args
147: * @param mixed $Result
148: */
149: public function onAfterUpdateSettings($Args, &$Result)
150: {
151: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
152:
153: $oUser = \Aurora\System\Api::getAuthenticatedUser();
154: if ($oUser && $oUser->isNormalOrTenant()) {
155: if (isset($Args['AllowDesktopNotifications'])) {
156: $oUser->setExtendedProp(self::GetName() . '::AllowDesktopNotifications', $Args['AllowDesktopNotifications']);
157: }
158: if (isset($Args['AutoRefreshIntervalMinutes'])) {
159: $oUser->setExtendedProp(self::GetName() . '::AutoRefreshIntervalMinutes', $Args['AutoRefreshIntervalMinutes']);
160: }
161: if (isset($Args['Theme'])) {
162: $oUser->setExtendedProp(self::GetName() . '::Theme', $Args['Theme']);
163: }
164:
165: $oCoreDecorator = \Aurora\Modules\Core\Module::Decorator();
166: $oCoreDecorator->UpdateUserObject($oUser);
167: }
168:
169: if ($oUser && $oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
170: if (isset($Args['Theme'])) {
171: $this->setConfig('Theme', $Args['Theme']);
172: }
173: $Result = $this->saveModuleConfig();
174: }
175: }
176:
177: public function GetTemplates()
178: {
179: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
180:
181: $oIntegrator = \Aurora\Modules\Core\Module::getInstance()->getIntegratorManager();
182: return $oIntegrator->compileTemplates();
183: }
184:
185: public function GetTranslation()
186: {
187: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
188:
189: $oIntegrator = \Aurora\Modules\Core\Module::getInstance()->getIntegratorManager();
190: list($sLanguage, $sTheme) = $oIntegrator->getThemeAndLanguage();
191:
192: return $oIntegrator->getLanguage($sLanguage);
193: }
194:
195: /**
196: * @ignore
197: */
198: public function SetHtmlOutputHeaders()
199: {
200: @\header('Content-Type: text/html; charset=utf-8', true);
201: $sContentSecurityPolicy = $this->oModuleSettings->ContentSecurityPolicy;
202: if (!empty($sContentSecurityPolicy)) {
203: $aArgs = [];
204: $aAddDefault = [];
205: $this->broadcastEvent(
206: 'AddToContentSecurityPolicyDefault',
207: $aArgs,
208: $aAddDefault
209: );
210: if (!empty($aAddDefault)) {
211: $aPieces = explode(';', $sContentSecurityPolicy);
212: foreach ($aPieces as $iIndex => $sPiece) {
213: $sPrepared = strtolower(trim($sPiece));
214: if (strpos($sPrepared, 'default-src') === 0) {
215: $aPieces[$iIndex] = implode(' ', array_merge([$sPiece], $aAddDefault));
216: }
217: }
218: $sContentSecurityPolicy = implode(';', $aPieces);
219: }
220: @\header('Content-Security-Policy: ' . $sContentSecurityPolicy, true);
221: }
222: }
223:
224: /**
225: * @ignore
226: */
227: public function EntryDefault()
228: {
229: $sResult = '';
230:
231: $oIntegrator = \Aurora\System\Managers\Integrator::getInstance();
232:
233: self::Decorator()->SetHtmlOutputHeaders();
234:
235: $sUserAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
236: if (!\strpos(\strtolower($sUserAgent), 'firefox')) {
237: @\header('Last-Modified: ' . \gmdate('D, d M Y H:i:s') . ' GMT');
238: }
239:
240: $oSettings = &\Aurora\System\Api::GetSettings();
241: if ($oSettings) {
242: if (($oSettings->CacheCtrl && isset($_COOKIE['aft-cache-ctrl']))) {
243: \Aurora\System\Api::setCookie(
244: 'aft-cache-ctrl',
245: '',
246: \strtotime('-1 hour')
247: );
248: \MailSo\Base\Http::SingletonInstance()->StatusHeader(304);
249: exit();
250: }
251: }
252:
253: $sResult = \file_get_contents($this->GetPath() . '/templates/Index.html');
254: if (\is_string($sResult)) {
255: if ($oSettings) {
256: $sFrameOptions = $oSettings->XFrameOptions;
257: if (0 < \strlen($sFrameOptions)) {
258: @\header('X-Frame-Options: ' . $sFrameOptions);
259: }
260: }
261:
262: $sResult = strtr($sResult, array(
263: '{{AppVersion}}' => Application::GetVersion(),
264: '{{IntegratorDir}}' => $oIntegrator->isRtl() ? 'rtl' : 'ltr',
265: '{{IntegratorLinks}}' => $oIntegrator->buildHeadersLink(),
266: '{{IntegratorBody}}' => $oIntegrator->buildBody()
267: ));
268: }
269:
270:
271: return $sResult;
272: }
273:
274: /**
275: * @ignore
276: */
277: public function EntryCompatibility()
278: {
279: $mResult = '';
280: if (basename(\MailSo\Base\Http::SingletonInstance()->GetFullUrl()) !== 'adminpanel') { //TODO
281: \header("Location: ./");
282: }
283:
284: $aCompatibilities = \Aurora\Modules\Core\Module::Decorator()->GetCompatibilities();
285: $sContent = '';
286: $bResult = true;
287: foreach ($aCompatibilities as $sModule => $aItems) {
288: $sContent .= "<div class=\"row\">
289: <h2>Module: " . $sModule . "</h2>
290: </div><br />";
291: foreach ($aItems as $aItem) {
292: $sValue = '';
293: if ($aItem['Result']) {
294: $sValue = $this->getSuccessHtmlValue($aItem['Value']);
295: } else {
296: if (is_array($aItem['Value']) && count($aItem['Value']) > 0) {
297: $sValue = $this->getErrorHtmlValue($aItem['Value'][0], isset($aItem['Value'][1]) ? $aItem['Value'][1] : '');
298: }
299: }
300: $sContent .= "<div class=\"row\">
301: <span class=\"field_label\"><b>" . $aItem['Name'] . ":</b> </span>
302: <span class=\"field_value_limit\">" . $sValue . "</span>
303: </div>";
304: $bResult &= $aItem['Result'];
305: }
306: }
307: $sContent .= "<br />";
308:
309: $sPath = $this->GetPath() . '/templates/Compatibility.html';
310: if (\file_exists($sPath)) {
311: $sResult = \file_get_contents($sPath);
312: if (\is_string($sResult)) {
313: $sResult = strtr($sResult, array(
314: '{{Compatibilities}}' => $sContent,
315: '{{Result}}' => $bResult ?
316: 'The current server environment meets all the requirements. Click Next to proceed.' :
317: 'Please make sure that all the requirements are met and click Retry.',
318:
319: '{{NextButtonHref}}' => ($bResult) ? './' : './?install',
320: '{{ResultClassSuffix}}' => ($bResult) ? '_ok' : '_error',
321: '{{NextButtonName}}' => ($bResult) ? 'next_btn' : 'retry_btn',
322: '{{NextButtonValue}}' => ($bResult) ? 'Next' : 'Retry'
323:
324: ));
325:
326: $mResult = $sResult;
327: }
328: }
329:
330: return $mResult;
331: }
332:
333: protected function getSuccessHtmlValue($sValue)
334: {
335: return '<span class="state_ok">' . $sValue . '</span>';
336: }
337:
338: protected function getErrorHtmlValue($sError, $sErrorHelp = '')
339: {
340: $sResult = '<span class="state_error">' . $sError . '</span>';
341: if (!empty($sErrorHelp)) {
342: $sResult .= '<span class="field_description">' . $sErrorHelp . '</span>';
343: }
344: return $sResult;
345: }
346:
347: protected function getWarningHtmlValue($sVarning, $sVarningHelp = '')
348: {
349: $sResult = '<span class="state_warning"><img src="./images/alarm.png"> Not detected. <br />' . $sVarning . '</span>';
350: if (!empty($sVarningHelp)) {
351: $sResult .= '<span class="field_description">' . $sVarningHelp . '</span>';
352: }
353: return $sResult;
354: }
355:
356: /**
357: * This subscription handles AuthToken cookie.
358: */
359: public function onAfterRunEntry(&$aArgs, &$mResult)
360: {
361: $sXClientHeader = $this->oHttp->GetHeader('X-Client');
362: // Set cookie in browser only
363: $bWebClient = strtolower($sXClientHeader) === 'webclient';
364:
365: if ($aArgs['EntryName'] === 'api' && $bWebClient ) {
366: $sAuthTokenKey = \Aurora\System\Application::AUTH_TOKEN_KEY;
367:
368: $oResult = @json_decode($mResult, true);
369:
370: if ($oResult) {
371: if (isset($oResult['ErrorCode']) && in_array($oResult['ErrorCode'], [\Aurora\System\Notifications::AuthError, \Aurora\System\Notifications::InvalidToken])) {
372: Api::unsetAuthTokenCookie();
373: } elseif (isset($oResult['Result']) && isset($oResult['Result'][$sAuthTokenKey])) {
374: // Moving AuthToken to cookies
375: Api::setAuthTokenCookie($oResult['Result'][$sAuthTokenKey]);
376: $oResult['Result'][$sAuthTokenKey] = true;
377: $mResult = \Aurora\System\Managers\Response::GetJsonFromObject('Json', $oResult);
378: } elseif (isset($aArgs['Module']) && $aArgs['Module'] === 'Core' && $aArgs['Method'] === 'Logout' && $oResult['Result'] === true) {
379: Api::unsetAuthTokenCookie();
380: }
381: }
382: }
383: }
384: }
385: