From daf61911f0235765bff6e0b678a0347e3f3358a2 Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Mon, 23 Sep 2024 12:50:19 +0100 Subject: [PATCH 01/31] ATAK Module Provide ATAK user management --- .DS_Store | Bin 0 -> 8196 bytes lam/.DS_Store | Bin 0 -> 6148 bytes lam/graphics/TakLogo.png | Bin 0 -> 1273 bytes lam/lib/.DS_Store | Bin 0 -> 10244 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 lam/.DS_Store create mode 100644 lam/graphics/TakLogo.png create mode 100644 lam/lib/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7fb6037173ebdd1db5ae721e1dc79b57c0f7125c GIT binary patch literal 8196 zcmeHMU2GIp6u#fIz>E{y;XTjZxmn{G>g_$jly15B9CEIYGX zuxVU1@&M|iO3;}2VEhX{D2hJ#VvL#?6A_6Lj3g$$=!-^pF)^MycNSx zFau!*!VH8N2s037;AY4G?b*D^H`w=uHXOqYgc-OcGr-q}D0NN~0WAyaFC8@aM*yPy z2;et**7yU_Xh0JIEelE-Y7nkOg)4$12827wqk&x_pk+aYJ40~zK=5V+ClvT^r}?A4 zIYUCwa11jLX5jh^@bal*7Sq{SX8hXoyJfmwJbn&RRrRddbL2U)D(_0~cgEA6?iJmn zUf998e#fygm1jM(-89BWwfdA}d%9^G1>eEU8Dz3^(6n>Tc+x4_IoJ113x%x6%Ba>b zF|oF-BNn}XRr_QtI?=Hz9*efEZl9b~sXh3q`T*AjaFNy)~gE#OQvgPEMs8Y z%z68VbvJ9+In&-ZLiw~kbFXO>lv>1;ep??i&emzQH4V2%B16TDd9Y}DhqV@27IQU4 zqKbA(-kGCd9(8iuGv{j}O_4igc?XxL3I>frtS*b(qiDL^?^0-DikNzzqPa@{0s6SY zb)~vm)^f^#rDqIFh}zY)vevB(mPBMiSg$@LD??dlY>a-iD`XExI%Vy1c{hLYR^9b_ z@`g?C<{_k-B9UP(@66?lTo6HIQ)G**osmb}{d&RZ87IVvi8e*H$+G_yNzP~5kTX5! zin5O@HPbGc1ZK*$z_i?wC?E0x)snbd0IQ{fFF%(piMZf3zFRf2I7_lEY&XlZ!|WJ) zp1s1}W2e~r>{Ip?JI5}tpV@EhcXo+gW`Cj@b5R2gwTNIb8qtL1Xu}$`BZ+Qo#WwVy zAG@#{d!S=49OO}ei^F&lPvL1C!E<;4FXA{}#annAC-DwG#%K5%XYdU!;0OGOi}(e9 z;1VuNvQ#V8N%N&vX_*w4I;5nuQR)@HU8>a1NF}yF-bXZ838;30n>^{n%(c_Jant54 z-D_^z+WCGSua^1s3l=Vl@)BCRuJa1k1T-sXycXp?u&0!R4{ZPAPX{H{)Z!(LYV!ag z-#9eA7ArNX&Eb_OpXJs|?@*hySyUT>ZCys}Y^n>vE{`=gYZ8^CVB;&BRjrDOP4Ru# z#E3<}bUdJ{S~ZoO(5-JImIX{QMbq#ilK(xN{}IezurusCc9H!`VEzkLkg*6+v?4~( zU5^A(=)wkqZ!h|g_8~k<0L~x_3pRoHAi;P7lXx6uJb|Nl2G9CXK8_Q32`}R{ypA{U zCf>yS5T0$T-BN@e6nb3nTCi5}2QQ)47cim+m6|lA24l7~tv!@N&iX<=iO=KA z?pBoQ#e+zhfthbIJG04t3A-5p5ba5{3s3_9iAq?gp!r5c7kW{!J^p$Azw z&4x?a9Qcn6(7Tf`fDr^Rgir4;Omv82ui-F>vvITeCJM#U+Im@*WmVq#_j>A&gYhJ5 z2cv7cy3jfa7k&_4#{Frxwt1@4aS*5dktv9yKBnAU#c8CcZ9Pe&%+&gJKvra>TdU7z zNBhSOb=W$XH`MI7)oiH!y@UC@BDc18PR_dz$z!UYEtvwpJ0lwo7x044(z>4fQJUy< zfMZlwgap6WFv@yKh)=(mYB2>PGr$Zm11rmb+wf#{Wmm`xV+NRk-!ee^gF+?r9Of3y z)`7;f0Ei_F8$q9X393;JJ%_nP96=Gb6w#In*J21;j&bGkJcqeOTMoiCAHv;PxDG{_ zyW{-Iq=WD*vSkLCfv*fu?}t*O^Z)GU{@*0AVFs9i)nq`FI)0~(YjbDo(kkh!Rj7BU qB$St1{3t=k9L1PRM{yn12*wpTh@QjTB1Ta7N5Ig)h8g%%2HpV~Kv9+e literal 0 HcmV?d00001 diff --git a/lam/graphics/TakLogo.png b/lam/graphics/TakLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb41fb6b2c43e01cec821d50822530aa9ebfd4f GIT binary patch literal 1273 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1dd5WK~zXft(I9R z=WP_nO=B=JruQXFgA0iwL@9E?ShBm4`SaS^ zT0ud<&CQL9ii#qtzmt9b{5eKDIy!`4aBwi6-rnB7(|Z^{IXPKkuB4=-zrSDTL6n>y zW=TfM%F62M>S|_YMsNlP2QM!#$HvBvj*g6tjiD457XuR$6QeMIlatfJ!UD>Uj*cih zJ3Aj99`5YyY;0@{4Gn$!_6^qi_wQkmjLFGKJ`)lWegk7>W|oqY($&?4G|BYx@3p1pqterY7u$h6W!WAK{3B zfk9ne9WlXi%!!3*0wn-go12?xjf{-AxVQ){@bdHXAtogy-MNs3g~i_99;Aqfh|0=J z;v+(|y}iwCBmn#S`<%tjf(6F)^))-6?d|QUsi_#@67SxpmFVbb3JF_$`uh6f6SraQ?(RY% zAAx~^@&Otf8wm>llnP*OZf+#-SLLCqs!Ev=5D_#A0WE~Nimk1!v=%%a9i80VTuF#= zhqP;JYu{6UX=!OBK;((T^z<~s1cS#ft<~Dv8Xg`_JrEp8R!IO^DJ?A}4yLB2uU@^v z2@?~O*RNj_3~AO!^4BUqM|~|gFA&pN0Xa(kxV~7KcU>)+e_g>!8D(F zd3l@%1_s!PsuTckC_X;^CowWI5;@r*h*9I}>MB_0=jZkH^*m!SXGFGHOG`_#y0}F* z`$^|@1O)}j3emmMcXxLeEY3V~U?>0q*~Q z`EghJu*j9{>gnkLSPW6R1&j9Z_3Kx5F(gEDPESw&m4%NVKi=m$$rlAqeiswxZ#0A|_5!~{fZYio!!k4IV;YMA@*;R6Rsnct-4 z<>kV{LOz*USf_{oFY7{ie0-eQE6$ddmh9El)mVS|^5q|}E=(}#uxM{@&zXamTz`1c jhv(<#vCc?&xaofY$pgqN#&m(_00000NkvXXu0mjfxt?H2 literal 0 HcmV?d00001 diff --git a/lam/lib/.DS_Store b/lam/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c3fa79f94ed220317a1918561dcf371e4578ba9e GIT binary patch literal 10244 zcmeHMU2GIp6h3EK=!_lcPzw}vvRhXH%M#jBTN*TM+b!DgQ|Pwz4`^q1MmjP(Q+8%| zfr=KNROH16L&WHlQDb7%AYgnm{t$gI(MAG^iN*&Le_k}ACMJ6B+*v}m@FYcgK%|FBMW*T+h1uCNuLyx=AxR{E01a~B!Qph#otoD> zRD=kG2!sfP2!sfP2;2+^&~G*?Vl|gx9U>4S5F&5|0k%Ix>0vSv$QdsEt%EB62tcxu z)PA9NItTbZ(Lg2wIm4yzN@McV0|u@b95Il)lRe71lS~A1hD+`a$lU>hnK3w_pg%kL zMg8u82`hpWra?)fwm!7gr*K2C}5`~J&1q-XhDzRGJ zo0@crDNpnAZkLuDW32(lvC^e?Exp?`isMRMw_|&nX&X7;!PE`vvS-M&b*I?n#-r06Elu%g>xQmF3EAF#-+vW%{cbXoWblv8OO?J zZFgK*lF_V8-qJjy)3Q?L5rcF!ZYJkg)=tMY*)Su+o^i-ywt1%EQP;_N-Tq+XijsGY zeuKs`3r3Pgbcaovk~u0Uy|H+CyThs=1wJ44;eRs~FnyckTH7+TdIN;KU^C8yD&646u0|$un z+_hD16BS(=w6wHgaaD)BNmRBH;uiY1x?g$OFNKoYjW1nn);Z~{)k z>u?&*z*+bRK7$MJ6?_Lj!!K|deuF=80aoKuRB;uq!F#b0H()EaVLK*qJMO?f977!q z+>eu(#R3-bFdo6j@kx9NkK;4=ES|y_@I`zHU%@x=Eqoi_!w>L7{20&U1^gPn#~<)V z{1q?b@A!vMDJ&8e3pGN6&?u}G;=*PjA@mCU!k922X#D$y(!R2{6kwo#gyj4T3-%4+ z)U`7#vgN^tdgZM*Z-!l5%yR1Xx~0pONAGD|-%6Qwg?XH)C2n5Ld7tKUlEbIDPla-d z^5l~vgOmrxq3h+KQpNIBoMpWm?vf+Q0?HKLT(z2-3n@!@b8S2lQ3Og(-fU`)$VvsJ zp5*&h$0Ks^y0Jr+l}buMZh1hZCIpSnE*gX-lz5}4{Rw;l7vUTDiKzV-R-%OU*nq2X z9Z`8Rb`qU?a0}6S7p8Ck2XP-xU>Y+-?kqZZ5Oe6_qxcxk;4yr{r}hba4o~9qKEYom zdcTUV5xr0289a;U@LhZ#KgC(Rh?mNUyKOFU@!R>tE&Dl~>kzg=1VRKt1VRKt1VRLE zNdzi*R8e;S-*f%{|8L3I!=OS0LIgMhSeZ;EyXd`i=q(3^-L*&QnV^Ri)^CPO--Rmw zI-aC|9ZwB*9q-=}kgjwm!2A-VXL5#1dZY5M{}~XT|L5MYh3EepbhY*WIsg9+F>&am literal 0 HcmV?d00001 From 52b8898ae570eb38c819ebe468f54b8a504139e1 Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Tue, 24 Sep 2024 12:09:39 +0100 Subject: [PATCH 02/31] Removed DS_Store files & extra logo Removed unnecessaryfiles --- .DS_Store | Bin 8196 -> 0 bytes lam/.DS_Store | Bin 6148 -> 0 bytes lam/graphics/TakLogo.png | Bin 1273 -> 0 bytes lam/lib/.DS_Store | Bin 10244 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 lam/.DS_Store delete mode 100644 lam/graphics/TakLogo.png delete mode 100644 lam/lib/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 7fb6037173ebdd1db5ae721e1dc79b57c0f7125c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMU2GIp6u#fIz>E{y;XTjZxmn{G>g_$jly15B9CEIYGX zuxVU1@&M|iO3;}2VEhX{D2hJ#VvL#?6A_6Lj3g$$=!-^pF)^MycNSx zFau!*!VH8N2s037;AY4G?b*D^H`w=uHXOqYgc-OcGr-q}D0NN~0WAyaFC8@aM*yPy z2;et**7yU_Xh0JIEelE-Y7nkOg)4$12827wqk&x_pk+aYJ40~zK=5V+ClvT^r}?A4 zIYUCwa11jLX5jh^@bal*7Sq{SX8hXoyJfmwJbn&RRrRddbL2U)D(_0~cgEA6?iJmn zUf998e#fygm1jM(-89BWwfdA}d%9^G1>eEU8Dz3^(6n>Tc+x4_IoJ113x%x6%Ba>b zF|oF-BNn}XRr_QtI?=Hz9*efEZl9b~sXh3q`T*AjaFNy)~gE#OQvgPEMs8Y z%z68VbvJ9+In&-ZLiw~kbFXO>lv>1;ep??i&emzQH4V2%B16TDd9Y}DhqV@27IQU4 zqKbA(-kGCd9(8iuGv{j}O_4igc?XxL3I>frtS*b(qiDL^?^0-DikNzzqPa@{0s6SY zb)~vm)^f^#rDqIFh}zY)vevB(mPBMiSg$@LD??dlY>a-iD`XExI%Vy1c{hLYR^9b_ z@`g?C<{_k-B9UP(@66?lTo6HIQ)G**osmb}{d&RZ87IVvi8e*H$+G_yNzP~5kTX5! zin5O@HPbGc1ZK*$z_i?wC?E0x)snbd0IQ{fFF%(piMZf3zFRf2I7_lEY&XlZ!|WJ) zp1s1}W2e~r>{Ip?JI5}tpV@EhcXo+gW`Cj@b5R2gwTNIb8qtL1Xu}$`BZ+Qo#WwVy zAG@#{d!S=49OO}ei^F&lPvL1C!E<;4FXA{}#annAC-DwG#%K5%XYdU!;0OGOi}(e9 z;1VuNvQ#V8N%N&vX_*w4I;5nuQR)@HU8>a1NF}yF-bXZ838;30n>^{n%(c_Jant54 z-D_^z+WCGSua^1s3l=Vl@)BCRuJa1k1T-sXycXp?u&0!R4{ZPAPX{H{)Z!(LYV!ag z-#9eA7ArNX&Eb_OpXJs|?@*hySyUT>ZCys}Y^n>vE{`=gYZ8^CVB;&BRjrDOP4Ru# z#E3<}bUdJ{S~ZoO(5-JImIX{QMbq#ilK(xN{}IezurusCc9H!`VEzkLkg*6+v?4~( zU5^A(=)wkqZ!h|g_8~k<0L~x_3pRoHAi;P7lXx6uJb|Nl2G9CXK8_Q32`}R{ypA{U zCf>yS5T0$T-BN@e6nb3nTCi5}2QQ)47cim+m6|lA24l7~tv!@N&iX<=iO=KA z?pBoQ#e+zhfthbIJG04t3A-5p5ba5{3s3_9iAq?gp!r5c7kW{!J^p$Azw z&4x?a9Qcn6(7Tf`fDr^Rgir4;Omv82ui-F>vvITeCJM#U+Im@*WmVq#_j>A&gYhJ5 z2cv7cy3jfa7k&_4#{Frxwt1@4aS*5dktv9yKBnAU#c8CcZ9Pe&%+&gJKvra>TdU7z zNBhSOb=W$XH`MI7)oiH!y@UC@BDc18PR_dz$z!UYEtvwpJ0lwo7x044(z>4fQJUy< zfMZlwgap6WFv@yKh)=(mYB2>PGr$Zm11rmb+wf#{Wmm`xV+NRk-!ee^gF+?r9Of3y z)`7;f0Ei_F8$q9X393;JJ%_nP96=Gb6w#In*J21;j&bGkJcqeOTMoiCAHv;PxDG{_ zyW{-Iq=WD*vSkLCfv*fu?}t*O^Z)GU{@*0AVFs9i)nq`FI)0~(YjbDo(kkh!Rj7BU qB$St1{3t=k9L1PRM{yn12*wpTh@QjTB1Ta7N5Ig)h8g%%2HpV~Kv9+e diff --git a/lam/graphics/TakLogo.png b/lam/graphics/TakLogo.png deleted file mode 100644 index 0bb41fb6b2c43e01cec821d50822530aa9ebfd4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1273 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1dd5WK~zXft(I9R z=WP_nO=B=JruQXFgA0iwL@9E?ShBm4`SaS^ zT0ud<&CQL9ii#qtzmt9b{5eKDIy!`4aBwi6-rnB7(|Z^{IXPKkuB4=-zrSDTL6n>y zW=TfM%F62M>S|_YMsNlP2QM!#$HvBvj*g6tjiD457XuR$6QeMIlatfJ!UD>Uj*cih zJ3Aj99`5YyY;0@{4Gn$!_6^qi_wQkmjLFGKJ`)lWegk7>W|oqY($&?4G|BYx@3p1pqterY7u$h6W!WAK{3B zfk9ne9WlXi%!!3*0wn-go12?xjf{-AxVQ){@bdHXAtogy-MNs3g~i_99;Aqfh|0=J z;v+(|y}iwCBmn#S`<%tjf(6F)^))-6?d|QUsi_#@67SxpmFVbb3JF_$`uh6f6SraQ?(RY% zAAx~^@&Otf8wm>llnP*OZf+#-SLLCqs!Ev=5D_#A0WE~Nimk1!v=%%a9i80VTuF#= zhqP;JYu{6UX=!OBK;((T^z<~s1cS#ft<~Dv8Xg`_JrEp8R!IO^DJ?A}4yLB2uU@^v z2@?~O*RNj_3~AO!^4BUqM|~|gFA&pN0Xa(kxV~7KcU>)+e_g>!8D(F zd3l@%1_s!PsuTckC_X;^CowWI5;@r*h*9I}>MB_0=jZkH^*m!SXGFGHOG`_#y0}F* z`$^|@1O)}j3emmMcXxLeEY3V~U?>0q*~Q z`EghJu*j9{>gnkLSPW6R1&j9Z_3Kx5F(gEDPESw&m4%NVKi=m$$rlAqeiswxZ#0A|_5!~{fZYio!!k4IV;YMA@*;R6Rsnct-4 z<>kV{LOz*USf_{oFY7{ie0-eQE6$ddmh9El)mVS|^5q|}E=(}#uxM{@&zXamTz`1c jhv(<#vCc?&xaofY$pgqN#&m(_00000NkvXXu0mjfxt?H2 diff --git a/lam/lib/.DS_Store b/lam/lib/.DS_Store deleted file mode 100644 index c3fa79f94ed220317a1918561dcf371e4578ba9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMU2GIp6h3EK=!_lcPzw}vvRhXH%M#jBTN*TM+b!DgQ|Pwz4`^q1MmjP(Q+8%| zfr=KNROH16L&WHlQDb7%AYgnm{t$gI(MAG^iN*&Le_k}ACMJ6B+*v}m@FYcgK%|FBMW*T+h1uCNuLyx=AxR{E01a~B!Qph#otoD> zRD=kG2!sfP2!sfP2;2+^&~G*?Vl|gx9U>4S5F&5|0k%Ix>0vSv$QdsEt%EB62tcxu z)PA9NItTbZ(Lg2wIm4yzN@McV0|u@b95Il)lRe71lS~A1hD+`a$lU>hnK3w_pg%kL zMg8u82`hpWra?)fwm!7gr*K2C}5`~J&1q-XhDzRGJ zo0@crDNpnAZkLuDW32(lvC^e?Exp?`isMRMw_|&nX&X7;!PE`vvS-M&b*I?n#-r06Elu%g>xQmF3EAF#-+vW%{cbXoWblv8OO?J zZFgK*lF_V8-qJjy)3Q?L5rcF!ZYJkg)=tMY*)Su+o^i-ywt1%EQP;_N-Tq+XijsGY zeuKs`3r3Pgbcaovk~u0Uy|H+CyThs=1wJ44;eRs~FnyckTH7+TdIN;KU^C8yD&646u0|$un z+_hD16BS(=w6wHgaaD)BNmRBH;uiY1x?g$OFNKoYjW1nn);Z~{)k z>u?&*z*+bRK7$MJ6?_Lj!!K|deuF=80aoKuRB;uq!F#b0H()EaVLK*qJMO?f977!q z+>eu(#R3-bFdo6j@kx9NkK;4=ES|y_@I`zHU%@x=Eqoi_!w>L7{20&U1^gPn#~<)V z{1q?b@A!vMDJ&8e3pGN6&?u}G;=*PjA@mCU!k922X#D$y(!R2{6kwo#gyj4T3-%4+ z)U`7#vgN^tdgZM*Z-!l5%yR1Xx~0pONAGD|-%6Qwg?XH)C2n5Ld7tKUlEbIDPla-d z^5l~vgOmrxq3h+KQpNIBoMpWm?vf+Q0?HKLT(z2-3n@!@b8S2lQ3Og(-fU`)$VvsJ zp5*&h$0Ks^y0Jr+l}buMZh1hZCIpSnE*gX-lz5}4{Rw;l7vUTDiKzV-R-%OU*nq2X z9Z`8Rb`qU?a0}6S7p8Ck2XP-xU>Y+-?kqZZ5Oe6_qxcxk;4yr{r}hba4o~9qKEYom zdcTUV5xr0289a;U@LhZ#KgC(Rh?mNUyKOFU@!R>tE&Dl~>kzg=1VRKt1VRKt1VRLE zNdzi*R8e;S-*f%{|8L3I!=OS0LIgMhSeZ;EyXd`i=q(3^-L*&QnV^Ri)^CPO--Rmw zI-aC|9ZwB*9q-=}kgjwm!2A-VXL5#1dZY5M{}~XT|L5MYh3EepbhY*WIsg9+F>&am From 45a235a7f665202237d64e805ff6b15d1465d29d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 15 Mar 2025 09:38:48 +0100 Subject: [PATCH 03/31] #418 fixed profile editor --- lam/lib/modules/posixAccount.inc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lam/lib/modules/posixAccount.inc b/lam/lib/modules/posixAccount.inc index d9f128979..043246e50 100644 --- a/lam/lib/modules/posixAccount.inc +++ b/lam/lib/modules/posixAccount.inc @@ -1382,10 +1382,13 @@ class posixAccount extends baseModule implements passwordService, AccountStatusP /** * Returns if groups with same name can be created. * + * @param ?string $typeId type ID * @return bool creation is possible */ - private function allowsToCreateGroupWithSameName(): bool { - $typeId = $this->getAccountContainer()->get_type()->getId(); + private function allowsToCreateGroupWithSameName(?string $typeId = null): bool { + if ($typeId === null) { + $typeId = $this->getAccountContainer()->get_type()->getId(); + } if (($this->get_scope() !== 'user') || $this->isBooleanConfigOptionSet('posixAccount_' . $typeId . '_hideCreateGroup')) { return false; @@ -2174,7 +2177,7 @@ class posixAccount extends baseModule implements passwordService, AccountStatusP if ($this->get_scope() == 'user') { // primary Unix group $primaryGroups = $groups; - $allowToCreateGroupWithUserName = $this->allowsToCreateGroupWithSameName(); + $allowToCreateGroupWithUserName = $this->allowsToCreateGroupWithSameName($typeId); if ($allowToCreateGroupWithUserName) { $primaryGroups = [_('Create group with same name') => self::CREATE_GROUP_WITH_SAME_NAME] + $primaryGroups; } From 82def23a070e4c466c006ddf3f9bbc7e94fc2ab9 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 15 Mar 2025 15:33:58 +0100 Subject: [PATCH 04/31] #418 fixed profile editor --- lam/HISTORY | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lam/HISTORY b/lam/HISTORY index 661b9993f..6caa73af2 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,6 +1,7 @@ June 2025 9.2 - Active Directory: allow to restore deleted entries in tree view (415) - + - Fixed bugs: + -> Unix: profile editor for users not working (418) 13.03.2025 9.1 - Usability improvements (347, 348, 360, 403) From 81eebda8bb8f9a1caf5dd5a2ab910b7b14ed5633 Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Tue, 18 Mar 2025 14:08:59 +0000 Subject: [PATCH 05/31] Udated TAK users module --- lam/lib/modules/takUser.inc | 285 ++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 lam/lib/modules/takUser.inc diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc new file mode 100644 index 000000000..1791694f2 --- /dev/null +++ b/lam/lib/modules/takUser.inc @@ -0,0 +1,285 @@ +get_scope(), ['user']); + } + + /** + * Returns meta data that is interpreted by parent class + * + * @return array array with meta data + * + * @see baseModule::get_metaData() + */ + function get_metaData() { + $return = []; + // icon + $return['icon'] = 'TakLogo.png'; + // alias name + $return["alias"] = _("TAK User"); + // this is a base module + $return["is_base"] = true; + // LDAP filter + $return["ldap_filter"] = ['or' => "(objectClass=takUser)"]; + // RDN attribute + $return["RDN"] = ["cn" => "normal"]; + // module dependencies + $return['dependencies'] = ['depends' => [], 'conflicts' => []]; + // managed object classes + $return['objectClasses'] = ['takUser']; + // managed attributes + $return['attributes'] = ['takCallsign', 'takRole', 'takColor' + ]; + // help Entries + $return['help'] = [ + 'takCallsign' => [ + "Headline" => _("Callsign"), 'attr' => 'takCallsign', + "Text" => _("The user\'s callsign to be displayed to other TAK users.") + ], + 'takRole' => [ + "Headline" => _("Role"), 'attr' => 'takRole', + "Text" => _("The user\'s role to be displayed to other TAK users.") + ], + 'takColor' => [ + "Headline" => _("Color"), 'attr' => 'takColor', + "Text" => _("The user\'s team color to be displayed to other TAK users.") + ], + ]; + // upload fields + $return['upload_columns'] = [ + [ + 'name' => 'takUser_takCallsign', + 'description' => _('TAK Callsign'), + 'help' => 'takCallsign', + 'example' => _('UK-ORG-01') + 'required' => true + ], + [ + 'name' => 'takUser_takRole', + 'description' => _('Role'), + 'help' => 'takRole', + 'default' => 'Team Member', + 'values' => 'Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9', + 'example' => _('Team Member') + 'required' => true + ], + [ + 'name' => 'takUser_takColor', + 'description' => _('Team Color'), + 'help' => 'takColor', + 'default' => 'Cyan', + 'values' => 'Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow', + 'example' => _('Cyan') + 'required' => true + ], + ]; + // available PDF fields + $return['PDF_fields'] = [ + 'takCallsign' => _('TAK callsign'), + 'takRole' => _('Role'), + 'takColor' => _('Team Color'), + ]; + // profile options + $profileContainer = new htmlResponsiveRow(); + $profileContainer->add(new htmlResponsiveInputField(_('Callsign'), 'takUser_takCallsign', null, 'takUserCallsign'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Role'), 'takUser_takRole', null, 'takUserRole'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Team'), 'takUser_takColor', null, 'takUserColor'), 12); + $return['profile_options'] = $profileContainer; + // profile checks + $return['profile_checks']['takUser_takCallsign'] = [ + 'type' => 'ext_preg', + 'regex' => 'ascii', + 'error_message' => $this->messages['takCallsign'][0]]; + $return['profile_checks']['takUser_takRole'] = [ + 'type' => 'ext_preg', + 'regex' => 'ascii', + 'error_message' => $this->messages['takRole'][0]]; + $return['profile_checks']['takUser_takColor'] = [ + 'type' => 'ext_preg', + 'regex' => 'ascii', + 'error_message' => $this->messages['takColor'][0]]; + return $return; + } + + /** + * This function fills the $messages variable with output messages from this module. + */ + function load_Messages() { + $this->messages['takCallsign'][0] = ['ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + $this->messages['takCallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; + $this->messages['takCallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose another callsign.')]; + $this->messages['takRole'][0] = ['ERROR', _('Role'), _('Role contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + $this->messages['takRole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; + $this->messages['takColor'][0] = ['ERROR', _('Team'), _('Role contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + $this->messages['takColor'][1] = ['ERROR', _('Please select a team color.')]; + $this->messages['takUsers'][0] = ['ERROR', _('takUsers'), _('Unable to add the user to the takUsers group')]; + } + + /** + * Returns the HTML meta data for the main account page. + * + * @return array HTML meta data + */ + function display_html_attributes() { + $return = new htmlResponsiveRow(); + // takCallsign + $this->addMultiValueInputTextField($return, 'takCallsign', _('TAK callsign')); + /** + * // takRole + * $this->addMultiValueInputTextField($return, 'takRole', _('Role')); + * // takColor + * $this->addSimpleInputTextField($return, 'takColor', _('Color')); + */ + // takRole + $this->new htmlSelect($return, array('takRoles'=> array('Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9')), array('Team Member')); + $this->setHasDescriptiveElements(true); + $this->setContainsOptgroups(true); + //takColor + $this->new htmlSelect($return, array('takColors'=> array('Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow')), array('Cyan')); + $this->setHasDescriptiveElements(true); + $this->setContainsOptgroups(true); + return $return; + } + + /** + * Processes user input of the primary module page. + * It checks if all input values are correct and updates the associated LDAP attributes. + * + * @return array list of info/error messages + */ + function process_attributes() { + $return = []; + $this->attributes['takCallsign'][0] = $_POST['takCallsign']; + $this->attributes['takRole'][0] = $_POST['takRole']; + $this->attributes['takColor'][0] = $_POST['takColor']; + // check if takCallsign is filled + if ($_POST['takCallsign'] == '') { + $return[] = $this->messages['takCallsign'][0]; + } + // check if takCallsign is unique + else if ($this->getAccountContainer()->isNewAccount || ($this->attributes['takCallsign'][0] != $this->orig['takCallsign'][0])) { + $suffix = $_POST['accountContainerSuffix']; + $search = searchLDAP($suffix, 'takCallsign=' . $_POST['takCallsign'], ['dn']); + if (sizeof($search) > 0) { + $return[] = $this->messages['takCallsign'][2]; + } + } + // check if takRole is filled + if ($_POST['takRole'] == '') { + $return[] = $this->messages['takRole'][1]; + } + // check if takColor is filled + if ($_POST['takColor'] == '') { + $return[] = $this->messages['takColor'][1]; + } + return $return; + } + + /** + * {@inheritDoc} + * @see baseModule::build_uploadAccounts() + */ + function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { + $errors = []; + for ($i = 0; $i < sizeof($rawAccounts); $i++) { + // add object class + if (!in_array('takUser', $partialAccounts[$i]['objectClass'])) { + $partialAccounts[$i]['objectClass'][] = 'takUser'; + } + // takCallsign + $partialAccounts[$i]['takCallsign'] = $rawAccounts[$i][$ids['takUser_takCallsign']]; + // takRole + $partialAccounts[$i]['takRole'] = $rawAccounts[$i][$ids['takUser_takRole']]; + // takColor + $partialAccounts[$i]['takColor'] = $rawAccounts[$i][$ids['takUser_takColor']]; + } + return $errors; + } + + /** + * {@inheritDoc} + * @see baseModule::get_pdfEntries() + */ + function get_pdfEntries($pdfKeys, $typeId) { + $return = []; + $this->addSimplePDFField($return, 'takCallsign', _('TAK callsign')); + $this->addSimplePDFField($return, 'takRole', _('Role')); + $this->addSimplePDFField($return, 'takColor', _('Color')); + return $return; + } + + /** + * Loads the values of an account profile into internal variables. + * + * @param array $profile hash array with profile values (identifier => value) + */ + function load_profile($profile) { + // profile mappings in meta data + parent::load_profile($profile); + // special profile options + if (isset($profile['takUser_takCallsign'][0]) && ($profile['takUser_takCallsign'][0] != '')) { + $this->attributes['takCallsign'] = explode(',', $profile['takUser_takCallsign'][0]); + } + if (isset($profile['takUser_takRole'][0]) && ($profile['takUser_takRole'][0] != '')) { + $this->attributes['takRole'] = explode(',', $profile['takUser_takRole'][0]); + } + if (isset($profile['takUser_takColor'][0]) && ($profile['takUser_takColor'][0] != '')) { + $this->attributes['takColor'] = explode(',', $profile['takUser_takColor'][0]); + } + } + + /** + * @inheritDoc + */ + public function getListAttributeDescriptions(ConfiguredType $type): array { + return [ + "takCallsign" => _("TAK callsign"), + "takRole" => _("Role"), + "takColor" => _("Color"), + ]; + } +} \ No newline at end of file From 2b72128271ecf69f97c04d9b386e786d7386465d Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Tue, 18 Mar 2025 15:27:13 +0000 Subject: [PATCH 06/31] Corrected typo --- lam/lib/modules/takUser.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index 1791694f2..b4661f788 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -95,7 +95,7 @@ class takUser extends baseModule { 'name' => 'takUser_takCallsign', 'description' => _('TAK Callsign'), 'help' => 'takCallsign', - 'example' => _('UK-ORG-01') + 'example' => _('UK-ORG-01'), 'required' => true ], [ @@ -104,7 +104,7 @@ class takUser extends baseModule { 'help' => 'takRole', 'default' => 'Team Member', 'values' => 'Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9', - 'example' => _('Team Member') + 'example' => _('Team Member'), 'required' => true ], [ @@ -113,7 +113,7 @@ class takUser extends baseModule { 'help' => 'takColor', 'default' => 'Cyan', 'values' => 'Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow', - 'example' => _('Cyan') + 'example' => _('Cyan'), 'required' => true ], ]; @@ -175,7 +175,7 @@ class takUser extends baseModule { * $this->addSimpleInputTextField($return, 'takColor', _('Color')); */ // takRole - $this->new htmlSelect($return, array('takRoles'=> array('Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9')), array('Team Member')); + $this->addMultiValueSelectField($return, 'takRole', _('Role'), array('takRoles'=> array('Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9')), array('Team Member')); $this->setHasDescriptiveElements(true); $this->setContainsOptgroups(true); //takColor From 6b81ac5e11d103bc4b6cedb44b63fd7bf805825e Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Tue, 18 Mar 2025 17:59:00 +0000 Subject: [PATCH 07/31] Improve dialogue box layouts --- lam/lib/modules/takUser.inc | 52 +++++++++++++++---------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index b4661f788..69057143f 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -62,7 +62,7 @@ class takUser extends baseModule { // alias name $return["alias"] = _("TAK User"); // this is a base module - $return["is_base"] = true; + $return["is_base"] = false; // LDAP filter $return["ldap_filter"] = ['or' => "(objectClass=takUser)"]; // RDN attribute @@ -77,16 +77,16 @@ class takUser extends baseModule { // help Entries $return['help'] = [ 'takCallsign' => [ - "Headline" => _("Callsign"), 'attr' => 'takCallsign', - "Text" => _("The user\'s callsign to be displayed to other TAK users.") + "Headline" => _("TAK Callsign"), 'attr' => 'takCallsign', + "Text" => _("The user's callsign to be displayed to other TAK users.") ], 'takRole' => [ - "Headline" => _("Role"), 'attr' => 'takRole', - "Text" => _("The user\'s role to be displayed to other TAK users.") + "Headline" => _("TAK Team Role"), 'attr' => 'takRole', + "Text" => _("The user's role to be displayed to other TAK users.") ], 'takColor' => [ - "Headline" => _("Color"), 'attr' => 'takColor', - "Text" => _("The user\'s team color to be displayed to other TAK users.") + "Headline" => _("TAK Team Color"), 'attr' => 'takColor', + "Text" => _("The user's team color to be displayed to other TAK users.") ], ]; // upload fields @@ -100,7 +100,7 @@ class takUser extends baseModule { ], [ 'name' => 'takUser_takRole', - 'description' => _('Role'), + 'description' => _('TAK Team Role'), 'help' => 'takRole', 'default' => 'Team Member', 'values' => 'Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9', @@ -109,7 +109,7 @@ class takUser extends baseModule { ], [ 'name' => 'takUser_takColor', - 'description' => _('Team Color'), + 'description' => _('TAK Team Color'), 'help' => 'takColor', 'default' => 'Cyan', 'values' => 'Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow', @@ -120,14 +120,14 @@ class takUser extends baseModule { // available PDF fields $return['PDF_fields'] = [ 'takCallsign' => _('TAK callsign'), - 'takRole' => _('Role'), - 'takColor' => _('Team Color'), + 'takRole' => _('TAK Team Role'), + 'takColor' => _('TAK Team Color'), ]; // profile options $profileContainer = new htmlResponsiveRow(); - $profileContainer->add(new htmlResponsiveInputField(_('Callsign'), 'takUser_takCallsign', null, 'takUserCallsign'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('Role'), 'takUser_takRole', null, 'takUserRole'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('Team'), 'takUser_takColor', null, 'takUserColor'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Callsign'), 'takUser_takCallsign', null, 'takUserCallsign'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Role'), 'takUser_takRole', null, 'takUserRole'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Color'), 'takUser_takColor', null, 'takUserColor'), 12); $return['profile_options'] = $profileContainer; // profile checks $return['profile_checks']['takUser_takCallsign'] = [ @@ -167,21 +167,11 @@ class takUser extends baseModule { function display_html_attributes() { $return = new htmlResponsiveRow(); // takCallsign - $this->addMultiValueInputTextField($return, 'takCallsign', _('TAK callsign')); - /** - * // takRole - * $this->addMultiValueInputTextField($return, 'takRole', _('Role')); - * // takColor - * $this->addSimpleInputTextField($return, 'takColor', _('Color')); - */ + $this->addSimpleInputTextField($return, 'takCallsign', _('TAK callsign'), true); // takRole - $this->addMultiValueSelectField($return, 'takRole', _('Role'), array('takRoles'=> array('Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9')), array('Team Member')); - $this->setHasDescriptiveElements(true); - $this->setContainsOptgroups(true); + $return->add(new htmlResponsiveSelect('takRole', ['Team Member', 'Team Leader', 'HQ', 'Sniper', 'Medic', 'Forward Observer', 'RTO', 'K9'], ['Team Member'], _('TAK Team Role'), 'takRole'), 12); //takColor - $this->new htmlSelect($return, array('takColors'=> array('Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow')), array('Cyan')); - $this->setHasDescriptiveElements(true); - $this->setContainsOptgroups(true); + $return->add(new htmlResponsiveSelect('takColor', ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow'], ['Cyan'], _('TAK Team Color'), 'takColor'), 12); return $return; } @@ -247,8 +237,8 @@ class takUser extends baseModule { function get_pdfEntries($pdfKeys, $typeId) { $return = []; $this->addSimplePDFField($return, 'takCallsign', _('TAK callsign')); - $this->addSimplePDFField($return, 'takRole', _('Role')); - $this->addSimplePDFField($return, 'takColor', _('Color')); + $this->addSimplePDFField($return, 'takRole', _('TAK Team Role')); + $this->addSimplePDFField($return, 'takColor', _('TAK Team Color')); return $return; } @@ -278,8 +268,8 @@ class takUser extends baseModule { public function getListAttributeDescriptions(ConfiguredType $type): array { return [ "takCallsign" => _("TAK callsign"), - "takRole" => _("Role"), - "takColor" => _("Color"), + "takRole" => _("TAK Team Role"), + "takColor" => _("TAK Team Color"), ]; } } \ No newline at end of file From 37255ff36a0b8d2d1902614899d8e500da4633c3 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 19 Mar 2025 07:42:10 +0100 Subject: [PATCH 08/31] support multiple values --- lam/lib/modules/inetOrgPerson.inc | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lam/lib/modules/inetOrgPerson.inc b/lam/lib/modules/inetOrgPerson.inc index 4d9e5d462..4a8ba0e1f 100644 --- a/lam/lib/modules/inetOrgPerson.inc +++ b/lam/lib/modules/inetOrgPerson.inc @@ -2730,19 +2730,19 @@ class inetOrgPerson extends baseModule implements passwordService, AccountStatus $attributes, $readOnlyFields, false, false, 'givenName'); $this->addSimpleSelfServiceTextField($return, 'lastName', _('Last name'), $fields, $attributes, $readOnlyFields, true, false, 'sn'); - $this->addSimpleSelfServiceTextField($return, 'mail', _('Email address'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'mail', _('Email address'), $fields, $attributes, $readOnlyFields); $this->addMultiValueSelfServiceTextField($return, 'labeledURI', _('Web site'), $fields, $attributes, $readOnlyFields, false, false, 'labeledURI'); - $this->addSimpleSelfServiceTextField($return, 'telephoneNumber', _('Telephone number'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'telephoneNumber', _('Telephone number'), $fields, $attributes, $readOnlyFields, false, false, 'telephoneNumber'); - $this->addSimpleSelfServiceTextField($return, 'homePhone', _('Home telephone number'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'homePhone', _('Home telephone number'), $fields, $attributes, $readOnlyFields, false, false, 'homePhone'); - $this->addSimpleSelfServiceTextField($return, 'mobile', _('Mobile telephone number'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'mobile', _('Mobile telephone number'), $fields, $attributes, $readOnlyFields); - $this->addSimpleSelfServiceTextField($return, 'faxNumber', _('Fax number'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'faxNumber', _('Fax number'), $fields, $attributes, $readOnlyFields, false, false, 'facsimileTelephoneNumber'); - $this->addSimpleSelfServiceTextField($return, 'pager', _('Pager'), $fields, + $this->addMultiValueSelfServiceTextField($return, 'pager', _('Pager'), $fields, $attributes, $readOnlyFields); $this->addMultiValueSelfServiceTextField($return, 'street', _('Street'), $fields, $attributes, $readOnlyFields); @@ -3200,22 +3200,22 @@ class inetOrgPerson extends baseModule implements passwordService, AccountStatus $this->checkSimpleSelfServiceTextField($return, 'lastName', $attributes, $fields, $readOnlyFields, 'realname', $this->messages['lastname'][0], $this->messages['lastname'][0], 'sn'); - $this->checkSimpleSelfServiceTextField($return, 'mail', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'mail', $attributes, $fields, $readOnlyFields, 'email', $this->messages['mail'][0]); $this->checkMultiValueSelfServiceTextField($return, 'labeledURI', $attributes, $fields, $readOnlyFields, null, null, null, 'labeledURI'); - $this->checkSimpleSelfServiceTextField($return, 'telephoneNumber', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'telephoneNumber', $attributes, $fields, $readOnlyFields, 'telephone', $this->messages['telephoneNumber'][0], null, 'telephoneNumber'); - $this->checkSimpleSelfServiceTextField($return, 'homePhone', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'homePhone', $attributes, $fields, $readOnlyFields, 'telephone', $this->messages['homePhone'][0], null, 'homePhone'); - $this->checkSimpleSelfServiceTextField($return, 'faxNumber', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'faxNumber', $attributes, $fields, $readOnlyFields, 'telephone', $this->messages['facsimileTelephoneNumber'][0], null, 'facsimileTelephoneNumber'); - $this->checkSimpleSelfServiceTextField($return, 'mobile', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'mobile', $attributes, $fields, $readOnlyFields, 'telephone', $this->messages['mobile'][0]); - $this->checkSimpleSelfServiceTextField($return, 'pager', $attributes, $fields, + $this->checkMultiValueSelfServiceTextField($return, 'pager', $attributes, $fields, $readOnlyFields, 'telephone', $this->messages['pager'][0]); $this->checkMultiValueSelfServiceTextField($return, 'street', $attributes, $fields, $readOnlyFields, 'street', $this->messages['street'][0]); From 910357d8afa38b67971e10c64aa641b2b579571d Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Wed, 19 Mar 2025 17:01:37 +0000 Subject: [PATCH 09/31] Removed redundant checks Consistent capitalisation of titles Improved html layout --- lam/lib/modules/takUser.inc | 57 +++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index 69057143f..566d7d28d 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -58,9 +58,9 @@ class takUser extends baseModule { function get_metaData() { $return = []; // icon - $return['icon'] = 'TakLogo.png'; + $return['icon'] = 'tak.png'; // alias name - $return["alias"] = _("TAK User"); + $return["alias"] = _("TAK"); // this is a base module $return["is_base"] = false; // LDAP filter @@ -77,15 +77,15 @@ class takUser extends baseModule { // help Entries $return['help'] = [ 'takCallsign' => [ - "Headline" => _("TAK Callsign"), 'attr' => 'takCallsign', + "Headline" => _("TAK callsign"), 'attr' => 'takCallsign', "Text" => _("The user's callsign to be displayed to other TAK users.") ], 'takRole' => [ - "Headline" => _("TAK Team Role"), 'attr' => 'takRole', + "Headline" => _("TAK team role"), 'attr' => 'takRole', "Text" => _("The user's role to be displayed to other TAK users.") ], 'takColor' => [ - "Headline" => _("TAK Team Color"), 'attr' => 'takColor', + "Headline" => _("TAK Team color"), 'attr' => 'takColor', "Text" => _("The user's team color to be displayed to other TAK users.") ], ]; @@ -93,14 +93,14 @@ class takUser extends baseModule { $return['upload_columns'] = [ [ 'name' => 'takUser_takCallsign', - 'description' => _('TAK Callsign'), + 'description' => _('TAK callsign'), 'help' => 'takCallsign', 'example' => _('UK-ORG-01'), 'required' => true ], [ 'name' => 'takUser_takRole', - 'description' => _('TAK Team Role'), + 'description' => _('TAK team role'), 'help' => 'takRole', 'default' => 'Team Member', 'values' => 'Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9', @@ -109,7 +109,7 @@ class takUser extends baseModule { ], [ 'name' => 'takUser_takColor', - 'description' => _('TAK Team Color'), + 'description' => _('TAK team color'), 'help' => 'takColor', 'default' => 'Cyan', 'values' => 'Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow', @@ -120,8 +120,8 @@ class takUser extends baseModule { // available PDF fields $return['PDF_fields'] = [ 'takCallsign' => _('TAK callsign'), - 'takRole' => _('TAK Team Role'), - 'takColor' => _('TAK Team Color'), + 'takRole' => _('TAK team role'), + 'takColor' => _('TAK team color'), ]; // profile options $profileContainer = new htmlResponsiveRow(); @@ -129,19 +129,14 @@ class takUser extends baseModule { $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Role'), 'takUser_takRole', null, 'takUserRole'), 12); $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Color'), 'takUser_takColor', null, 'takUserColor'), 12); $return['profile_options'] = $profileContainer; - // profile checks - $return['profile_checks']['takUser_takCallsign'] = [ - 'type' => 'ext_preg', - 'regex' => 'ascii', - 'error_message' => $this->messages['takCallsign'][0]]; - $return['profile_checks']['takUser_takRole'] = [ - 'type' => 'ext_preg', - 'regex' => 'ascii', - 'error_message' => $this->messages['takRole'][0]]; - $return['profile_checks']['takUser_takColor'] = [ - 'type' => 'ext_preg', - 'regex' => 'ascii', - 'error_message' => $this->messages['takColor'][0]]; + /** + * not working: will not allow - + * // profile checks + * $return['profile_checks']['takUser_takCallsign'] = [ + * 'type' => 'ext_preg', + * 'regex' => 'ascii', + * 'error_message' => $this->messages['takCallsign'][0]]; + */ return $return; } @@ -152,9 +147,7 @@ class takUser extends baseModule { $this->messages['takCallsign'][0] = ['ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; $this->messages['takCallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; $this->messages['takCallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose another callsign.')]; - $this->messages['takRole'][0] = ['ERROR', _('Role'), _('Role contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; $this->messages['takRole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; - $this->messages['takColor'][0] = ['ERROR', _('Team'), _('Role contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; $this->messages['takColor'][1] = ['ERROR', _('Please select a team color.')]; $this->messages['takUsers'][0] = ['ERROR', _('takUsers'), _('Unable to add the user to the takUsers group')]; } @@ -166,12 +159,14 @@ class takUser extends baseModule { */ function display_html_attributes() { $return = new htmlResponsiveRow(); + // Section title + $return->add(new htmlSubTitle(_('General'))); // takCallsign $this->addSimpleInputTextField($return, 'takCallsign', _('TAK callsign'), true); // takRole - $return->add(new htmlResponsiveSelect('takRole', ['Team Member', 'Team Leader', 'HQ', 'Sniper', 'Medic', 'Forward Observer', 'RTO', 'K9'], ['Team Member'], _('TAK Team Role'), 'takRole'), 12); + $return->add(new htmlResponsiveSelect('takRole', ['Team Member', 'Team Leader', 'HQ', 'Sniper', 'Medic', 'Forward Observer', 'RTO', 'K9'], ['Team Member'], _('TAK team role'), 'takRole'), 12); //takColor - $return->add(new htmlResponsiveSelect('takColor', ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow'], ['Cyan'], _('TAK Team Color'), 'takColor'), 12); + $return->add(new htmlResponsiveSelect('takColor', ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow'], ['Cyan'], _('TAK team color'), 'takColor'), 12); return $return; } @@ -237,8 +232,8 @@ class takUser extends baseModule { function get_pdfEntries($pdfKeys, $typeId) { $return = []; $this->addSimplePDFField($return, 'takCallsign', _('TAK callsign')); - $this->addSimplePDFField($return, 'takRole', _('TAK Team Role')); - $this->addSimplePDFField($return, 'takColor', _('TAK Team Color')); + $this->addSimplePDFField($return, 'takRole', _('TAK team role')); + $this->addSimplePDFField($return, 'takColor', _('TAK team color')); return $return; } @@ -268,8 +263,8 @@ class takUser extends baseModule { public function getListAttributeDescriptions(ConfiguredType $type): array { return [ "takCallsign" => _("TAK callsign"), - "takRole" => _("TAK Team Role"), - "takColor" => _("TAK Team Color"), + "takRole" => _("TAK team role"), + "takColor" => _("TAK team color"), ]; } } \ No newline at end of file From 1fb946d8737d24199469f7f7a07a018f6b2e620c Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Wed, 19 Mar 2025 20:07:10 +0100 Subject: [PATCH 10/31] #419 switch to ldap_modify --- lam/HISTORY | 1 + 1 file changed, 1 insertion(+) diff --git a/lam/HISTORY b/lam/HISTORY index 6caa73af2..7519e8c56 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -2,6 +2,7 @@ June 2025 9.2 - Active Directory: allow to restore deleted entries in tree view (415) - Fixed bugs: -> Unix: profile editor for users not working (418) + -> Custom fields: problems with deleting facsimileTelephoneNumber (419) 13.03.2025 9.1 - Usability improvements (347, 348, 360, 403) From 875679b17b1d75d2edf5c162cb5533a6ec5c05ce Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 21 Mar 2025 07:37:53 +0100 Subject: [PATCH 11/31] refactoring --- lam/lib/account.inc | 14 +++++++++++++- lam/tests/lib/AccountTest.php | 11 ++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lam/lib/account.inc b/lam/lib/account.inc index 3afa4f5c4..0a3ff74fe 100644 --- a/lam/lib/account.inc +++ b/lam/lib/account.inc @@ -3,7 +3,7 @@ This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) Copyright (C) 2003 - 2006 Tilo Lutz - 2009 - 2024 Roland Gruber + 2009 - 2025 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 @@ -77,6 +77,18 @@ function in_array_ignore_case($needle, $haystack) { return false; } +/** + * Checks if two arrays have the same content. + * + * @param array $array1 array 1 + * @param array $array2 array 2 + * @return bool same content + */ +function areArrayContentsEqual(array $array1, array $array2): bool { + $intersect = array_intersect($array1, $array2); + return ((count($array1) === count($array2)) && (count($intersect) === count($array1))); +} + /** * Sorts an array in natural order by its keys. * diff --git a/lam/tests/lib/AccountTest.php b/lam/tests/lib/AccountTest.php index c3bcc63e4..ac1a95d5d 100644 --- a/lam/tests/lib/AccountTest.php +++ b/lam/tests/lib/AccountTest.php @@ -2,7 +2,7 @@ use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2018 - 2024 Roland Gruber + Copyright (C) 2018 - 2025 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 @@ -325,4 +325,13 @@ class AccountTest extends TestCase { $this->assertFalse(get_preg('ab?c:80', 'hostAndPort')); } + function testAreArrayContentsEqual() { + $this->assertTrue(areArrayContentsEqual([], [])); + $this->assertFalse(areArrayContentsEqual(['1'], [])); + $this->assertFalse(areArrayContentsEqual([], ['1'])); + $this->assertTrue(areArrayContentsEqual(['a', 'b', 'c'], ['c', 'b', 'a'])); + $this->assertFalse(areArrayContentsEqual(['a', 'b', 'c'], ['a', 'c', 'd'])); + $this->assertFalse(areArrayContentsEqual(['a', 'b', 'c'], ['a', 'c'])); + } + } From 346f7721f32041ed394b088aafdf15b32675582d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 21 Mar 2025 07:46:49 +0100 Subject: [PATCH 12/31] refactoring --- lam/lib/baseModule.inc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lam/lib/baseModule.inc b/lam/lib/baseModule.inc index 662042a85..e77c7ae81 100644 --- a/lam/lib/baseModule.inc +++ b/lam/lib/baseModule.inc @@ -9,7 +9,7 @@ use LAM\PDF\PDFImage; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 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 @@ -1954,8 +1954,7 @@ abstract class baseModule { $container['messages'][] = $requiredMessage; } $valuesOld = $attributes[$ldapAttrName] ?? []; - $intersect = array_intersect($valuesOld, $valuesNew); - if ((count($valuesOld) !== count($valuesNew)) || (count($intersect) !== count($valuesOld))) { + if (!areArrayContentsEqual($valuesOld, $valuesNew)) { $container['mod'][$ldapAttrName] = $valuesNew; } } From e148f2a9eb676de7aa6feb1b8835214cc3167256 Mon Sep 17 00:00:00 2001 From: Mini workgroups Ltd Date: Fri, 21 Mar 2025 14:35:20 +0000 Subject: [PATCH 13/31] Addressed requested changes except checks --- lam/lib/modules/takUser.inc | 176 ++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 87 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index 566d7d28d..9446f1da5 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -2,16 +2,11 @@ /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2010 - 2024 Roland Gruber + Copyright (C) 2003 - 2006 Tilo Lutz + 2005 - 2025 Roland Gruber + This code + 2024 Mark Halliday -*/ - -/** - * Manages TAK User roles. - * - * @package modules - * @author Mark Halliday - * Copyright (C) 2024 Mark Halliday 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 the Free Software Foundation; either version 2 of the License, or @@ -25,19 +20,19 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * Manages the object class "takuser" for users. + * + * @package modules + * + * @author Mark Halliday */ use LAM\TYPES\ConfiguredType; -/** - * Manages TAK User roles. - * - * @package modules - */ -class takUser extends baseModule { - - /** regex for date values */ - const DATE_REGEX = '/^([\d]{1,2})\\.([\d]{1,2})\\.([\d]{4})( ([\d]{1,2}):([\d]{1,2}))?$/'; +class takuser extends baseModule { /** * Returns true if this module can manage accounts of the current type, otherwise false. @@ -48,6 +43,14 @@ class takUser extends baseModule { return in_array($this->get_scope(), ['user']); } + /** possible roles */ + private $roletypes = ['Team Member', 'Team Leader', 'HQ', 'RTO', 'K9', 'Medic', 'Forward Observer', 'Sniper']; + + /** possible colors */ + private $colortypes = ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow']; + + + /** * Returns meta data that is interpreted by parent class * @@ -64,70 +67,70 @@ class takUser extends baseModule { // this is a base module $return["is_base"] = false; // LDAP filter - $return["ldap_filter"] = ['or' => "(objectClass=takUser)"]; + $return["ldap_filter"] = ['or' => "(objectClass=takuser)"]; // RDN attribute $return["RDN"] = ["cn" => "normal"]; // module dependencies $return['dependencies'] = ['depends' => [], 'conflicts' => []]; // managed object classes - $return['objectClasses'] = ['takUser']; + $return['objectClasses'] = ['takuser']; // managed attributes - $return['attributes'] = ['takCallsign', 'takRole', 'takColor' + $return['attributes'] = ['takcallsign', 'takrole', 'takcolor' ]; // help Entries $return['help'] = [ - 'takCallsign' => [ - "Headline" => _("TAK callsign"), 'attr' => 'takCallsign', - "Text" => _("The user's callsign to be displayed to other TAK users.") + 'takcallsign' => [ + "Headline" => _("TAK callsign"), 'attr' => 'takcallsign', + "Text" => _("The user's callsign to be displayed to other TAK users. It must be unique.") ], - 'takRole' => [ - "Headline" => _("TAK team role"), 'attr' => 'takRole', + 'takrole' => [ + "Headline" => _("TAK team role"), 'attr' => 'takrole', "Text" => _("The user's role to be displayed to other TAK users.") ], - 'takColor' => [ - "Headline" => _("TAK Team color"), 'attr' => 'takColor', + 'takcolor' => [ + "Headline" => _("TAK Team color"), 'attr' => 'takcolor', "Text" => _("The user's team color to be displayed to other TAK users.") ], ]; // upload fields $return['upload_columns'] = [ [ - 'name' => 'takUser_takCallsign', + 'name' => 'takuser_takcallsign', 'description' => _('TAK callsign'), - 'help' => 'takCallsign', + 'help' => 'takcallsign', 'example' => _('UK-ORG-01'), 'required' => true ], [ - 'name' => 'takUser_takRole', + 'name' => 'takuser_takrole', 'description' => _('TAK team role'), - 'help' => 'takRole', + 'help' => 'takrole', 'default' => 'Team Member', - 'values' => 'Team Member, Team Leader, HQ, Sniper, Medic, Forward Observer, RTO, K9', + 'values' => implode(", ", $this->roletypes), 'example' => _('Team Member'), 'required' => true ], [ - 'name' => 'takUser_takColor', + 'name' => 'takuser_takcolor', 'description' => _('TAK team color'), - 'help' => 'takColor', + 'help' => 'takcolor', 'default' => 'Cyan', - 'values' => 'Blue, Brown, Cyan, Dark Blue, Dark Green, Green, Magenta, Maroon, Orange, Purple, Red, Teal, White, Yellow', + 'values' => implode(", ", $this->colortypes), 'example' => _('Cyan'), 'required' => true ], ]; // available PDF fields $return['PDF_fields'] = [ - 'takCallsign' => _('TAK callsign'), - 'takRole' => _('TAK team role'), - 'takColor' => _('TAK team color'), + 'takcallsign' => _('TAK callsign'), + 'takrole' => _('TAK team role'), + 'takcolor' => _('TAK team color'), ]; // profile options $profileContainer = new htmlResponsiveRow(); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Callsign'), 'takUser_takCallsign', null, 'takUserCallsign'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Role'), 'takUser_takRole', null, 'takUserRole'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Color'), 'takUser_takColor', null, 'takUserColor'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Callsign'), 'takuser_takcallsign', null, 'takusercallsign'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); $return['profile_options'] = $profileContainer; /** * not working: will not allow - @@ -144,12 +147,11 @@ class takUser extends baseModule { * This function fills the $messages variable with output messages from this module. */ function load_Messages() { - $this->messages['takCallsign'][0] = ['ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; - $this->messages['takCallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; - $this->messages['takCallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose another callsign.')]; - $this->messages['takRole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; - $this->messages['takColor'][1] = ['ERROR', _('Please select a team color.')]; - $this->messages['takUsers'][0] = ['ERROR', _('takUsers'), _('Unable to add the user to the takUsers group')]; + $this->messages['takcallsign'][0] = ['ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + $this->messages['takcallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; + $this->messages['takcallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose another callsign.')]; + $this->messages['takrole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; + $this->messages['takcolor'][1] = ['ERROR', _('Please select a team color.')]; } /** @@ -162,11 +164,11 @@ class takUser extends baseModule { // Section title $return->add(new htmlSubTitle(_('General'))); // takCallsign - $this->addSimpleInputTextField($return, 'takCallsign', _('TAK callsign'), true); + $this->addSimpleInputTextField($return, 'takcallsign', _('TAK callsign'), true); // takRole - $return->add(new htmlResponsiveSelect('takRole', ['Team Member', 'Team Leader', 'HQ', 'Sniper', 'Medic', 'Forward Observer', 'RTO', 'K9'], ['Team Member'], _('TAK team role'), 'takRole'), 12); + $return->add(new htmlResponsiveSelect('takrole', $this->roletypes, _('TAK team role'), 'takrole'), 12); //takColor - $return->add(new htmlResponsiveSelect('takColor', ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow'], ['Cyan'], _('TAK team color'), 'takColor'), 12); + $return->add(new htmlResponsiveSelect('takcolor', $this->colortypes, ['Cyan'], _('TAK team color'), 'takcolor'), 12); return $return; } @@ -178,28 +180,28 @@ class takUser extends baseModule { */ function process_attributes() { $return = []; - $this->attributes['takCallsign'][0] = $_POST['takCallsign']; - $this->attributes['takRole'][0] = $_POST['takRole']; - $this->attributes['takColor'][0] = $_POST['takColor']; - // check if takCallsign is filled - if ($_POST['takCallsign'] == '') { - $return[] = $this->messages['takCallsign'][0]; + $this->attributes['takcallsign'][0] = $_POST['takcallsign']; + $this->attributes['takrole'][0] = $_POST['takrole']; + $this->attributes['takcolor'][0] = $_POST['takcolor']; + // check if takcallsign is filled + if ($_POST['takcallsign'] == '') { + $return[] = $this->messages['takcallsign'][0]; } - // check if takCallsign is unique - else if ($this->getAccountContainer()->isNewAccount || ($this->attributes['takCallsign'][0] != $this->orig['takCallsign'][0])) { + // check if takcallsign is unique + else if (empty($this->orig['takcallsign'][0]) || ($this->attributes['takcallsign'][0] != $this->orig['takcallsign'][0])) { $suffix = $_POST['accountContainerSuffix']; - $search = searchLDAP($suffix, 'takCallsign=' . $_POST['takCallsign'], ['dn']); + $search = searchLDAP($suffix, 'takcallsign=' . $_POST['takcallsign'], ['dn']); if (sizeof($search) > 0) { - $return[] = $this->messages['takCallsign'][2]; + $return[] = $this->messages['takcallsign'][2]; } } - // check if takRole is filled - if ($_POST['takRole'] == '') { - $return[] = $this->messages['takRole'][1]; + // check if takrole is filled + if ($_POST['takrole'] == '') { + $return[] = $this->messages['takrole'][1]; } - // check if takColor is filled - if ($_POST['takColor'] == '') { - $return[] = $this->messages['takColor'][1]; + // check if takcolor is filled + if ($_POST['takcolor'] == '') { + $return[] = $this->messages['takcolor'][1]; } return $return; } @@ -212,15 +214,15 @@ class takUser extends baseModule { $errors = []; for ($i = 0; $i < sizeof($rawAccounts); $i++) { // add object class - if (!in_array('takUser', $partialAccounts[$i]['objectClass'])) { - $partialAccounts[$i]['objectClass'][] = 'takUser'; + if (!in_array('takuser', $partialAccounts[$i]['objectClass'])) { + $partialAccounts[$i]['objectClass'][] = 'takuser'; } - // takCallsign - $partialAccounts[$i]['takCallsign'] = $rawAccounts[$i][$ids['takUser_takCallsign']]; - // takRole - $partialAccounts[$i]['takRole'] = $rawAccounts[$i][$ids['takUser_takRole']]; - // takColor - $partialAccounts[$i]['takColor'] = $rawAccounts[$i][$ids['takUser_takColor']]; + // takcallsign + $partialAccounts[$i]['takcallsign'] = $rawAccounts[$i][$ids['takuser_takcallsign']]; + // takrole + $partialAccounts[$i]['takrole'] = $rawAccounts[$i][$ids['takuser_takrole']]; + // takcolor + $partialAccounts[$i]['takcolor'] = $rawAccounts[$i][$ids['takuser_takcolor']]; } return $errors; } @@ -231,9 +233,9 @@ class takUser extends baseModule { */ function get_pdfEntries($pdfKeys, $typeId) { $return = []; - $this->addSimplePDFField($return, 'takCallsign', _('TAK callsign')); - $this->addSimplePDFField($return, 'takRole', _('TAK team role')); - $this->addSimplePDFField($return, 'takColor', _('TAK team color')); + $this->addSimplePDFField($return, 'takcallsign', _('TAK callsign')); + $this->addSimplePDFField($return, 'takrole', _('TAK team role')); + $this->addSimplePDFField($return, 'takcolor', _('TAK team color')); return $return; } @@ -246,14 +248,14 @@ class takUser extends baseModule { // profile mappings in meta data parent::load_profile($profile); // special profile options - if (isset($profile['takUser_takCallsign'][0]) && ($profile['takUser_takCallsign'][0] != '')) { - $this->attributes['takCallsign'] = explode(',', $profile['takUser_takCallsign'][0]); + if (isset($profile['takuser_takcallsign'][0]) && ($profile['takuser_takcallsign'][0] != '')) { + $this->attributes['takcallsign'] = $profile['takuser_takcallsign'][0]; } - if (isset($profile['takUser_takRole'][0]) && ($profile['takUser_takRole'][0] != '')) { - $this->attributes['takRole'] = explode(',', $profile['takUser_takRole'][0]); + if (isset($profile['takuser_takrole'][0]) && ($profile['takuser_takrole'][0] != '')) { + $this->attributes['takrole'] = $profile['takuser_takrole'][0]; } - if (isset($profile['takUser_takColor'][0]) && ($profile['takUser_takColor'][0] != '')) { - $this->attributes['takColor'] = explode(',', $profile['takUser_takColor'][0]); + if (isset($profile['takuser_takcolor'][0]) && ($profile['takuser_takcolor'][0] != '')) { + $this->attributes['takcolor'] = $profile['takuser_takcolor'][0]; } } @@ -262,9 +264,9 @@ class takUser extends baseModule { */ public function getListAttributeDescriptions(ConfiguredType $type): array { return [ - "takCallsign" => _("TAK callsign"), - "takRole" => _("TAK team role"), - "takColor" => _("TAK team color"), + "takcallsign" => _("TAK callsign"), + "takrole" => _("TAK team role"), + "takcolor" => _("TAK team color"), ]; } } \ No newline at end of file From 155aea7fc7c5ae4c0ee754b29bc8cc829be10b77 Mon Sep 17 00:00:00 2001 From: gruberroland Date: Mon, 24 Mar 2025 07:38:57 +0100 Subject: [PATCH 14/31] Update copyright --- lam/lib/modules/takUser.inc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index 9446f1da5..cb1e5c667 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -2,10 +2,7 @@ /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2006 Tilo Lutz - 2005 - 2025 Roland Gruber - This code - 2024 Mark Halliday + Copyright (C) 2024 Mark Halliday 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 @@ -20,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ /** @@ -269,4 +267,4 @@ class takuser extends baseModule { "takcolor" => _("TAK team color"), ]; } -} \ No newline at end of file +} From b05875949d26ffede821f03d93f875557cc4238d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 25 Mar 2025 10:42:03 +0100 Subject: [PATCH 15/31] TAK support --- lam/docs/schema/tak-Windows.ldif | 100 +++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 lam/docs/schema/tak-Windows.ldif diff --git a/lam/docs/schema/tak-Windows.ldif b/lam/docs/schema/tak-Windows.ldif new file mode 100644 index 000000000..0fba52bed --- /dev/null +++ b/lam/docs/schema/tak-Windows.ldif @@ -0,0 +1,100 @@ +# +# LDAP schema for LAM TAK functionality +# +# This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) +# Copyright (C) 2025 Roland Gruber +# +# +# OID bases: +# 1.3.6.1.4.1.34955 Roland Gruber Softwareentwicklung +# 1.3.6.1.4.1.34955.1 attributes +# 1.3.6.1.4.1.34955.2 object classes +# +# Please replace DOMAIN_TOP_DN with your LDAP suffix (e.g. dc=windows,dc=test). +# +# Installation: ldifde -v -i -f tak-Windows.ldif +# +# +# Version: 1 +# 1: initial release (LAM 9.2) +# + +dn: CN=takCallsign,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +changetype: add +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.100 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +rangeLower: 4 +cn: takCallsign +name: takCallsign +lDAPDisplayName: takCallsign +description: TAK callsign + +dn: CN=takRole,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +changetype: add +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.101 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +cn: takRole +name: takRole +lDAPDisplayName: takRole +description: TAK team role + +dn: CN=takColor,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +changetype: add +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.102 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +cn: takColor +name: takColor +LDAPDisplayName: takColor +Description: TAK team color + +dn: +changetype: modify +add: schemaUpdateNow +schemaUpdateNow: 1 +- + +dn: CN=takUser,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +changetype: add +objectClass: top +objectClass: classSchema +governsID: 1.3.6.1.4.1.34955.2.10 +cn: takUser +lDAPDisplayName: takUser +subClassOf: top +objectClassCategory: 3 +mustContain: cn +mayContain: takCallsign +mayContain: takRole +mayContain: takColor +description: TAK user +possSuperiors: top + +dn: +changetype: modify +add: schemaUpdateNow +schemaUpdateNow: 1 +- + +dn: CN=User,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +changetype: modify +add: auxiliaryClass +auxiliaryClass: takUser +- + +dn: +changetype: modify +add: schemaUpdateNow +schemaUpdateNow: 1 +- From 21db31d43decc1ea4f2802f6c21204070c27512b Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 25 Mar 2025 11:21:14 +0100 Subject: [PATCH 16/31] TAK support --- lam/docs/schema/tak-Samba4-attributes.ldif | 58 +++++++++++++++++++++ lam/docs/schema/tak-Samba4-objectClass.ldif | 36 +++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 lam/docs/schema/tak-Samba4-attributes.ldif create mode 100644 lam/docs/schema/tak-Samba4-objectClass.ldif diff --git a/lam/docs/schema/tak-Samba4-attributes.ldif b/lam/docs/schema/tak-Samba4-attributes.ldif new file mode 100644 index 000000000..38b93523a --- /dev/null +++ b/lam/docs/schema/tak-Samba4-attributes.ldif @@ -0,0 +1,58 @@ +# +# LDAP schema for LAM TAK functionality +# +# This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) +# Copyright (C) 2025 Roland Gruber +# +# +# OID bases: +# 1.3.6.1.4.1.34955 Roland Gruber Softwareentwicklung +# 1.3.6.1.4.1.34955.1 attributes +# 1.3.6.1.4.1.34955.2 object classes +# +# Please replace DOMAIN_TOP_DN with your LDAP suffix (e.g. dc=samba4,dc=test). +# This file must be installed first. +# +# Installation: ldbmodify -H /var/lib/samba/private/sam.ldb tak-Samba4-attributes.ldif --option="dsdb:schema update allowed"=true +# +# +# Version: 1 +# 1: initial release (LAM 9.2) +# + +dn: CN=takCallsign,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.100 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +rangeLower: 4 +cn: takCallsign +name: takCallsign +lDAPDisplayName: takCallsign +description: TAK callsign + +dn: CN=takRole,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.101 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +cn: takRole +name: takRole +lDAPDisplayName: takRole +description: TAK team role + +dn: CN=takColor,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +objectClass: top +objectClass: attributeSchema +attributeID: 1.3.6.1.4.1.34955.1.102 +attributeSyntax: 2.5.5.12 +oMSyntax: 64 +isSingleValued: TRUE +cn: takColor +name: takColor +LDAPDisplayName: takColor +Description: TAK team color diff --git a/lam/docs/schema/tak-Samba4-objectClass.ldif b/lam/docs/schema/tak-Samba4-objectClass.ldif new file mode 100644 index 000000000..25d165988 --- /dev/null +++ b/lam/docs/schema/tak-Samba4-objectClass.ldif @@ -0,0 +1,36 @@ +# +# LDAP schema for LAM TAK functionality +# +# This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) +# Copyright (C) 2025 Roland Gruber +# +# +# OID bases: +# 1.3.6.1.4.1.34955 Roland Gruber Softwareentwicklung +# 1.3.6.1.4.1.34955.1 attributes +# 1.3.6.1.4.1.34955.2 object classes +# +# Please replace DOMAIN_TOP_DN with your LDAP suffix (e.g. dc=samba4,dc=test). +# This file must be installed second. +# +# Installation: ldbmodify -H /var/lib/samba/private/sam.ldb tak-Samba4-objectClass.ldif --option="dsdb:schema update allowed"=true +# +# +# Version: 1 +# 1: initial release (LAM 9.2) +# + +dn: CN=takUser,CN=Schema,CN=Configuration,DOMAIN_TOP_DN +objectClass: top +objectClass: classSchema +governsID: 1.3.6.1.4.1.34955.2.10 +cn: takUser +lDAPDisplayName: takUser +subClassOf: top +objectClassCategory: 3 +mustContain: cn +mayContain: takCallsign +mayContain: takRole +mayContain: takColor +description: TAK user +possSuperiors: top From 26a05afc9dbf7a49b8995d3c4783b7a59ae31888 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 25 Mar 2025 11:33:12 +0100 Subject: [PATCH 17/31] TAK support --- lam/docs/schema/tak-OpenLDAP.ldif | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lam/docs/schema/tak-OpenLDAP.ldif diff --git a/lam/docs/schema/tak-OpenLDAP.ldif b/lam/docs/schema/tak-OpenLDAP.ldif new file mode 100644 index 000000000..a9d69372c --- /dev/null +++ b/lam/docs/schema/tak-OpenLDAP.ldif @@ -0,0 +1,33 @@ +# +# LDAP schema for LAM TAK functionality +# +# This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) +# Copyright (C) 2025 Roland Gruber +# +# +# OID bases: +# 1.3.6.1.4.1.34955 Roland Gruber Softwareentwicklung +# 1.3.6.1.4.1.34955.1 attributes +# 1.3.6.1.4.1.34955.2 object classes +# +# Installation: +# ldapadd -x -W -H ldap://localhost -D "cn=admin,dc=company,dc=com" -f tak-OpenLDAP.ldif +# +# Please replace "localhost" with your LDAP server and "cn=admin,dc=company,dc=com" with your LDAP admin user (usually starts with cn=admin or cn=manager). +# +# In some cases you might need to import directly on the OpenLDAP server as root: +# ldapadd -Y EXTERNAL -H ldapi:/// -f tak-OpenLDAP.ldif +# +# Version: 1 +# +# Changelog: +# 1: initial release (LAM 9.2) +# + +dn: cn=tak,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: tak +olcAttributeTypes: ( 1.3.6.1.4.1.34955.1.100 NAME 'takCallsign' DESC 'TAK callsign' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.4.1.34955.1.101 NAME 'takRole' DESC 'TAK team role' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.4.1.34955.1.102 NAME 'takColor' DESC 'TAK team color' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcObjectClasses: ( 1.3.6.1.4.1.34955.2.10 NAME 'takUser' DESC 'TAK user' SUP top AUXILIARY MAY ( takCallsign $ takRole $ takColor ) MUST ( cn ) ) From 3f1d6d8ae62faf8fa5c58ed4b6669e7fb4fc1ef8 Mon Sep 17 00:00:00 2001 From: gruberroland Date: Wed, 26 Mar 2025 07:52:40 +0100 Subject: [PATCH 18/31] Update takUser.inc --- lam/lib/modules/takUser.inc | 122 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 69 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index cb1e5c667..32b0e5e40 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -2,7 +2,8 @@ /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2024 Mark Halliday + Copyright (C) 2025 Mark Halliday + 2025 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 @@ -20,17 +21,17 @@ */ +use LAM\TYPES\ConfiguredType; + /** * Manages the object class "takuser" for users. * * @package modules * * @author Mark Halliday + * @author Roland Gruber */ - -use LAM\TYPES\ConfiguredType; - -class takuser extends baseModule { +class takUser extends baseModule { /** * Returns true if this module can manage accounts of the current type, otherwise false. @@ -42,10 +43,10 @@ class takuser extends baseModule { } /** possible roles */ - private $roletypes = ['Team Member', 'Team Leader', 'HQ', 'RTO', 'K9', 'Medic', 'Forward Observer', 'Sniper']; + private const ROLE_TYPES = ['Team Member', 'Team Leader', 'HQ', 'RTO', 'K9', 'Medic', 'Forward Observer', 'Sniper']; /** possible colors */ - private $colortypes = ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow']; + private const COLOR_TYPES = ['Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Green', 'Green', 'Magenta', 'Maroon', 'Orange', 'Purple', 'Red', 'Teal', 'White', 'Yellow']; @@ -56,37 +57,35 @@ class takuser extends baseModule { * * @see baseModule::get_metaData() */ - function get_metaData() { + public function get_metaData() { $return = []; // icon $return['icon'] = 'tak.png'; // alias name $return["alias"] = _("TAK"); - // this is a base module - $return["is_base"] = false; // LDAP filter - $return["ldap_filter"] = ['or' => "(objectClass=takuser)"]; + $return["ldap_filter"] = ['or' => "(objectClass=takUser)"]; // RDN attribute $return["RDN"] = ["cn" => "normal"]; // module dependencies $return['dependencies'] = ['depends' => [], 'conflicts' => []]; // managed object classes - $return['objectClasses'] = ['takuser']; + $return['objectClasses'] = ['takUser']; // managed attributes $return['attributes'] = ['takcallsign', 'takrole', 'takcolor' ]; // help Entries $return['help'] = [ 'takcallsign' => [ - "Headline" => _("TAK callsign"), 'attr' => 'takcallsign', + "Headline" => _("Callsign"), 'attr' => 'takCallsign', "Text" => _("The user's callsign to be displayed to other TAK users. It must be unique.") ], 'takrole' => [ - "Headline" => _("TAK team role"), 'attr' => 'takrole', + "Headline" => _("Team role"), 'attr' => 'takRole', "Text" => _("The user's role to be displayed to other TAK users.") ], 'takcolor' => [ - "Headline" => _("TAK Team color"), 'attr' => 'takcolor', + "Headline" => _("Team color"), 'attr' => 'takColor', "Text" => _("The user's team color to be displayed to other TAK users.") ], ]; @@ -94,41 +93,41 @@ class takuser extends baseModule { $return['upload_columns'] = [ [ 'name' => 'takuser_takcallsign', - 'description' => _('TAK callsign'), + 'description' => _('Callsign'), 'help' => 'takcallsign', 'example' => _('UK-ORG-01'), 'required' => true ], [ 'name' => 'takuser_takrole', - 'description' => _('TAK team role'), + 'description' => _('Team role'), 'help' => 'takrole', 'default' => 'Team Member', - 'values' => implode(", ", $this->roletypes), + 'values' => implode(", ", self::ROLE_TYPES), 'example' => _('Team Member'), 'required' => true ], [ 'name' => 'takuser_takcolor', - 'description' => _('TAK team color'), + 'description' => _('Team color'), 'help' => 'takcolor', 'default' => 'Cyan', - 'values' => implode(", ", $this->colortypes), + 'values' => implode(", ", self::COLOR_TYPES), 'example' => _('Cyan'), 'required' => true ], ]; // available PDF fields $return['PDF_fields'] = [ - 'takcallsign' => _('TAK callsign'), - 'takrole' => _('TAK team role'), - 'takcolor' => _('TAK team color'), + 'takcallsign' => _('Callsign'), + 'takrole' => _('Team role'), + 'takcolor' => _('Team color'), ]; // profile options $profileContainer = new htmlResponsiveRow(); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Callsign'), 'takuser_takcallsign', null, 'takusercallsign'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('TAK Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Callsign'), 'takuser_takcallsign', null, 'takusercallsign'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); $return['profile_options'] = $profileContainer; /** * not working: will not allow - @@ -144,63 +143,50 @@ class takuser extends baseModule { /** * This function fills the $messages variable with output messages from this module. */ - function load_Messages() { - $this->messages['takcallsign'][0] = ['ERROR', _('User name'), _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + public function load_Messages() { + $this->messages['takcallsign'][0] = ['ERROR', _('Callsign'), _('Callsign contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; $this->messages['takcallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; - $this->messages['takcallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose another callsign.')]; + $this->messages['takcallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose a different callsign.')]; $this->messages['takrole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; $this->messages['takcolor'][1] = ['ERROR', _('Please select a team color.')]; } /** - * Returns the HTML meta data for the main account page. - * - * @return array HTML meta data + * {@inheritDoc} */ - function display_html_attributes() { + public function display_html_attributes() { $return = new htmlResponsiveRow(); - // Section title - $return->add(new htmlSubTitle(_('General'))); // takCallsign - $this->addSimpleInputTextField($return, 'takcallsign', _('TAK callsign'), true); + $this->addSimpleInputTextField($return, 'takcallsign', _('Callsign'), true); // takRole - $return->add(new htmlResponsiveSelect('takrole', $this->roletypes, _('TAK team role'), 'takrole'), 12); + $takRole = $this->attributes['takrole'][0] ?? ''; + $return->add(new htmlResponsiveSelect('takrole', self::ROLE_TYPES, [$takRole], _('Team role'), 'takrole')); //takColor - $return->add(new htmlResponsiveSelect('takcolor', $this->colortypes, ['Cyan'], _('TAK team color'), 'takcolor'), 12); + $takColor = $this->attributes['takcolor'][0] ?? 'Cyan'; + $return->add(new htmlResponsiveSelect('takcolor', self::COLOR_TYPES, [$takColor], _('Team color'), 'takcolor')); return $return; } /** - * Processes user input of the primary module page. - * It checks if all input values are correct and updates the associated LDAP attributes. - * - * @return array list of info/error messages + * {@inheritDoc} */ - function process_attributes() { + public function process_attributes() { $return = []; $this->attributes['takcallsign'][0] = $_POST['takcallsign']; $this->attributes['takrole'][0] = $_POST['takrole']; $this->attributes['takcolor'][0] = $_POST['takcolor']; - // check if takcallsign is filled + // check if callsign is filled if ($_POST['takcallsign'] == '') { $return[] = $this->messages['takcallsign'][0]; } - // check if takcallsign is unique - else if (empty($this->orig['takcallsign'][0]) || ($this->attributes['takcallsign'][0] != $this->orig['takcallsign'][0])) { - $suffix = $_POST['accountContainerSuffix']; + // check if callsign is unique + else if (empty($this->orig['takcallsign'][0]) || ($this->attributes['takcallsign'][0] !== $this->orig['takcallsign'][0])) { + $suffix = $this->getAccountContainer()->get_type()->getSuffix(); $search = searchLDAP($suffix, 'takcallsign=' . $_POST['takcallsign'], ['dn']); if (sizeof($search) > 0) { $return[] = $this->messages['takcallsign'][2]; } } - // check if takrole is filled - if ($_POST['takrole'] == '') { - $return[] = $this->messages['takrole'][1]; - } - // check if takcolor is filled - if ($_POST['takcolor'] == '') { - $return[] = $this->messages['takcolor'][1]; - } return $return; } @@ -208,12 +194,12 @@ class takuser extends baseModule { * {@inheritDoc} * @see baseModule::build_uploadAccounts() */ - function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { + public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { $errors = []; for ($i = 0; $i < sizeof($rawAccounts); $i++) { // add object class - if (!in_array('takuser', $partialAccounts[$i]['objectClass'])) { - $partialAccounts[$i]['objectClass'][] = 'takuser'; + if (!in_array('takUser', $partialAccounts[$i]['objectClass'])) { + $partialAccounts[$i]['objectClass'][] = 'takUser'; } // takcallsign $partialAccounts[$i]['takcallsign'] = $rawAccounts[$i][$ids['takuser_takcallsign']]; @@ -229,20 +215,18 @@ class takuser extends baseModule { * {@inheritDoc} * @see baseModule::get_pdfEntries() */ - function get_pdfEntries($pdfKeys, $typeId) { + public function get_pdfEntries($pdfKeys, $typeId) { $return = []; - $this->addSimplePDFField($return, 'takcallsign', _('TAK callsign')); - $this->addSimplePDFField($return, 'takrole', _('TAK team role')); - $this->addSimplePDFField($return, 'takcolor', _('TAK team color')); + $this->addSimplePDFField($return, 'takcallsign', _('Callsign')); + $this->addSimplePDFField($return, 'takrole', _('Team role')); + $this->addSimplePDFField($return, 'takcolor', _('Team color')); return $return; } /** - * Loads the values of an account profile into internal variables. - * - * @param array $profile hash array with profile values (identifier => value) + * @inheritDoc */ - function load_profile($profile) { + public function load_profile($profile) { // profile mappings in meta data parent::load_profile($profile); // special profile options @@ -262,9 +246,9 @@ class takuser extends baseModule { */ public function getListAttributeDescriptions(ConfiguredType $type): array { return [ - "takcallsign" => _("TAK callsign"), - "takrole" => _("TAK team role"), - "takcolor" => _("TAK team color"), + "takcallsign" => _("Callsign"), + "takrole" => _("Team role"), + "takcolor" => _("Team color"), ]; } } From 64ca7d8d2833515ad12081933dc7f658054ec310 Mon Sep 17 00:00:00 2001 From: gruberroland Date: Wed, 26 Mar 2025 07:58:16 +0100 Subject: [PATCH 19/31] Update takUser.inc --- lam/lib/modules/takUser.inc | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index 32b0e5e40..c4e665993 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -24,7 +24,7 @@ use LAM\TYPES\ConfiguredType; /** - * Manages the object class "takuser" for users. + * Manages the object class "takUser" for users. * * @package modules * @@ -129,14 +129,6 @@ class takUser extends baseModule { $profileContainer->add(new htmlResponsiveInputField(_('Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); $profileContainer->add(new htmlResponsiveInputField(_('Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); $return['profile_options'] = $profileContainer; - /** - * not working: will not allow - - * // profile checks - * $return['profile_checks']['takUser_takCallsign'] = [ - * 'type' => 'ext_preg', - * 'regex' => 'ascii', - * 'error_message' => $this->messages['takCallsign'][0]]; - */ return $return; } @@ -230,14 +222,14 @@ class takUser extends baseModule { // profile mappings in meta data parent::load_profile($profile); // special profile options - if (isset($profile['takuser_takcallsign'][0]) && ($profile['takuser_takcallsign'][0] != '')) { - $this->attributes['takcallsign'] = $profile['takuser_takcallsign'][0]; + if (!empty($profile['takuser_takcallsign'][0])) { + $this->attributes['takcallsign'][0] = $profile['takuser_takcallsign'][0]; } - if (isset($profile['takuser_takrole'][0]) && ($profile['takuser_takrole'][0] != '')) { - $this->attributes['takrole'] = $profile['takuser_takrole'][0]; + if (!empty($profile['takuser_takrole'][0])) { + $this->attributes['takrole'][0] = $profile['takuser_takrole'][0]; } - if (isset($profile['takuser_takcolor'][0]) && ($profile['takuser_takcolor'][0] != '')) { - $this->attributes['takcolor'] = $profile['takuser_takcolor'][0]; + if (!empty($profile['takuser_takcolor'][0])) { + $this->attributes['takcolor'][0] = $profile['takuser_takcolor'][0]; } } From 59cd3d7b7857873b24e36bab37d949ffb60b80e2 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 16:53:11 +0100 Subject: [PATCH 20/31] fixed validation --- lam/lib/baseModule.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lam/lib/baseModule.inc b/lam/lib/baseModule.inc index e77c7ae81..a122fc4b5 100644 --- a/lam/lib/baseModule.inc +++ b/lam/lib/baseModule.inc @@ -1799,7 +1799,7 @@ abstract class baseModule { * @param array $container return value of checkSelfServiceOptions() * @param String $name attribute name * @param array $attributes LDAP attributes - * @param string $fields input fields + * @param array $fields input fields * @param array $readOnlyFields list of read-only fields * @param String $validationID validation ID for get_preg() * @param array $validationMessage validation message data (defaults to $this->messages[$name][0]) @@ -1822,11 +1822,11 @@ abstract class baseModule { $container['add'][$ldapAttrName] = [$_POST[$fieldName]]; } } - elseif (isset($attributes[$ldapAttrName])) { + else { if ($requiredMessage !== null) { $container['messages'][] = $requiredMessage; } - else { + elseif (isset($attributes[$ldapAttrName])) { $container['del'][$ldapAttrName] = $attributes[$ldapAttrName]; } } From f8521ed1d1f66d9abb83092eeda0b82f431b65e1 Mon Sep 17 00:00:00 2001 From: gruberroland Date: Thu, 27 Mar 2025 16:54:19 +0100 Subject: [PATCH 21/31] self-service --- lam/lib/modules/takUser.inc | 105 +++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index c4e665993..e49ad6c04 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -39,7 +39,7 @@ class takUser extends baseModule { * @return boolean true if module fits */ public function can_manage() { - return in_array($this->get_scope(), ['user']); + return $this->get_scope() == 'user'; } /** possible roles */ @@ -51,11 +51,7 @@ class takUser extends baseModule { /** - * Returns meta data that is interpreted by parent class - * - * @return array array with meta data - * - * @see baseModule::get_metaData() + * {@inheritDoc} */ public function get_metaData() { $return = []; @@ -95,8 +91,9 @@ class takUser extends baseModule { 'name' => 'takuser_takcallsign', 'description' => _('Callsign'), 'help' => 'takcallsign', - 'example' => _('UK-ORG-01'), - 'required' => true + 'example' => 'UK-ORG-01', + 'required' => true, + 'unique' => true ], [ 'name' => 'takuser_takrole', @@ -104,7 +101,7 @@ class takUser extends baseModule { 'help' => 'takrole', 'default' => 'Team Member', 'values' => implode(", ", self::ROLE_TYPES), - 'example' => _('Team Member'), + 'example' => 'Team Member', 'required' => true ], [ @@ -113,7 +110,7 @@ class takUser extends baseModule { 'help' => 'takcolor', 'default' => 'Cyan', 'values' => implode(", ", self::COLOR_TYPES), - 'example' => _('Cyan'), + 'example' => 'Cyan', 'required' => true ], ]; @@ -129,16 +126,22 @@ class takUser extends baseModule { $profileContainer->add(new htmlResponsiveInputField(_('Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); $profileContainer->add(new htmlResponsiveInputField(_('Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); $return['profile_options'] = $profileContainer; + // self-service field settings + $return['selfServiceFieldSettings'] = [ + 'takcallsign' => _('Callsign'), + 'takrole' => _('Team role'), + 'takcolor' => _('Team color'), + ]; return $return; } /** - * This function fills the $messages variable with output messages from this module. + * {@inheritDoc} */ public function load_Messages() { $this->messages['takcallsign'][0] = ['ERROR', _('Callsign'), _('Callsign contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; - $this->messages['takcallsign'][1] = ['ERROR', _('Please enter a unique callsign.')]; - $this->messages['takcallsign'][2] = ['ERROR', _('A TAK login with this callsign already exists. Please choose a different callsign.')]; + $this->messages['takcallsign'][1] = ['ERROR', _('A TAK login with this callsign already exists. Please choose a different callsign.')]; + $this->messages['takcallsign'][2] = ['ERROR', _('Callsign'), _('This field is required.')]; $this->messages['takrole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; $this->messages['takcolor'][1] = ['ERROR', _('Please select a team color.')]; } @@ -169,6 +172,9 @@ class takUser extends baseModule { $this->attributes['takcolor'][0] = $_POST['takcolor']; // check if callsign is filled if ($_POST['takcallsign'] == '') { + $return[] = $this->messages['takcallsign'][2]; + } + elseif (!get_preg($_POST['takcallsign'], 'username')) { $return[] = $this->messages['takcallsign'][0]; } // check if callsign is unique @@ -176,7 +182,7 @@ class takUser extends baseModule { $suffix = $this->getAccountContainer()->get_type()->getSuffix(); $search = searchLDAP($suffix, 'takcallsign=' . $_POST['takcallsign'], ['dn']); if (sizeof($search) > 0) { - $return[] = $this->messages['takcallsign'][2]; + $return[] = $this->messages['takcallsign'][1]; } } return $return; @@ -233,6 +239,76 @@ class takUser extends baseModule { } } + /** + * @inheritDoc + */ + public function getSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) { + $return = []; + if ($passwordChangeOnly) { + return $return; // only password fields as long no LDAP content can be read + } + if (!in_array_ignore_case('takUser', $attributes['objectClass'])) { + return $return; + } + $this->addSimpleSelfServiceTextField($return, 'takcallsign', _('Callsign'), $fields, $attributes, $readOnlyFields, true); + if (in_array('takrole', $fields)) { + $role = ''; + if (isset($attributes['takrole'][0])) { + $role = $attributes['takrole'][0]; + } + if (in_array('takrole', $readOnlyFields)) { + $field = new htmlOutputText($role); + } + else { + $selected = []; + if (!empty($attributes['takrole'][0])) { + $selected = [$attributes['takrole'][0]]; + } + $field = new htmlSelect('takUser_takrole', self::ROLE_TYPES, $selected); + } + $return['takrole'] = new htmlResponsiveRow( + new htmlLabel('takUser_takrole', $this->getSelfServiceLabel('takrole', _('Team role'))), $field + ); + } + if (in_array('takcolor', $fields)) { + $color = ''; + if (isset($attributes['takcolor'][0])) { + $color = $attributes['takcolor'][0]; + } + if (in_array('takcolor', $readOnlyFields)) { + $field = new htmlOutputText($color); + } + else { + $selected = []; + if (!empty($attributes['takcolor'][0])) { + $selected = [$attributes['takcolor'][0]]; + } + $field = new htmlSelect('takUser_takcolor', self::COLOR_TYPES, $selected); + } + $return['takcolor'] = new htmlResponsiveRow( + new htmlLabel('takUser_takcolor', $this->getSelfServiceLabel('takcolor', _('Team color'))), $field + ); + } + return $return; + } + + /** + * @inheritDoc + */ + public function checkSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) { + $return = ['messages' => [], 'add' => [], 'del' => [], 'mod' => [], 'info' => []]; + if ($passwordChangeOnly) { + return $return; // skip processing if only a password change is done + } + if (!in_array_ignore_case('takUser', $attributes['objectClass'])) { + return $return; + } + $this->checkSimpleSelfServiceTextField($return, 'takcallsign', $attributes, $fields, $readOnlyFields, 'username', $this->messages['takcallsign'][0], $this->messages['takcallsign'][2]); + $this->checkSimpleSelfServiceTextField($return, 'takrole', $attributes, $fields, $readOnlyFields); + $this->checkSimpleSelfServiceTextField($return, 'takcolor', $attributes, $fields, $readOnlyFields); + return $return; + } + /** * @inheritDoc */ @@ -243,4 +319,5 @@ class takUser extends baseModule { "takcolor" => _("Team color"), ]; } + } From da0009ac9df6fd233b4be0d40dd6960831b5da3e Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 17:04:07 +0100 Subject: [PATCH 22/31] fixed validation --- lam/templates/upload/massBuildAccounts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lam/templates/upload/massBuildAccounts.php b/lam/templates/upload/massBuildAccounts.php index 8e437f837..71af045f7 100644 --- a/lam/templates/upload/massBuildAccounts.php +++ b/lam/templates/upload/massBuildAccounts.php @@ -13,7 +13,7 @@ use LamTemporaryFilesManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2004 - 2024 Roland Gruber + Copyright (C) 2004 - 2025 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 @@ -155,7 +155,7 @@ if ($_FILES['inputfile'] && ($_FILES['inputfile']['size'] > 0)) { $checkcolumns = []; $columns = []; foreach ($uploadColumns as $uploadColumn) { - $columns = array_merge($columns, $uploadColumns); + $columns = array_merge($columns, $uploadColumn); } foreach ($columns as $column) { if (isset($column['required']) && ($column['required'] === true)) { From 96de462359f44a427d9f35d0bb463016660d436d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 19:44:21 +0100 Subject: [PATCH 23/31] refactoring --- lam/lib/baseModule.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lam/lib/baseModule.inc b/lam/lib/baseModule.inc index a122fc4b5..7ac10822b 100644 --- a/lam/lib/baseModule.inc +++ b/lam/lib/baseModule.inc @@ -1034,7 +1034,7 @@ abstract class baseModule { if (!empty($regex)) { $this->checkUploadRegex($regexIDs, $rawAccounts[$position][$ids[$colName]], $message, $position, $errors); } - $partialAccounts[$position][$attrName] = trim($rawAccounts[$position][$ids[$colName]]); + $partialAccounts[$position][$attrName] = [trim($rawAccounts[$position][$ids[$colName]])]; } // multi-value else { From 50328a22b2914a8bad3cae2232e96587cfe8148e Mon Sep 17 00:00:00 2001 From: gruberroland Date: Thu, 27 Mar 2025 19:44:59 +0100 Subject: [PATCH 24/31] upload --- lam/lib/modules/takUser.inc | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index e49ad6c04..fc08b38cd 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -39,7 +39,7 @@ class takUser extends baseModule { * @return boolean true if module fits */ public function can_manage() { - return $this->get_scope() == 'user'; + return $this->get_scope() === 'user'; } /** possible roles */ @@ -122,9 +122,9 @@ class takUser extends baseModule { ]; // profile options $profileContainer = new htmlResponsiveRow(); - $profileContainer->add(new htmlResponsiveInputField(_('Callsign'), 'takuser_takcallsign', null, 'takusercallsign'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('Team Role'), 'takuser_takrole', null, 'takuserrole'), 12); - $profileContainer->add(new htmlResponsiveInputField(_('Team Color'), 'takuser_takcolor', null, 'takusercolor'), 12); + $profileContainer->add(new htmlResponsiveInputField(_('Callsign'), 'takuser_takcallsign', null, 'takusercallsign')); + $profileContainer->add(new htmlResponsiveInputField(_('Team Role'), 'takuser_takrole', null, 'takuserrole')); + $profileContainer->add(new htmlResponsiveInputField(_('Team Color'), 'takuser_takcolor', null, 'takusercolor')); $return['profile_options'] = $profileContainer; // self-service field settings $return['selfServiceFieldSettings'] = [ @@ -142,8 +142,9 @@ class takUser extends baseModule { $this->messages['takcallsign'][0] = ['ERROR', _('Callsign'), _('Callsign contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; $this->messages['takcallsign'][1] = ['ERROR', _('A TAK login with this callsign already exists. Please choose a different callsign.')]; $this->messages['takcallsign'][2] = ['ERROR', _('Callsign'), _('This field is required.')]; - $this->messages['takrole'][1] = ['ERROR', _('Role'), _('Please select a role.')]; - $this->messages['takcolor'][1] = ['ERROR', _('Please select a team color.')]; + $this->messages['takcallsign'][3] = ['ERROR', _('Account %s:') . ' takuser_takcallsign', _('Callsign contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_')]; + $this->messages['takrole'][0] = ['ERROR', _('Account %s:') . ' takuser_takrole', _('Please enter a valid option:') . ' ' . implode(', ', self::ROLE_TYPES)]; + $this->messages['takcolor'][0] = ['ERROR', _('Account %s:') . ' takuser_takcolor', _('Please enter a valid option:') . ' ' . implode(', ', self::COLOR_TYPES)]; } /** @@ -200,11 +201,15 @@ class takUser extends baseModule { $partialAccounts[$i]['objectClass'][] = 'takUser'; } // takcallsign - $partialAccounts[$i]['takcallsign'] = $rawAccounts[$i][$ids['takuser_takcallsign']]; - // takrole - $partialAccounts[$i]['takrole'] = $rawAccounts[$i][$ids['takuser_takrole']]; - // takcolor - $partialAccounts[$i]['takcolor'] = $rawAccounts[$i][$ids['takuser_takcolor']]; + $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'takuser_takcallsign', 'takcallsign', 'username', $this->messages['takcallsign'][3], $errors); + $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'takuser_takrole', 'takrole', null, null, $errors); + if (!in_array($partialAccounts[$i]['takrole'][0], self::ROLE_TYPES)) { + $errors[] = array_merge($this->messages['takrole'][0], [[$i]]); + } + $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'takuser_takcolor', 'takcolor', null, null, $errors); + if (!in_array($partialAccounts[$i]['takcolor'][0], self::COLOR_TYPES)) { + $errors[] = array_merge($this->messages['takcolor'][0], [[$i]]); + } } return $errors; } From a68f78fbece7ff025c5383fb10c281feba02706d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 20:10:29 +0100 Subject: [PATCH 25/31] TAK support --- lam/HISTORY | 1 + lam/docs/manual-sources/chapter-modules.xml | 25 ++++++++++++++++++++ lam/docs/manual-sources/images/mod_tak1.png | Bin 0 -> 32798 bytes lam/docs/manual-sources/images/mod_tak2.png | Bin 0 -> 22630 bytes 4 files changed, 26 insertions(+) create mode 100644 lam/docs/manual-sources/images/mod_tak1.png create mode 100644 lam/docs/manual-sources/images/mod_tak2.png diff --git a/lam/HISTORY b/lam/HISTORY index 7519e8c56..1b6a16b84 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,4 +1,5 @@ June 2025 9.2 + - TAK support added - Active Directory: allow to restore deleted entries in tree view (415) - Fixed bugs: -> Unix: profile editor for users not working (418) diff --git a/lam/docs/manual-sources/chapter-modules.xml b/lam/docs/manual-sources/chapter-modules.xml index cca247743..bbe659032 100644 --- a/lam/docs/manual-sources/chapter-modules.xml +++ b/lam/docs/manual-sources/chapter-modules.xml @@ -2510,6 +2510,31 @@ AuthorizedKeysCommandUser root + +
+ TAK + + The TAK module + supports the Team Awareness Kit or Tactical Assault Kit (TAK) with the + Android Team Awareness Kit (ATAK). + + You can define callsigns, team roles and colors for users. + + Add the TAK module for users in your server profile: + + + + + + Now you can manage the TAK attributes for users. + + LAM Pro users can add these attributes to the self-service profile + if needed. + + + + +
diff --git a/lam/docs/manual-sources/images/mod_tak1.png b/lam/docs/manual-sources/images/mod_tak1.png new file mode 100644 index 0000000000000000000000000000000000000000..5caf6d0a438a4bceab59afd80d16659e38a2bea1 GIT binary patch literal 32798 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 DqEV4j literal 0 HcmV?d00001 diff --git a/lam/docs/manual-sources/images/mod_tak2.png b/lam/docs/manual-sources/images/mod_tak2.png new file mode 100644 index 0000000000000000000000000000000000000000..a5e97b7c0028cb135b5c2a19bf581d14221c8e11 GIT binary patch literal 22630 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%0A1~T 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 Date: Thu, 27 Mar 2025 20:23:36 +0100 Subject: [PATCH 26/31] refactoring --- lam/lib/baseModule.inc | 12 +++++------- lam/lib/modules/takUser.inc | 7 +++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lam/lib/baseModule.inc b/lam/lib/baseModule.inc index 7ac10822b..6e6e47e52 100644 --- a/lam/lib/baseModule.inc +++ b/lam/lib/baseModule.inc @@ -1822,13 +1822,11 @@ abstract class baseModule { $container['add'][$ldapAttrName] = [$_POST[$fieldName]]; } } - else { - if ($requiredMessage !== null) { - $container['messages'][] = $requiredMessage; - } - elseif (isset($attributes[$ldapAttrName])) { - $container['del'][$ldapAttrName] = $attributes[$ldapAttrName]; - } + elseif ($requiredMessage !== null) { + $container['messages'][] = $requiredMessage; + } + elseif (isset($attributes[$ldapAttrName])) { + $container['del'][$ldapAttrName] = $attributes[$ldapAttrName]; } } } diff --git a/lam/lib/modules/takUser.inc b/lam/lib/modules/takUser.inc index fc08b38cd..ede66a210 100644 --- a/lam/lib/modules/takUser.inc +++ b/lam/lib/modules/takUser.inc @@ -179,10 +179,10 @@ class takUser extends baseModule { $return[] = $this->messages['takcallsign'][0]; } // check if callsign is unique - else if (empty($this->orig['takcallsign'][0]) || ($this->attributes['takcallsign'][0] !== $this->orig['takcallsign'][0])) { + elseif (empty($this->orig['takcallsign'][0]) || ($this->attributes['takcallsign'][0] !== $this->orig['takcallsign'][0])) { $suffix = $this->getAccountContainer()->get_type()->getSuffix(); $search = searchLDAP($suffix, 'takcallsign=' . $_POST['takcallsign'], ['dn']); - if (sizeof($search) > 0) { + if (!empty($search)) { $return[] = $this->messages['takcallsign'][1]; } } @@ -195,12 +195,11 @@ class takUser extends baseModule { */ public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { $errors = []; - for ($i = 0; $i < sizeof($rawAccounts); $i++) { + for ($i = 0; $i < count($rawAccounts); $i++) { // add object class if (!in_array('takUser', $partialAccounts[$i]['objectClass'])) { $partialAccounts[$i]['objectClass'][] = 'takUser'; } - // takcallsign $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'takuser_takcallsign', 'takcallsign', 'username', $this->messages['takcallsign'][3], $errors); $this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'takuser_takrole', 'takrole', null, null, $errors); if (!in_array($partialAccounts[$i]['takrole'][0], self::ROLE_TYPES)) { From 6fb6f71ccc58827cc77886a00841089194e4f897 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 20:25:30 +0100 Subject: [PATCH 27/31] refactoring --- phpstan.neon | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 5e35a2ff9..9f05bfdc6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -27,7 +27,6 @@ parameters: - '#Call to an undefined method object::.*#' - '#Parameter \#2 \$string of function explode expects string, .* given.#' - '#Parameter \#2 \$result of function ldap_.* expects LDAP\\Result, array\|LDAP\\Result given.#' - - '#Cannot access an offset on mixed.#' - '#Cannot access offset .* on mixed.#' - '#Cannot access offset .* on array\|int.#' - '#Cannot access an offset on array\|Countable.#' @@ -47,4 +46,3 @@ parameters: - '#Binary operation .* between .* and .* results in an error.#' - '#Parameter \#. .* of (function|method) .* expects .*, mixed given.#' - '#Cannot access property .* on mixed#' - - '#Cannot use \+\+ on mixed.#' From f9582cb97ccdb913e7b8ebab574e8fe0f20b8711 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 20:37:29 +0100 Subject: [PATCH 28/31] docs --- lam-packaging/debian/copyright | 4 ++++ lam/copyright | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lam-packaging/debian/copyright b/lam-packaging/debian/copyright index d7fbad28d..b5abe01ac 100644 --- a/lam-packaging/debian/copyright +++ b/lam-packaging/debian/copyright @@ -17,6 +17,8 @@ time. * lib/modules/automount.inc * lib/modules/bindDLZ.inc * lib/modules/bindDLZXfr.inc +* lib/modules/bindDyndbRecord.inc +* lib/modules/bindDyndbZone.inc * lib/modules/customBaseType.inc * lib/modules/customFields.inc * lib/modules/customScripts.inc @@ -56,6 +58,7 @@ time. * lib/modules/rfc2307bisAutomount.inc * lib/modules/rfc2307bisPosixGroup.inc * lib/modules/selfRegistration.inc +* lib/modules/simpleSecurityObject.inc * lib/modules/sudoRole.inc * lib/modules/uidObject.inc * lib/modules/webauthn.inc @@ -64,6 +67,7 @@ time. * lib/types/alias.inc * lib/types/automountType.inc * lib/types/bind.inc +* lib/types/bindDyndbType.inc * lib/types/customType.inc * lib/types/gon.inc * lib/types/kopanoAddressListType.inc diff --git a/lam/copyright b/lam/copyright index 5006b917f..65fdf483e 100644 --- a/lam/copyright +++ b/lam/copyright @@ -17,6 +17,8 @@ time. * lib/modules/automount.inc * lib/modules/bindDLZ.inc * lib/modules/bindDLZXfr.inc +* lib/modules/bindDyndbRecord.inc +* lib/modules/bindDyndbZone.inc * lib/modules/customBaseType.inc * lib/modules/customFields.inc * lib/modules/customScripts.inc @@ -56,6 +58,7 @@ time. * lib/modules/rfc2307bisAutomount.inc * lib/modules/rfc2307bisPosixGroup.inc * lib/modules/selfRegistration.inc +* lib/modules/simpleSecurityObject.inc * lib/modules/sudoRole.inc * lib/modules/uidObject.inc * lib/modules/webauthn.inc @@ -64,6 +67,7 @@ time. * lib/types/alias.inc * lib/types/automountType.inc * lib/types/bind.inc +* lib/types/bindDyndbType.inc * lib/types/customType.inc * lib/types/gon.inc * lib/types/kopanoAddressListType.inc From 2e95a4b05ee652fa31a8eb0ef7f2cb941cd7373c Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 27 Mar 2025 20:38:17 +0100 Subject: [PATCH 29/31] docs --- lam-packaging/debian/copyright | 2 +- lam/copyright | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lam-packaging/debian/copyright b/lam-packaging/debian/copyright index b5abe01ac..10e6b63a7 100644 --- a/lam-packaging/debian/copyright +++ b/lam-packaging/debian/copyright @@ -1,4 +1,4 @@ -This software is copyright (c) 2003 - 2024 by Roland Gruber +This software is copyright (c) 2003 - 2025 by Roland Gruber If you purchased a copy of LDAP Account Manager Pro then the following files are licensed under the conditions which you accepted at purchase diff --git a/lam/copyright b/lam/copyright index 65fdf483e..51425b7f4 100644 --- a/lam/copyright +++ b/lam/copyright @@ -437,7 +437,7 @@ E: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -F: +F: 3-Clause BSD License Redistribution and use in source and binary forms, with or without From 82a76469bced3ce1b6e73888f4c9a4d0535b65e9 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 28 Mar 2025 07:57:04 +0100 Subject: [PATCH 30/31] TAK support --- lam/docs/manual-sources/appendix-schema.xml | 24 ++++++++++++++--- lam/docs/manual-sources/chapter-modules.xml | 25 ++++++++++++++++++ lam/docs/manual-sources/images/schema_tak.png | Bin 0 -> 1273 bytes 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 lam/docs/manual-sources/images/schema_tak.png diff --git a/lam/docs/manual-sources/appendix-schema.xml b/lam/docs/manual-sources/appendix-schema.xml index d22cab80b..c369ed91e 100644 --- a/lam/docs/manual-sources/appendix-schema.xml +++ b/lam/docs/manual-sources/appendix-schema.xml @@ -467,7 +467,7 @@ dhcp.schema - docs/schema/dhcp.schema + Part of LAM installation: docs/schema/dhcp.schema The LDAP suffix should be set to your dhcpServer entry. @@ -486,7 +486,7 @@ schema.ldif - part of bind-dyndb-ldap + Part of bind-dyndb-ldap LAM Pro only @@ -505,7 +505,7 @@ dlz.schema - part of Bind + Part of Bind DLZ patch LAM Pro only @@ -821,6 +821,24 @@ LAM Pro only, requires DDS extension on LDAP server side + + + + + + + + + TAK + + takUser + + tak-*.ldif + + Part of LAM installation: docs/schema/tak-*.ldif + + + diff --git a/lam/docs/manual-sources/chapter-modules.xml b/lam/docs/manual-sources/chapter-modules.xml index bbe659032..e03e61617 100644 --- a/lam/docs/manual-sources/chapter-modules.xml +++ b/lam/docs/manual-sources/chapter-modules.xml @@ -2520,6 +2520,31 @@ AuthorizedKeysCommandUser root You can define callsigns, team roles and colors for users. + LDAP schema + + The module expects that TAK users use the object class "takUser" + and the attributes "takCallsign", "takRole" and "takColor". You can find + matching schema files in /usr/share/ldap-account-manager/docs/schema + (DEB/RPM) or docs/schema (tar.bz2). Please see the beginning of the + files for installation instructions. + + + + OpenLDAP: tak-OpenLDAP.ldif + + + + Samba 4: tak-Samba4-attributes.ldif and + tak-Samba4-objectClass.ldif + + + + Windows (AD): tak-Windows.ldif + + + + Configuration + Add the TAK module for users in your server profile: diff --git a/lam/docs/manual-sources/images/schema_tak.png b/lam/docs/manual-sources/images/schema_tak.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb41fb6b2c43e01cec821d50822530aa9ebfd4f GIT binary patch literal 1273 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1dd5WK~zXft(I9R z=WP_nO=B=JruQXFgA0iwL@9E?ShBm4`SaS^ zT0ud<&CQL9ii#qtzmt9b{5eKDIy!`4aBwi6-rnB7(|Z^{IXPKkuB4=-zrSDTL6n>y zW=TfM%F62M>S|_YMsNlP2QM!#$HvBvj*g6tjiD457XuR$6QeMIlatfJ!UD>Uj*cih zJ3Aj99`5YyY;0@{4Gn$!_6^qi_wQkmjLFGKJ`)lWegk7>W|oqY($&?4G|BYx@3p1pqterY7u$h6W!WAK{3B zfk9ne9WlXi%!!3*0wn-go12?xjf{-AxVQ){@bdHXAtogy-MNs3g~i_99;Aqfh|0=J z;v+(|y}iwCBmn#S`<%tjf(6F)^))-6?d|QUsi_#@67SxpmFVbb3JF_$`uh6f6SraQ?(RY% zAAx~^@&Otf8wm>llnP*OZf+#-SLLCqs!Ev=5D_#A0WE~Nimk1!v=%%a9i80VTuF#= zhqP;JYu{6UX=!OBK;((T^z<~s1cS#ft<~Dv8Xg`_JrEp8R!IO^DJ?A}4yLB2uU@^v z2@?~O*RNj_3~AO!^4BUqM|~|gFA&pN0Xa(kxV~7KcU>)+e_g>!8D(F zd3l@%1_s!PsuTckC_X;^CowWI5;@r*h*9I}>MB_0=jZkH^*m!SXGFGHOG`_#y0}F* z`$^|@1O)}j3emmMcXxLeEY3V~U?>0q*~Q z`EghJu*j9{>gnkLSPW6R1&j9Z_3Kx5F(gEDPESw&m4%NVKi=m$$rlAqeiswxZ#0A|_5!~{fZYio!!k4IV;YMA@*;R6Rsnct-4 z<>kV{LOz*USf_{oFY7{ie0-eQE6$ddmh9El)mVS|^5q|}E=(}#uxM{@&zXamTz`1c jhv(<#vCc?&xaofY$pgqN#&m(_00000NkvXXu0mjfxt?H2 literal 0 HcmV?d00001 From 51d643a89680fec0fc63476524427f960cdebbf6 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 30 Mar 2025 14:24:35 +0200 Subject: [PATCH 31/31] #419 switch to ldap_modify --- lam/lib/modules.inc | 106 ++++++++++-------------------- lam/lib/modules/inetOrgPerson.inc | 22 ------- lam/lib/modules/nisnetgroup.inc | 28 +------- 3 files changed, 35 insertions(+), 121 deletions(-) diff --git a/lam/lib/modules.inc b/lam/lib/modules.inc index 02d6053d4..70e4b7ef4 100644 --- a/lam/lib/modules.inc +++ b/lam/lib/modules.inc @@ -1589,87 +1589,49 @@ class accountContainer { */ function save_module_attributes($attributes, $orig) { $return = []; - $toadd = []; - $tomodify = []; - $torem = []; - $notchanged = []; - // get list of all attributes - $attr_names = array_keys($attributes); - $orig_names = array_keys($orig); + $toModify = []; + $notChanged = []; + // cleanup + foreach ($attributes as $name => &$values) { + // remove empty values + $values = array_values($values); + for ($i = 0; $i < count($values); $i++) { + if ($values[$i] === '') { + unset($values[$i]); + } + } + $values = array_values($values); + // remove empty list of values + if (sizeof($values) === 0) { + unset($attributes[$name]); + } + } // find deleted attributes (in $orig but no longer in $attributes) - foreach ($orig_names as $value) { - if (!isset($attributes[$value])) { - $torem[$value] = $orig[$value]; + foreach ($orig as $name => $value) { + if (!isset($attributes[$name]) || (sizeof($attributes[$name]) === 0)) { + $toModify[$name] = []; } } // find changed attributes - foreach ($attr_names as $name) { - // find deleted attributes - if (isset($orig[$name]) && is_array($orig[$name])) { - foreach ($orig[$name] as $value) { - if (is_array($attributes[$name])) { - if (!in_array($value, $attributes[$name], true) - && ($value !== null) - && ($value !== '')) { - $torem[$name][] = $value; - } - } - elseif (($value !== null) && ($value !== '')) { - $torem[$name][] = $value; - } - } + foreach ($attributes as $name => $value) { + // new attributes + if (!isset($orig[$name])) { + $toModify[$name] = $value; } - // find new attributes - if (isset($attributes[$name]) && is_array($attributes[$name])) { - foreach ($attributes[$name] as $value) { - if (isset($orig[$name]) && is_array($orig[$name])) { - if (!in_array($value, $orig[$name], true) - && ($value !== null) - && ($value !== '')) { - $toadd[$name][] = $value; - } - } - elseif (($value !== null) && ($value !== '')) { - $toadd[$name][] = $value; - } - } + // changed attributes + elseif (!areArrayContentsEqual($value, $orig[$name])) { + $toModify[$name] = $value; } - // find unchanged attributes - if (isset($orig[$name]) && is_array($orig[$name]) && is_array($attributes[$name])) { - foreach ($attributes[$name] as $value) { - if (($value !== null) && ($value !== '') && in_array($value, $orig[$name], true)) { - $notchanged[$name][] = $value; - } - } + // unchanged attributes + else { + $notChanged[$name] = $value; } } - // create modify with add and remove - $attributes2 = array_keys($toadd); - for ($i = 0; $i < count($attributes2); $i++) { - if (isset($torem[$attributes2[$i]]) && ($toadd[$attributes2[$i]] !== []) && (count($torem[$attributes2[$i]]) > 0)) { - // found attribute which should be modified - $tomodify[$attributes2[$i]] = $toadd[$attributes2[$i]]; - // merge unchanged values - if (isset($notchanged[$attributes2[$i]])) { - $tomodify[$attributes2[$i]] = array_merge($tomodify[$attributes2[$i]], $notchanged[$attributes2[$i]]); - unset($notchanged[$attributes2[$i]]); - } - // remove old add and remove commands - unset($toadd[$attributes2[$i]]); - unset($torem[$attributes2[$i]]); - } + if ($toModify !== []) { + $return[$this->dn_orig]['modify'] = $toModify; } - if ($toadd !== []) { - $return[$this->dn_orig]['add'] = $toadd; - } - if ($torem !== []) { - $return[$this->dn_orig]['remove'] = $torem; - } - if ($tomodify !== []) { - $return[$this->dn_orig]['modify'] = $tomodify; - } - if ($notchanged !== []) { - $return[$this->dn_orig]['notchanged'] = $notchanged; + if ($notChanged !== []) { + $return[$this->dn_orig]['notchanged'] = $notChanged; } return $return; } diff --git a/lam/lib/modules/inetOrgPerson.inc b/lam/lib/modules/inetOrgPerson.inc index 4a8ba0e1f..dcfdeb232 100644 --- a/lam/lib/modules/inetOrgPerson.inc +++ b/lam/lib/modules/inetOrgPerson.inc @@ -894,28 +894,6 @@ class inetOrgPerson extends baseModule implements passwordService, AccountStatus return []; } $return = parent::save_attributes(); - // postalAddress, registeredAddress, facsimileTelephoneNumber and jpegPhoto need special removing - if (isset($return[$this->getAccountContainer()->dn_orig]['remove']['postalAddress'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['postalAddress'] = $this->attributes['postalAddress']; - unset($return[$this->getAccountContainer()->dn_orig]['remove']['postalAddress']); - } - if (isset($return[$this->getAccountContainer()->dn_orig]['remove']['registeredAddress'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['registeredAddress'] = $this->attributes['registeredAddress']; - unset($return[$this->getAccountContainer()->dn_orig]['remove']['registeredAddress']); - } - if (isset($return[$this->getAccountContainer()->dn_orig]['remove']['facsimileTelephoneNumber'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['facsimileTelephoneNumber'] = $this->attributes['facsimileTelephoneNumber']; - unset($return[$this->getAccountContainer()->dn_orig]['remove']['facsimileTelephoneNumber']); - } - if (isset($return[$this->getAccountContainer()->dn_orig]['add']['facsimileTelephoneNumber']) - && isset($this->orig['facsimileTelephoneNumber']) && (count($this->orig['facsimileTelephoneNumber']) > 0)) { - $return[$this->getAccountContainer()->dn_orig]['modify']['facsimileTelephoneNumber'] = $this->attributes['facsimileTelephoneNumber']; - unset($return[$this->getAccountContainer()->dn_orig]['add']['facsimileTelephoneNumber']); - } - if (isset($return[$this->getAccountContainer()->dn_orig]['remove']['jpegPhoto'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['jpegPhoto'] = []; - unset($return[$this->getAccountContainer()->dn_orig]['remove']['jpegPhoto']); - } // add information about clear text password if ($this->clearTextPassword != null) { $return[$this->getAccountContainer()->dn_orig]['info']['userPasswordClearText'][0] = $this->clearTextPassword; diff --git a/lam/lib/modules/nisnetgroup.inc b/lam/lib/modules/nisnetgroup.inc index 66d3b45ed..4722d907b 100644 --- a/lam/lib/modules/nisnetgroup.inc +++ b/lam/lib/modules/nisnetgroup.inc @@ -8,7 +8,7 @@ use LAM\TYPES\ConfiguredType; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2009 - 2024 Roland Gruber + Copyright (C) 2009 - 2025 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 @@ -159,32 +159,6 @@ class nisnetgroup extends baseModule { $this->messages['domain'][0] = ['ERROR', _('Domain name'), _('Domain name is invalid!')]; } - /** - * Returns a list of modifications which have to be made to the LDAP account. - * - * @return array list of modifications - *
This function returns an array with 3 entries: - *
array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... ) - *
DN is the DN to change. It may be possible to change several DNs (e.g. create a new user and add him to some groups via attribute memberUid) - *
"add" are attributes which have to be added to LDAP entry - *
"remove" are attributes which have to be removed from LDAP entry - *
"modify" are attributes which have to been modified in LDAP entry - *
"info" are values with informational value (e.g. to be used later by pre/postModify actions) - */ - function save_attributes() { - $return = $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig); - // nisNetgroupTriple needs special changing - if (isset($return[$this->getAccountContainer()->dn_orig]['remove']['nisNetgroupTriple'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['nisNetgroupTriple'] = $this->attributes['nisNetgroupTriple']; - unset($return[$this->getAccountContainer()->dn_orig]['remove']['nisNetgroupTriple']); - } - if (isset($return[$this->getAccountContainer()->dn_orig]['add']['nisNetgroupTriple'])) { - $return[$this->getAccountContainer()->dn_orig]['modify']['nisNetgroupTriple'] = $this->attributes['nisNetgroupTriple']; - unset($return[$this->getAccountContainer()->dn_orig]['add']['nisNetgroupTriple']); - } - return $return; - } - /** * Returns the HTML meta data for the main account page. *