From b65125beaf88f083ac3ac816d1048fa5c2ea5865 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 10 Apr 2020 20:34:36 +0200 Subject: [PATCH 01/29] added dev mode --- lam/lib/account.inc | 10 ++++++++++ lam/templates/config/index.php | 2 +- lam/tests/lib/accountTest.php | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lam/lib/account.inc b/lam/lib/account.inc index c216d6b7..497e199a 100644 --- a/lam/lib/account.inc +++ b/lam/lib/account.inc @@ -1751,6 +1751,16 @@ function getLAMVersionText() { return $text . ' - ' . LAMVersion(); } +/** + * Returns if the given release is a developer version. + * + * @param string version + * @return bool is developer version + */ +function isDeveloperVersion($version) { + return strpos($version, 'DEV') !== false; +} + /** * LAM exception with title and message. * diff --git a/lam/templates/config/index.php b/lam/templates/config/index.php index 48d4fef2..1aa9544f 100644 --- a/lam/templates/config/index.php +++ b/lam/templates/config/index.php @@ -2,7 +2,7 @@ /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2019 Roland Gruber + Copyright (C) 2003 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/lam/tests/lib/accountTest.php b/lam/tests/lib/accountTest.php index bca309c1..b33623bd 100644 --- a/lam/tests/lib/accountTest.php +++ b/lam/tests/lib/accountTest.php @@ -24,7 +24,7 @@ include_once __DIR__ . '/../../lib/account.inc'; include_once __DIR__ . '/../../lib/security.inc'; /** - * LAMConfig test case. + * account.inc test cases. * * @author Roland Gruber */ @@ -155,4 +155,13 @@ class AccountTest extends TestCase { $this->assertFalse(isCommandlineSafeEmailAddress('test+abc@example.com')); } + /** + * Tests isDeveloperVersion() + */ + function testIsDeveloperVersion() { + $this->assertFalse(isDeveloperVersion('0.4.1')); + $this->assertFalse(isDeveloperVersion('3.2.RC1')); + $this->assertTrue(isDeveloperVersion('4.5.DEV')); + } + } From 541684d49f887ad14bc3808605161ccd04ad829a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 11 Apr 2020 15:41:26 +0200 Subject: [PATCH 02/29] responsive --- lam/style/500_layout.css | 10 +++- lam/templates/config/index.php | 85 ++++++++++++---------------------- 2 files changed, 38 insertions(+), 57 deletions(-) diff --git a/lam/style/500_layout.css b/lam/style/500_layout.css index 114a243d..c66c5dcd 100644 --- a/lam/style/500_layout.css +++ b/lam/style/500_layout.css @@ -86,6 +86,10 @@ a img { border: 0px; } +a.img-padding1 img { + padding: 1rem; +} + a.classicBold { color: blue; text-decoration: none; @@ -457,6 +461,10 @@ table.collapse { display: none; } +.display-as-block { + display: block; +} + .nowrap { white-space: nowrap; } @@ -484,7 +492,7 @@ div.smallScroll { } .fullwidth { - width: 100%; + width: 100%!important; } .halfwidth { diff --git a/lam/templates/config/index.php b/lam/templates/config/index.php index 1aa9544f..939a72e2 100644 --- a/lam/templates/config/index.php +++ b/lam/templates/config/index.php @@ -42,6 +42,9 @@ setlanguage(); echo $_SESSION['header']; printHeaderContents(_("Configuration overview"), '../..'); +$tabindex = 0; +$content = new htmlResponsiveRow(); + ?> @@ -59,69 +62,39 @@ printHeaderContents(_("Configuration overview"), '../..');

- - \n"; - } - else { - echo "\n"; - } - ?> - - - - - - - - - \n"; - echo "\n"; - echo "\n"; - echo "\n"; - } - ?> -
 
 
- - general settings - - - - - -
- - server settings - - - - - -
\n"; - echo "\n"; - echo "\"self\n"; - echo "\n"; - echo "\n"; - echo "\n"; - echo _("Edit self service"); - echo "\n"; - echo "
-


+ + setCSSClasses(array('maxrow fullwidth roundedShadowBox spacing5')); + $mainCfgLink = new htmlLink(_("Edit general settings"), 'mainlogin.php', '../../graphics/bigTools.png'); + $mainCfgLink->setCSSClasses(array('img-padding1 display-as-block')); + $topContent->add($mainCfgLink, 12); + $cfgLink = new htmlLink(_("Edit server profiles"), 'conflogin.php', '../../graphics/profiles.png'); + $cfgLink->setCSSClasses(array('img-padding1 display-as-block')); + $topContent->add($cfgLink, 12); + if (isLAMProVersion()) { + $selfServiceLink = new htmlLink(_("Edit self service"), '../selfService/adminLogin.php', '../../graphics/bigPeople.png'); + $selfServiceLink->setCSSClasses(array('img-padding1 display-as-block')); + $topContent->add($selfServiceLink, 12); + } + $content->add($topContent, 12); + $content->addVerticalSpacer('4rem'); + ?> printLicenseInfo(); - echo "

"; + $content->add($printer->getLicenseInfo(), 12); + $content->addVerticalSpacer('2rem'); } - ?> -

 back 

-



+ $content->add(new htmlLink(_("Back to login"), '../login.php', '../../graphics/undo.png'), 12); + $content->addVerticalSpacer('2rem'); + + parseHtml('none', $content, array(), true, $tabindex, 'none'); + + ?> From 8af2132926909fb22828c1be0979046052b14776 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 12 Apr 2020 12:39:52 +0200 Subject: [PATCH 03/29] import/export config --- lam/lib/config.inc | 24 ++++++++++ lam/lib/persistence.inc | 58 +++++++++++++++++++++++ lam/tests/lib/LAMCfgMainTest.php | 35 ++++++++++++++ lam/tests/lib/modules/persistenceTest.php | 53 +++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 lam/lib/persistence.inc create mode 100644 lam/tests/lib/modules/persistenceTest.php diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 87616d83..6b8d24ea 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2681,6 +2681,30 @@ class LAMCfgMain { $this->reload(); } + /** + * Exports the configuration data. + * + * @return array config data + */ + public function exportData() { + $data = array(); + foreach ($this->settings as $setting) { + $data[$setting] = $this->$setting; + } + return $data; + } + + /** + * Imports configuration data. + * + * @param array $data config data + */ + public function importData($data) { + foreach ($data as $dataKey => $dataValue) { + $this->$dataKey = $dataValue; + } + } + /** * Reloads preferences from config file config.cfg * diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc new file mode 100644 index 00000000..60f54783 --- /dev/null +++ b/lam/lib/persistence.inc @@ -0,0 +1,58 @@ +_getMainConfigData(); + return json_encode($jsonData); + } + + /** + * Internal function to read master configuration. + * + * @return array data + */ + public function _getMainConfigData() { + $mainCfg = new \LAMCfgMain(); + return $mainCfg->exportData(); + } + +} + diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index aa4b2d64..1b5bad13 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -120,4 +120,39 @@ class LAMCfgMainTest extends TestCase { $this->assertFalse($this->conf->showLicenseWarningOnScreen()); } + /** + * Tests the export. + */ + public function testExportData() { + $this->conf->passwordMinLower = 3; + $this->conf->sessionTimeout = 240; + $this->conf->logLevel = LOG_ERR; + $this->conf->mailServer = 'mailserver'; + + $data = $this->conf->exportData(); + + $this->assertEquals(3, $data['passwordMinLower']); + $this->assertEquals(240, $data['sessionTimeout']); + $this->assertEquals(LOG_ERR, $data['logLevel']); + $this->assertEquals('mailserver', $data['mailServer']); + } + + /** + * Tests the import. + */ + public function testImportData() { + $importData = array(); + $importData['passwordMinLower'] = 3; + $importData['sessionTimeout'] = 240; + $importData['logLevel'] = LOG_ERR; + $importData['mailServer'] = 'mailserver'; + + $this->conf->importData($importData); + + $this->assertEquals(3, $this->conf->passwordMinLower); + $this->assertEquals(240, $this->conf->sessionTimeout); + $this->assertEquals(LOG_ERR, $this->conf->logLevel); + $this->assertEquals('mailserver', $this->conf->mailServer); + } + } \ No newline at end of file diff --git a/lam/tests/lib/modules/persistenceTest.php b/lam/tests/lib/modules/persistenceTest.php new file mode 100644 index 00000000..09eb98b0 --- /dev/null +++ b/lam/tests/lib/modules/persistenceTest.php @@ -0,0 +1,53 @@ + 'val', + 'confMainKey2' => 4, + 'confMainKey3' => '', + ); + $expectedJson = json_encode(array('mainconfig' => $mainData)); + + $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') + ->setMethods(array('_getMainConfigData')) + ->getMock(); + $exporter->method('_getMainConfigData')->willReturn($mainData); + + $json = $exporter->exportAsJson(); + + $this->assertEquals($expectedJson, $json); + } + +} + From f0f81c085b1df6576bc0577ba4959f3b78037e3b Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 12 Apr 2020 13:51:36 +0200 Subject: [PATCH 04/29] added export --- lam/lib/config.inc | 3 + lam/templates/config/confImportExport.php | 168 ++++++++++++++++++ lam/templates/config/index.php | 9 + lam/tests/lib/LAMCfgMainTest.php | 14 ++ .../lib/{modules => }/persistenceTest.php | 2 +- 5 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 lam/templates/config/confImportExport.php rename lam/tests/lib/{modules => }/persistenceTest.php (96%) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 6b8d24ea..5c26bc2e 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2701,6 +2701,9 @@ class LAMCfgMain { */ public function importData($data) { foreach ($data as $dataKey => $dataValue) { + if (!(is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) { + throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue)); + } $this->$dataKey = $dataValue; } } diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php new file mode 100644 index 00000000..d51b8cda --- /dev/null +++ b/lam/templates/config/confImportExport.php @@ -0,0 +1,168 @@ +checkPassword($_SESSION["mainconf_password"])) { + $exporter = new ConfigDataExporter(); + if (!headers_sent()) { + header('Content-Type: application/json; charset=utf-8'); + header('Content-disposition: attachment; filename=lam-config.json'); + } + echo $exporter->exportAsJson(); + exit; +} + +echo $_SESSION['header']; +printHeaderContents(_("Import and export configuration"), '../..'); + +?> + + + + + + + +
+ +
+
+

+ setCSSClasses(array('maxrow fullwidth roundedShadowBox spacing5')); + if ($message !== null) { + $loginContent->add($message, 12); + } + $pwdInput = new htmlResponsiveInputField(_("Master password"), 'password', '', '236'); + $pwdInput->setIsPassword(true); + $loginContent->add($pwdInput, 12); + $loginContent->addLabel(new htmlOutputText(' ', false)); + $loginContent->addField(new htmlButton('submitLogin', _("Ok"))); + + $content->add($loginContent, 12); + + parseHtml(null, $content, array(), false, $tabindex, null); + } + + /** + * Checks the login password. + * + * @param LAMCfgMain $cfg main config + * @return bool login ok + */ + function checkLogin($cfg) { + $password = $_POST['password']; + if ($cfg->checkPassword($password)) { + $_SESSION["mainconf_password"] = $password; + return true; + } + showLoginDialog(new htmlStatusMessage('ERROR', _('The password is invalid! Please try again.'))); + return false; + } + + /** + * Displays the import/export functions. + * + * @param LAMCfgMain $cfg main config + */ + function displayImportExport($cfg) { + $tabindex = 0; + $content = new htmlResponsiveRow(); + + $content->add(new htmlSubTitle(_('Export')), 12); + $content->add(new htmlButton('exportConfig', _('Export')), 12); + + $content->add(new htmlSubTitle(_('Import')), 12); + + parseHtml(null, $content, array(), false, $tabindex, null); + } + + ?> +
+ + diff --git a/lam/templates/config/index.php b/lam/templates/config/index.php index 939a72e2..d6c2360c 100644 --- a/lam/templates/config/index.php +++ b/lam/templates/config/index.php @@ -1,4 +1,7 @@ setCSSClasses(array('img-padding1 display-as-block')); $topContent->add($selfServiceLink, 12); } + if (isDeveloperVersion(LAMVersion())) { + $topContent->addVerticalSpacer('1rem'); + $importExportLink = new htmlLink(_("Import and export configuration"), 'confImportExport.php', '../../graphics/confImportExport.png'); + $importExportLink->setCSSClasses(array('img-padding1 display-as-block')); + $topContent->add($importExportLink, 12); + } $content->add($topContent, 12); $content->addVerticalSpacer('4rem'); ?> diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index 1b5bad13..1368311f 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -155,4 +155,18 @@ class LAMCfgMainTest extends TestCase { $this->assertEquals('mailserver', $this->conf->mailServer); } + /** + * Tests the import with invalid data. + */ + public function testImportData_invalid() { + $importData = array(); + $importData['passwordMinLower'] = 3; + $importData['sessionTimeout'] = 240; + $importData['logLevel'] = LOG_ERR; + $importData['mailServer'] = new LAMLanguage('de_de', 'UTF-8', 'DE'); + + $this->expectException(LAMException::class); + $this->conf->importData($importData); + } + } \ No newline at end of file diff --git a/lam/tests/lib/modules/persistenceTest.php b/lam/tests/lib/persistenceTest.php similarity index 96% rename from lam/tests/lib/modules/persistenceTest.php rename to lam/tests/lib/persistenceTest.php index 09eb98b0..1e511569 100644 --- a/lam/tests/lib/modules/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -22,7 +22,7 @@ use PHPUnit\Framework\TestCase; */ -include_once __DIR__ . '/../../../lib/persistence.inc'; +include_once __DIR__ . '/../../lib/persistence.inc'; /** * ConfigDataExporter test case. From 9ed53f51de2c6a474f3c43fa517ab313926f4b56 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 12 Apr 2020 13:52:11 +0200 Subject: [PATCH 05/29] added export --- lam/graphics/confImportExport.png | Bin 0 -> 2732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lam/graphics/confImportExport.png diff --git a/lam/graphics/confImportExport.png b/lam/graphics/confImportExport.png new file mode 100644 index 0000000000000000000000000000000000000000..460d384cac565d535b264a92cbb715b185928364 GIT binary patch literal 2732 zcmV;d3RCroP)47`b%4J!bMJi%Ns)XKMbe^3ijr)Lq9iw#E!k0H%WG`NHex460o7>%H7#1Z&5sHR zY_thb6fNok1-i&c(*{ip6pov=id&?S0Y_dcS#h*bvScNc9<^{MB|a{1yYJpRGyTCQ zS}Bsk=->`8Kkk|Hon_9P5$7DAWaRV1?NJnMFvj#b=lY#U2`Y&J_KlP2&35Fw6Z!Z0KZLyE;>R4SJ* zBuVn4bSCq!1O5F+{$Bw3{P6afYcnrqGnpsXu3hW3wzeW7pVTNyr4nOfW6Tx`#~N2O zzkJ7*f$x7JfbsE3Jvlk?Cx!XBuk>%)D4%E`WH)G$7rbGxlk06tw zG{4B{b01=?{kNXp-sji%^i2O!fXT_JAU~Y{USY2AT`%8t z=dXNq8KAGPPrdZgA00^r{yn#D+C-&V1q@oeCD)6_2`ChzEY2>e)qvHiu3Djt#VjF! zGdNK=XK~JfKq^QvJvGVP!b0hl0|#z9_Wt`50A8h1U6)FyxBI@w;=%$+l7Mp{9^wg} z$Wlz5AfixWaT*jF7u^&~1r~!d3Y`5|xpP50OK6aSR8_<1!EF=dU|?q z$azT|8)q$x^NVD&O&F8liGb2Tg7O3}R-ipZ3CdcO1FNK-iZ@aZjB`jNIA<_eoN?d` zMg(z=YG{b#geZ>UWq^^9ktq>M1d^L8 zfO9wp7K?M>jKMmGvlizB?R#9la)q9*HCz}Px^hEICB_76y1G3P%hco)##-VeAxc8x zBq272*ceO_lNf_F7HeZnY%n&tX&Q`+vBr_a24gMO7~;ednFN!>3||;x%hqj#wJ5a= z(9z!BQA?8a-o1MnJb8j_CPT{i@pTH{^9Yo}R|@6%cv|Cm8m$Aoz{k_+`t;E{g|AX5 ztw{MAr4*hHNco!F${as_{{!y-%zZR8HqqU^_TwA~f`G-j0)^`ZKKIlU96EfM-SyC}(krL+J$VdH6xVk)!`jXJ;E-y}g_sJVisc zVOdno%*I91N`9Q z8TbL7=aKaTnwqk#YFkaYQsJG$Z_&G^iye30!w>)K5Y}2oMn^`MJy5Asl2vVOrek%E z6DLk`&)qw?|GxXVetnuFN8V#qdnfDHtz$)VE1u`!tU;W^)k~#P8d2bB4~L~xEHg4X z!qrQcXlTfC$JT8`2^>Ftn)$g|1_lP0pP&Eu6s%sox+69=``q3y^2cBQ8fQNI5a%F{ zYCQhfBg_|O`RMFfO64+{OqS;6CUUuzWHMPq32_v$xVT7Ru0W++Mu1y7JK4EoC&y0? z^1|UeN&A`y%74mp&+R3h%CL5Ach53FLqkKbxUk4jKF^o`;P?641Fx{EZ53nV6CC*a ze`L?&pXHfnK2JImFg7~M;Hg1IN3N2@OGao>q*4J}`ukbGVI#`(ICJJK|Nh1gc&+qX zH2-Q7dalS@`LD6Ry~^41=g76Trk4Tc=H_P8X`TGwhljlTc72)`zxovpzHyLJvB>Di z7;heWlc{Uh*m3uEHr=|3zV)|py-)FxV)J!Vyxpet5rv^`R^3*Bbh<=ZO-{|4; z&>WQ*nzsm#9Z2!bCml00)04{p*REZkPo=V@wYRjl965FjfX5$woLVj9_{mdT9KOiV zN9P#6c!91pYv^cir&dddO^g6aDYBUi^9%EgjgL|-S2>j4&(7cNSXivuN^AeEvu*TR8`r`Y-1Ynh#is4mu+nkZ8#RIuVfT9eNBTt8YZ z7QWfr?3{Db)YR0xd0@+de}3)ty`_Z$fA!aYLq~fD7xKe2W*T_lWHO+E`pH zQ7)G#%oZq?OIYW~q|)SCb7V7Vo_Ht3bIJlawMR#!FPLmkG6p@-z~7q#Hcu z->c3|ALz>Ief#!hW~Qgr=;-*K&71r6qYv%or#~B_S}8L&F~P*-B-JoNYoC_p7PKEA zUO=w3h1Ql<(%B|bnFgNuZi@DMGK^h~s1!=v+n8`>+QYe+S}kFX_NYYQG7jr&mr18H zfA;#^mnEOix1Kw9uKV?W`QHBSo(;eL*@qwGw%a!{a^({59DSdQKO3SNhOAtY+NhK` zw6bE1Ss@}Tj~qEN@SRs*eWA0f_u)7SWykjIJo@m1jE;|U?)*>5U(7QzGfge7VU1nt ze3m}6_DH3CT3d7U_O4^&#tnS&KX0M)k!EH`OH>LKR+g*ehqBeFZ*=WYN=;g87q!-r zCn6D0*|lre=*pIsSHJb`?}W7|e(WE<^=<#Hx8LE{p8W#5?!A`>9@tG-sZc7H2&+{R zW0&iI7x*+}vovJV#Ia?1W|~?Up%GNQlUK^((1}uN5eSu12EYRnpbQi?4Gavw^wNt5 z4<0-?7uCW?K00@{Y#^?iM;SkX$hp$T6p5U52K5eWQ%iRlT>pC97X z;2^g4n+Sr{OYNL9f>Jo32t?HTSPw8j4VVMS<#J}_G9H797!w9x+ zY$75K>TS7OdTnSR1vCOVpdILnqo{XsYHHo^@bKE{>FM@jvDg}hwM>#EzQdulHkouf z%x1HTZEbDW*Y);}clY#M`P9pcFLXWB)G$9;rZ`)nWueTa%T06BuXL>vk>zKE2T-R; zoO2e4flA#YQ+^N(cXxNM?C$Pv19Hx}70%j5V~md=O6f!?RS}UQP^imY(?RiR=V(-d zQx@mMIkcX;S@|-+a#<9y&bhcAmJnC~ChML_0cjBlL~CDZedAr?>Uk8^Ls Date: Sun, 12 Apr 2020 14:00:23 +0200 Subject: [PATCH 06/29] check which settings are imported --- lam/lib/config.inc | 4 ++++ lam/tests/lib/LAMCfgMainTest.php | 1 + 2 files changed, 5 insertions(+) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 5c26bc2e..50b95c91 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2701,6 +2701,10 @@ class LAMCfgMain { */ public function importData($data) { foreach ($data as $dataKey => $dataValue) { + if (!in_array($dataKey, $this->settings)) { + logNewMessage(LOG_WARNING, 'Ignored setting during import: ' . $dataKey); + continue; + } if (!(is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) { throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue)); } diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index 1368311f..812c2a95 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -146,6 +146,7 @@ class LAMCfgMainTest extends TestCase { $importData['sessionTimeout'] = 240; $importData['logLevel'] = LOG_ERR; $importData['mailServer'] = 'mailserver'; + $importData['IGNORE_ME'] = 'ignore'; $this->conf->importData($importData); From 00c5a014b420d6852b23c79333e5d46266fe67eb Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 12 Apr 2020 21:51:19 +0200 Subject: [PATCH 07/29] import main config --- lam/lib/config.inc | 2 +- lam/lib/persistence.inc | 137 +++++++++++++++++++++- lam/templates/config/confImportExport.php | 86 +++++++++++++- lam/tests/lib/LAMCfgMainTest.php | 2 + lam/tests/lib/persistenceTest.php | 2 +- 5 files changed, 223 insertions(+), 6 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 50b95c91..201ddf2c 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2705,7 +2705,7 @@ class LAMCfgMain { logNewMessage(LOG_WARNING, 'Ignored setting during import: ' . $dataKey); continue; } - if (!(is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) { + if (!(($dataValue === null) || is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) { throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue)); } $this->$dataKey = $dataValue; diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 60f54783..dadbeb31 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -1,5 +1,8 @@ _getMainConfigData(); + $jsonData['mainConfig'] = $this->_getMainConfigData(); return json_encode($jsonData); } @@ -50,9 +53,137 @@ class ConfigDataExporter { * @return array data */ public function _getMainConfigData() { - $mainCfg = new \LAMCfgMain(); + $mainCfg = new LAMCfgMain(); return $mainCfg->exportData(); } } +/** + * Importer for LAM's configuration data. + */ +class ConfigDataImporter { + + /** + * Returns a list of possible import objects. + * + * @param string $json JSON data + * @return ImporterStep[] steps + * @throws LAMException if invalid format + */ + public function getPossibleImportSteps($json) { + $data = json_decode($json); + if ($data === null) { + throw new LAMException(_('Unable to read import file.')); + } + $steps = array(); + foreach ($data as $key => $value) { + switch ($key) { + case 'mainConfig': + $steps[] = new ImporterStep(_('General settings'), 'mainConfig', $value); + break; + default: + logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); + } + } + if (empty($steps)) { + throw new LAMException(_('Unable to read import file.')); + } + return $steps; + } + + /** + * Runs the actual import. + * + * @param ImporterStep[] $steps import steps + * @throws LAMException if error occured + */ + public function runImport($steps) { + foreach ($steps as $step) { + if (!$step->isActive()) { + continue; + } + $key = $step->getKey(); + switch ($key) { + case 'mainConfig': + $cfgMain = new LAMCfgMain(); + $cfgMain->importData($step->getValue()); + $cfgMain->save(); + break; + default: + logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); + } + } + } + +} + +/** + * Step of the import process. + */ +class ImporterStep { + + private $label; + private $key; + private $value; + private $active = false; + + /** + * Constructor. + * + * @param string $label label + * @param string $key key + * @param array $value value + */ + public function __construct($label, $key, &$value) { + $this->label = $label; + $this->key = $key; + $this->value = $value; + } + + /** + * Returns the label. + * + * @return string label + */ + public function getLabel() { + return $this->label; + } + + /** + * Returns the key. + * + * @return string key + */ + public function getKey() { + return $this->key; + } + + /** + * Returns if this step should be executed. + * + * @return bool active + */ + public function isActive(): bool { + return $this->active; + } + + /** + * Sets if this step should be executed. + * + * @param bool $active active + */ + public function setActive(bool $active) { + $this->active = $active; + } + + /** + * Returns the value. + * + * @return string value + */ + public function getValue() { + return $this->value; + } + +} diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index d51b8cda..0f461f71 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -1,13 +1,18 @@ -
+

setIsPassword(true); + $pwdInput->setCSSClasses(array('lam-initial-focus')); $loginContent->add($pwdInput, 12); $loginContent->addLabel(new htmlOutputText(' ', false)); $loginContent->addField(new htmlButton('submitLogin', _("Ok"))); @@ -127,8 +133,21 @@ printHeaderContents(_("Import and export configuration"), '../..'); $content->add($loginContent, 12); parseHtml(null, $content, array(), false, $tabindex, null); + renderBackLink(); } + /** + * Renders the link back to login page. + */ + function renderBackLink() { + $tabindex = 0; + $content = new htmlResponsiveRow(); + $content->addVerticalSpacer('2rem'); + $content->add(new htmlLink(_('Back to login'), '../login.php', '../../graphics/undo.png'), 12); + $content->addVerticalSpacer('1rem'); + parseHtml(null, $content, array(), false, $tabindex, null); + } + /** * Checks the login password. * @@ -158,8 +177,73 @@ printHeaderContents(_("Import and export configuration"), '../..'); $content->add(new htmlButton('exportConfig', _('Export')), 12); $content->add(new htmlSubTitle(_('Import')), 12); + renderImportPart($content); parseHtml(null, $content, array(), false, $tabindex, null); + renderBackLink(); + } + + /** + * Renders the import area. + * + * @param htmlResponsiveRow $content content where to add import part + */ + function renderImportPart($content) { + $fileContent = null; + $validUpload = false; + $importSteps = array(); + if (isset($_POST['importConfig'])) { + $handle = fopen($_FILES['import-file']['tmp_name'], "r"); + $data = fread($handle, 100000000); + fclose($handle); + try { + $importer = new ConfigDataImporter(); + $importSteps = $importer->getPossibleImportSteps($data); + $tmpFile = __DIR__ . '/../../tmp/internal/import_' . getRandomNumber() . '.tmp'; + $file = @fopen($tmpFile, "w"); + if ($file) { + fputs($file, $data); + fclose($file); + chmod($tmpFile, 0600); + } + $_SESSION['configImportFile'] = $tmpFile; + $validUpload = true; + } + catch (LAMException $e) { + $content->add(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()), 12); + } + } + if (!isset($_POST['importConfigConfirm']) && !$validUpload) { + $content->add(new htmlInputFileUpload('import-file'), 12); + $content->add(new htmlButton('importConfig', _('Submit')), 12); + } + elseif (isset($_POST['importConfig'])) { + $content->add(new htmlOutputText(_('Import steps')), 12); + foreach ($importSteps as $importStep) { + $content->add(new htmlResponsiveInputCheckbox('step_' . $importStep->getKey(), true, $importStep->getLabel()), 12); + } + $content->add(new htmlButton('importConfigConfirm', _('Import')), 12); + $content->add(new htmlButton('importCancel', _('Cancel')), 12); + } + elseif (isset($_POST['importConfigConfirm'])) { + $handle = fopen($_SESSION['configImportFile'], "r"); + $data = fread($handle, 100000000); + fclose($handle); + try { + $importer = new ConfigDataImporter(); + $importSteps = $importer->getPossibleImportSteps($data); + foreach ($importSteps as $importStep) { + $importStep->setActive(isset($_POST['step_' . $importStep->getKey()])); + } + $importer->runImport($importSteps); + unlink($_SESSION['configImportFile']); + $content->add(new htmlStatusMessage('INFO', _('Configuration import ended successful.')), 12); + } + catch (LAMException $e) { + $content->add(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()), 12); + $content->add(new htmlButton('importCancel', _('Cancel')), 12); + } + } } ?> diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index 812c2a95..d622ddd2 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -146,6 +146,7 @@ class LAMCfgMainTest extends TestCase { $importData['sessionTimeout'] = 240; $importData['logLevel'] = LOG_ERR; $importData['mailServer'] = 'mailserver'; + $importData['allowedHosts'] = null; $importData['IGNORE_ME'] = 'ignore'; $this->conf->importData($importData); @@ -154,6 +155,7 @@ class LAMCfgMainTest extends TestCase { $this->assertEquals(240, $this->conf->sessionTimeout); $this->assertEquals(LOG_ERR, $this->conf->logLevel); $this->assertEquals('mailserver', $this->conf->mailServer); + $this->assertNull($this->conf->allowedHosts); } /** diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index 1e511569..a02e031d 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -37,7 +37,7 @@ class ConfigDataExporterTest extends TestCase { 'confMainKey2' => 4, 'confMainKey3' => '', ); - $expectedJson = json_encode(array('mainconfig' => $mainData)); + $expectedJson = json_encode(array('mainConfig' => $mainData)); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') ->setMethods(array('_getMainConfigData')) From e8d421ae0416652b4849b6b0a3f49a1560feb579 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Apr 2020 15:40:33 +0200 Subject: [PATCH 08/29] added certificates to export --- lam/lib/config.inc | 46 +++++++++++++++ lam/lib/persistence.inc | 72 ++++++++++++++++++++--- lam/templates/config/confImportExport.php | 7 ++- lam/tests/lib/persistenceTest.php | 8 ++- 4 files changed, 122 insertions(+), 11 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 201ddf2c..06a6eca7 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -2698,6 +2698,7 @@ class LAMCfgMain { * Imports configuration data. * * @param array $data config data + * @throws LAMException import error */ public function importData($data) { foreach ($data as $dataKey => $dataValue) { @@ -2712,6 +2713,51 @@ class LAMCfgMain { } } + /** + * Returns the content of the server certificates file + * + * @return null|string certificates + */ + public function exportCertificates() { + $fileName = $this->getSSLCaCertPath(); + if ($fileName === null) { + return null; + } + $content = null; + $handle = @fopen($fileName, "r"); + if ($handle) { + $content = fread($handle, 10000000); + fclose($handle); + } + return $content; + } + + /** + * Imports the server certificates. + * + * @param null|string $certsContent certificates + * @throws LAMException write to file failed + */ + public function importCertificates($certsContent) { + $fileName = $this->getSSLCaCertPath(); + if (empty($certsContent)) { + if ($fileName !== null) { + unlink($fileName); + } + return; + } + $fileName = $this->getInternalSSLCaCertFileName(); + $handle = @fopen($fileName, "wb"); + if ($handle) { + fputs($handle, $certsContent); + fclose($handle); + @chmod($fileName, 0600); + } + else { + throw new LAMException(printf(_('Unable to write file %s.'), $fileName)); + } + } + /** * Reloads preferences from config file config.cfg * diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index dadbeb31..46401611 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -42,21 +42,52 @@ class ConfigDataExporter { * Exports LAM's configuration data in JSON format. */ public function exportAsJson() { + $mainCfg = $this->_getMainConfiguration(); $jsonData = array(); - $jsonData['mainConfig'] = $this->_getMainConfigData(); + $jsonData['mainConfig'] = $this->_getMainConfigData($mainCfg); + $jsonData['certificates'] = $this->_getCertificates($mainCfg); + /** + * TODO + * + * webauthn + * server profiles + * server profile job database + * account profiles + * PDF profiles + * self service profiles + */ return json_encode($jsonData); } + /** + * Returns the main configuration. + * + * @return LAMCfgMain main config + */ + public function _getMainConfiguration() { + return new LAMCfgMain(); + } + /** * Internal function to read master configuration. * + * @param LAMCfgMain $mainCfg main config * @return array data */ - public function _getMainConfigData() { - $mainCfg = new LAMCfgMain(); + public function _getMainConfigData($mainCfg) { return $mainCfg->exportData(); } + /** + * Returns the certificate file content. + * + * @param LAMCfgMain $mainCfg main config + * @return array data + */ + public function _getCertificates($mainCfg) { + return $mainCfg->exportCertificates(); + } + } /** @@ -82,6 +113,9 @@ class ConfigDataImporter { case 'mainConfig': $steps[] = new ImporterStep(_('General settings'), 'mainConfig', $value); break; + case 'certificates': + $steps[] = new ImporterStep(_('SSL certificates'), 'certificates', $value); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -96,7 +130,7 @@ class ConfigDataImporter { * Runs the actual import. * * @param ImporterStep[] $steps import steps - * @throws LAMException if error occured + * @throws LAMException if error occurred */ public function runImport($steps) { foreach ($steps as $step) { @@ -106,9 +140,10 @@ class ConfigDataImporter { $key = $step->getKey(); switch ($key) { case 'mainConfig': - $cfgMain = new LAMCfgMain(); - $cfgMain->importData($step->getValue()); - $cfgMain->save(); + $this->importMainConfig($step->getValue()); + break; + case 'certificates': + $this->importCertificates($step->getValue()); break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); @@ -116,6 +151,29 @@ class ConfigDataImporter { } } + /** + * Imports the main configuration. + * + * @param array $data main config data + * @throws LAMException error during import + */ + private function importMainConfig($data) { + $cfgMain = new LAMCfgMain(); + $cfgMain->importData($data); + $cfgMain->save(); + } + + /** + * Imports the SSL certificates. + * + * @param null|string $data file content + * @throws LAMException error during import + */ + private function importCertificates($data) { + $cfgMain = new LAMCfgMain(); + $cfgMain->importCertificates($data); + } + } /** diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 0f461f71..7c54e8b4 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -1,6 +1,7 @@ add(new htmlResponsiveInputCheckbox('step_' . $importStep->getKey(), true, $importStep->getLabel()), 12); } - $content->add(new htmlButton('importConfigConfirm', _('Import')), 12); - $content->add(new htmlButton('importCancel', _('Cancel')), 12); + $buttonGroup = new htmlGroup(); + $buttonGroup->addElement(new htmlButton('importConfigConfirm', _('Import'))); + $buttonGroup->addElement(new htmlButton('importCancel', _('Cancel'))); + $content->add($buttonGroup, 12); } elseif (isset($_POST['importConfigConfirm'])) { $handle = fopen($_SESSION['configImportFile'], "r"); diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index a02e031d..622b19b2 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -37,12 +37,16 @@ class ConfigDataExporterTest extends TestCase { 'confMainKey2' => 4, 'confMainKey3' => '', ); - $expectedJson = json_encode(array('mainConfig' => $mainData)); + $expectedJson = json_encode(array( + 'mainConfig' => $mainData, + 'certificates' => 'certs' + )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') - ->setMethods(array('_getMainConfigData')) + ->setMethods(array('_getMainConfigData', '_getCertificates')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); + $exporter->method('_getCertificates')->willReturn('certs'); $json = $exporter->exportAsJson(); From 9936c834db1e1fccfb757b71ee9ba27729e947be Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 19 Apr 2020 20:39:57 +0200 Subject: [PATCH 09/29] onclick for span --- lam/lib/html.inc | 53 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 7b077aaf..5acfeb49 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -3644,6 +3644,8 @@ class htmlSpan extends htmlElement { /** htmlElement that generates inner content */ private $content = null; + /** onclick handler */ + private $onclick = null; /** * Constructor. @@ -3674,13 +3676,27 @@ class htmlSpan extends htmlElement { if (($this->cssClasses != null) && (sizeof($this->cssClasses) > 0)) { $classesValue = ' class="' . implode(' ', $this->cssClasses) . '"'; } - echo ''; + $onclickHandler = ''; + if (!empty($this->onclick)) { + $onclickHandler = ' onclick="' . $this->onclick . '"'; + } + echo ''; if ($this->content != null) { $return = $this->content->generateHTML($module, $input, $values, $restricted, $tabindex, $scope); } echo ''; return $return; } + + /** + * Sets the onclick event. + * + * @param string $event event handler code + */ + public function setOnclick($event) { + $this->onclick = $event; + } + } /** @@ -4715,6 +4731,8 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { private $renderParentHtml = false; /** long label */ private $longLabel = false; + /** label after checkbox */ + private $labelAfterCheckbox = false; /** * Constructor. @@ -4751,14 +4769,23 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { $row = new htmlResponsiveRow(); $tabletColumnsLabel = 6; $tabletColumnsBox = 6; + $mobileColumnsLabel = 10; + $mobileColumnsBox = 2; if ($this->longLabel) { $tabletColumnsLabel = 10; $tabletColumnsBox = 2; } + if ($this->labelAfterCheckbox) { + $tmp = $mobileColumnsLabel; + $mobileColumnsLabel = $mobileColumnsLabel; + $mobileColumnsBox = $mobileColumnsLabel; + $tmp = $tabletColumnsLabel; + $tabletColumnsLabel = $tabletColumnsBox; + $tabletColumnsBox = $tabletColumnsLabel; + } // label text - $labelGroup = new htmlGroup(); - $labelGroup->addElement(new htmlOutputText($this->label)); - $row->add($labelGroup, 10, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveLabel'); + $text = new htmlSpan(new htmlOutputText($this->label)); + $text->setOnclick('jQuery(\'#' . $this->name . '\').prop(\'checked\',!jQuery(\'#' . $this->name . '\').prop(\'checked\')); jQuery(\'#' . $this->name . '\').change();'); // input field $fieldGroup = new htmlGroup(); $fieldGroup->addElement($this); @@ -4767,7 +4794,14 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { $helpLink->setCSSClasses(array('margin-left5 align-unset-img')); $fieldGroup->addElement($helpLink); } - $row->add($fieldGroup, 2, $tabletColumnsBox, $tabletColumnsBox, 'responsiveField nowrap'); + if ($this->labelAfterCheckbox) { + $row->add($fieldGroup, $mobileColumnsBox, $tabletColumnsBox, $tabletColumnsBox, 'responsiveLabel nowrap'); + $row->add($text, $mobileColumnsLabel, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveField'); + } + else { + $row->add($text, $mobileColumnsLabel, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveLabel'); + $row->add($fieldGroup, $mobileColumnsBox, $tabletColumnsBox, $tabletColumnsBox, 'responsiveField nowrap'); + } return $row->generateHTML($module, $input, $values, $restricted, $tabindex, $scope); } @@ -4779,6 +4813,15 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { return '.row'; } + /** + * Sets if the label should be shown after the checkbox instead before it. + * + * @param bool $labelAfterCheckbox show label after box + */ + public function setLabelAfterCheckbox($labelAfterCheckbox = true) { + $this->labelAfterCheckbox = $labelAfterCheckbox; + } + } /** From 24a6e142516817de4b246bb639a413e0ca9e01aa Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 19 Apr 2020 20:40:40 +0200 Subject: [PATCH 10/29] export server profiles --- lam/lib/config.inc | 52 +++++++++++++++++++ lam/lib/persistence.inc | 49 ++++++++++++++++-- lam/templates/config/confImportExport.php | 16 +++++- lam/tests/lib/LAMConfigTest.php | 61 ++++++++++++++++++++++- lam/tests/lib/persistenceTest.php | 10 +++- 5 files changed, 180 insertions(+), 8 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 06a6eca7..6419f1d0 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -649,6 +649,58 @@ class LAMConfig { $this->reload(); } + /** + * Returns the server profile data. + * + * @return array data + */ + public function exportData() { + $data = array(); + $settingsToIgnore = array('modules', 'types', 'tools', 'jobs'); + foreach ($this->settings as $setting) { + if (in_array($setting, $settingsToIgnore)) { + continue; + } + $data[$setting] = $this->$setting; + } + $data['typeSettings'] = $this->typeSettings; + $data['moduleSettings'] = $this->moduleSettings; + $data['toolSettings'] = $this->toolSettings; + $data['jobSettings'] = $this->jobSettings; + return $data; + } + + /** + * Imports server profile data. + * + * @param array $data config data + * @throws LAMException import error + */ + public function importData($data) { + $settingsToIgnore = array('modules', 'types', 'tools', 'jobs'); + foreach ($data as $dataKey => $dataValue) { + if (in_array($dataKey, $settingsToIgnore)) { + continue; + } + if (!in_array($dataKey, $this->settings)) { + logNewMessage(LOG_WARNING, 'Ignored setting during import: ' . $dataKey); + continue; + } + if (!(($dataValue === null) || is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) { + throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue)); + } + $this->$dataKey = $dataValue; + } + $typeSettingsData = !empty($data['typeSettings']) && is_array($data['typeSettings']) ? $data['typeSettings'] : array(); + $this->typeSettings = $typeSettingsData; + $moduleSettingsData = !empty($data['moduleSettings']) && is_array($data['moduleSettings']) ? $data['moduleSettings'] : array(); + $this->moduleSettings = $moduleSettingsData; + $toolSettingsData = !empty($data['toolSettings']) && is_array($data['toolSettings']) ? $data['toolSettings'] : array(); + $this->toolSettings = $toolSettingsData; + $jobSettingsData = !empty($data['jobSettings']) && is_array($data['jobSettings']) ? $data['jobSettings'] : array(); + $this->jobSettings = $jobSettingsData; + } + /** * Reloads preferences from config file * diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 46401611..498b0d2c 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -1,6 +1,7 @@ _getMainConfigData($mainCfg); $jsonData['certificates'] = $this->_getCertificates($mainCfg); + $jsonData['serverProfiles'] = $this->_getServerProfiles(); /** * TODO * - * webauthn * server profiles * server profile job database * account profiles * PDF profiles * self service profiles + * webauthn */ return json_encode($jsonData); } @@ -88,6 +90,21 @@ class ConfigDataExporter { return $mainCfg->exportCertificates(); } + /** + * Returns the content of the server profiles. + * + * @return array $data + */ + public function _getServerProfiles() { + $data = array(); + $profileNames = getConfigProfiles(); + foreach ($profileNames as $profileName) { + $serverProfile = new LAMConfig($profileName); + $data[$profileName] = $serverProfile->exportData(); + } + return $data; + } + } /** @@ -103,7 +120,7 @@ class ConfigDataImporter { * @throws LAMException if invalid format */ public function getPossibleImportSteps($json) { - $data = json_decode($json); + $data = json_decode($json, true); if ($data === null) { throw new LAMException(_('Unable to read import file.')); } @@ -116,6 +133,13 @@ class ConfigDataImporter { case 'certificates': $steps[] = new ImporterStep(_('SSL certificates'), 'certificates', $value); break; + case 'serverProfiles': + $mainStep = new ImporterStep(_('Server profiles'), 'serverProfiles', $value); + foreach ($value as $profileName => $profileData) { + $mainStep->addSubStep(new ImporterStep($profileName, 'serverProfile_' . $profileName, $profileData)); + } + $steps[] = $mainStep; + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -185,6 +209,7 @@ class ImporterStep { private $key; private $value; private $active = false; + private $subSteps = array(); /** * Constructor. @@ -196,7 +221,7 @@ class ImporterStep { public function __construct($label, $key, &$value) { $this->label = $label; $this->key = $key; - $this->value = $value; + $this->value = &$value; } /** @@ -244,4 +269,22 @@ class ImporterStep { return $this->value; } + /** + * Adds a sub-step. + * + * @param ImporterStep $subStep sub-step + */ + public function addSubStep($subStep) { + $this->subSteps[] = $subStep; + } + + /** + * Returns the sub-steps. + * + * @return ImporterStep[] sub-steps + */ + public function getSubSteps() { + return $this->subSteps; + } + } diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 7c54e8b4..e6548749 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -221,7 +221,21 @@ printHeaderContents(_("Import and export configuration"), '../..'); elseif (isset($_POST['importConfig'])) { $content->add(new htmlOutputText(_('Import steps')), 12); foreach ($importSteps as $importStep) { - $content->add(new htmlResponsiveInputCheckbox('step_' . $importStep->getKey(), true, $importStep->getLabel()), 12); + $stepKey = 'step_' . $importStep->getKey(); + $stepCheckbox = new htmlResponsiveInputCheckbox($stepKey, true, $importStep->getLabel()); + $stepCheckbox->setLabelAfterCheckbox(); + $subStepIds = array(); + $content->add($stepCheckbox, 12); + $content->addVerticalSpacer('0.3rem'); + foreach ($importStep->getSubSteps() as $subStep) { + $subStepKey = 'step_' . $subStep->getKey(); + $subStepIds[] = $subStepKey; + $subStepCheckbox = new htmlResponsiveInputCheckbox($subStepKey, true, ' - ' . $subStep->getLabel()); + $subStepCheckbox->setLabelAfterCheckbox(); + $content->add($subStepCheckbox, 12); + } + $stepCheckbox->setTableRowsToShow($subStepIds); + $content->addVerticalSpacer('1rem'); } $buttonGroup = new htmlGroup(); $buttonGroup->addElement(new htmlButton('importConfigConfirm', _('Import'))); diff --git a/lam/tests/lib/LAMConfigTest.php b/lam/tests/lib/LAMConfigTest.php index 972c5403..b6f9212c 100644 --- a/lam/tests/lib/LAMConfigTest.php +++ b/lam/tests/lib/LAMConfigTest.php @@ -3,7 +3,7 @@ use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2016 - 2019 Roland Gruber + Copyright (C) 2016 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ use PHPUnit\Framework\TestCase; */ -include_once 'lam/tests/utils/configuration.inc'; +include_once __DIR__ . '/../utils/configuration.inc'; /** * LAMConfig test case. @@ -858,6 +858,63 @@ class LAMConfigTest extends TestCase { $this->assertEquals($sizeTypeSettings, sizeof($this->lAMConfig->get_typeSettings())); } + /** + * Tests the export. + */ + public function testExportData() { + $this->lAMConfig->set_defaultLanguage('lang'); + $this->lAMConfig->set_ServerURL('myserver'); + $this->lAMConfig->set_typeSettings(array('typetest' => '1')); + $this->lAMConfig->set_moduleSettings(array('modtest' => '1')); + $this->lAMConfig->setToolSettings(array('tooltest' => '1')); + $this->lAMConfig->setJobSettings(array('jobtest' => '1')); + + $data = $this->lAMConfig->exportData(); + + $this->assertEquals('lang', $data['defaultLanguage']); + $this->assertEquals('myserver', $data['ServerURL']); + $this->assertEquals(array('typetest' => '1'), $data['typeSettings']); + $this->assertEquals(array('modtest' => '1'), $data['moduleSettings']); + $this->assertEquals(array('tooltest' => '1'), $data['toolSettings']); + $this->assertEquals(array('jobtest' => '1'), $data['jobSettings']); + } + + /** + * Tests the import. + */ + public function testImportData() { + $importData = array(); + $importData['ServerURL'] = 'testserver'; + $importData['defaultLanguage'] = 'de_DE.utf8'; + $importData['typeSettings'] = array('typetest' => 'value'); + $importData['toolSettings'] = array('tooltest' => 'value'); + $importData['moduleSettings'] = array('modtest' => 'value'); + $importData['jobSettings'] = array('jobtest' => 'value'); + $importData['IGNORE_ME'] = 'ignore'; + + $this->lAMConfig->importData($importData); + + $this->assertEquals('testserver', $this->lAMConfig->get_ServerURL()); + $this->assertEquals('de_DE.utf8', $this->lAMConfig->get_defaultLanguage()); + $this->assertEquals(array('typetest' => 'value'), $this->lAMConfig->get_typeSettings()); + $this->assertEquals(array('tooltest' => 'value'), $this->lAMConfig->getToolSettings()); + $this->assertEquals(array('modtest' => 'value'), $this->lAMConfig->get_moduleSettings()); + $this->assertEquals(array('jobtest' => 'value'), $this->lAMConfig->getJobSettings()); + } + + /** + * Tests the import with invalid data. + */ + public function testImportData_invalid() { + $importData = array(); + $importData['ServerURL'] = 'testserver'; + $importData['typeSettings'] = array('typetest' => 'value'); + $importData['defaultLanguage'] = new LAMLanguage('de_de', 'UTF-8', 'DE'); + + $this->expectException(LAMException::class); + $this->lAMConfig->importData($importData); + } + /** * Saves the config */ diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index 622b19b2..a39b81ba 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -37,16 +37,22 @@ class ConfigDataExporterTest extends TestCase { 'confMainKey2' => 4, 'confMainKey3' => '', ); + $profileData = array( + 'profile1' => array('ServerURL' => 'myserver'), + 'profile2' => array('ServerURL' => 'myserver2'), + ); $expectedJson = json_encode(array( 'mainConfig' => $mainData, - 'certificates' => 'certs' + 'certificates' => 'certs', + 'serverProfiles' => $profileData, )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') - ->setMethods(array('_getMainConfigData', '_getCertificates')) + ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); $exporter->method('_getCertificates')->willReturn('certs'); + $exporter->method('_getServerProfiles')->willReturn($profileData); $json = $exporter->exportAsJson(); From 8d50dd59b040500a10b24b530e8993ba69421f09 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 26 Apr 2020 08:55:09 +0200 Subject: [PATCH 11/29] server profiles import --- lam/VERSION | 2 +- lam/lib/config.inc | 23 ++++++++++++++++++++ lam/lib/persistence.inc | 26 +++++++++++++++++++++-- lam/templates/config/confImportExport.php | 3 +++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lam/VERSION b/lam/VERSION index a50da158..610207b4 100644 --- a/lam/VERSION +++ b/lam/VERSION @@ -1 +1 @@ -7.2.RC1 +7.2.DEV diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 6419f1d0..d0e87366 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -667,6 +667,17 @@ class LAMConfig { $data['moduleSettings'] = $this->moduleSettings; $data['toolSettings'] = $this->toolSettings; $data['jobSettings'] = $this->jobSettings; + if ($this->jobsDatabase === 'SQLite') { + $dbFileName = __DIR__ . '/../config/' . $this->getName() . '.sqlite'; + if (is_file($dbFileName) && is_readable($dbFileName)) { + $file = @fopen($dbFileName, "r"); + if ($file) { + $dbData = fread($file, 100000000); + fclose($file); + $data['jobSQLite'] = base64_encode($dbData); + } + } + } return $data; } @@ -699,6 +710,14 @@ class LAMConfig { $this->toolSettings = $toolSettingsData; $jobSettingsData = !empty($data['jobSettings']) && is_array($data['jobSettings']) ? $data['jobSettings'] : array(); $this->jobSettings = $jobSettingsData; + if (!empty($data['jobSQLite'])) { + $dbFileName = __DIR__ . '/../config/' . $this->getName() . '.sqlite'; + $file = @fopen($dbFileName, "wb"); + if ($file) { + fputs($file, base64_decode($data['jobSQLite'])); + fclose($file); + } + } } /** @@ -811,6 +830,10 @@ class LAMConfig { /** Saves preferences to config file */ public function save() { $conffile = $this->getPath(); + if (!file_exists($conffile)) { + $newFile = fopen($conffile, 'wb'); + fclose($newFile); + } if (is_file($conffile) && is_readable($conffile)) { $file = fopen($conffile, "r"); $file_array = array(); diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 498b0d2c..a5321862 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -169,6 +169,9 @@ class ConfigDataImporter { case 'certificates': $this->importCertificates($step->getValue()); break; + case 'serverProfiles': + $this->importServerProfiles($step); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -198,6 +201,25 @@ class ConfigDataImporter { $cfgMain->importCertificates($data); } + /** + * Imports the server profiles. + * + * @param ImporterStep $step step + * @throws LAMException error during import + */ + private function importServerProfiles($step) { + foreach ($step->getSubSteps() as $profileStep) { + if (!$profileStep->isActive()) { + continue; + } + $data = $profileStep->getValue(); + $profileName = str_replace('serverProfile_', '', $profileStep->getKey()); + $serverProfile = new LAMConfig($profileName); + $serverProfile->importData($data); + $serverProfile->save(); + } + } + } /** @@ -218,10 +240,10 @@ class ImporterStep { * @param string $key key * @param array $value value */ - public function __construct($label, $key, &$value) { + public function __construct($label, $key, $value) { $this->label = $label; $this->key = $key; - $this->value = &$value; + $this->value = $value; } /** diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index e6548749..90ff6484 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -251,6 +251,9 @@ printHeaderContents(_("Import and export configuration"), '../..'); $importSteps = $importer->getPossibleImportSteps($data); foreach ($importSteps as $importStep) { $importStep->setActive(isset($_POST['step_' . $importStep->getKey()])); + foreach ($importStep->getSubSteps() as $subStep) { + $subStep->setActive(isset($_POST['step_' . $subStep->getKey()])); + } } $importer->runImport($importSteps); unlink($_SESSION['configImportFile']); From 7fcc2cf38dba3f89acc2c446d0e654ca0659f383 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 27 Apr 2020 21:53:50 +0200 Subject: [PATCH 12/29] error handling --- lam/lib/persistence.inc | 9 ++++++++- lam/templates/config/confImportExport.php | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index a5321862..9acd66ab 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -208,6 +208,7 @@ class ConfigDataImporter { * @throws LAMException error during import */ private function importServerProfiles($step) { + $failedProfiles = array(); foreach ($step->getSubSteps() as $profileStep) { if (!$profileStep->isActive()) { continue; @@ -216,7 +217,13 @@ class ConfigDataImporter { $profileName = str_replace('serverProfile_', '', $profileStep->getKey()); $serverProfile = new LAMConfig($profileName); $serverProfile->importData($data); - $serverProfile->save(); + $result = $serverProfile->save(); + if ($result === LAMConfig::SAVE_FAIL) { + $failedProfiles[] = $profileName; + } + } + if (!empty($failedProfiles)) { + throw new LAMException(_('Unable to save server profile.'), implode(', ', $failedProfiles)); } } diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 90ff6484..7a6f214b 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -261,7 +261,7 @@ printHeaderContents(_("Import and export configuration"), '../..'); } catch (LAMException $e) { $content->add(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()), 12); - $content->add(new htmlButton('importCancel', _('Cancel')), 12); + $content->add(new htmlButton('importCancel', _('Back')), 12); } } } From c9d32bf2de01ac800c5b8b30390a0c72e97ac7d1 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 2 May 2020 18:51:33 +0200 Subject: [PATCH 13/29] refactoring --- lam/lib/modules.inc | 4 ++-- lam/lib/profiles.inc | 24 +++++++++++++----------- lam/templates/profedit/profilepage.php | 6 +++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lam/lib/modules.inc b/lam/lib/modules.inc index aacba5a4..b97f69e3 100644 --- a/lam/lib/modules.inc +++ b/lam/lib/modules.inc @@ -1454,7 +1454,7 @@ class accountContainer { */ private function loadProfileIfRequested() { if (isset($_POST['accountContainerLoadProfile']) && isset($_POST['accountContainerSelectLoadProfile'])) { - $profile = \LAM\PROFILES\loadAccountProfile($_POST['accountContainerSelectLoadProfile'], $this->type->getId()); + $profile = \LAM\PROFILES\loadAccountProfile($_POST['accountContainerSelectLoadProfile'], $this->type->getId(), $_SESSION['config']->getName()); $this->lastLoadedProfile = $_POST['accountContainerSelectLoadProfile']; // pass profile to each module $modules = array_keys($this->module); @@ -1775,7 +1775,7 @@ class accountContainer { $this->lastLoadedProfile = $cookieProfileName; } } - $profile = \LAM\PROFILES\loadAccountProfile($profileName, $this->type->getId()); + $profile = \LAM\PROFILES\loadAccountProfile($profileName, $this->type->getId(), $_SESSION['config']->getName()); // pass profile to each module $modules = array_keys($this->module); foreach ($modules as $module) $this->module[$module]->load_profile($profile); diff --git a/lam/lib/profiles.inc b/lam/lib/profiles.inc index 1c05cccc..c6818793 100644 --- a/lam/lib/profiles.inc +++ b/lam/lib/profiles.inc @@ -5,7 +5,7 @@ use \LAMException; $Id$ This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2018 Roland Gruber + Copyright (C) 2003 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -80,20 +80,21 @@ function profileExists($name, $typeId) { } /** -* Loads an profile of the given account type -* -* @param string $profile name of the profile (without . extension) -* @param string $typeId account type -* @return array hash array (attribute => value) -*/ -function loadAccountProfile($profile, $typeId) { + * Loads an profile of the given account type + * + * @param string $profile name of the profile (without . extension) + * @param string $typeId account type + * @param string $serverProfileName server profile name + * @return array hash array (attribute => value) + */ +function loadAccountProfile($profile, $typeId, $serverProfileName) { $typeManager = new \LAM\TYPES\TypeManager(); $type = $typeManager->getConfiguredType($typeId); if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) { return false; } $settings = array(); - $file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $_SESSION['config']->getName() . '/' . $profile . "." . $typeId; + $file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId; if (is_file($file)) { $file = @fopen($file, "r"); if ($file) { @@ -133,9 +134,10 @@ function loadAccountProfile($profile, $typeId) { * @param array $attributes hash array (attribute => value) * @param string $profile name of the account profile (without . extension) * @param string $typeId account type + * @param string $serverProfileName server profile name * @return boolean true, if saving succeeded */ -function saveAccountProfile($attributes, $profile, $typeId) { +function saveAccountProfile($attributes, $profile, $typeId, $serverProfileName) { if (!isLoggedIn()) return false; // check profile name and type id $typeManager = new \LAM\TYPES\TypeManager(); @@ -146,7 +148,7 @@ function saveAccountProfile($attributes, $profile, $typeId) { if (!is_array($attributes)) { return false; } - $path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $_SESSION['config']->getName() . '/' . $profile . "." . $typeId; + $path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId; $file = @fopen($path, "w"); if ($file) { // write attributes diff --git a/lam/templates/profedit/profilepage.php b/lam/templates/profedit/profilepage.php index d6973745..aac4f8a2 100644 --- a/lam/templates/profedit/profilepage.php +++ b/lam/templates/profedit/profilepage.php @@ -10,7 +10,7 @@ use \htmlSubTitle; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2019 Roland Gruber + Copyright (C) 2003 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -127,7 +127,7 @@ if (isset($_POST['save'])) { $errors = checkProfileOptions($_POST['accounttype'], $options); if (sizeof($errors) == 0) { // input data is valid, save profile // save profile - if (\LAM\PROFILES\saveAccountProfile($options, $_POST['profname'], $_POST['accounttype'])) { + if (\LAM\PROFILES\saveAccountProfile($options, $_POST['profname'], $_POST['accounttype'], $_SESSION['config']->getName())) { metaRefresh('profilemain.php?savedSuccessfully=' . $_POST['profname']); exit(); } @@ -168,7 +168,7 @@ if (isset($_POST['save'])) { } } elseif (isset($_GET['edit'])) { - $old_options = \LAM\PROFILES\loadAccountProfile($_GET['edit'], $type->getId()); + $old_options = \LAM\PROFILES\loadAccountProfile($_GET['edit'], $type->getId(), $_SESSION['config']->getName()); } // display formular From 9c6e30a03ba0b9d2a3446439232b705621af7403 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 3 May 2020 10:32:35 +0200 Subject: [PATCH 14/29] account profile export --- lam/lib/config.inc | 7 +- lam/lib/persistence.inc | 80 +++++++++++++++++++++-- lam/lib/profiles.inc | 17 ++--- lam/templates/config/confImportExport.php | 4 +- lam/templates/profedit/profilepage.php | 2 +- lam/tests/lib/persistenceTest.php | 12 +++- 6 files changed, 100 insertions(+), 22 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index d0e87366..4fe7bdb9 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -688,7 +688,8 @@ class LAMConfig { * @throws LAMException import error */ public function importData($data) { - $settingsToIgnore = array('modules', 'types', 'tools', 'jobs'); + $settingsToIgnore = array('modules', 'types', 'tools', 'jobs', 'typeSettings', + 'moduleSettings', 'toolSettings', 'jobSettings', 'jobSQLite'); foreach ($data as $dataKey => $dataValue) { if (in_array($dataKey, $settingsToIgnore)) { continue; @@ -1850,7 +1851,7 @@ class LAMConfig { /** * Returns a list of active account types. * - * @return array list of types + * @return string[] list of types */ public function get_ActiveTypes() { if (($this->activeTypes == '') || !isset($this->activeTypes)) { @@ -1864,7 +1865,7 @@ class LAMConfig { /** * Sets the list of active types. * - * @param array list of types + * @param string[] list of types */ public function set_ActiveTypes($types) { $this->activeTypes = implode(",", $types); diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 9acd66ab..e1a0f933 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -3,6 +3,9 @@ namespace LAM\PERSISTENCE; use LAMCfgMain; use LAMConfig; use LAMException; +use function LAM\PROFILES\getAccountProfiles; +use function LAM\PROFILES\loadAccountProfile; +use function LAM\PROFILES\saveAccountProfile; /* @@ -33,6 +36,7 @@ use LAMException; */ include_once __DIR__ . '/config.inc'; +include_once __DIR__ . '/profiles.inc'; /** * Exporter for LAM's configuration data. @@ -47,12 +51,16 @@ class ConfigDataExporter { $jsonData = array(); $jsonData['mainConfig'] = $this->_getMainConfigData($mainCfg); $jsonData['certificates'] = $this->_getCertificates($mainCfg); - $jsonData['serverProfiles'] = $this->_getServerProfiles(); + $serverProfileNames = getConfigProfiles(); + $serverProfiles = array(); + foreach ($serverProfileNames as $serverProfileName) { + $serverProfiles[$serverProfileName] = new \LAMConfig($serverProfileName); + } + $jsonData['serverProfiles'] = $this->_getServerProfiles($serverProfiles); + $jsonData['accountProfiles'] = $this->_getAccountProfiles($serverProfiles); /** * TODO * - * server profiles - * server profile job database * account profiles * PDF profiles * self service profiles @@ -93,18 +101,37 @@ class ConfigDataExporter { /** * Returns the content of the server profiles. * + * @param array $serverProfiles list of server profiles (name => object) * @return array $data */ - public function _getServerProfiles() { + public function _getServerProfiles($serverProfiles) { $data = array(); - $profileNames = getConfigProfiles(); - foreach ($profileNames as $profileName) { - $serverProfile = new LAMConfig($profileName); + foreach ($serverProfiles as $profileName => $serverProfile) { $data[$profileName] = $serverProfile->exportData(); } return $data; } + /** + * Returns the content of the account profiles. + * + * @param array $serverProfiles list of server profiles (name => object) + * @return array $data + */ + public function _getAccountProfiles($serverProfiles) { + $data = array(); + foreach ($serverProfiles as $profileName => $serverProfile) { + foreach ($serverProfile->get_ActiveTypes() as $typeId) { + $accountProfileNames = getAccountProfiles($typeId, $profileName); + foreach ($accountProfileNames as $accountProfileName) { + $accountProfile = loadAccountProfile($accountProfileName, $typeId, $profileName); + $data[$profileName][$typeId][$accountProfileName] = $accountProfile; + } + } + } + return $data; + } + } /** @@ -140,6 +167,13 @@ class ConfigDataImporter { } $steps[] = $mainStep; break; + case 'accountProfiles': + $mainStep = new ImporterStep(_('Account profiles'), 'accountProfiles', $value); + foreach ($value as $profileName => $profileData) { + $mainStep->addSubStep(new ImporterStep($profileName, 'accountProfile_' . $profileName, $profileData)); + } + $steps[] = $mainStep; + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -172,6 +206,9 @@ class ConfigDataImporter { case 'serverProfiles': $this->importServerProfiles($step); break; + case 'accountProfiles': + $this->importAccountProfiles($step); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -227,6 +264,35 @@ class ConfigDataImporter { } } + /** + * Imports the account profiles. + * + * @param ImporterStep $step step + * @throws LAMException error during import + */ + private function importAccountProfiles($step) { + $failedProfiles = array(); + foreach ($step->getSubSteps() as $profileStep) { + if (!$profileStep->isActive()) { + continue; + } + $data = $profileStep->getValue(); + $serverProfileName = str_replace('accountProfile_', '', $profileStep->getKey()); + $serverProfile = new LAMConfig($serverProfileName); + foreach ($data as $typeId => $accountProfiles) { + foreach ($accountProfiles as $accountProfileName => $accountProfileData) { + $result = saveAccountProfile($accountProfileData, $accountProfileName, $typeId, $serverProfile); + if (!$result) { + $failedProfiles[] = $serverProfileName . ':' . $typeId . ':' . $accountProfileName; + } + } + } + } + if (!empty($failedProfiles)) { + throw new LAMException(_('Unable to save account profile.'), implode(', ', $failedProfiles)); + } + } + } /** diff --git a/lam/lib/profiles.inc b/lam/lib/profiles.inc index c6818793..6825843f 100644 --- a/lam/lib/profiles.inc +++ b/lam/lib/profiles.inc @@ -88,9 +88,8 @@ function profileExists($name, $typeId) { * @return array hash array (attribute => value) */ function loadAccountProfile($profile, $typeId, $serverProfileName) { - $typeManager = new \LAM\TYPES\TypeManager(); - $type = $typeManager->getConfiguredType($typeId); - if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) { + if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId)) { + logNewMessage(LOG_NOTICE, "Invalid account profile name: $serverProfileName:$profile:$typeId"); return false; } $settings = array(); @@ -134,21 +133,22 @@ function loadAccountProfile($profile, $typeId, $serverProfileName) { * @param array $attributes hash array (attribute => value) * @param string $profile name of the account profile (without . extension) * @param string $typeId account type - * @param string $serverProfileName server profile name + * @param \LAMConfig $serverProfile server profile * @return boolean true, if saving succeeded */ -function saveAccountProfile($attributes, $profile, $typeId, $serverProfileName) { - if (!isLoggedIn()) return false; +function saveAccountProfile($attributes, $profile, $typeId, $serverProfile) { // check profile name and type id - $typeManager = new \LAM\TYPES\TypeManager(); + $typeManager = new \LAM\TYPES\TypeManager($serverProfile); $type = $typeManager->getConfiguredType($typeId); if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) { + logNewMessage(LOG_NOTICE, 'Invalid account profile name', $profile . ':' . $typeId); return false; } if (!is_array($attributes)) { + logNewMessage(LOG_NOTICE, 'Invalid account profile data'); return false; } - $path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId; + $path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfile->getName() . '/' . $profile . "." . $typeId; $file = @fopen($path, "w"); if ($file) { // write attributes @@ -166,6 +166,7 @@ function saveAccountProfile($attributes, $profile, $typeId, $serverProfileName) fclose($file); } else { + logNewMessage(LOG_NOTICE, 'Unable to open account profile file: ' . $path); return false; } return true; diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 7a6f214b..7e434559 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -211,7 +211,7 @@ printHeaderContents(_("Import and export configuration"), '../..'); $validUpload = true; } catch (LAMException $e) { - $content->add(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()), 12); + $content->add(new htmlStatusMessage('ERROR', htmlspecialchars($e->getTitle()), htmlspecialchars($e->getMessage())), 12); } } if (!isset($_POST['importConfigConfirm']) && !$validUpload) { @@ -260,7 +260,7 @@ printHeaderContents(_("Import and export configuration"), '../..'); $content->add(new htmlStatusMessage('INFO', _('Configuration import ended successful.')), 12); } catch (LAMException $e) { - $content->add(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()), 12); + $content->add(new htmlStatusMessage('ERROR', htmlspecialchars($e->getTitle()), htmlspecialchars($e->getMessage())), 12); $content->add(new htmlButton('importCancel', _('Back')), 12); } } diff --git a/lam/templates/profedit/profilepage.php b/lam/templates/profedit/profilepage.php index aac4f8a2..7de209c6 100644 --- a/lam/templates/profedit/profilepage.php +++ b/lam/templates/profedit/profilepage.php @@ -127,7 +127,7 @@ if (isset($_POST['save'])) { $errors = checkProfileOptions($_POST['accounttype'], $options); if (sizeof($errors) == 0) { // input data is valid, save profile // save profile - if (\LAM\PROFILES\saveAccountProfile($options, $_POST['profname'], $_POST['accounttype'], $_SESSION['config']->getName())) { + if (\LAM\PROFILES\saveAccountProfile($options, $_POST['profname'], $_POST['accounttype'], $_SESSION['config'])) { metaRefresh('profilemain.php?savedSuccessfully=' . $_POST['profname']); exit(); } diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index a39b81ba..27a5d37a 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -41,18 +41,28 @@ class ConfigDataExporterTest extends TestCase { 'profile1' => array('ServerURL' => 'myserver'), 'profile2' => array('ServerURL' => 'myserver2'), ); + $accountProfileData = array( + 'profile1' => array('user' => array('default' => array('key' => 'value'))), + 'profile1' => array( + 'user' => array('default' => array('key' => 'value')), + 'group' => array('default' => array('key' => 'value')), + ), + ); $expectedJson = json_encode(array( 'mainConfig' => $mainData, 'certificates' => 'certs', 'serverProfiles' => $profileData, + 'accountProfiles' => $accountProfileData, )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') - ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles')) + ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles', + '_getAccountProfiles')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); $exporter->method('_getCertificates')->willReturn('certs'); $exporter->method('_getServerProfiles')->willReturn($profileData); + $exporter->method('_getAccountProfiles')->willReturn($accountProfileData); $json = $exporter->exportAsJson(); From 81587a9b00a3c463d35597150bfc64bb8c1a521d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 3 May 2020 10:59:35 +0200 Subject: [PATCH 15/29] refactoring --- lam/lib/lists.inc | 2 +- lam/lib/pdfstruct.inc | 32 ++++++++++++++--------------- lam/templates/pdfedit/pdfmain.php | 12 +++++------ lam/templates/pdfedit/pdfpage.php | 4 ++-- lam/templates/upload/masscreate.php | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lam/lib/lists.inc b/lam/lib/lists.inc index 7ad71fc6..d8aa0feb 100644 --- a/lam/lib/lists.inc +++ b/lam/lib/lists.inc @@ -759,7 +759,7 @@ class lamList { $selAccounts[] = $id; } // get possible PDF structures - $pdf_structures = \LAM\PDF\getPDFStructures($this->type->getId()); + $pdf_structures = \LAM\PDF\getPDFStructures($this->type->getId(), $_SESSION['config']->getName()); $this->printHeader(); diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index c6b7635d..3a17cb5b 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -44,18 +44,15 @@ include_once(__DIR__ . "/ldap.inc"); * @param string $typeId the account type * @param string $profile server profile name * - * @return array All available PDF structure definitions for the submitted account + * @return string[] All available PDF structure definitions for the submitted account * scope. Each entry is a string being the filename that may be passed to the * createModulePDF() function as second argument. */ -function getPDFStructures($typeId, $profile = null) { +function getPDFStructures($typeId, $profile) { $return = array(); if (!preg_match('/[a-zA-Z]+/', $typeId)) { return null; } - if (!isset($profile)) { - $profile = $_SESSION['config']->getName(); - } $path = dirname(__FILE__) . '/../config/pdf/' . $profile; if(is_dir($path)) { $dirHandle = opendir($path); @@ -75,14 +72,14 @@ function getPDFStructures($typeId, $profile = null) { * * @param string $typeId account type * @param string $name Name of definition to delete - * + * @param string $serverProfileName server profile name * @return boolean True if file was deleted or false if a problem occurred. */ -function deletePDFStructure($typeId, $name) { +function deletePDFStructure($typeId, $name, $serverProfileName) { if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/',$typeId)) { return false; } - $file = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml'; + $file = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/' . $name . '.' . $typeId . '.xml'; if(is_file($file) && is_writable($file)) { return unlink($file); } @@ -95,11 +92,12 @@ function deletePDFStructure($typeId, $name) { /** * This function returns an array with all aviliable logo images. * + * @param string $serverProfileName server profile name * @return array list of logo files */ -function getAvailableLogos() { +function getAvailableLogos($serverProfileName) { $return = array(); - $dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/'; + $dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/'; $dirHandle = opendir($dirPath); while($file = readdir($dirHandle)) { if(!is_dir($file) && $file != '.' && $file != '..' && preg_match('/\\.(jpg|png)$/i',$file)) { @@ -165,13 +163,14 @@ function copyStructureToTemplates($sourceType, $sourceName) { * * @param String $file full path of temporary file * @param String $name file name + * @param string $serverProfileName server profile name * @return StatusMessage status message to display */ -function uploadPDFLogo($file, $name) { +function uploadPDFLogo($file, $name, $serverProfileName) { if (!preg_match('/[a-zA-Z0-9_-]+\\.(png)|(jpg)/i', $name)) { return new htmlStatusMessage('ERROR', _('Unable to upload logo file.'), _('The file name must end with ".png" or ".jpg".')); } - $dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/'; + $dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/'; $success = copy($file, $dirPath . '/' . $name); if ($success) { return new htmlStatusMessage('INFO', _('Uploaded logo file.'), $name); @@ -185,12 +184,13 @@ function uploadPDFLogo($file, $name) { * Deletes a PDF logo file. * * @param String $name file name + * @param string $serverProfileName server profile name * @return StatusMessage status message to display */ -function deletePDFLogo($name) { +function deletePDFLogo($name, $serverProfileName) { // check if valid file $found = false; - $logos = getAvailableLogos(); + $logos = getAvailableLogos($serverProfileName); foreach ($logos as $logo) { if ($logo['filename'] === $name) { $found = true; @@ -205,7 +205,7 @@ function deletePDFLogo($name) { $activeTypes = $typeManager->getConfiguredTypes(); $reader = new PDFStructureReader(); foreach ($activeTypes as $type) { - $structures = getPDFStructures($type->getId()); + $structures = getPDFStructures($type->getId(), $serverProfileName); foreach ($structures as $structure) { try { $data = $reader->read($type->getId(), $structure); @@ -220,7 +220,7 @@ function deletePDFLogo($name) { } } // delete file - $dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/'; + $dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/'; $success = @unlink($dirPath . '/' . $name); if ($success) { return new htmlStatusMessage('INFO', _('Logo file deleted.'), $name); diff --git a/lam/templates/pdfedit/pdfmain.php b/lam/templates/pdfedit/pdfmain.php index 12c3b779..6a65ec6c 100644 --- a/lam/templates/pdfedit/pdfmain.php +++ b/lam/templates/pdfedit/pdfmain.php @@ -21,7 +21,7 @@ use \LAM\TYPES\TypeManager; This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) Copyright (C) 2003 - 2006 Michael Duergner - 2005 - 2018 Roland Gruber + 2005 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -107,7 +107,7 @@ $container->add(new htmlTitle(_('PDF editor')), 12); if (isset($_POST['deleteProfile']) && ($_POST['deleteProfile'] == 'true')) { $typeToDelete = $typeManager->getConfiguredType($_POST['profileDeleteType']); // delete structure - if (\LAM\PDF\deletePDFStructure($_POST['profileDeleteType'], $_POST['profileDeleteName'])) { + if (\LAM\PDF\deletePDFStructure($_POST['profileDeleteType'], $_POST['profileDeleteName'], $_SESSION['config']->getName())) { $message = new htmlStatusMessage('INFO', _('Deleted PDF structure.'), $typeToDelete->getAlias() . ': ' . htmlspecialchars($_POST['profileDeleteName'])); $container->add($message, 12); } @@ -173,13 +173,13 @@ if (!empty($_POST['export'])) { if (isset($_POST['uploadLogo']) && !empty($_FILES['logoUpload']) && !empty($_FILES['logoUpload']['size'])) { $file = $_FILES['logoUpload']['tmp_name']; $filename = $_FILES['logoUpload']['name']; - $container->add(\LAM\PDF\uploadPDFLogo($file, $filename), 12); + $container->add(\LAM\PDF\uploadPDFLogo($file, $filename, $_SESSION['config']->getName()), 12); } // delete logo file if (isset($_POST['delLogo'])) { $toDel = $_POST['logo']; - $container->add(\LAM\PDF\deletePDFLogo($toDel), 12); + $container->add(\LAM\PDF\deletePDFLogo($toDel, $_SESSION['config']->getName()), 12); } // get list of account types @@ -192,7 +192,7 @@ foreach ($sortedTypes as $typeId => $title) { 'scope' => $type->getScope(), 'title' => $title, 'icon' => $type->getIcon(), - 'templates' => \LAM\PDF\getPDFStructures($type->getId())); + 'templates' => \LAM\PDF\getPDFStructures($type->getId(), $_SESSION['config']->getName())); $availableTypes[$title] = $type->getId(); } // check if a template should be edited @@ -275,7 +275,7 @@ include __DIR__ . '/../../lib/adminHeader.inc'; // manage logos $container->addVerticalSpacer('4rem'); $container->add(new htmlSubTitle(_('Manage logos')), 12); - $logos = \LAM\PDF\getAvailableLogos(); + $logos = \LAM\PDF\getAvailableLogos($_SESSION['config']->getName()); $logoOptions = array(); foreach ($logos as $logo) { $file = $logo['filename']; diff --git a/lam/templates/pdfedit/pdfpage.php b/lam/templates/pdfedit/pdfpage.php index db105c77..9d715473 100644 --- a/lam/templates/pdfedit/pdfpage.php +++ b/lam/templates/pdfedit/pdfpage.php @@ -22,7 +22,7 @@ use LAM\PDF\PDFStructureWriter; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) Copyright (C) 2003 - 2006 Michael Duergner - 2007 - 2019 Roland Gruber + 2007 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -218,7 +218,7 @@ else if (isset($_POST['pdfname'])) { // headline $headline = $_SESSION['currentPDFStructure']->getTitle(); // logo -$logoFiles = \LAM\PDF\getAvailableLogos(); +$logoFiles = \LAM\PDF\getAvailableLogos($_SESSION['config']->getName()); $logos = array(_('No logo') => 'none'); foreach($logoFiles as $logoFile) { $logos[$logoFile['filename'] . ' (' . $logoFile['infos'][0] . ' x ' . $logoFile['infos'][1] . ")"] = $logoFile['filename']; diff --git a/lam/templates/upload/masscreate.php b/lam/templates/upload/masscreate.php index 95c4196d..1f76e2a1 100644 --- a/lam/templates/upload/masscreate.php +++ b/lam/templates/upload/masscreate.php @@ -294,7 +294,7 @@ function showMainPage(\LAM\TYPES\ConfiguredType $type, $selectedModules) { $pdfCheckbox = new htmlResponsiveInputCheckbox('createPDF', $createPDF, _('Create PDF files')); $pdfCheckbox->setTableRowsToShow(array('pdfStructure', 'pdf_font')); $row->add($pdfCheckbox, 12); - $pdfStructures = \LAM\PDF\getPDFStructures($type->getId()); + $pdfStructures = \LAM\PDF\getPDFStructures($type->getId(), $_SESSION['config']->getName()); $pdfSelected = array(); if (isset($_POST['pdfStructure'])) { $pdfSelected = array($_POST['pdfStructure']); From 665ca9daad393d1c6561fbf3b4e897845cddb749 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 20:22:41 +0200 Subject: [PATCH 16/29] refactoring --- lam/lib/pdf.inc | 2 +- lam/lib/pdfstruct.inc | 15 +++++++++++++-- lam/templates/pdfedit/pdfpage.php | 2 +- lam/tests/lib/pdfstructTest.php | 2 ++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lam/lib/pdf.inc b/lam/lib/pdf.inc index de2120d2..cdf8f54a 100644 --- a/lam/lib/pdf.inc +++ b/lam/lib/pdf.inc @@ -61,7 +61,7 @@ include_once('pdfstruct.inc'); function createModulePDF($accounts, $pdf_structure, $font, $returnAsString = false) { $account_type = $accounts[0]->get_type(); // Get PDF structure from xml file - $reader = new PDFStructureReader(); + $reader = new PDFStructureReader($_SESSION['config']->getName()); $structure = $reader->read($account_type->getId(), $pdf_structure); // get list of PDF keys $pdfKeys = array(); diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index 3a17cb5b..db46732b 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -203,7 +203,7 @@ function deletePDFLogo($name, $serverProfileName) { // check if still in use $typeManager = new \LAM\TYPES\TypeManager(); $activeTypes = $typeManager->getConfiguredTypes(); - $reader = new PDFStructureReader(); + $reader = new PDFStructureReader($serverProfileName); foreach ($activeTypes as $type) { $structures = getPDFStructures($type->getId(), $serverProfileName); foreach ($structures as $structure) { @@ -301,6 +301,17 @@ function installPDFTemplates() { */ class PDFStructureReader { + private $serverProfileName; + + /** + * Constructor. + * + * @param $serverProfileName server profile name + */ + public function __construct($serverProfileName) { + $this->serverProfileName = $serverProfileName; + } + /** * Reads a PDF structure. * @@ -324,7 +335,7 @@ class PDFStructureReader { * @return string file name */ protected function getFileName($typeId, $name) { - return dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml'; + return dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName . '/' . $name . '.' . $typeId . '.xml'; } /** diff --git a/lam/templates/pdfedit/pdfpage.php b/lam/templates/pdfedit/pdfpage.php index 9d715473..061fb726 100644 --- a/lam/templates/pdfedit/pdfpage.php +++ b/lam/templates/pdfedit/pdfpage.php @@ -105,7 +105,7 @@ if(isset($_GET['abort'])) { // Load PDF structure from file if it is not defined in session if(!isset($_SESSION['currentPDFStructure'])) { // Load structure file to be edit - $reader = new PDFStructureReader(); + $reader = new PDFStructureReader($_SESSION['config']->getName()); try { if(isset($_GET['edit'])) { $_SESSION['currentPDFStructure'] = $reader->read($type->getId(), $_GET['edit']); diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index bf0ba93d..c7906b72 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -41,6 +41,7 @@ class ReadStructureTest extends TestCase { */ public function testRead() { $reader = $this->getMockBuilder('\LAM\PDF\PDFStructureReader') + ->setConstructorArgs(array('test')) ->setMethods(array('getFileName')) ->getMock(); $reader->method('getFileName')->willReturn($this->getTestFileName('test.xml')); @@ -99,6 +100,7 @@ class ReadStructureTest extends TestCase { fclose($fileHandle); // read structure $reader = $this->getMockBuilder('\LAM\PDF\PDFStructureReader') + ->setConstructorArgs(array('test')) ->setMethods(array('getFileName')) ->getMock(); $reader->method('getFileName')->willReturn($file); From a246fde0e286ab6ac8abbac3de78c70568937a2c Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 20:27:50 +0200 Subject: [PATCH 17/29] refactoring --- lam/lib/pdfstruct.inc | 15 +++++++++++++-- lam/templates/pdfedit/pdfpage.php | 2 +- lam/tests/lib/pdfstructTest.php | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index db46732b..69270f2e 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -422,6 +422,17 @@ class PDFStructureReader { */ class PDFStructureWriter { + private $serverProfileName; + + /** + * Constructor. + * + * @param $serverProfileName server profile name + */ + public function __construct($serverProfileName) { + $this->serverProfileName = $serverProfileName; + } + /** * Writes the PDF structure to disk. * @@ -447,10 +458,10 @@ class PDFStructureWriter { throw new \LAMException(_('PDF structure name not valid'), _('The name for that PDF-structure you submitted is not valid. A valid name must consist of the following characters: \'a-z\',\'A-Z\',\'0-9\',\'_\',\'-\'.')); } - if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName())) { + if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName)) { throw new \LAMException(_('Could not save PDF structure, access denied.')); } - return dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml'; + return dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName . '/' . $name . '.' . $typeId . '.xml'; } /** diff --git a/lam/templates/pdfedit/pdfpage.php b/lam/templates/pdfedit/pdfpage.php index 061fb726..f9e17dbd 100644 --- a/lam/templates/pdfedit/pdfpage.php +++ b/lam/templates/pdfedit/pdfpage.php @@ -135,7 +135,7 @@ if (!empty($_POST['form_submit'])) { // main pdf structure page $saveErrors = array(); if(isset($_GET['submit'])) { - $writer = new PDFStructureWriter(); + $writer = new PDFStructureWriter($_SESSION['config']->getName()); try { $writer->write($type->getId(), $_POST['pdfname'], $_SESSION['currentPDFStructure']); unset($_SESSION['currentPDFStructure']); diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index c7906b72..0f0d73ae 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -106,7 +106,7 @@ class ReadStructureTest extends TestCase { $reader->method('getFileName')->willReturn($file); $structure = $reader->read('type', 'name'); // create writer and get output XML - $writer = new PDFStructureWriter(); + $writer = new PDFStructureWriter('test'); $xml = $writer->getXML($structure); // compare $this->assertEquals($originalXML, $xml); From ffd74d88e4baa688ab7835251f4fa557572641ca Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 21:10:47 +0200 Subject: [PATCH 18/29] config export --- lam/lib/pdfstruct.inc | 46 ++++++++++++++++++ lam/tests/lib/pdfstructTest.php | 83 ++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index 69270f2e..c9bff0e8 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -542,6 +542,28 @@ class PDFStructure { private $sections = array(); + /** + * Returns an array representation of the structure. + * + * @return array export data + */ + public function export() { + $data = array(); + $data['title'] = $this->title; + $data['foldingMarks'] = $this->foldingMarks; + $data['logo'] = $this->logo; + $data['sections'] = array(); + foreach($this->sections as $section) { + $type = ($section instanceof PDFTextSection) ? 'text' : 'entry'; + $sectionData = $section->export(); + $data['sections'][] = array( + 'type' => $type, + 'data' => $sectionData + ); + } + return $data; + } + /** * Returns the logo file path. * @@ -634,6 +656,15 @@ class PDFTextSection { $this->text = $text; } + /** + * Exports the section. + * + * @return string text + */ + public function export() { + return $this->getText(); + } + /** * Returns the text. * @@ -664,6 +695,21 @@ class PDFEntrySection { $this->title = $title; } + /** + * Exports the section. + * + * @return array export data + */ + public function export() { + $data = array(); + $data['title'] = $this->title; + $data['entries'] = array(); + foreach($this->getEntries() as $entry) { + $data['entries'][] = $entry->getKey(); + } + return $data; + } + /** * Returns if the title is an attribute value. * diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index 0f0d73ae..d290d751 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -1,4 +1,6 @@ export(); + + $this->assertEquals('sometext', $data); + } + +} + +/** + * Tests PDFEntrySection + */ +class PDFEntrySectionTest extends TestCase { + + public function testExport() { + $section = new PDFEntrySection('mytitle'); + $section->setEntries(array(new PDFSectionEntry('key1'), new PDFSectionEntry('key2'))); + + $data = $section->export(); + + $expected = array( + 'title' => 'mytitle', + 'entries' => array('key1', 'key2') + ); + + $this->assertEquals($expected, $data); + } + +} + +/** + * Tests PDFStructure + */ +class PDFStructureTest extends TestCase { + + public function testExport() { + $structure = new PDFStructure(); + $structure->setFoldingMarks(PDFStructure::FOLDING_STANDARD); + $structure->setLogo('somelogo'); + $structure->setTitle('mytitle'); + $entrySection = new PDFEntrySection('sometitle'); + $entrySection->setEntries(array(new PDFSectionEntry('key1'))); + $structure->setSections(array( + new PDFTextSection('sometext'), + $entrySection + )); + + $data = $structure->export(); + + $expected = array( + 'title' => 'mytitle', + 'foldingMarks' => PDFStructure::FOLDING_STANDARD, + 'logo' => 'somelogo', + 'sections' => array( + array( + 'type' => 'text', + 'data' => 'sometext' + ), + array( + 'type' => 'entry', + 'data' => array( + 'title' => 'sometitle', + 'entries' => array('key1') + ) + ) + ) + ); + + $this->assertEquals($expected, $data); + } + +} + ?> \ No newline at end of file From 2db6bf23ebf0c36dfb762013a050d9a305bea0af Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 21:15:26 +0200 Subject: [PATCH 19/29] refactoring --- lam/lib/html.inc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 5acfeb49..8f196be9 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -4775,14 +4775,6 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { $tabletColumnsLabel = 10; $tabletColumnsBox = 2; } - if ($this->labelAfterCheckbox) { - $tmp = $mobileColumnsLabel; - $mobileColumnsLabel = $mobileColumnsLabel; - $mobileColumnsBox = $mobileColumnsLabel; - $tmp = $tabletColumnsLabel; - $tabletColumnsLabel = $tabletColumnsBox; - $tabletColumnsBox = $tabletColumnsLabel; - } // label text $text = new htmlSpan(new htmlOutputText($this->label)); $text->setOnclick('jQuery(\'#' . $this->name . '\').prop(\'checked\',!jQuery(\'#' . $this->name . '\').prop(\'checked\')); jQuery(\'#' . $this->name . '\').change();'); From 9b4261ca365e2b3ac61aafc9f734f4201f6ca213 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 21:18:19 +0200 Subject: [PATCH 20/29] refactoring --- lam/templates/config/confImportExport.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 7e434559..57ba7fd4 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -103,13 +103,11 @@ printHeaderContents(_("Import and export configuration"), '../..'); } // check login - if (isset($_POST['submitLogin'])) { - if (!checkLogin($cfg)) { - exit(); - } + if (isset($_POST['submitLogin']) && !checkLogin($cfg)) { + exit(); } - displayImportExport($cfg); + displayImportExport(); /** * Shows the login dialog for the configuration master password. @@ -167,10 +165,8 @@ printHeaderContents(_("Import and export configuration"), '../..'); /** * Displays the import/export functions. - * - * @param LAMCfgMain $cfg main config */ - function displayImportExport($cfg) { + function displayImportExport() { $tabindex = 0; $content = new htmlResponsiveRow(); @@ -190,7 +186,6 @@ printHeaderContents(_("Import and export configuration"), '../..'); * @param htmlResponsiveRow $content content where to add import part */ function renderImportPart($content) { - $fileContent = null; $validUpload = false; $importSteps = array(); if (isset($_POST['importConfig'])) { From 40fd19e3bf62427686ed408ad4901174af6ee54e Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 7 May 2020 21:31:21 +0200 Subject: [PATCH 21/29] refactoring --- lam/tests/lib/pdfstructTest.php | 40 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index d290d751..f74d480b 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -31,12 +31,12 @@ use PHPUnit\Framework\TestCase; include_once __DIR__ . '/../../lib/pdfstruct.inc'; /** - * Reads a sample PDF structure. + * Tests classes in pdfstruct.inc. * * @author Roland Gruber * */ -class ReadStructureTest extends TestCase { +class PdfStructTest extends TestCase { /** * Reads the sample structure. @@ -114,14 +114,10 @@ class ReadStructureTest extends TestCase { $this->assertEquals($originalXML, $xml); } -} - -/** - * Tests PDFTextSection - */ -class PDFTextSectionTest extends TestCase { - - public function testExport() { + /** + * Tests PDFTextSection + */ + public function testExportPDFTextSection() { $section = new PDFTextSection('sometext'); $data = $section->export(); @@ -129,14 +125,10 @@ class PDFTextSectionTest extends TestCase { $this->assertEquals('sometext', $data); } -} - -/** - * Tests PDFEntrySection - */ -class PDFEntrySectionTest extends TestCase { - - public function testExport() { + /** + * Tests PDFEntrySection + */ + public function testExportPDFEntrySection() { $section = new PDFEntrySection('mytitle'); $section->setEntries(array(new PDFSectionEntry('key1'), new PDFSectionEntry('key2'))); @@ -150,14 +142,10 @@ class PDFEntrySectionTest extends TestCase { $this->assertEquals($expected, $data); } -} - -/** - * Tests PDFStructure - */ -class PDFStructureTest extends TestCase { - - public function testExport() { + /** + * Tests PDFStructure + */ + public function testExportPDFStructure() { $structure = new PDFStructure(); $structure->setFoldingMarks(PDFStructure::FOLDING_STANDARD); $structure->setLogo('somelogo'); From 6fc259d718d330e7f9dcb02451e56204ecc5ad68 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 12 May 2020 20:58:56 +0200 Subject: [PATCH 22/29] config import and export --- lam/lib/pdfstruct.inc | 47 +++++++++++++++++++++++- lam/lib/persistence.inc | 36 ++++++++++++++++++- lam/tests/lib/pdfstructTest.php | 59 +++++++++++++++++++++++++++++++ lam/tests/lib/persistenceTest.php | 14 +++++++- 4 files changed, 153 insertions(+), 3 deletions(-) diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index c9bff0e8..1df4e334 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -93,7 +93,7 @@ function deletePDFStructure($typeId, $name, $serverProfileName) { * This function returns an array with all aviliable logo images. * * @param string $serverProfileName server profile name - * @return array list of logo files + * @return array list of logo files (array('filename' => PATH, 'infos' => array(width, height))) */ function getAvailableLogos($serverProfileName) { $return = array(); @@ -564,6 +564,35 @@ class PDFStructure { return $data; } + /** + * Imports an array representation of the structure. + * + * @param array $data import data + */ + public function import($data) { + if (isset($data['title'])) { + $this->title = $data['title']; + } + if (isset($data['foldingMarks'])) { + $this->foldingMarks = $data['foldingMarks']; + } + if (isset($data['logo'])) { + $this->logo = $data['logo']; + } + if (isset($data['sections'])) { + foreach($data['sections'] as $section) { + if ($section['type'] === 'text') { + $this->sections[] = new PDFTextSection($section['data']); + } + else { + $entrySection = new PDFEntrySection(null); + $entrySection->import($section['data']); + $this->sections[] = $entrySection; + } + } + } + } + /** * Returns the logo file path. * @@ -710,6 +739,22 @@ class PDFEntrySection { return $data; } + /** + * Imports the section. + * + * @param array $data import data + */ + public function import($data) { + if (isset($data['title'])) { + $this->title = $data['title']; + } + if ($data['entries']) { + foreach($data['entries'] as $entry) { + $this->entries[] = new PDFSectionEntry($entry); + } + } + } + /** * Returns if the title is an attribute value. * diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index e1a0f933..edb400ab 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -1,8 +1,11 @@ _getServerProfiles($serverProfiles); $jsonData['accountProfiles'] = $this->_getAccountProfiles($serverProfiles); + $jsonData['pdfProfiles'] = $this->_getPdfProfiles($serverProfiles); /** * TODO * - * account profiles * PDF profiles * self service profiles * webauthn @@ -132,6 +135,36 @@ class ConfigDataExporter { return $data; } + /** + * Returns the content of the PDF profiles. + * + * @param array $serverProfiles list of server profiles (name => object) + * @return array $data + */ + public function _getPdfProfiles($serverProfiles) { + $data = array(); + foreach ($serverProfiles as $profileName => $serverProfile) { + foreach ($serverProfile->get_ActiveTypes() as $typeId) { + $pdfProfileNames = getPDFStructures($typeId, $profileName); + $reader = new PDFStructureReader($profileName); + foreach ($pdfProfileNames as $pdfProfileName) { + $pdfStructure = $reader->read($typeId, $pdfProfileName); + $data[$profileName]['structures'][$typeId][$pdfProfileName] = $pdfStructure->export(); + } + } + $logoData = getAvailableLogos($profileName); + foreach ($logoData as $logo) { + $logoFileName = $logo['filename']; + $logoPath = __DIR__ . '/../config/pdf/' . $profileName . '/logos/' . $logoFileName; + $handle = fopen($logoPath, 'r'); + $logoBinary = fread($handle, 100000000); + fclose($handle); + $data[$profileName]['logos'][$logoFileName] = base64_encode($logoBinary); + } + } + return $data; + } + } /** @@ -174,6 +207,7 @@ class ConfigDataImporter { } $steps[] = $mainStep; break; + // TODO PDF structures default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } diff --git a/lam/tests/lib/pdfstructTest.php b/lam/tests/lib/pdfstructTest.php index f74d480b..58933af4 100644 --- a/lam/tests/lib/pdfstructTest.php +++ b/lam/tests/lib/pdfstructTest.php @@ -181,6 +181,65 @@ class PdfStructTest extends TestCase { $this->assertEquals($expected, $data); } + /** + * Tests import in PDFEntrySection. + */ + public function testImportPDFEntrySection() { + $data = array( + 'title' => 'mytitle', + 'entries' => array('e1', 'e2') + ); + + $section = new PDFEntrySection(null); + $section->import($data); + + $this->assertEquals('mytitle', $section->getTitle()); + $entries = $section->getEntries(); + $this->assertEquals(2, sizeof($entries)); + $this->assertEquals('e1', ($entries[0]->getKey())); + $this->assertEquals('e2', ($entries[1]->getKey())); + } + + /** + * Tests the import in PDFStructure. + */ + public function testImportPDFStructure() { + $data = array( + 'title' => 'mytitle', + 'foldingMarks' => PDFStructure::FOLDING_STANDARD, + 'logo' => 'logo', + 'sections' => array( + array( + 'type' => 'text', + 'data' => 'textvalue' + ), + array( + 'type' => 'entry', + 'data' => array( + 'title' => 'etitle', + 'entries' => array('e1', 'e2') + ) + ), + ) + ); + + $structure = new PDFStructure(); + $structure->import($data); + + $this->assertEquals('mytitle', $structure->getTitle()); + $this->assertEquals(PDFStructure::FOLDING_STANDARD, $structure->getFoldingMarks()); + $this->assertEquals('logo', $structure->getLogo()); + $sections = $structure->getSections(); + $this->assertEquals(2, sizeof($sections)); + $this->assertTrue($sections[0] instanceof PDFTextSection); + $this->assertEquals('textvalue', $sections[0]->getText()); + $this->assertTrue($sections[1] instanceof PDFEntrySection); + $entries = $sections[1]->getEntries(); + $this->assertEquals(2, sizeof($entries)); + $this->assertEquals('e1', $entries[0]->getKey()); + $this->assertEquals('e2', $entries[1]->getKey()); + } + } ?> \ No newline at end of file diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index 27a5d37a..36a9ddf6 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -48,21 +48,33 @@ class ConfigDataExporterTest extends TestCase { 'group' => array('default' => array('key' => 'value')), ), ); + $pdfData = array( + 'profile1' => array('structures' => array( + 'user' => array( + 'default' => array('key' => 'value')) + )), + 'profile1' => array('structures' => array( + 'user' => array('default' => array('key' => 'value')), + 'group' => array('default' => array('key' => 'value')), + )), + ); $expectedJson = json_encode(array( 'mainConfig' => $mainData, 'certificates' => 'certs', 'serverProfiles' => $profileData, 'accountProfiles' => $accountProfileData, + 'pdfProfiles' => $pdfData, )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles', - '_getAccountProfiles')) + '_getAccountProfiles', '_getPdfProfiles')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); $exporter->method('_getCertificates')->willReturn('certs'); $exporter->method('_getServerProfiles')->willReturn($profileData); $exporter->method('_getAccountProfiles')->willReturn($accountProfileData); + $exporter->method('_getPdfProfiles')->willReturn($pdfData); $json = $exporter->exportAsJson(); From 0a72bc9635718307680d43050466915d08b371da Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 13 May 2020 20:47:12 +0200 Subject: [PATCH 23/29] config import and export --- lam/lib/html.inc | 1 + lam/lib/pdfstruct.inc | 9 ++-- lam/lib/persistence.inc | 63 ++++++++++++++++++++++- lam/templates/config/confImportExport.php | 12 +++-- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 8f196be9..bf6c515e 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -4777,6 +4777,7 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox { } // label text $text = new htmlSpan(new htmlOutputText($this->label)); + $text->setCSSClasses($this->cssClasses); $text->setOnclick('jQuery(\'#' . $this->name . '\').prop(\'checked\',!jQuery(\'#' . $this->name . '\').prop(\'checked\')); jQuery(\'#' . $this->name . '\').change();'); // input field $fieldGroup = new htmlGroup(); diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index 1df4e334..e80591de 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -118,7 +118,7 @@ function getAvailableLogos($serverProfileName) { * @param \LAM\TYPES\ConfiguredType $sourceType source type * @param string $sourceStructureName structure name * @param \LAM\TYPES\ConfiguredType $targetType target type - * @throws Exception + * @throws LAMException error during copy */ function copyStructure($sourceType, $sourceStructureName, $targetType) { if (!isValidPDFStructureName($sourceStructureName)) { @@ -427,7 +427,7 @@ class PDFStructureWriter { /** * Constructor. * - * @param $serverProfileName server profile name + * @param string $serverProfileName server profile name */ public function __construct($serverProfileName) { $this->serverProfileName = $serverProfileName; @@ -439,6 +439,7 @@ class PDFStructureWriter { * @param string $typeId type ID * @param string $name structure name * @param PDFStructure $structure structure + * @throws LAMException error during write */ public function write($typeId, $name, $structure) { $fileName = $this->getFileName($typeId, $name); @@ -452,6 +453,7 @@ class PDFStructureWriter { * @param string $typeId type ID * @param string $name structure name * @return string file name + * @throws LAMException file not valid or not writable */ protected function getFileName($typeId, $name) { if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/', $typeId)) { @@ -510,6 +512,7 @@ class PDFStructureWriter { * * @param string $xml XML * @param string $file file name + * @throws LAMException error during write */ protected function writeXML($xml, $file) { $handle = @fopen($file,'w'); @@ -713,7 +716,7 @@ class PDFTextSection { class PDFEntrySection { private $title; - private $entries; + private $entries = array(); /** * Constructor diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index edb400ab..849641aa 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -1,11 +1,14 @@ $profileData) { + $mainStep->addSubStep(new ImporterStep($profileName, 'pdfProfile_' . $profileName, $profileData)); + } + $steps[] = $mainStep; + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -243,6 +252,9 @@ class ConfigDataImporter { case 'accountProfiles': $this->importAccountProfiles($step); break; + case 'pdfProfiles': + $this->importPdfProfiles($step); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -327,6 +339,53 @@ class ConfigDataImporter { } } + /** + * Imports the PDF profiles. + * + * @param ImporterStep $step step + * @throws LAMException error during import + */ + private function importPdfProfiles($step) { + $failedProfiles = array(); + foreach ($step->getSubSteps() as $profileStep) { + if (!$profileStep->isActive()) { + continue; + } + $data = $profileStep->getValue(); + $serverProfileName = str_replace('pdfProfile_', '', $profileStep->getKey()); + if (isset($data['structures'])) { + $writer = new PDFStructureWriter($serverProfileName); + foreach ($data['structures'] as $typeId => $pdfProfiles) { + foreach ($pdfProfiles as $pdfProfileName => $pdfProfileData) { + $structure = new PDFStructure(); + $structure->import($pdfProfileData); + try { + $writer->write($typeId, $pdfProfileName, $structure); + } + catch (LAMException $e) { + logNewMessage('ERROR', $e->getMessage()); + $failedProfiles[] = $serverProfileName . ':' . $typeId . ':' . $pdfProfileName; + } + } + } + } + if (isset($data['logos'])) { + foreach ($data['logos'] as $logoFileName => $logoData) { + $tempFilePath = tempnam("/tmp", "lam"); + $tempFile = fopen($tempFilePath, "w"); + $logoBinary = base64_decode($logoData); + fwrite($tempFile, $logoBinary); + fclose($tempFile); + uploadPDFLogo($tempFilePath, $logoFileName, $serverProfileName); + unlink($tempFilePath); + } + } + } + if (!empty($failedProfiles)) { + throw new LAMException(_('Could not save PDF structure, access denied.'), implode(', ', $failedProfiles)); + } + } + } /** diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 57ba7fd4..5bf10110 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -189,10 +189,13 @@ printHeaderContents(_("Import and export configuration"), '../..'); $validUpload = false; $importSteps = array(); if (isset($_POST['importConfig'])) { - $handle = fopen($_FILES['import-file']['tmp_name'], "r"); - $data = fread($handle, 100000000); - fclose($handle); try { + if (empty($_FILES['import-file']['tmp_name'])) { + throw new LAMException('The file you uploaded is too large. Please check php.ini, upload_max_size setting'); + } + $handle = fopen($_FILES['import-file']['tmp_name'], "r"); + $data = fread($handle, 100000000); + fclose($handle); $importer = new ConfigDataImporter(); $importSteps = $importer->getPossibleImportSteps($data); $tmpFile = __DIR__ . '/../../tmp/internal/import_' . getRandomNumber() . '.tmp'; @@ -219,13 +222,14 @@ printHeaderContents(_("Import and export configuration"), '../..'); $stepKey = 'step_' . $importStep->getKey(); $stepCheckbox = new htmlResponsiveInputCheckbox($stepKey, true, $importStep->getLabel()); $stepCheckbox->setLabelAfterCheckbox(); + $stepCheckbox->setCSSClasses(array('bold')); $subStepIds = array(); $content->add($stepCheckbox, 12); $content->addVerticalSpacer('0.3rem'); foreach ($importStep->getSubSteps() as $subStep) { $subStepKey = 'step_' . $subStep->getKey(); $subStepIds[] = $subStepKey; - $subStepCheckbox = new htmlResponsiveInputCheckbox($subStepKey, true, ' - ' . $subStep->getLabel()); + $subStepCheckbox = new htmlResponsiveInputCheckbox($subStepKey, true, $subStep->getLabel()); $subStepCheckbox->setLabelAfterCheckbox(); $content->add($subStepCheckbox, 12); } From 5151d96592512ed4f6a7b5104b672899c888af64 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 16 May 2020 11:10:37 +0200 Subject: [PATCH 24/29] refactoring --- lam/lib/profiles.inc | 88 +++++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/lam/lib/profiles.inc b/lam/lib/profiles.inc index 6825843f..c09d2464 100644 --- a/lam/lib/profiles.inc +++ b/lam/lib/profiles.inc @@ -92,10 +92,25 @@ function loadAccountProfile($profile, $typeId, $serverProfileName) { logNewMessage(LOG_NOTICE, "Invalid account profile name: $serverProfileName:$profile:$typeId"); return false; } - $settings = array(); $file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId; - if (is_file($file)) { - $file = @fopen($file, "r"); + try { + return readAccountProfileFile($file); + } catch (LAMException $e) { + StatusMessage('ERROR', $e->getTitle(), $e->getMessage()); + } +} + +/** + * Reads an account profile from the given file name. + * + * @param string $fileName file name + * @return array hash array (attribute => value) + * @throws LAMException error reading file + */ +function readAccountProfileFile($fileName) { + $settings = array(); + if (is_file($fileName)) { + $file = @fopen($fileName, "r"); if ($file) { while (!feof($file)) { $line = fgets($file, 1024); @@ -114,15 +129,16 @@ function loadAccountProfile($profile, $typeId, $serverProfileName) { } } fclose($file); + return $settings; } else { - StatusMessage("ERROR", "", _("Unable to load profile!") . " " . $file); + throw new LAMException(_("Unable to load profile!"), $fileName); } } else { - StatusMessage("ERROR", "", _("Unable to load profile!") . " " . $file); + throw new LAMException(_("Unable to load profile!"), $fileName); } - return $settings; + return array(); } /** @@ -254,7 +270,34 @@ function copyAccountProfileToTemplates($sourceType, $sourceProfileName) { * Installs template profiles to the current server profile. */ function installProfileTemplates() { - $templatePath = dirname(__FILE__) . '/../config/templates/profiles'; + $allTemplates = getProfileTemplateNames(); + $basePath = dirname(__FILE__) . '/../config/profiles/' . $_SESSION['config']->getName(); + if (!file_exists($basePath)) { + mkdir($basePath, 0700, true); + } + $typeManager = new \LAM\TYPES\TypeManager(); + foreach ($typeManager->getConfiguredTypes() as $type) { + if (empty($allTemplates[$type->getScope()])) { + continue; + } + foreach ($allTemplates[$type->getScope()] as $templateName) { + $path = $basePath . '/' . $templateName . '.' . $type->getId(); + if (!is_file($path)) { + $template = getProfileTemplateFileName($type->getScope(), $templateName); + logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path); + @copy($template, $path); + } + } + } +} + +/** + * Returns a list of all global profile templates. + * + * @return array names (array('user' => array('default', 'extra'))) + */ +function getProfileTemplateNames() { + $templatePath = __DIR__ . '/../config/templates/profiles'; $templateDir = @dir($templatePath); $allTemplates = array(); if ($templateDir) { @@ -269,24 +312,17 @@ function installProfileTemplates() { $entry = $templateDir->read(); } } - $basePath = dirname(__FILE__) . '/../config/profiles/' . $_SESSION['config']->getName(); - if (!file_exists($basePath)) { - mkdir($basePath, 0700, true); - } - $typeManager = new \LAM\TYPES\TypeManager(); - foreach ($typeManager->getConfiguredTypes() as $type) { - if (empty($allTemplates[$type->getScope()])) { - continue; - } - foreach ($allTemplates[$type->getScope()] as $templateName) { - $path = $basePath . '/' . $templateName . '.' . $type->getId(); - if (!is_file($path)) { - $template = $templatePath . '/' . $templateName . '.' . $type->getScope(); - logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path); - @copy($template, $path); - } - } - } + return $allTemplates; +} + +/** + * Returns the file name of a global template. + * + * @param string $scope e.g. user + * @param string $name profile name + * @return string file name + */ +function getProfileTemplateFileName($scope, $name) { + return __DIR__ . '/../config/templates/profiles' . '/' . $name . '.' . $scope; } -?> From 32b5a14226ec53078ce45f4182900ba710173e45 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 22 May 2020 21:02:13 +0200 Subject: [PATCH 25/29] import and export account profile templates --- lam/lib/persistence.inc | 46 +++++++++++++ lam/lib/profiles.inc | 80 ++++++++++++++++++----- lam/templates/config/confImportExport.php | 7 +- lam/tests/lib/persistenceTest.php | 8 ++- 4 files changed, 122 insertions(+), 19 deletions(-) diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 849641aa..59274f7f 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -10,7 +10,10 @@ use function LAM\PDF\getAvailableLogos; use function LAM\PDF\getPDFStructures; use function LAM\PDF\uploadPDFLogo; use function LAM\PROFILES\getAccountProfiles; +use function LAM\PROFILES\getProfileTemplateNames; +use function LAM\PROFILES\installTemplateAccountProfile; use function LAM\PROFILES\loadAccountProfile; +use function LAM\PROFILES\loadTemplateAccountProfile; use function LAM\PROFILES\saveAccountProfile; /* @@ -51,6 +54,7 @@ class ConfigDataExporter { /** * Exports LAM's configuration data in JSON format. + * @throws LAMException error during export */ public function exportAsJson() { $mainCfg = $this->_getMainConfiguration(); @@ -64,6 +68,7 @@ class ConfigDataExporter { } $jsonData['serverProfiles'] = $this->_getServerProfiles($serverProfiles); $jsonData['accountProfiles'] = $this->_getAccountProfiles($serverProfiles); + $jsonData['accountProfileTemplates'] = $this->_getAccountProfileTemplates($serverProfiles); $jsonData['pdfProfiles'] = $this->_getPdfProfiles($serverProfiles); /** * TODO @@ -71,6 +76,7 @@ class ConfigDataExporter { * pdf/account templates /config/templates * self service profiles * webauthn + * cron job runs */ return json_encode($jsonData); } @@ -138,6 +144,25 @@ class ConfigDataExporter { return $data; } + /** + * Returns the content of the account profile templates. + * + * @param array $serverProfiles list of server profiles (name => object) + * @return array $data + * @throws LAMException error reading template + */ + public function _getAccountProfileTemplates($serverProfiles) { + $data = array(); + $accountProfileTemplateNames = getProfileTemplateNames(); + foreach ($accountProfileTemplateNames as $scope => $templateNames) { + foreach ($templateNames as $templateName) { + $accountProfileTemplate = loadTemplateAccountProfile($templateName, $scope); + $data[$scope][$templateName] = $accountProfileTemplate; + } + } + return $data; + } + /** * Returns the content of the PDF profiles. * @@ -210,6 +235,9 @@ class ConfigDataImporter { } $steps[] = $mainStep; break; + case 'accountProfileTemplates': + $steps[] = new ImporterStep(_('Account profiles') . ' - ' . _('Global templates'), 'accountProfileTemplates', $value); + break; case 'pdfProfiles': $mainStep = new ImporterStep(_('PDF structures'), 'pdfProfiles', $value); foreach ($value as $profileName => $profileData) { @@ -252,6 +280,9 @@ class ConfigDataImporter { case 'accountProfiles': $this->importAccountProfiles($step); break; + case 'accountProfileTemplates': + $this->importAccountProfileTemplates($step); + break; case 'pdfProfiles': $this->importPdfProfiles($step); break; @@ -339,6 +370,21 @@ class ConfigDataImporter { } } + /** + * Imports the account profile templates. + * + * @param ImporterStep $step step + * @throws LAMException error during import + */ + private function importAccountProfileTemplates($step) { + $data = $step->getValue(); + foreach ($data as $typeId => $accountProfileTemplates) { + foreach ($accountProfileTemplates as $accountProfileTemplateName => $accountProfileData) { + installTemplateAccountProfile($typeId, $accountProfileTemplateName, $accountProfileData); + } + } + } + /** * Imports the PDF profiles. * diff --git a/lam/lib/profiles.inc b/lam/lib/profiles.inc index c09d2464..a45fe0f1 100644 --- a/lam/lib/profiles.inc +++ b/lam/lib/profiles.inc @@ -1,8 +1,8 @@ read(); while ($entry){ @@ -90,7 +89,7 @@ function profileExists($name, $typeId) { function loadAccountProfile($profile, $typeId, $serverProfileName) { if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId)) { logNewMessage(LOG_NOTICE, "Invalid account profile name: $serverProfileName:$profile:$typeId"); - return false; + return array(); } $file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId; try { @@ -98,6 +97,7 @@ function loadAccountProfile($profile, $typeId, $serverProfileName) { } catch (LAMException $e) { StatusMessage('ERROR', $e->getTitle(), $e->getMessage()); } + return array(); } /** @@ -118,7 +118,6 @@ function readAccountProfileFile($fileName) { continue; // ignore comments } // search keywords - $parts = array(); $parts = explode(": ", $line); if (sizeof($parts) == 2) { $option = $parts[0]; @@ -138,7 +137,6 @@ function readAccountProfileFile($fileName) { else { throw new LAMException(_("Unable to load profile!"), $fileName); } - return array(); } /** @@ -154,10 +152,10 @@ function readAccountProfileFile($fileName) { */ function saveAccountProfile($attributes, $profile, $typeId, $serverProfile) { // check profile name and type id - $typeManager = new \LAM\TYPES\TypeManager($serverProfile); + $typeManager = new TypeManager($serverProfile); $type = $typeManager->getConfiguredType($typeId); if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) { - logNewMessage(LOG_NOTICE, 'Invalid account profile name', $profile . ':' . $typeId); + logNewMessage(LOG_NOTICE, 'Invalid account profile name: ' . $profile . ':' . $typeId); return false; } if (!is_array($attributes)) { @@ -165,13 +163,24 @@ function saveAccountProfile($attributes, $profile, $typeId, $serverProfile) { return false; } $path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfile->getName() . '/' . $profile . "." . $typeId; - $file = @fopen($path, "w"); + return writeProfileDataToFile($path, $attributes); +} + +/** + * Writes the profile data to the given file. + * + * @param string $fileName file name + * @param array $data profile data + * @return bool writing was ok + */ +function writeProfileDataToFile($fileName, $data) { + $file = @fopen($fileName, "w"); if ($file) { - // write attributes - $keys = array_keys($attributes); + // write attributes + $keys = array_keys($data); for ($i = 0; $i < sizeof($keys); $i++) { - if (isset($attributes[$keys[$i]])) { - $line = $keys[$i] . ": " . implode("+::+", $attributes[$keys[$i]]) . "\n"; + if (isset($data[$keys[$i]])) { + $line = $keys[$i] . ": " . implode("+::+", $data[$keys[$i]]) . "\n"; } else { $line = $keys[$i] . ": \n"; @@ -182,7 +191,7 @@ function saveAccountProfile($attributes, $profile, $typeId, $serverProfile) { fclose($file); } else { - logNewMessage(LOG_NOTICE, 'Unable to open account profile file: ' . $path); + logNewMessage(LOG_NOTICE, 'Unable to open account profile file: ' . $fileName); return false; } return true; @@ -199,7 +208,7 @@ function delAccountProfile($file, $typeId) { if (!isLoggedIn()) { return false; } - $typeManager = new \LAM\TYPES\TypeManager(); + $typeManager = new TypeManager(); $type = $typeManager->getConfiguredType($typeId); if (!isValidProfileName($file) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) { return false; @@ -226,7 +235,7 @@ function isValidProfileName($name) { * @param \LAM\TYPES\ConfiguredType $sourceType source type * @param string $sourceProfileName profile name * @param \LAM\TYPES\ConfiguredType $targetType target type - * @throws Exception + * @throws LAMException error during copy */ function copyAccountProfile($sourceType, $sourceProfileName, $targetType) { if (!isValidProfileName($sourceProfileName)) { @@ -249,7 +258,7 @@ function copyAccountProfile($sourceType, $sourceProfileName, $targetType) { * * @param \LAM\TYPES\ConfiguredType $sourceType source type * @param string $sourceProfileName profile name - * @throws Exception + * @throws LAMException error during copy */ function copyAccountProfileToTemplates($sourceType, $sourceProfileName) { if (!isValidProfileName($sourceProfileName)) { @@ -275,7 +284,7 @@ function installProfileTemplates() { if (!file_exists($basePath)) { mkdir($basePath, 0700, true); } - $typeManager = new \LAM\TYPES\TypeManager(); + $typeManager = new TypeManager(); foreach ($typeManager->getConfiguredTypes() as $type) { if (empty($allTemplates[$type->getScope()])) { continue; @@ -326,3 +335,40 @@ function getProfileTemplateFileName($scope, $name) { return __DIR__ . '/../config/templates/profiles' . '/' . $name . '.' . $scope; } +/** + * Loads a template profile of the given account scope. + * + * @param string $profile name of the profile (without . extension) + * @param string $scope account type + * @return array hash array (attribute => value) + * @throws LAMException error reading profile template + */ +function loadTemplateAccountProfile($profile, $scope) { + if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $scope)) { + logNewMessage(LOG_NOTICE, "Invalid account profile name: $profile:$scope"); + return array(); + } + $fileName = getProfileTemplateFileName($scope, $profile); + return readAccountProfileFile($fileName); +} + +/** + * Installs a single template from the given data. + * + * @param string $scope account type (e.g. user) + * @param string $name template name + * @param array $data profile data + * @throws LAMException error saving file + */ +function installTemplateAccountProfile($scope, $name, $data) { + if (!isValidProfileName($name) || !preg_match("/^[a-z0-9_]+$/i", $scope)) { + logNewMessage(LOG_NOTICE, "Invalid account profile name: $name:$scope"); + return; + } + $fileName = getProfileTemplateFileName($scope, $name); + $success = writeProfileDataToFile($fileName, $data); + if (!$success) { + throw new LAMException('Unable to write account profile template: ' . $fileName); + } +} + diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 5bf10110..182ae2a3 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -69,7 +69,12 @@ if (isset($_POST['exportConfig']) && $cfg->checkPassword($_SESSION["mainconf_pas header('Content-Type: application/json; charset=utf-8'); header('Content-disposition: attachment; filename=lam-config.json'); } - echo $exporter->exportAsJson(); + try { + echo $exporter->exportAsJson(); + } + catch (LAMException $e) { + logNewMessage('ERROR', $e->getTitle() . ' ' . $e->getMessage()); + } exit; } diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index 36a9ddf6..3d6eb5c1 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -48,6 +48,10 @@ class ConfigDataExporterTest extends TestCase { 'group' => array('default' => array('key' => 'value')), ), ); + $accountProfileTemplateData = array( + 'user' => array('default' => array('key' => 'value')), + 'group' => array('default' => array('key' => 'value')), + ); $pdfData = array( 'profile1' => array('structures' => array( 'user' => array( @@ -63,17 +67,19 @@ class ConfigDataExporterTest extends TestCase { 'certificates' => 'certs', 'serverProfiles' => $profileData, 'accountProfiles' => $accountProfileData, + 'accountProfileTemplates' => $accountProfileTemplateData, 'pdfProfiles' => $pdfData, )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles', - '_getAccountProfiles', '_getPdfProfiles')) + '_getAccountProfiles', '_getAccountProfileTemplates', '_getPdfProfiles')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); $exporter->method('_getCertificates')->willReturn('certs'); $exporter->method('_getServerProfiles')->willReturn($profileData); $exporter->method('_getAccountProfiles')->willReturn($accountProfileData); + $exporter->method('_getAccountProfileTemplates')->willReturn($accountProfileTemplateData); $exporter->method('_getPdfProfiles')->willReturn($pdfData); $json = $exporter->exportAsJson(); From 7bd799bee3fe5d522c40f6b9aeba274c033e8008 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 30 May 2020 12:35:22 +0200 Subject: [PATCH 26/29] PDF global templates export --- lam/lib/html.inc | 9 ++ lam/lib/pdfstruct.inc | 116 +++++++++++++++++----- lam/lib/persistence.inc | 88 +++++++++++++++- lam/lib/profiles.inc | 2 +- lam/templates/config/confImportExport.php | 1 + 5 files changed, 184 insertions(+), 32 deletions(-) diff --git a/lam/lib/html.inc b/lam/lib/html.inc index bf6c515e..21fb7b7d 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -3082,6 +3082,15 @@ class htmlStatusMessage extends htmlElement { return array(); } + /** + * Returns the message type. + * + * @return String type + */ + public function getType() { + return $this->type; + } + } /** diff --git a/lam/lib/pdfstruct.inc b/lam/lib/pdfstruct.inc index e80591de..ef73e703 100644 --- a/lam/lib/pdfstruct.inc +++ b/lam/lib/pdfstruct.inc @@ -34,6 +34,11 @@ use \LAM\ImageUtils\ImageManipulationFactory; /** LAM configuration */ include_once(__DIR__ . "/config.inc"); +/** + * Use as server profile name to manage global templates. + */ +const GLOBAL_PROFILE = '__GLOBAL__'; + /** LDAP object */ include_once(__DIR__ . "/ldap.inc"); @@ -164,12 +169,15 @@ function copyStructureToTemplates($sourceType, $sourceName) { * @param String $file full path of temporary file * @param String $name file name * @param string $serverProfileName server profile name - * @return StatusMessage status message to display + * @return htmlStatusMessage status message to display */ function uploadPDFLogo($file, $name, $serverProfileName) { if (!preg_match('/[a-zA-Z0-9_-]+\\.(png)|(jpg)/i', $name)) { return new htmlStatusMessage('ERROR', _('Unable to upload logo file.'), _('The file name must end with ".png" or ".jpg".')); } + if ($serverProfileName === GLOBAL_PROFILE) { + $serverProfileName = '../templates/pdf'; + } $dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/'; $success = copy($file, $dirPath . '/' . $name); if ($success) { @@ -242,21 +250,8 @@ function isValidPDFStructureName($name) { * Installs template structures to the current server profile. */ function installPDFTemplates() { - $templatePath = dirname(__FILE__) . '/../config/templates/pdf'; - $templateDir = @dir($templatePath); - $allTemplates = array(); - if ($templateDir) { - $entry = $templateDir->read(); - while ($entry){ - $parts = explode('.', $entry); - if ((strlen($entry) > 3) && (sizeof($parts) == 3)) { - $name = $parts[0]; - $scope = $parts[1]; - $allTemplates[$scope][] = $name; - } - $entry = $templateDir->read(); - } - } + $templatePath = __DIR__ . '/../config/templates/pdf'; + $allTemplates = getPdfTemplateNames(); $basePath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName(); if (!file_exists($basePath)) { mkdir($basePath, 0700, true); @@ -278,20 +273,75 @@ function installPDFTemplates() { if (!file_exists($basePath . '/logos')) { mkdir($basePath . '/logos'); } - $templatePath = dirname(__FILE__) . '/../config/templates/pdf/logos'; + $logos = getPdfTemplateLogoNames(); + foreach ($logos as $logo) { + $path = $basePath . '/logos/' . $logo; + $template = $templatePath . '/logos/' . $logo; + if (!is_file($path)) { + logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path); + @copy($template, $path); + } + } +} + +/** + * Returns all PDF template names. + * + * @return array names (array('user' => array('default'))) + */ +function getPdfTemplateNames() { + $templatePath = __DIR__ . '/../config/templates/pdf'; $templateDir = @dir($templatePath); + $allTemplates = array(); if ($templateDir) { $entry = $templateDir->read(); while ($entry){ - $path = $basePath . '/logos/' . $entry; - if ((strpos($entry, '.') !== 0) && !is_file($path)) { - $template = $templatePath . '/' . $entry; - logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path); - @copy($template, $path); + $parts = explode('.', $entry); + if ((strlen($entry) > 3) && (sizeof($parts) == 3)) { + $name = $parts[0]; + $scope = $parts[1]; + $allTemplates[$scope][] = $name; } $entry = $templateDir->read(); } } + return $allTemplates; +} + +/** + * Returns all PDF template logo names. + * + * @return array names (array('user' => array('default.png'))) + */ +function getPdfTemplateLogoNames() { + $templatePath = __DIR__ . '/../config/templates/pdf/logos'; + $templateDir = @dir($templatePath); + $logos = array(); + if ($templateDir) { + $entry = $templateDir->read(); + while ($entry){ + if ((strpos($entry, '.') !== 0) && is_file($templatePath . '/' . $entry)) { + $logos[] = $entry; + } + $entry = $templateDir->read(); + } + } + return $logos; +} + +/** + * Returns the binary data of the PDF template logo. + * + * @param string $name file name (without path) + * @return string binary + */ +function getPdfTemplateLogoBinary($name) { + $templatePath = __DIR__ . '/../config/templates/pdf/logos'; + $fileName = $templatePath . '/' . $name; + $handle = fopen($fileName, 'r'); + $logoBinary = fread($handle, 100000000); + fclose($handle); + return $logoBinary; } /** @@ -309,7 +359,12 @@ class PDFStructureReader { * @param $serverProfileName server profile name */ public function __construct($serverProfileName) { - $this->serverProfileName = $serverProfileName; + if ($serverProfileName === GLOBAL_PROFILE) { + $this->serverProfileName = '../templates/pdf'; + } + else { + $this->serverProfileName = $serverProfileName; + } } /** @@ -345,6 +400,7 @@ class PDFStructureReader { * @return PDFStructure structure */ private function readPDFFile($file) { + logNewMessage(LOG_DEBUG, $file); $xml = new \XMLReader(); $xml->open($file); $structure = new PDFStructure(); @@ -430,7 +486,12 @@ class PDFStructureWriter { * @param string $serverProfileName server profile name */ public function __construct($serverProfileName) { - $this->serverProfileName = $serverProfileName; + if ($serverProfileName === GLOBAL_PROFILE) { + $this->serverProfileName = '../templates/pdf'; + } + else { + $this->serverProfileName = $serverProfileName; + } } /** @@ -460,10 +521,11 @@ class PDFStructureWriter { throw new \LAMException(_('PDF structure name not valid'), _('The name for that PDF-structure you submitted is not valid. A valid name must consist of the following characters: \'a-z\',\'A-Z\',\'0-9\',\'_\',\'-\'.')); } - if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName)) { - throw new \LAMException(_('Could not save PDF structure, access denied.')); + $baseDir = __DIR__ . '/../config/pdf/' . $this->serverProfileName; + if(!is_writable($baseDir)) { + throw new \LAMException(_('Could not save PDF structure, access denied to ' . $baseDir . '.')); } - return dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName . '/' . $name . '.' . $typeId . '.xml'; + return $baseDir . '/' . $name . '.' . $typeId . '.xml'; } /** diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 59274f7f..5ad8b67d 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -8,6 +8,9 @@ use LAMConfig; use LAMException; use function LAM\PDF\getAvailableLogos; use function LAM\PDF\getPDFStructures; +use function LAM\PDF\getPdfTemplateLogoBinary; +use function LAM\PDF\getPdfTemplateLogoNames; +use function LAM\PDF\getPdfTemplateNames; use function LAM\PDF\uploadPDFLogo; use function LAM\PROFILES\getAccountProfiles; use function LAM\PROFILES\getProfileTemplateNames; @@ -68,8 +71,9 @@ class ConfigDataExporter { } $jsonData['serverProfiles'] = $this->_getServerProfiles($serverProfiles); $jsonData['accountProfiles'] = $this->_getAccountProfiles($serverProfiles); - $jsonData['accountProfileTemplates'] = $this->_getAccountProfileTemplates($serverProfiles); + $jsonData['accountProfileTemplates'] = $this->_getAccountProfileTemplates(); $jsonData['pdfProfiles'] = $this->_getPdfProfiles($serverProfiles); + $jsonData['pdfProfileTemplates'] = $this->_getPdfProfileTemplates(); /** * TODO * @@ -147,11 +151,10 @@ class ConfigDataExporter { /** * Returns the content of the account profile templates. * - * @param array $serverProfiles list of server profiles (name => object) * @return array $data * @throws LAMException error reading template */ - public function _getAccountProfileTemplates($serverProfiles) { + public function _getAccountProfileTemplates() { $data = array(); $accountProfileTemplateNames = getProfileTemplateNames(); foreach ($accountProfileTemplateNames as $scope => $templateNames) { @@ -193,6 +196,29 @@ class ConfigDataExporter { return $data; } + /** + * Returns the content of the account profile templates. + * + * @return array $data + * @throws LAMException error reading template + */ + public function _getPdfProfileTemplates() { + $data = array(); + $pdfTemplateNames = getPdfTemplateNames(); + $reader = new PDFStructureReader(\LAM\PDF\GLOBAL_PROFILE); + foreach ($pdfTemplateNames as $scope => $templateNames) { + foreach ($templateNames as $templateName) { + $pdfStructure = $reader->read($scope, $templateName); + $data['structures'][$scope][$templateName] = $pdfStructure->export(); + } + } + $logoNames = getPdfTemplateLogoNames(); + foreach ($logoNames as $logoName) { + $data['logos'][$logoName] = base64_encode(getPdfTemplateLogoBinary($logoName)); + } + return $data; + } + } /** @@ -245,6 +271,9 @@ class ConfigDataImporter { } $steps[] = $mainStep; break; + case 'pdfProfileTemplates': + $steps[] = new ImporterStep(_('PDF structures') . ' - ' . _('Global templates'), 'pdfProfileTemplates', $value); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -286,6 +315,9 @@ class ConfigDataImporter { case 'pdfProfiles': $this->importPdfProfiles($step); break; + case 'pdfProfileTemplates': + $this->importPdfProfileTemplates($step); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -409,7 +441,7 @@ class ConfigDataImporter { $writer->write($typeId, $pdfProfileName, $structure); } catch (LAMException $e) { - logNewMessage('ERROR', $e->getMessage()); + logNewMessage(LOG_ERR, $e->getTitle() . ' ' . $e->getMessage()); $failedProfiles[] = $serverProfileName . ':' . $typeId . ':' . $pdfProfileName; } } @@ -432,6 +464,54 @@ class ConfigDataImporter { } } + /** + * Imports the PDF profile templates. + * + * @param ImporterStep $step step + * @throws LAMException error during import + */ + private function importPdfProfileTemplates($step) { + $failedNames = array(); + $data = $step->getValue(); + if (isset($data['structures'])) { + $writer = new PDFStructureWriter(\LAM\PDF\GLOBAL_PROFILE); + foreach ($data['structures'] as $typeId => $pdfProfiles) { + foreach ($pdfProfiles as $pdfProfileName => $pdfProfileData) { + $structure = new PDFStructure(); + $structure->import($pdfProfileData); + try { + $writer->write($typeId, $pdfProfileName, $structure); + } + catch (LAMException $e) { + $failedNames[] = $typeId . ':' . $pdfProfileName; + logNewMessage(LOG_ERR, $e->getTitle() . ' ' . $e->getMessage()); + } + } + } + } + $failedLogos = array(); + if (isset($data['logos'])) { + foreach ($data['logos'] as $logoFileName => $logoData) { + $tempFilePath = tempnam("/tmp", "lam"); + $tempFile = fopen($tempFilePath, "w"); + $logoBinary = base64_decode($logoData); + fwrite($tempFile, $logoBinary); + fclose($tempFile); + $message = uploadPDFLogo($tempFilePath, $logoFileName, \LAM\PDF\GLOBAL_PROFILE); + unlink($tempFilePath); + if ($message->getType() === 'ERROR') { + $failedLogos[] = $logoFileName; + } + } + } + if (!empty($failedNames)) { + throw new LAMException(_('Could not save PDF structure, access denied.'), implode(', ', $failedNames)); + } + if (!empty($failedLogos)) { + throw new LAMException(_('Unable to upload logo file.'), implode(', ', $failedLogos)); + } + } + } /** diff --git a/lam/lib/profiles.inc b/lam/lib/profiles.inc index a45fe0f1..3a9711b1 100644 --- a/lam/lib/profiles.inc +++ b/lam/lib/profiles.inc @@ -114,7 +114,7 @@ function readAccountProfileFile($fileName) { if ($file) { while (!feof($file)) { $line = fgets($file, 1024); - if (($line == "\n")||($line[0] == "#")) { + if (($line == '') || ($line == "\n") || ($line[0] == "#")) { continue; // ignore comments } // search keywords diff --git a/lam/templates/config/confImportExport.php b/lam/templates/config/confImportExport.php index 182ae2a3..ea2e6677 100644 --- a/lam/templates/config/confImportExport.php +++ b/lam/templates/config/confImportExport.php @@ -262,6 +262,7 @@ printHeaderContents(_("Import and export configuration"), '../..'); $importer->runImport($importSteps); unlink($_SESSION['configImportFile']); $content->add(new htmlStatusMessage('INFO', _('Configuration import ended successful.')), 12); + $content->add(new htmlButton('importNew', _('New import')), 12); } catch (LAMException $e) { $content->add(new htmlStatusMessage('ERROR', htmlspecialchars($e->getTitle()), htmlspecialchars($e->getMessage())), 12); From aa43b4721bc3b884bc730522cd53297981e7c876 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 1 Jun 2020 10:34:34 +0200 Subject: [PATCH 27/29] store self service profiles as JSON --- composer.json | 3 ++- lam/lib/persistence.inc | 1 - lam/lib/selfService.inc | 36 +++++++++++++++++++++++++++++-- lam/tests/lib/selfServiceTest.php | 21 ++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index f2015f0d..7c61b448 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "squizlabs/php_codesniffer" : "3.4.0" }, "require": { - "ext-ldap": "*" + "ext-ldap": "*", + "ext-json": "*" } } diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index 5ad8b67d..b5f32782 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -77,7 +77,6 @@ class ConfigDataExporter { /** * TODO * - * pdf/account templates /config/templates * self service profiles * webauthn * cron job runs diff --git a/lam/lib/selfService.inc b/lam/lib/selfService.inc index 7d8b63c9..c96e0c82 100644 --- a/lam/lib/selfService.inc +++ b/lam/lib/selfService.inc @@ -213,7 +213,13 @@ function loadSelfServiceProfile($name, $scope) { $file = @fopen($file, "r"); if ($file) { $data = fread($file, 10000000); - $profile = unserialize($data); + $profileData = @json_decode($data, true); + if ($profileData === null) { + $profile = unserialize($data); + } + else { + $profile = selfServiceProfile::import($profileData); + } fclose($file); } else { @@ -249,7 +255,7 @@ function saveSelfServiceProfile($name, $scope, $profile) { $file = @fopen($path, "w"); if ($file) { // write settings to file - fputs($file, serialize($profile)); + fputs($file, json_encode($profile->export())); // close file fclose($file); } @@ -521,6 +527,32 @@ class selfServiceProfile { $this->baseUrl = ''; } + /** + * Converts the export data back to a self service profile. + * + * @param array $data export data + * @return selfServiceProfile profile + */ + public static function import($data) { + $profile = new selfServiceProfile(); + $vars = get_class_vars(selfServiceProfile::class); + foreach ($data as $key => $value) { + if (in_array($key, $vars)) { + $profile->$key = $value; + } + } + return $profile; + } + + /** + * Returns a representation of the self service profile. + * + * @return array self service profile data + */ + public function export() { + return json_decode(json_encode($this), true); + } + /** * Returns the server's base URL (e.g. https://www.example.com). * diff --git a/lam/tests/lib/selfServiceTest.php b/lam/tests/lib/selfServiceTest.php index 67e3ce23..b7e65859 100644 --- a/lam/tests/lib/selfServiceTest.php +++ b/lam/tests/lib/selfServiceTest.php @@ -3,7 +3,7 @@ use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2019 Roland Gruber + Copyright (C) 2019 - 2020 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ use PHPUnit\Framework\TestCase; */ -require_once 'lam/lib/selfService.inc'; +require_once __DIR__ . '/../../lib/selfService.inc'; /** * Checks selfServiceProfile. @@ -43,6 +43,23 @@ class selfServiceProfileTest extends TestCase { $this->assertEquals('https://test.com', $profile->getBaseUrl()); } + public function testImportExport() { + $profile = new selfServiceProfile(); + $moduleSettings = array('x1' => 'y1', 'x2' => 'y2'); + $profile->moduleSettings = $moduleSettings; + $profile->baseColor = 'green'; + $profile->language = 'de_DE@UTF8'; + $profile->pageHeader = 'header'; + + $export = $profile->export(); + $importedProfile = selfServiceProfile::import($export); + + $this->assertEquals($moduleSettings, $importedProfile->moduleSettings); + $this->assertEquals('green', $importedProfile->baseColor); + $this->assertEquals('de_DE@UTF8', $importedProfile->language); + $this->assertEquals('header', $importedProfile->pageHeader); + } + } ?> \ No newline at end of file From f091b653b8bb10ed46fe43df7b4d9a60e4330c9b Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 1 Jun 2020 10:52:55 +0200 Subject: [PATCH 28/29] store self service profiles as JSON --- lam/lib/persistence.inc | 52 ++++++++++++++++++++++++++++++++++++++++- lam/lib/selfService.inc | 4 ++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index b5f32782..f83079d8 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -6,6 +6,7 @@ use LAM\PDF\PDFStructureWriter; use LAMCfgMain; use LAMConfig; use LAMException; +use selfServiceProfile; use function LAM\PDF\getAvailableLogos; use function LAM\PDF\getPDFStructures; use function LAM\PDF\getPdfTemplateLogoBinary; @@ -74,10 +75,10 @@ class ConfigDataExporter { $jsonData['accountProfileTemplates'] = $this->_getAccountProfileTemplates(); $jsonData['pdfProfiles'] = $this->_getPdfProfiles($serverProfiles); $jsonData['pdfProfileTemplates'] = $this->_getPdfProfileTemplates(); + $jsonData['selfServiceProfiles'] = $this->_getSelfServiceProfiles(); /** * TODO * - * self service profiles * webauthn * cron job runs */ @@ -218,6 +219,26 @@ class ConfigDataExporter { return $data; } + /** + * Returns the content of the self service profiles. + * + * @return array data + */ + public function _getSelfServiceProfiles() { + $data = array(); + $profileTypes = getSelfServiceProfiles(); + foreach ($profileTypes as $profileType => $profileNames) { + foreach ($profileNames as $profileName) { + $profile = loadSelfServiceProfile($profileName, $profileType); + if ($profile === false) { + continue; + } + $data[$profileType][$profileName] = $profile->export(); + } + } + return $data; + } + } /** @@ -273,6 +294,9 @@ class ConfigDataImporter { case 'pdfProfileTemplates': $steps[] = new ImporterStep(_('PDF structures') . ' - ' . _('Global templates'), 'pdfProfileTemplates', $value); break; + case 'selfServiceProfiles': + $steps[] = new ImporterStep(_('Self service profiles'), 'selfServiceProfiles', $value); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -317,6 +341,9 @@ class ConfigDataImporter { case 'pdfProfileTemplates': $this->importPdfProfileTemplates($step); break; + case 'selfServiceProfiles': + $this->importSelfServiceProfiles($step); + break; default: logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key); } @@ -511,6 +538,29 @@ class ConfigDataImporter { } } + /** + * Imports the self service profiles. + * + * @param ImporterStep $step importer step + * @throws LAMException error saving profiles + */ + private function importSelfServiceProfiles($step) { + $failedNames = array(); + $data = $step->getValue(); + foreach ($data as $typeId => $profileData) { + foreach ($profileData as $profileName => $currentProfileData) { + $profile = selfServiceProfile::import($currentProfileData); + $result = saveSelfServiceProfile($profileName, $typeId, $profile); + if (!$result) { + $failedNames[] = $profileName; + } + } + } + if (!empty($failedNames)) { + throw new LAMException(_('Unable to save profile!'), implode(', ', $failedNames)); + } + } + } /** diff --git a/lam/lib/selfService.inc b/lam/lib/selfService.inc index c96e0c82..201bd536 100644 --- a/lam/lib/selfService.inc +++ b/lam/lib/selfService.inc @@ -170,7 +170,7 @@ function checkSelfServiceOptions($scope, $fields, $attributes, $passwordChangeOn /** * Returns a list of all available self service profiles (without .conf) * -* @return array profile names (array( => array(, , ...))) +* @return array profile names (array('account type' => array('profile1', 'profile2'))) */ function getSelfServiceProfiles() { $types = LAM\TYPES\getTypes(); @@ -201,7 +201,7 @@ function getSelfServiceProfiles() { * * @param string $name profile name * @param string $scope account type -* @return selfServiceProfile true if file was readable +* @return false|selfServiceProfile profile or false if file was not readable */ function loadSelfServiceProfile($name, $scope) { if (!preg_match("/^[0-9a-z _-]+$/i", $name) || !preg_match("/^[0-9a-z _-]+$/i", $scope)) { From ee75385e7daf9aa93f3a49373f465f71ce521581 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 17 Jun 2020 12:57:18 +0200 Subject: [PATCH 29/29] fixed tests --- lam/lib/persistence.inc | 1 - lam/tests/lib/persistenceTest.php | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lam/lib/persistence.inc b/lam/lib/persistence.inc index f83079d8..2c720b57 100644 --- a/lam/lib/persistence.inc +++ b/lam/lib/persistence.inc @@ -80,7 +80,6 @@ class ConfigDataExporter { * TODO * * webauthn - * cron job runs */ return json_encode($jsonData); } diff --git a/lam/tests/lib/persistenceTest.php b/lam/tests/lib/persistenceTest.php index 3d6eb5c1..a3ce3f3b 100644 --- a/lam/tests/lib/persistenceTest.php +++ b/lam/tests/lib/persistenceTest.php @@ -62,6 +62,14 @@ class ConfigDataExporterTest extends TestCase { 'group' => array('default' => array('key' => 'value')), )), ); + $pdfTemplateData = array( + 'user' => array('default' => array('key' => 'value')), + 'group' => array('default' => array('key' => 'value')), + ); + $selfServiceData = array( + 'profile1' => array('key' => 'value'), + 'profile2' => array('key' => 'value'), + ); $expectedJson = json_encode(array( 'mainConfig' => $mainData, 'certificates' => 'certs', @@ -69,11 +77,14 @@ class ConfigDataExporterTest extends TestCase { 'accountProfiles' => $accountProfileData, 'accountProfileTemplates' => $accountProfileTemplateData, 'pdfProfiles' => $pdfData, + 'pdfProfileTemplates' => $pdfTemplateData, + 'selfServiceProfiles' => $selfServiceData, )); $exporter = $this->getMockBuilder('\LAM\PERSISTENCE\ConfigDataExporter') ->setMethods(array('_getMainConfigData', '_getCertificates', '_getServerProfiles', - '_getAccountProfiles', '_getAccountProfileTemplates', '_getPdfProfiles')) + '_getAccountProfiles', '_getAccountProfileTemplates', '_getPdfProfiles', + '_getPdfProfileTemplates', '_getSelfServiceProfiles')) ->getMock(); $exporter->method('_getMainConfigData')->willReturn($mainData); $exporter->method('_getCertificates')->willReturn('certs'); @@ -81,6 +92,8 @@ class ConfigDataExporterTest extends TestCase { $exporter->method('_getAccountProfiles')->willReturn($accountProfileData); $exporter->method('_getAccountProfileTemplates')->willReturn($accountProfileTemplateData); $exporter->method('_getPdfProfiles')->willReturn($pdfData); + $exporter->method('_getPdfProfileTemplates')->willReturn($pdfTemplateData); + $exporter->method('_getSelfServiceProfiles')->willReturn($selfServiceData); $json = $exporter->exportAsJson();