prepare intranet for first Dokploy deployment
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="5058.3882"
|
||||
viewBox="0 0 5058.3882 766.70679"
|
||||
enable-background="new 0 0 5200 800"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="intranet.svg"
|
||||
height="766.70679"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview25"
|
||||
pagecolor="#f1f2f4"
|
||||
bordercolor="#000000"
|
||||
borderopacity="1"
|
||||
showgrid="false"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.052980769"
|
||||
inkscape:cx="-764.42831"
|
||||
inkscape:cy="-934.30127"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1358"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg" />
|
||||
<style
|
||||
id="style1">path { fill: #f1f2f4; opacity="1" stroke="none" style="display:inline"}</style>
|
||||
<path
|
||||
id="1_i"
|
||||
d="m 55.846921,280.65323 c -0.785523,160.7849 1.212471,321.0364 6.316604,482.0026 -10.618347,0 -20.714839,0 -30.811332,0 -9.993119,0 -19.986239,0 -31.226396,0 C 11.316134,508.41933 15.377435,254.38003 0,0 21.078011,0 41.14803,0 60.747296,0 59.11366,93.408 57.480641,186.7806 55.846921,280.65323 Z" />
|
||||
<path
|
||||
id="2_n"
|
||||
d="m 263.94938,455.03133 c -0.094,102.7806 -2.31018,205.0743 2.11413,307.6643 -17.28564,0 -34.04425,0 -51.74792,0 11.72375,-253.883 14.63104,-507.8318 -0.0664,-762.2574 1.96695,-0.1725 3.37747,-0.4015 4.78839,-0.4039 15.4993,-0.026 30.99961,-0.095 46.49674,0.09 1.79605,0.021 4.29101,1.0033 5.25723,2.3834 29.33283,41.8961 58.50794,83.9026 87.72083,125.8828 88.01974,126.4882 176.68716,252.5439 260.47421,381.9064 30.3267,46.8228 58.979,94.7306 88.3378,142.1788 2.9187,4.7172 5.4344,9.6837 8.1371,14.5346 0.4771,-0.084 0.9542,-0.1676 1.4313,-0.2513 0,-1.8538 0.051,-3.7093 -0.01,-5.5612 -1.3987,-43.9697 -3.1534,-87.9316 -4.1195,-131.9107 -0.9443,-42.9836 -1.3427,-85.9837 -1.5278,-128.9785 -0.4152,-96.4939 -0.363,-192.99 -0.8986,-289.483 -0.1895,-34.1557 -1.5371,-68.305 -2.3427,-102.4575 -0.059,-2.4917 -0.01,-4.9859 -0.01,-7.9146 16.9793,0 33.8638,0 50.7475,0 -8.1759,253.8841 -9.1391,507.8516 0.09,762.4987 -10.439,0 -21.2036,0.1765 -31.9487,-0.2031 -1.4963,-0.053 -3.172,-2.6637 -4.2991,-4.3938 -21.73,-33.3585 -43.6975,-66.5681 -65.0128,-100.1909 -84.3111,-132.9982 -173.89784,-262.4539 -262.97652,-392.2553 -39.96744,-58.2388 -79.65716,-116.6683 -119.39981,-175.0609 -3.8371,-5.6378 -7.14658,-11.634 -10.72168,-17.4512 -0.73645,-1.1983 -1.56519,-2.3399 -3.27585,-3.3485 3.66208,128.2398 4.84033,256.3529 2.76215,384.9832 z" />
|
||||
<path
|
||||
id="3_t"
|
||||
d="m 1383.2471,45.7185 c -50.642,-0.8569 -100.7924,-1.7344 -150.9444,-2.5139 -9.1775,-0.1426 -8.562,-1.4584 -8.9499,8.4 -4.6272,117.5814 -6.1254,235.21883 -5.1343,352.87233 0.8647,102.6436 2.7607,205.2785 4.1234,307.9183 0.2186,16.473 0.031,32.9515 0.031,49.8292 -18.988,0 -39.025,0 -58.7251,0 0,-9.6737 -0.2366,-19.2759 0.037,-28.8634 2.1898,-76.6167 5.2717,-153.2178 6.513,-229.8489 1.2874,-79.468 1.6011,-158.9717 0.8603,-238.4459 -0.5403,-57.96713 -3.4699,-115.91223 -5.3622,-173.86623 -0.4622,-14.1552 -1.1482,-28.3041 -1.4823,-42.4618 -0.098,-4.1707 -1.2757,-5.7989 -5.7866,-5.6855 -43.9765,1.1051 -87.9588,1.9644 -131.9378,2.9721 -37.31584,0.855 -74.62925,1.8169 -111.94445,2.7077 -1.1154,0.027 -2.2374,-0.2138 -3.6374,-0.3589 0,-16.1464 0,-32.0675 0,-48.3736 188.52605,0 376.89005,0 565.92395,0 -0.7413,15.745 -1.4809,31.4546 -2.2509,47.8118 -30.601,-0.7008 -60.7217,-1.3905 -91.3332,-2.0933 z" />
|
||||
<path
|
||||
id="4_r"
|
||||
d="m 1693.066,762.82672 c -0.7055,1.5411 -1.4462,3.6166 -2.205,3.6232 -20.2795,0.1767 -40.561,0.1407 -61.944,0.1407 15.0303,-254.4632 17.6845,-508.3694 2.6879,-762.98801 2.3573,0 4.1279,0.028 5.8975,0 65.3069,-1.1859 130.6113,-2.5171 195.921,-3.5202 45.6857,-0.7017 90.89,2.9055 134.6044,17.4884 30.7028,10.2424 58.0708,25.8477 79.4777,50.6849 19.125,22.1897 30.1187,48.1807 34.6248,76.878 7.3323,46.6937 3.1809,92.11211 -17.7588,135.03271 -19.3518,39.6654 -49.6182,69.4095 -87.0791,92.004 -11.0918,6.6899 -22.9963,12.0273 -34.4651,18.1044 -1.2696,0.6729 -2.1201,2.1372 -3.1651,3.2344 0.9088,0.7843 1.6939,1.9721 2.7445,2.2944 36.1333,11.0854 63.6491,34.041 86.894,62.9085 19.6672,24.4246 34.4917,51.813 48.4719,79.6813 35.5154,70.7976 64.3426,144.4186 91.9143,218.5624 1.0901,2.9319 2.1233,5.8851 3.481,9.6554 -4.3962,0 -8.1399,0 -11.8838,0 -11.9995,0 -24.0019,-0.1566 -35.9963,0.092 -3.7735,0.078 -5.4329,-1.1069 -6.7889,-4.6875 -23.8288,-62.9311 -49.8635,-124.9229 -79.7014,-185.2701 -15.07,-30.4794 -31.0022,-60.4575 -50.9167,-88.1612 -17.3465,-24.1305 -40.2879,-41.3755 -66.3296,-54.9509 -16.0514,-8.3675 -33.5768,-10.739 -51.2111,-11.9236 -60.4316,-4.0596 -120.9238,-2.3967 -181.4035,-1.2929 -0.818,0.015 -1.6336,0.18 -3.2119,0.3637 0,1.7754 -0.019,3.5351 0,5.2943 1.0211,82.6516 1.7776,165.3077 3.2138,247.9521 0.5122,29.4727 2.6728,58.9167 4.1247,88.8036 m -8.2547,-715.70671 c -0.3651,2.015 -1.081,4.0366 -1.0428,6.0439 0.5898,30.9884 1.8343,61.975 1.8316,92.9618 -0.01,73.99691 -0.5904,147.99381 -0.9398,221.99061 -0.01,2.0834 0,4.1669 0,7.4907 8.4679,0 16.1119,0 23.7561,7e-4 31.4994,0.02 63.0038,0.3861 94.4965,-0.065 18.6283,-0.2666 37.338,-0.928 55.8349,-2.9912 34.8654,-3.8887 67.622,-14.6499 97.1828,-33.9769 31.875,-20.84 55.9978,-48.1322 68.4575,-84.6419 10.281,-30.1245 11.4106,-61.01651 6.5383,-92.26621 -7.3345,-47.0424 -32.3579,-80.0179 -77.2124,-97.3932 -32.8736,-12.7342 -67.2872,-17.6183 -102.1238,-19.4808 -55.3125,-2.9572 -110.6172,-1.0149 -166.7777,2.3271 z" />
|
||||
<path
|
||||
id="5_a"
|
||||
d="m 2327.7505,752.85213 c 19.2041,-45.4364 37.4165,-90.8864 57.5408,-135.4733 54.7898,-121.3885 110.5935,-242.3189 165.5835,-363.6176 30.3787,-67.01033 60.198,-134.28313 86.1362,-203.18563 3.1062,-8.2519 6.0257,-16.5901 8.6145,-25.0164 2.5359,-8.2529 4.5389,-16.6696 6.9214,-25.5592 21.1829,0 43.064,0 64.2639,0 17.3108,61.5161 43.5442,119.3058 69.3809,177.1244 53.384,119.46383 108.0369,238.35983 161.8311,357.64143 28.3308,62.8204 56.5305,125.719 80.7556,190.2853 3.8511,10.2639 6.7737,20.8787 10.0288,31.3614 0.53,1.7069 0.5525,3.5715 0.8721,5.8006 -20.2281,0 -40.1001,0 -59.3702,0 -13.7429,-62.2083 -39.8989,-119.74 -63.768,-178.0492 -13.052,-31.8849 -27.1612,-63.336 -40.6372,-95.0493 -1.4331,-3.3725 -3.0796,-4.7511 -6.9056,-4.7478 -124.3234,0.1053 -248.6472,0.09 -372.9707,0.07 -2.7417,-5e-4 -4.9179,-0.01 -6.3181,3.3023 -29.9043,70.669 -60.5156,141.0623 -85.6504,213.6276 -5.9753,17.2508 -10.1294,35.1353 -15.0029,52.761 -0.6965,2.5188 -0.7229,5.2229 -1.0989,8.147 -20.7207,0 -41.302,0 -62.7038,0 0.8281,-3.1485 1.603,-6.0952 2.497,-9.4221 m 276.5979,-525.8677 c -32.832,72.7061 -65.664,145.4123 -98.6044,218.3583 116.7929,0 232.8803,0 349.3957,0 -0.6238,-1.7314 -1.0427,-3.139 -1.6248,-4.4756 -39.3452,-90.357 -78.6225,-180.7436 -118.0979,-271.04363 -17.6528,-40.3805 -37.3188,-79.9036 -51.6972,-121.8717 -2.7461,-1.0229 -4.2273,0.01 -5.0967,3.1074 -1.3469,4.7975 -2.8616,9.592 -4.821,14.1669 -23.0066,53.7071 -46.1097,107.3729 -69.4537,161.75833 z" />
|
||||
<path
|
||||
id="6_n"
|
||||
d="m 3241.2241,675.02045 c 0.6673,29.4491 1.3304,58.3981 2.0013,87.6769 -16.9424,0 -33.701,0 -51.4034,0 11.7352,-253.8879 14.6209,-507.8365 -0.057,-762.26152 1.9609,-0.1712 3.372,-0.3992 4.7834,-0.4015 15.4993,-0.026 30.9998,-0.095 46.4968,0.09 1.7954,0.021 4.2896,1.005 5.2561,2.3853 29.333,41.896 58.5081,83.9025 87.7207,125.8825 88.0191,126.48822 176.6868,252.54332 260.4734,381.90562 30.3267,46.8226 58.979,94.7303 88.3377,142.1783 2.9184,4.717 5.4338,9.6833 8.1365,14.5339 0.477,-0.084 0.9543,-0.16751 1.4313,-0.2513 0,-1.8539 0.052,-3.7095 -0.01,-5.5616 -1.3989,-43.9696 -3.1499,-87.9314 -4.1208,-131.9104 -0.9419,-42.6501 -1.3448,-85.3168 -1.5264,-127.9782 -0.4116,-96.6604 -0.3533,-193.32302 -0.8872,-289.98242 -0.1897,-34.3223 -1.5447,-68.6381 -2.354,-102.9572 -0.059,-2.4919 -0.01,-4.9864 -0.01,-7.9151 16.9785,0 33.863,0 50.7471,0 -8.1768,253.88292 -9.1368,507.84992 0.089,762.49881 -10.436,0 -21.2007,0.17651 -31.9455,-0.203 -1.4964,-0.053 -3.1731,-2.6624 -4.2999,-4.3922 -21.7307,-33.35829 -43.698,-66.56799 -65.0134,-100.19069 -84.3152,-132.9974 -173.8948,-262.4575 -262.9819,-392.2526 -39.5003,-57.54982 -78.7505,-115.27102 -118.032,-172.97042 -4.3061,-6.3253 -8.0596,-13.0268 -12.0869,-19.5428 -0.7402,-1.1976 -1.5667,-2.342 -3.5991,-3.2771 -0.089,1.8293 -0.2928,3.6614 -0.2495,5.4876 1.0673,44.9812 2.7861,89.9574 3.1115,134.94372 0.5664,78.3216 0.3504,156.6495 0.325,234.9747 -0.025,76.3304 -0.2212,152.6607 -0.3382,229.49099 z" />
|
||||
<path
|
||||
id="7_e"
|
||||
d="m 4126.4746,718.67753 c 71.4199,-2.2664 142.3417,-4.5288 214.061,-6.8168 -0.7451,16.4174 -1.5098,33.2761 -2.2896,50.4655 -150.0732,0 -299.7773,0 -449.3708,0 0,-9.0219 -0.2398,-17.8086 0.043,-26.5785 1.0991,-34.1419 2.4472,-68.2758 3.5473,-102.4177 1.9158,-59.4573 4.5193,-118.9072 5.3506,-178.381 0.9199,-65.8076 0.7197,-131.6446 -0.034,-197.457 -0.5263,-45.9768 -2.6347,-91.93993 -4.3493,-137.89753 -1.4288,-38.2954 -3.3152,-76.5737 -4.9873,-114.8601 -0.064,-1.4648 -0.01,-2.9348 -0.01,-4.7344 138.3159,0 276.373,0 415.1914,0 -0.7642,15.7183 -1.521,31.2789 -2.314,47.5817 -2.6108,0 -5.0278,0.027 -7.4439,0 -114.8071,-1.4635 -229.6137,-2.946 -344.4211,-4.3742 -9.5456,-0.1187 -8.282,-0.8195 -8.2141,8.2124 0.7046,93.8221 1.4219,187.64413 2.1331,281.46623 0.018,2.3219 0,4.6441 0,7.8306 96.0235,-0.3875 191.7266,-1.1139 287.7359,-7.079 0,17.5543 0,34.787 0,51.832 -95.5908,-1.947 -191.2661,-3.8957 -287.7454,-5.8608 0.2673,114.2965 -1.0813,227.1922 2.9629,341.1996 60.1511,-0.7109 119.9011,-1.4171 180.1502,-2.1271 z" />
|
||||
<path
|
||||
id="8_t"
|
||||
d="m 4643.8493,0.005 c 138.2856,0 276.0713,0 414.5386,0 -0.7608,16.1367 -1.501,31.8451 -2.2613,47.9687 -2.6269,0 -4.7666,0.035 -6.9052,-0.01 -78.6382,-1.466 -157.2764,-2.9459 -235.9146,-4.4006 -8.1318,-0.1504 -8.1011,-0.082 -8.4204,7.9567 -5.8862,148.0727 -6.2427,296.21243 -4.646,444.35493 0.9448,87.642 3.2334,175.2695 4.9151,262.9035 0.022,1.1504 0,2.3017 0,3.8394 -20.2554,0 -40.292,0 -59.9736,0 0,-9.7037 -0.2476,-19.3086 0.039,-28.8974 1.8487,-61.7897 3.9976,-123.5708 5.6973,-185.3643 2.5986,-94.4601 3.6372,-188.9431 1.8257,-283.4266 -0.9419,-49.1413 -2.9234,-98.26313 -4.52,-147.39093 -0.7364,-22.648 -1.7842,-45.2863 -2.4356,-67.9363 -0.1338,-4.647 -1.436,-6.2972 -6.3467,-6.1719 -57.6347,1.4701 -115.2749,2.7291 -172.9135,4.0528 -23.4888,0.5394 -46.9776,1.1033 -70.4664,1.6419 -0.9853,0.023 -1.9736,-0.099 -3.4404,-0.1796 0,-16.1404 0,-32.1629 0,-48.9453 50.2334,0 100.4795,0 151.2256,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,865 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" version="1.1" viewBox="0 0 566.929 566.929">
|
||||
<!-- Generator: Adobe Illustrator 29.8.2, SVG Export Plug-In . SVG Version: 2.1.1 Build 3) -->
|
||||
<rect x="0" y="-.006" width="566.93" height="566.94" fill="#2786cf"/>
|
||||
<path d="M198.546,432.37c-.147,0-.308-.03-.469-.049l-71.276-10.782c-1.709-.253-2.867-1.855-2.618-3.554.254-1.68,1.783-2.832,3.55-2.608l71.281,10.791c1.7.254,2.862,1.846,2.608,3.545-.235,1.534-1.568,2.657-3.076,2.657h0ZM372.855,432.155c-1.523,0-2.851-1.114-3.066-2.657-.254-1.699.918-3.281,2.597-3.535l71.306-10.576c1.758-.215,3.281.928,3.516,2.617.253,1.7-.918,3.291-2.598,3.545l-71.306,10.567c-.156.029-.293.039-.449.039h0ZM202.804,405.339c-.459,0-.928-.107-1.373-.322l-84.855-41.609c-1.538-.742-2.178-2.617-1.421-4.16.742-1.543,2.632-2.188,4.16-1.426l84.856,41.609c1.538.752,2.182,2.608,1.421,4.17-.528,1.094-1.646,1.738-2.788,1.738h0ZM368.637,405.134c-1.162,0-2.256-.644-2.802-1.738-.743-1.563-.098-3.418,1.445-4.17l84.841-41.404c1.543-.742,3.398-.108,4.17,1.426.722,1.543.097,3.418-1.455,4.16l-84.831,41.404c-.43.215-.899.322-1.368.322h0ZM215.05,380.995c-.747,0-1.504-.264-2.1-.811l-84.894-77.8c-1.255-1.162-1.348-3.134-.186-4.394,1.167-1.26,3.135-1.338,4.395-.196l84.884,77.79c1.27,1.172,1.358,3.155.196,4.405-.611.674-1.456,1.006-2.295,1.006h0ZM356.372,380.926c-.84,0-1.68-.332-2.285-1.006-1.172-1.269-1.094-3.222.195-4.394l84.899-77.722c1.25-1.152,3.223-1.074,4.375.196,1.172,1.259,1.094,3.232-.195,4.394l-84.899,77.722c-.586.547-1.348.81-2.09.81h0ZM234.15,361.748c-1.045,0-2.075-.547-2.661-1.495l-68.63-112.583c-.888-1.485-.429-3.399,1.026-4.278,1.469-.898,3.398-.439,4.287,1.035l68.634,112.565c.899,1.465.43,3.398-1.035,4.297-.508.293-1.064.459-1.621.459h0ZM337.174,361.601c-.567,0-1.094-.156-1.622-.459-1.464-.889-1.914-2.812-1.035-4.287l68.767-112.418c.898-1.474,2.832-1.924,4.277-1.025,1.465.879,1.934,2.802,1.035,4.267l-68.766,112.428c-.586.967-1.621,1.494-2.656,1.494h0ZM313.083,349.111c-.274,0-.547-.029-.84-.117-1.641-.469-2.617-2.178-2.149-3.819l38.299-137.314c.469-1.646,2.158-2.627,3.877-2.153,1.621.464,2.598,2.173,2.129,3.828l-38.299,137.299c-.39,1.368-1.64,2.276-3.017,2.276h0ZM258.514,349.101c-1.357,0-2.607-.898-2.998-2.266l-38.504-137.309c-.458-1.655.503-3.364,2.159-3.828,1.689-.484,3.369.503,3.838,2.158l38.503,137.3c.459,1.65-.507,3.359-2.158,3.828-.283.088-.576.117-.84.117h0Z" fill="#fff"/>
|
||||
<path d="M297.624,445.094h-23.837c-2.344,0-4.248-1.904-4.248-4.248V138.959h-73.664c-2.334,0-4.238-1.924-4.238-4.283v-24.085c0-2.344,1.904-4.253,4.238-4.253h73.644l.01-55.14c0-2.344,1.904-4.253,4.258-4.253h23.856c2.364,0,4.239,1.909,4.239,4.253v55.14h73.649c2.353,0,4.267,1.909,4.267,4.253v24.085c0,2.359-1.914,4.283-4.267,4.283l-73.649-.015v301.902c0,2.344-1.895,4.248-4.258,4.248h0ZM275.75,438.893h19.93V136.971c0-2.343,1.924-4.248,4.327-4.248l73.571.015v-20.18h-73.63c-2.363,0-4.268-1.904-4.268-4.248v-55.144h-19.94l-.01,55.144c0,2.344-1.894,4.248-4.219,4.248h-73.668v20.18h73.649c2.363,0,4.258,1.89,4.258,4.219v301.936h0Z" fill="#fff"/>
|
||||
<path d="M396.009,475.873l-41.003.127c-1.875,2.754-3.946,5.381-6.163,7.911h-126.206c-2.275-2.618-4.438-5.352-6.264-8.047l-40.706.009c-3.936,0-7.124,3.184-7.124,7.12s3.188,7.119,7.124,7.119l36.395-.049c.142.019.288.049.43.049h183.518c3.926,0,7.139-3.184,7.139-7.1,0-3.955-3.213-7.139-7.139-7.139Z" fill="#fff"/>
|
||||
<path d="M249.921,504.603h-29.926c-3.936,0-7.144,3.164-7.144,7.109,0,1.905.738,3.692,2.08,5.039,1.353,1.338,3.145,2.11,5.064,2.11h131.513c1.915,0,3.711-.772,5.059-2.11,1.328-1.347,2.07-3.134,2.07-5.039,0-3.945-3.183-7.109-7.129-7.109h-29.959c-10.85,5.137-22.988,8.008-35.82,8.008s-24.94-2.871-35.809-8.008Z" fill="#fff"/>
|
||||
<path d="M420.501,447.106h-53.045c-.606,2.676-1.348,5.371-2.188,8.027h-158.978c-.933-2.676-1.758-5.4-2.344-8.027h-52.908c-3.931,0-7.119,3.203-7.119,7.129s3.188,7.129,7.119,7.129l54.651-.02h157.314v.01l57.498.01c3.935,0,7.138-3.194,7.138-7.129s-3.203-7.129-7.138-7.129Z" fill="#fff"/>
|
||||
<metadata>
|
||||
<i:aipgfRef id="adobe_illustrator_pgf"/>
|
||||
<i:aipgf id="adobe_illustrator_pgf" i:pgfEncoding="zstd/base64" i:pgfVersion="24">
|
||||
<![CDATA[
|
||||
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
|
||||
]]>
|
||||
</i:aipgf>
|
||||
</metadata>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg id="plus-add-more-detail" style="enable-background:new 0 0 15 15;" version="1.1" viewBox="0 0 15 15" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M7.5,0C3.364,0,0,3.364,0,7.5S3.364,15,7.5,15S15,11.636,15,7.5S11.636,0,7.5,0z M7.5,14C3.916,14,1,11.084,1,7.5 S3.916,1,7.5,1S14,3.916,14,7.5S11.084,14,7.5,14z"/><polygon points="8,3.5 7,3.5 7,7 3.5,7 3.5,8 7,8 7,11.5 8,11.5 8,8 11.5,8 11.5,7 8,7 "/></svg>
|
||||
|
After Width: | Height: | Size: 492 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.2893 5.70708C13.8988 5.31655 13.2657 5.31655 12.8751 5.70708L7.98768 10.5993C7.20729 11.3805 7.2076 12.6463 7.98837 13.427L12.8787 18.3174C13.2693 18.7079 13.9024 18.7079 14.293 18.3174C14.6835 17.9269 14.6835 17.2937 14.293 16.9032L10.1073 12.7175C9.71678 12.327 9.71678 11.6939 10.1073 11.3033L14.2893 7.12129C14.6799 6.73077 14.6799 6.0976 14.2893 5.70708Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 538 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.71069 18.2929C10.1012 18.6834 10.7344 18.6834 11.1249 18.2929L16.0123 13.4006C16.7927 12.6195 16.7924 11.3537 16.0117 10.5729L11.1213 5.68254C10.7308 5.29202 10.0976 5.29202 9.70708 5.68254C9.31655 6.07307 9.31655 6.70623 9.70708 7.09676L13.8927 11.2824C14.2833 11.6729 14.2833 12.3061 13.8927 12.6966L9.71069 16.8787C9.32016 17.2692 9.32016 17.9023 9.71069 18.2929Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 544 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="#FEFEFE" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.06 13.79C15.91 13.94 15.72 14.01 15.53 14.01C15.34 14.01 15.15 13.94 15 13.79L12 10.79L9 13.79C8.71 14.08 8.23 14.08 7.94 13.79C7.65 13.5 7.65 13.02 7.94 12.73L11.47 9.2C11.76 8.91 12.24 8.91 12.53 9.2L16.06 12.73C16.35 13.03 16.35 13.5 16.06 13.79Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3C4.55228 3 5 3.44772 5 4V5H7V4C7 3.44772 7.44772 3 8 3C8.55228 3 9 3.44772 9 4V5H11V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V5H15V4C15 3.44772 15.4477 3 16 3C16.5523 3 17 3.44772 17 4V5H19V4C19 3.44772 19.4477 3 20 3C20.5523 3 21 3.44772 21 4V6V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6V4C3 3.44772 3.44772 3 4 3ZM19 7H16H12H8H5V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V7ZM13 10C13 10.5523 12.5523 11 12 11C11.4477 11 11 10.5523 11 10C11 9.44771 11.4477 9 12 9C12.5523 9 13 9.44771 13 10ZM16 11C16.5523 11 17 10.5523 17 10C17 9.44771 16.5523 9 16 9C15.4477 9 15 9.44771 15 10C15 10.5523 15.4477 11 16 11ZM9 13C9 13.5523 8.55228 14 8 14C7.44772 14 7 13.5523 7 13C7 12.4477 7.44772 12 8 12C8.55228 12 9 12.4477 9 13ZM12 14C12.5523 14 13 13.5523 13 13C13 12.4477 12.5523 12 12 12C11.4477 12 11 12.4477 11 13C11 13.5523 11.4477 14 12 14ZM17 13C17 13.5523 16.5523 14 16 14C15.4477 14 15 13.5523 15 13C15 12.4477 15.4477 12 16 12C16.5523 12 17 12.4477 17 13ZM8 17C8.55228 17 9 16.5523 9 16C9 15.4477 8.55228 15 8 15C7.44772 15 7 15.4477 7 16C7 16.5523 7.44772 17 8 17ZM13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15C12.5523 15 13 15.4477 13 16ZM16 17C16.5523 17 17 16.5523 17 16C17 15.4477 16.5523 15 16 15C15.4477 15 15 15.4477 15 16C15 16.5523 15.4477 17 16 17Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style></defs><title/><g id="cross"><line class="cls-1" x1="7" x2="25" y1="7" y2="25"/><line class="cls-1" x1="7" x2="25" y1="25" y2="7"/></g></svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.293,11.293l4-4A1,1,0,1,1,6.707,8.707L3.414,12l3.293,3.293a1,1,0,1,1-1.414,1.414l-4-4A1,1,0,0,1,1.293,11.293Zm17.414-4a1,1,0,1,0-1.414,1.414L20.586,12l-3.293,3.293a1,1,0,1,0,1.414,1.414l4-4a1,1,0,0,0,0-1.414ZM13.039,4.726l-4,14a1,1,0,0,0,.686,1.236A1.053,1.053,0,0,0,10,20a1,1,0,0,0,.961-.726l4-14a1,1,0,1,0-1.922-.548Z"/></svg>
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Icons" x="-1024" y="0" width="1280" height="800" style="fill:none;"/><g id="Icons1" serif:id="Icons"><g id="Strike"></g><g id="H1"></g><g id="H2"></g><g id="H3"></g><g id="list-ul"></g><g id="hamburger-1"></g><g id="hamburger-2"></g><g id="list-ol"></g><g id="list-task"></g><g id="trash"></g><g id="vertical-menu"></g><g id="horizontal-menu"></g><g id="sidebar-2"></g><g id="Pen"></g><g id="Pen1" serif:id="Pen"><path d="M55.944,51.712l0,4.201l-33.652,-0.027l4.71,-4.174l28.942,0Zm-7.555,-43.712c1.649,0 2.505,0.128 4.752,2.011c2.294,1.921 2.707,3.419 2.803,5.087c0.102,1.795 -0.504,3.976 -2.188,5.681l-31.961,31.961c-0.52,0.475 -0.629,0.45 -0.977,0.553l-10.226,2.557c-1.472,0.299 -2.854,-1.049 -2.55,-2.55l2.557,-10.226c0.1,-0.334 0.133,-0.517 0.553,-0.976c10.696,-10.697 21.195,-21.594 32.09,-32.087c1.421,-1.335 3.497,-2.011 5.147,-2.011Zm-32.375,35.182l-1.477,1.477l-1.566,6.262l6.262,-1.566c0.493,-0.492 0.985,-0.983 1.479,-1.474l-4.698,-4.699Zm30.176,-20.573l-4.802,-4.801l-22.493,22.493l4.712,4.713c7.549,-7.448 15.196,-14.801 22.583,-22.405Zm2.826,-2.936c0.618,-0.648 1.234,-1.298 1.848,-1.951c1.673,-1.826 0.443,-5.454 -2.307,-5.578c-0.056,-0.002 -0.112,-0.002 -0.168,-0.002c-0.861,0.016 -1.699,0.372 -2.312,0.977l-1.807,1.808l4.746,4.746Z" style="fill-rule:nonzero;"/></g><g id="clock"></g><g id="external-link"></g><g id="hr"></g><g id="info"></g><g id="warning"></g><g id="plus-circle"></g><g id="minus-circle"></g><g id="vue"></g><g id="cog"></g><g id="logo"></g><g id="eye-slash"></g><g id="eye"></g><g id="toggle-off"></g><g id="shredder"></g><g id="spinner--loading--dots-" serif:id="spinner [loading, dots]"></g><g id="react"></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
|
||||
<rect style="opacity:0.2" width="25" height="13" x="3" y="15" rx="1.25" ry="1.25"/>
|
||||
<path style="fill:#60924b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
|
||||
<rect style="opacity:0.2" width="25" height="13" x="3" y="13" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#87b158" width="25" height="13" x="3" y="14" rx="1.25" ry="1.25"/>
|
||||
<path style="opacity:0.1;fill:#ffffff" d="M 4.25,5 C 3.557,5 3,5.5575 3,6.25 V 7.25 C 3,6.5575 3.557,6 4.25,6 H 12.75 C 13.443,6 14,6.5575 14,7.25 V 8 H 26.75 C 27.442,8 28,8.5575 28,9.25 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
|
||||
<rect style="opacity:0.2" width="25" height="13" x="3" y="15" rx="1.25" ry="1.25"/>
|
||||
<path style="fill:#727272" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
|
||||
<rect style="opacity:0.2" width="25" height="13" x="3" y="13" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#8e8e8e" width="25" height="13" x="3" y="14" rx="1.25" ry="1.25"/>
|
||||
<path style="opacity:0.1;fill:#ffffff" d="M 4.25,5 C 3.557,5 3,5.5575 3,6.25 V 7.25 C 3,6.5575 3.557,6 4.25,6 H 12.75 C 13.443,6 14,6.5575 14,7.25 V 8 H 26.75 C 27.442,8 28,8.5575 28,9.25 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
|
||||
<path style="fill:#dd772f" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#ee923a" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
|
||||
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
|
||||
<path style="fill:#bf4b4b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e25252" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
|
||||
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
|
||||
<path style="fill:#b58f1b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
|
||||
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
|
||||
<rect style="fill:#e2b322" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
|
||||
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" ?><svg data-name="Livello 1" id="Livello_1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><title/>
|
||||
<path d="M64,39A25,25,0,1,0,89,64,25,25,0,0,0,64,39Zm0,44A19,19,0,1,1,83,64,19,19,0,0,1,64,83Z"/>
|
||||
<path d="M121,48h-8.93a1,1,0,0,1-.94-.68,49.9,49.9,0,0,0-2-4.85,1,1,0,0,1,.18-1.15L115.62,35a7,7,0,0,0,0-9.9L102.89,12.38a7,7,0,0,0-9.9,0l-6.31,6.31a1,1,0,0,1-1.15.18,49.76,49.76,0,0,0-4.85-2,1,1,0,0,1-.68-.94V7a7,7,0,0,0-7-7H55a7,7,0,0,0-7,7v8.93a1,1,0,0,1-.68.94,49.9,49.9,0,0,0-4.85,2,1,1,0,0,1-1.15-.18L35,12.38a7,7,0,0,0-9.9,0L12.38,25.11a7,7,0,0,0,0,9.9l6.31,6.31a1,1,0,0,1,.18,1.15,49.76,49.76,0,0,0-2,4.85,1,1,0,0,1-.94.68H7a7,7,0,0,0-7,7V73a7,7,0,0,0,7,7h8.93a1,1,0,0,1,.94.68,49.9,49.9,0,0,0,2,4.85,1,1,0,0,1-.18,1.15L12.38,93a7,7,0,0,0,0,9.9l12.73,12.73a7,7,0,0,0,9.9,0l6.31-6.31a1,1,0,0,1,1.15-.18,49.76,49.76,0,0,0,4.85,2,1,1,0,0,1,.68.94V121a7,7,0,0,0,7,7H73a7,7,0,0,0,7-7v-8.93a1,1,0,0,1,.68-.94,49.9,49.9,0,0,0,4.85-2,1,1,0,0,1,1.15.18L93,115.62a7,7,0,0,0,9.9,0l12.73-12.73a7,7,0,0,0,0-9.9l-6.31-6.31a1,1,0,0,1-.18-1.15,49.76,49.76,0,0,0,2-4.85,1,1,0,0,1,.94-.68H121a7,7,0,0,0,7-7V55A7,7,0,0,0,121,48Zm1,25a1,1,0,0,1-1,1h-8.93a7,7,0,0,0-6.6,4.69,43.9,43.9,0,0,1-1.76,4.26,7,7,0,0,0,1.35,8l6.31,6.31a1,1,0,0,1,0,1.41L98.65,111.38a1,1,0,0,1-1.41,0l-6.31-6.31a7,7,0,0,0-8-1.35,43.88,43.88,0,0,1-4.27,1.76,7,7,0,0,0-4.68,6.6V121a1,1,0,0,1-1,1H55a1,1,0,0,1-1-1v-8.93a7,7,0,0,0-4.69-6.6,43.9,43.9,0,0,1-4.26-1.76,7,7,0,0,0-8,1.35l-6.31,6.31a1,1,0,0,1-1.41,0L16.62,98.65a1,1,0,0,1,0-1.41l6.31-6.31a7,7,0,0,0,1.35-8,43.88,43.88,0,0,1-1.76-4.27A7,7,0,0,0,15.93,74H7a1,1,0,0,1-1-1V55a1,1,0,0,1,1-1h8.93a7,7,0,0,0,6.6-4.69,43.9,43.9,0,0,1,1.76-4.26,7,7,0,0,0-1.35-8l-6.31-6.31a1,1,0,0,1,0-1.41L29.35,16.62a1,1,0,0,1,1.41,0l6.31,6.31a7,7,0,0,0,8,1.35,43.88,43.88,0,0,1,4.27-1.76A7,7,0,0,0,54,15.93V7a1,1,0,0,1,1-1H73a1,1,0,0,1,1,1v8.93a7,7,0,0,0,4.69,6.6,43.9,43.9,0,0,1,4.26,1.76,7,7,0,0,0,8-1.35l6.31-6.31a1,1,0,0,1,1.41,0l12.73,12.73a1,1,0,0,1,0,1.41l-6.31,6.31a7,7,0,0,0-1.35,8,43.88,43.88,0,0,1,1.76,4.27,7,7,0,0,0,6.6,4.68H121a1,1,0,0,1,1,1Z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 512 512">
|
||||
<path d="M377.9 105.9L500.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L377.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1-128 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM160 96L96 96c-17.7 0-32 14.3-32 32l0 256c0 17.7 14.3 32 32 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c-53 0-96-43-96-96L0 128C0 75 43 32 96 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
␍<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
|
||||
␍<g>
|
||||
␍<path d="m28.6 11.4h5.1a1.11 1.11 0 0 0 1.1-1.1 1.32 1.32 0 0 0 -.3-.8l-7.2-7.1a.89.89 0 0 0 -.7-.3 1.11 1.11 0 0 0 -1.1 1.1v5.1a3.12 3.12 0 0 0 3.1 3.1z"/>
|
||||
␍<path d="m49.83 32.45a1.43 1.43 0 0 0 -1.39-1.45h-12a1.43 1.43 0 0 0 -1.44 1.44v1.44a1.43 1.43 0 0 0 1.4 1.44h6.14l-11 11a1.42 1.42 0 0 0 0 2l1 1a1.54 1.54 0 0 0 1.09.45 1.32 1.32 0 0 0 .94-.38l11-11v6a1.43 1.43 0 0 0 1.43 1.44h1.53a1.43 1.43 0 0 0 1.47-1.4z"/>
|
||||
␍<path d="m18.35 20.68h-1.46v4.93h1.46a1.56 1.56 0 0 0 1.56-1.1 3.93 3.93 0 0 0 .24-1.44 3.61 3.61 0 0 0 -.36-1.77 1.53 1.53 0 0 0 -1.44-.62z"/>
|
||||
␍<path d="m11.29 20.93a1.27 1.27 0 0 0 -.84-.25h-1.45v2.23h1.46a1.18 1.18 0 0 0 .84-.28 1.09 1.09 0 0 0 .3-.86 1 1 0 0 0 -.33-.84z"/>
|
||||
␍<path d="m33.24 16h-7.84a4.67 4.67 0 0 1 -4.7-4.6v-7.8a1.42 1.42 0 0 0 -1.34-1.5h-12.56a4.67 4.67 0 0 0 -4.7 4.6v29.4a4.6 4.6 0 0 0 4.62 4.6h17.88v-.1a15.92 15.92 0 0 1 10.2-14.9v-8.1a1.58 1.58 0 0 0 -1.56-1.6zm-20.75 7.65a2.93 2.93 0 0 1 -1.89.56h-1.6v2.71h-1.51v-7.55h3.24a2.64 2.64 0 0 1 1.76.63 2.3 2.3 0 0 1 .66 1.8 2.35 2.35 0 0 1 -.66 1.85zm8.66 1.9a2.73 2.73 0 0 1 -2.53 1.45h-3.26v-7.63h3.26a3.85 3.85 0 0 1 1.17.17 2.49 2.49 0 0 1 1.28.95 3.43 3.43 0 0 1 .54 1.22 5.6 5.6 0 0 1 .14 1.29 5 5 0 0 1 -.6 2.55zm7.92-4.84h-3.81v1.74h3.34v1.31h-3.34v3.16h-1.57v-7.54h5.38z"/>
|
||||
␍</g>
|
||||
␍</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Icons" x="-896" y="-128" width="1280" height="800" style="fill:none;"/><g id="Icons1" serif:id="Icons"><g id="Strike"></g><g id="H1"></g><g id="H2"></g><g id="H3"></g><g id="list-ul"></g><g id="hamburger-1"></g><g id="hamburger-2"></g><g id="list-ol"></g><g id="list-task"></g><g id="trash"></g><g id="vertical-menu"></g><g id="horizontal-menu"></g><g id="sidebar-2"></g><g id="Pen"></g><g id="Pen1" serif:id="Pen"></g><g id="clock"></g><g id="external-link"></g><g id="hr"></g><g id="info"></g><g id="warning"></g><g id="plus-circle"></g><g id="minus-circle"></g><g><path id="caret-down" d="M45.274,29.772c-1.094,-8.15 -8.075,-14.435 -16.525,-14.435c-9.203,0 -16.674,7.471 -16.674,16.674c0,9.203 7.471,16.675 16.674,16.675c4.423,0 8.664,-1.757 11.791,-4.884l2.862,2.863c-3.886,3.886 -9.157,6.069 -14.653,6.069c-11.437,0 -20.723,-9.285 -20.723,-20.723c0,-11.437 9.286,-20.723 20.723,-20.723c10.623,0 19.379,7.994 20.582,18.294l3.551,-3.551l3.118,3.117l-8.792,8.792l-8.796,-8.796l3.118,-3.117l3.744,3.745Z" style="fill-rule:nonzero;"/></g><g id="vue"></g><g id="cog"></g><g id="logo"></g><g id="eye-slash"></g><g id="eye"></g><g id="toggle-off"></g><g id="shredder"></g><g id="spinner--loading--dots-" serif:id="spinner [loading, dots]"></g><g id="react"></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M1.75 4.5a.25.25 0 00-.25.25v14.5c0 .138.112.25.25.25h20.5a.25.25 0 00.25-.25V4.75a.25.25 0 00-.25-.25H1.75zM0 4.75C0 3.784.784 3 1.75 3h20.5c.966 0 1.75.784 1.75 1.75v14.5A1.75 1.75 0 0122.25 21H1.75A1.75 1.75 0 010 19.25V4.75z"/>
|
||||
<path d="M9 15.584V8.416a.5.5 0 01.77-.42l5.576 3.583a.5.5 0 010 .842L9.77 16.005a.5.5 0 01-.77-.42z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 515 B |
@@ -0,0 +1,9 @@
|
||||
<footer id="footer">
|
||||
<div class="legal-links">
|
||||
<a href="https://nak-reinickendorf.de/impressum.php">Impressum</a>
|
||||
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
|
||||
<a href="https://nak-reinickendorf.de/datenschutz.php">Datenschutz</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/snippets/contact_modal.php'; ?>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php /* Name: header.php
|
||||
Beschreibung: Ausgabe des Seitenkopfes inkl. Intranet-Menü
|
||||
- Profil-Leiste */
|
||||
?>
|
||||
<header>
|
||||
<div class="header_menue unselectable" id="header_menue">
|
||||
|
||||
<a class="nak-logo-link" href="<?php echo $g_httpHomePath; ?>">
|
||||
<img src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK" class="nak-logo-icon">
|
||||
<div class="nak-logo-text">
|
||||
<span class="nak-name">Neuapostolische Kirche</span>
|
||||
<span class="nak-gemeinde">Berlin-Reinickendorf</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="header_profile" id="header_profile">
|
||||
|
||||
<?php // Profilbild
|
||||
// Generiere den Dateinamen basierend auf Benutzer-ID und Datum/Uhrzeit
|
||||
$profileImg = glob($g_profilePicturePath."{$_SESSION['user_id']}-*.jpg");
|
||||
|
||||
// Überprüfe, ob eine passende Datei existiert
|
||||
if (!empty($profileImg)) {
|
||||
// Sortiere die Dateien nach Datum/Uhrzeit und nimm die neueste
|
||||
usort($profileImg, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
$latestProfileImg = $profileImg[0];
|
||||
|
||||
// Entferne den absoluten Pfad
|
||||
$headerProfileImg = str_replace($_SERVER['DOCUMENT_ROOT'], '', $latestProfileImg);
|
||||
} else {
|
||||
// Standardbild, wenn keine passende Datei gefunden wurde
|
||||
$headerProfileImg = "/intranet_repo/general_files/profile_pictures/default.jpg";
|
||||
}
|
||||
|
||||
// Bild anzeigen
|
||||
echo "<img src='$headerProfileImg' alt='Profil' id='header_profile_img' onclick='openCloseHeaderProfile();'>";
|
||||
?>
|
||||
|
||||
<div class="header_profile_bar" onclick="window.location.href='<?php echo $g_httpLogoutPath; ?>';">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
|
||||
<span>Abmelden</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php /* Name: html_head.php
|
||||
Beschreibung: Zentraler HTML Head für das Intranet und Kalli
|
||||
|
||||
Anforderung: Umschliessendes Tag für diese Datei erforderlich: <head>...</head> */
|
||||
|
||||
|
||||
?>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Language" content="de">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/intranet_repo/projects/_central/img/nak-logo.svg">
|
||||
<link rel="shortcut icon" href="/intranet_repo/projects/_central/img/favicon.ico">
|
||||
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/nak-design.css">
|
||||
|
||||
<script src="/intranet_repo/projects/_central/scripts/intranet.js"></script>
|
||||
|
||||
<title>NAK Berlin-Reinickendorf</title>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php /* Name: button_edit.php
|
||||
Beschreibung: Button-Ausgabe für die Umschaltung auf den Bearbeitungsmodus
|
||||
|
||||
GET-Parameter: action=edit
|
||||
id=[$edit_id] */
|
||||
?>
|
||||
|
||||
<div class="right">
|
||||
|
||||
<?php
|
||||
if (isset($_GET['action']) && $_GET['action'] == 'edit') {
|
||||
|
||||
display_button('edit','Bearbeiten','right');
|
||||
} else {
|
||||
|
||||
if (isset($edit_id)) {
|
||||
echo "<a href='".$g_httpCurrentCleanUrl."?action=edit&id=$edit_id' style='display:table-cell;'>";
|
||||
} else {
|
||||
echo "<a href='".$g_httpCurrentCleanUrl."?action=edit' style='display:table-cell;'>";
|
||||
}
|
||||
display_button('edit','Bearbeiten','right');
|
||||
echo "</a>";
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php /* Name: contact_modal.php
|
||||
Beschreibung: Globales Kontaktformular-Modal
|
||||
- Wird überall eingebunden wo technik@-Link war
|
||||
- Sendet per AJAX an process_contact.php */
|
||||
?>
|
||||
|
||||
<!-- Kontaktformular Modal -->
|
||||
<div class="standard_overlay" id="contactModal" onclick="closeContactModalOnBackdrop(event)">
|
||||
<div style="position:relative;width:min(520px,calc(100vw - 24px));max-height:min(700px,calc(100vh - 24px));overflow:auto;background:#FFF;border-radius:0;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
|
||||
<button type="button" onclick="closeContactModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border-radius:0;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
|
||||
|
||||
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,var(--theme-color),var(--theme-hover));border-radius:0;">
|
||||
<div style="font-size:1.3em;font-weight:850;">Admin kontaktieren</div>
|
||||
<div style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;">Ihr Anliegen wird an den Administrator weitergeleitet.</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:22px 24px 24px 24px;">
|
||||
<form id="contactForm" onsubmit="submitContactForm(event)">
|
||||
<?php
|
||||
$isLoggedIn = isset($user) && !empty($user['first_name']) && !empty($user['mail']);
|
||||
$prefillName = $isLoggedIn ? htmlspecialchars($user['first_name'] . ' ' . $user['last_name']) : '';
|
||||
$prefillMail = $isLoggedIn ? htmlspecialchars($user['mail']) : '';
|
||||
$readonlyAttr = $isLoggedIn ? 'readonly' : '';
|
||||
$readonlyStyle = $isLoggedIn ? 'cursor:not-allowed;opacity:0.7;' : '';
|
||||
?>
|
||||
|
||||
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Name</label>
|
||||
<input type="text" name="contact_name" id="contact_name" required <?= $readonlyAttr ?>
|
||||
value="<?= $prefillName ?>"
|
||||
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;<?= $readonlyStyle ?>">
|
||||
|
||||
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">E-Mail</label>
|
||||
<input type="email" name="contact_email" id="contact_email" required <?= $readonlyAttr ?>
|
||||
value="<?= $prefillMail ?>"
|
||||
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;<?= $readonlyStyle ?>">
|
||||
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<div style="margin:-8px 0 12px 0;font-size:0.82em;color:hsl(210,14%,50%);font-weight:600;">Ihre Kontaktdaten werden automatisch aus Ihrem Profil übernommen.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Betreff</label>
|
||||
<input type="text" name="contact_subject" id="contact_subject" required
|
||||
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;">
|
||||
|
||||
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Nachricht</label>
|
||||
<textarea name="contact_message" id="contact_message" rows="5" required
|
||||
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:16px;resize:vertical;box-sizing:border-box;font-family:inherit;"></textarea>
|
||||
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;">
|
||||
<button type="button" onclick="closeContactModal()" style="padding:8px 16px;border:1px solid hsl(210,18%,84%);border-radius:0;background:#FFF;font-weight:850;font-size:0.86em;cursor:pointer;">Abbrechen</button>
|
||||
<button type="submit" id="contactSubmitBtn" style="padding:8px 16px;border:1px solid var(--theme-color);border-radius:0;background:linear-gradient(135deg,var(--theme-color),var(--theme-hover));color:#FFF;font-weight:850;font-size:0.86em;cursor:pointer;">Nachricht senden</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="contactFormResult" style="display:none;margin-top:12px;padding:10px 14px;border-radius:0;font-weight:700;text-align:center;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openContactModal() {
|
||||
document.getElementById('contactModal').style.display = 'flex';
|
||||
}
|
||||
function closeContactModal() {
|
||||
document.getElementById('contactModal').style.display = 'none';
|
||||
document.getElementById('contactFormResult').style.display = 'none';
|
||||
document.getElementById('contactSubmitBtn').disabled = false;
|
||||
document.getElementById('contactSubmitBtn').textContent = 'Nachricht senden';
|
||||
}
|
||||
function closeContactModalOnBackdrop(event) {
|
||||
if (event.target.id === 'contactModal') closeContactModal();
|
||||
}
|
||||
function submitContactForm(event) {
|
||||
event.preventDefault();
|
||||
const form = document.getElementById('contactForm');
|
||||
const resultDiv = document.getElementById('contactFormResult');
|
||||
const submitBtn = document.getElementById('contactSubmitBtn');
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Wird gesendet...';
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
fetch('/intranet_repo/projects/adminpanel/process_contact.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
resultDiv.style.display = 'block';
|
||||
if (data.success) {
|
||||
resultDiv.style.background = 'hsl(145, 50%, 93%)';
|
||||
resultDiv.style.color = 'hsl(145, 50%, 28%)';
|
||||
resultDiv.textContent = 'Nachricht erfolgreich gesendet!';
|
||||
form.reset();
|
||||
setTimeout(() => closeContactModal(), 2000);
|
||||
} else {
|
||||
resultDiv.style.background = 'hsl(0, 78%, 95%)';
|
||||
resultDiv.style.color = 'hsl(0, 78%, 40%)';
|
||||
resultDiv.textContent = data.error || 'Ein Fehler ist aufgetreten.';
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Nachricht senden';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.style.background = 'hsl(0, 78%, 95%)';
|
||||
resultDiv.style.color = 'hsl(0, 78%, 40%)';
|
||||
resultDiv.textContent = 'Verbindungsfehler. Bitte erneut versuchen.';
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Nachricht senden';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape' && document.getElementById('contactModal').style.display === 'flex') {
|
||||
closeContactModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/*
|
||||
Name: countdown.php
|
||||
Beschreibung: Reload der aktuellen Seite mit Countdown
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="countdown"><span id="countdown">120</span></div>
|
||||
|
||||
<script>
|
||||
var countdown = document.getElementById("countdown").textContent;
|
||||
|
||||
let pauseCountdown = false; //Von außen kann der Countdown angehalten werden: pauseCountdown = true
|
||||
|
||||
var countdownTimer = setInterval(function() {
|
||||
|
||||
if (!pauseCountdown) {
|
||||
|
||||
countdown--;
|
||||
document.getElementById('countdown').textContent = countdown;
|
||||
|
||||
if ((countdown <= 0)) {
|
||||
|
||||
clearInterval(countdownTimer);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
}, 1000);
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php /* Name: overlay_addNewUser.php
|
||||
Beschreibung: Neuen Nutzer anlegen in einem Overlay
|
||||
- wird in admin.php aufgerufen */
|
||||
|
||||
// Neuen Nutzer anlegen im Overlay (Formular) ?>
|
||||
<div class="standard_overlay" id="addNewUser">
|
||||
<div class="bg_blur"></div>
|
||||
<form method="post" action="">
|
||||
<div class="eventbox_mini center">
|
||||
<div class="exit" onclick="openCloseOverlay('addNewUser')">
|
||||
<?php display_button('cancel','','center'); ?>
|
||||
</div>
|
||||
<h3 style="margin-top:15px;margin-left:5px;margin-bottom:15px">Neuen Nutzer anlegen</h3>
|
||||
<div class="item">
|
||||
<input class="field" placeholder="E-Mailadresse" type="text" name="newMail"required>
|
||||
<input class="button" type="submit" name="addRecord" value="Hinzufügen">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php /* Name: overlay_message.php
|
||||
Beschreibung: Ausgabe einer Message als Toast-Notification
|
||||
|
||||
Anforderung: Ausgabe möglichst zum <body>-Beginn. */
|
||||
|
||||
|
||||
// Mapping aller Intranet-Messageboxen mit Typ und Ausgabetext
|
||||
function getMessageInfo($message) {
|
||||
return match ($message) {
|
||||
1 => ['type' => 'success', 'text' => "Passwort zurückgesetzt."],
|
||||
2 => ['type' => 'success', 'text' => "Nutzer entfernt."],
|
||||
3 => ['type' => 'success', 'text' => "Nutzer angelegt."],
|
||||
4 => ['type' => 'error', 'text' => "Nutzer existiert bereits."],
|
||||
5 => ['type' => 'success', 'text' => "Speicherung erfolgt."],
|
||||
6 => ['type' => 'success', 'text' => "Einladungsmail gesendet."],
|
||||
7 => ['type' => 'error', 'text' => "Bitte Name und E-Mail ausfüllen."],
|
||||
8 => ['type' => 'success', 'text' => "Brandschutzschulung abgeschlossen. Vielen Dank für deine Teilnahme!"],
|
||||
|
||||
default => ['type' => 'error', 'text' => "Unbekannter Fehler."],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Laden der Message
|
||||
if (isset($_GET["message"])) {
|
||||
$message = intval($_GET["message"]);
|
||||
$messageInfo = getMessageInfo($message);
|
||||
$toastType = $messageInfo['type'];
|
||||
$toastText = $messageInfo['text'];
|
||||
|
||||
?>
|
||||
<div class="nak-toast nak-toast--<?= $toastType ?>" id="nakToast">
|
||||
<div class="nak-toast__icon">
|
||||
<?php if ($toastType === 'success'): ?>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
<?php else: ?>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<span class="nak-toast__text"><?= htmlspecialchars($toastText) ?></span>
|
||||
<button class="nak-toast__close" onclick="dismissNakToast()" aria-label="Schließen">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var toast = document.getElementById('nakToast');
|
||||
if (!toast) return;
|
||||
// Slide in
|
||||
requestAnimationFrame(function() {
|
||||
toast.classList.add('nak-toast--visible');
|
||||
});
|
||||
// Auto-dismiss after 4s
|
||||
var timer = setTimeout(function() { dismissNakToast(); }, 4000);
|
||||
toast._timer = timer;
|
||||
// Clean URL
|
||||
if (window.history && window.history.replaceState) {
|
||||
var url = new URL(window.location);
|
||||
url.searchParams.delete('message');
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
})();
|
||||
function dismissNakToast() {
|
||||
var toast = document.getElementById('nakToast');
|
||||
if (!toast || toast.classList.contains('nak-toast--hiding')) return;
|
||||
clearTimeout(toast._timer);
|
||||
toast.classList.add('nak-toast--hiding');
|
||||
toast.classList.remove('nak-toast--visible');
|
||||
setTimeout(function() { toast.remove(); }, 350);
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php } ?>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php /* Name: overlay_profile.php
|
||||
Beschreibung: Profil eines ausgewählten Nutzers im Overlay */
|
||||
?>
|
||||
|
||||
<?php
|
||||
if (isset($_SESSION["profile_id"])) {
|
||||
$pdo = require($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
|
||||
$sql = "SELECT * FROM user_general_information
|
||||
WHERE user_id = {$_SESSION["profile_id"]}";
|
||||
$result = $pdo->query($sql);
|
||||
$partner = $result->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($partner["mail"])) {
|
||||
$sql_groups = "SELECT * FROM user_permissions WHERE user_id = ?";
|
||||
$stmt = $pdo->prepare($sql_groups);
|
||||
$stmt->execute([$_SESSION["profile_id"]]);
|
||||
$groups = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
else {
|
||||
header("Location:{$g_httpLoginPath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Profile image
|
||||
$profileImg = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$_SESSION["profile_id"]}-*.jpg");
|
||||
if (!empty($profileImg)) {
|
||||
usort($profileImg, function($a, $b) { return filemtime($b) - filemtime($a); });
|
||||
$profileImgUrl = str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
|
||||
} else {
|
||||
$profileImgUrl = "/intranet_repo/general_files/profile_pictures/default.jpg";
|
||||
}
|
||||
|
||||
$fullName = trim(($partner["first_name"] ?? '') . ' ' . ($partner["last_name"] ?? ''));
|
||||
$isRegistered = !empty($partner["first_name"]);
|
||||
?>
|
||||
|
||||
<div class="profile-modal-card">
|
||||
<button type="button" class="exit" aria-label="Schließen" onclick="openCloseProfile(0, '<?= $profile_container_name ?>', '<?= $profile_overlay_id ?>');">×</button>
|
||||
|
||||
<div class="profile-modal-hero">
|
||||
<img class="profile-modal-img" src="<?= htmlspecialchars($profileImgUrl) ?>" alt="Profilbild">
|
||||
<div>
|
||||
<?php if ($isRegistered): ?>
|
||||
<div class="profile-modal-title"><?= htmlspecialchars($fullName) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="profile-modal-title" style="color:rgba(255,255,255,0.7);">Nicht registriert</div>
|
||||
<?php endif; ?>
|
||||
<div class="profile-modal-subtitle">
|
||||
<span><?= htmlspecialchars($partner["mail"]) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-modal-body">
|
||||
<?php if ($partner["phone"] != null): ?>
|
||||
<div class="profile-modal-field">
|
||||
<div class="profile-modal-field-label">Telefon</div>
|
||||
<div class="profile-modal-field-value">
|
||||
<a href="tel:0<?= htmlspecialchars($partner["phone"]) ?>">0<?= htmlspecialchars($partner["phone"]) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($groups)): ?>
|
||||
<div class="profile-modal-field">
|
||||
<div class="profile-modal-field-label">Gruppen</div>
|
||||
<div class="profile-modal-chips">
|
||||
<?php
|
||||
foreach ($groups as $key => $value) {
|
||||
if ($value == 1) {
|
||||
echo '<span class="profile-modal-chip">' . htmlspecialchars($key) . '</span>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,199 @@
|
||||
////==========================================//
|
||||
//// GLOBALER BEREICH //
|
||||
////////////////////////////////////////////////
|
||||
//// Globale Konstanten
|
||||
const url = new URL(window.location.href);
|
||||
const basisUrl = url.origin + url.pathname;
|
||||
////
|
||||
////////////////////////////////////////////////
|
||||
////==========================================//
|
||||
|
||||
|
||||
|
||||
////==========================================//
|
||||
//// FUNKTIONEN und LISTENER //
|
||||
////////////////////////////////////////////////
|
||||
////
|
||||
|
||||
// Position der Elemente – min-height wird jetzt vollständig per CSS gelöst (nak-design.css)
|
||||
// Die alte JS-Berechnung wurde entfernt, da sie nach dem Laden die Höhe reduzierte.
|
||||
|
||||
|
||||
// JavaScript zum Erkennen der Scroll-Richtung und zum Umschalten der Klasse
|
||||
// Funktioniert sowohl bei Body-Scroll als auch bei internem Container-Scroll (z.B. eventbox_table)
|
||||
var lastScrollTop = 0;
|
||||
var lastContainerScrollTop = 0;
|
||||
|
||||
function handleHeaderScroll(scrollTop, lastScroll) {
|
||||
const elemHeaderMenue = document.getElementById('header_menue');
|
||||
const elemHeaderTitle = document.getElementById('header_title');
|
||||
|
||||
// Dynamische Höhe aus CSS-Variable lesen
|
||||
const headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 64;
|
||||
|
||||
// Kein Einfahren, wenn der Content auf die Seite passt (kein Scrollen nötig)
|
||||
if (document.documentElement.scrollHeight <= window.innerHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Am unteren Ende der Seite: Header wieder einblenden
|
||||
const atBottom = (window.innerHeight + window.scrollY) >= (document.documentElement.scrollHeight - 5);
|
||||
|
||||
if (scrollTop > lastScroll && !atBottom) {
|
||||
// Menü raus bei Scroll nach unten
|
||||
if (elemHeaderMenue) {
|
||||
elemHeaderMenue.style.top = `-${headerHeight}px`;
|
||||
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
|
||||
}
|
||||
if (elemHeaderTitle) {
|
||||
elemHeaderTitle.style.top = "0";
|
||||
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
|
||||
}
|
||||
} else {
|
||||
// Menü wieder rein bei Scroll nach oben
|
||||
if (elemHeaderMenue) {
|
||||
elemHeaderMenue.style.top = "0";
|
||||
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
|
||||
}
|
||||
if (elemHeaderTitle) {
|
||||
elemHeaderTitle.style.top = `${headerHeight}px`;
|
||||
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Body/Document Scroll
|
||||
document.addEventListener("scroll", function() {
|
||||
var st = window.scrollY || document.documentElement.scrollTop;
|
||||
handleHeaderScroll(st, lastScrollTop);
|
||||
lastScrollTop = st <= 0 ? 0 : st;
|
||||
}, false);
|
||||
|
||||
// Auch bei internem Container-Scroll (eventbox_table, display_within_dev)
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const scrollContainers = document.querySelectorAll('.eventbox_table, form.display_within_dev');
|
||||
scrollContainers.forEach(function(container) {
|
||||
var lastCST = 0;
|
||||
container.addEventListener("scroll", function() {
|
||||
var st = container.scrollTop;
|
||||
handleHeaderScroll(st, lastCST);
|
||||
lastCST = st <= 0 ? 0 : st;
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function openCloseHeaderProfile() {
|
||||
if (window.innerWidth >= 769) {
|
||||
// Desktop: click on profile image goes to profile page
|
||||
window.location.href = '/intranet_repo/projects/intranet/profile.php';
|
||||
} else {
|
||||
// Mobile: toggle expand/collapse
|
||||
const hpElem = document.getElementById('header_profile');
|
||||
hpElem.classList.toggle('header_profile_slideLeft');
|
||||
}
|
||||
}
|
||||
|
||||
function openCloseOverlay(id) {
|
||||
//Overlay ein-/ausblenden - beim Ausblenden zurück auf eigene Seite
|
||||
const elem = document.getElementById(id);
|
||||
|
||||
if (elem.style.display === "flex") {
|
||||
elem.style.display = "none";
|
||||
} else {
|
||||
elem.style.display = "flex";
|
||||
}
|
||||
}
|
||||
|
||||
function openCloseProfile(id, name, overlay) {
|
||||
// Profil per Klick öffnen/schließen, alle anderen immer schließen
|
||||
|
||||
const profiles = document.getElementsByName(name); //Ausgewählter Profilbereich (visit, calendar, ...)
|
||||
const oProfile = document.getElementById(overlay); //Ausgewähltes Overlay
|
||||
const aProfile = document.getElementById(id); //Ausgewähltes Profil
|
||||
const aId = (id > 0) ? aProfile.id : 0; //Ausweichen, wenn kein Profil gewählt
|
||||
|
||||
console.log('id:' + id);
|
||||
console.log('overlay:' + overlay);
|
||||
console.log('name:' + name);
|
||||
|
||||
if (oProfile.style.display === "flex") { //Overlay ein-/ausblenden
|
||||
oProfile.style.display = "none";
|
||||
} else {
|
||||
oProfile.style.display = "flex";
|
||||
//TODO: folgender ScrollTop greift nicht, WARUM?
|
||||
//oProfile.scrollTop = 0; //Hinscrollen zum Beginn des Overlays, vor allem bei @Media
|
||||
}
|
||||
|
||||
for (let i = 0; i < profiles.length; i++) {
|
||||
|
||||
const cProfile = profiles[i]; //Aktuelles Profil (Style: profile_container)
|
||||
|
||||
if (cProfile.id === aId && cProfile.style.display !== "block") {
|
||||
profiles[i].style.display = "block";
|
||||
} else {
|
||||
profiles[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
document.activeElement.blur(); //Fokus entfernen
|
||||
|
||||
// TEST console.log(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function mix(content) { //wird aktuell nicht eingesetzt, Daten werden per SQL randomized
|
||||
const list = document.getElementById(content);
|
||||
const items = Array.from(list.children);
|
||||
console.log('Vor dem Mischen:', items.map(item => item.textContent || item.innerText));
|
||||
|
||||
for (let i = items.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[items[i], items[j]] = [items[j], items[i]];
|
||||
}
|
||||
|
||||
console.log('Nach dem Mischen:', items.map(item => item.textContent || item.innerText));
|
||||
items.forEach(item => list.appendChild(item));
|
||||
}
|
||||
|
||||
|
||||
function showHideText(element) {
|
||||
element.classList.toggle("open");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Per Taste Overlay verlassen
|
||||
document.addEventListener('keydown', function(event) {
|
||||
const profileVisitOverlay = document.getElementById('profile_visit_overlay');
|
||||
const profileCalendarOverlay = document.getElementById('profile_calendar_overlay');
|
||||
const addNewUser = document.getElementById('addNewUser');
|
||||
const showMessage = document.getElementById('showMessage');
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
if (profileVisitOverlay && profileVisitOverlay.style.display === 'flex') {
|
||||
openCloseProfile(0, 'profile_visit', 'profile_visit_overlay');
|
||||
}
|
||||
if (profileCalendarOverlay && profileCalendarOverlay.style.display === 'flex') {
|
||||
openCloseProfile(0, 'profile_calendar', 'profile_calendar_overlay');
|
||||
}
|
||||
if (addNewUser && addNewUser.style.display === 'flex') {
|
||||
openCloseOverlay('addNewUser');
|
||||
}
|
||||
} else if (event.key === 'Enter' || event.key === 'Return' || event.key === " ") {
|
||||
if (showMessage && showMessage.style.display === 'flex') {
|
||||
openCloseOverlay('showMessage'); // ausblenden
|
||||
window.location.href=basisUrl; // zur aktuellen Seite zurückkehren
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fokus auf passendes Element beim Laden der Intranet-Seiten
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelector("#admintable tr[tabindex='0']")?.focus(); // Adminpanel
|
||||
document.querySelector("#visittable tr[tabindex='0']")?.focus(); // Besuchsstellen
|
||||
});
|
||||
@@ -0,0 +1,247 @@
|
||||
////==========================================//
|
||||
////GLOBALER BEREICH//
|
||||
////////////////////////////////////////////////
|
||||
////Globale Konstanten
|
||||
|
||||
////
|
||||
////////////////////////////////////////////////
|
||||
////==========================================//
|
||||
|
||||
|
||||
|
||||
////==========================================//
|
||||
////FUNKTIONEN und LISTENER//
|
||||
////////////////////////////////////////////////
|
||||
////
|
||||
|
||||
|
||||
// Warten, bis das DOM vollstaendig geladen ist
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var menuItems = document.querySelectorAll('.content-tab-bar__item[data-target]');
|
||||
|
||||
// Event-Listener fuer Klickereignisse auf Menueintraege
|
||||
menuItems.forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
// Entferne die aktive Klasse von allen Menueintraegen
|
||||
menuItems.forEach(function(menuItem) {
|
||||
menuItem.classList.remove('active');
|
||||
});
|
||||
|
||||
// Fuege die aktive Klasse zum geklickten Menuelement hinzu
|
||||
item.classList.add('active');
|
||||
|
||||
// Verstecke alle Inhaltsbereiche
|
||||
document.querySelectorAll('.content_seelsorge').forEach(function(section) {
|
||||
section.classList.remove('active');
|
||||
});
|
||||
|
||||
// Zeige den entsprechenden Inhalt an
|
||||
var contentId = item.getAttribute('data-target');
|
||||
document.getElementById(contentId).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ESC-Taste schliesst offene Modals
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeCalendarHelp();
|
||||
closeDayModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Wechsel der Kalenderblaetter Monat 1 und 2
|
||||
function switchCalendar(type) {
|
||||
const calendar_header_1 = document.querySelector('#calendar_header_1');
|
||||
const calendar_header_2 = document.querySelector('#calendar_header_2');
|
||||
const calendar_1 = document.querySelectorAll('[name="calendar_1"]');
|
||||
const calendar_2 = document.querySelectorAll('[name="calendar_2"]');
|
||||
|
||||
const isLeft = type === "left";
|
||||
calendar_header_1.style.display = isLeft ? "flex" : "none";
|
||||
calendar_header_2.style.display = isLeft ? "none" : "flex";
|
||||
|
||||
calendar_1.forEach(cal => cal.style.visibility = isLeft ? "visible" : "collapse");
|
||||
calendar_2.forEach(cal => cal.style.visibility = isLeft ? "collapse" : "visible");
|
||||
}
|
||||
|
||||
|
||||
// Tages-Modal oeffnen
|
||||
async function openDayModal(calendarDate) {
|
||||
if (!calendarDate) return;
|
||||
|
||||
const modal = document.getElementById('seelsorge-day-modal');
|
||||
const body = document.getElementById('modal-day-body');
|
||||
const title = document.getElementById('modal-day-title');
|
||||
const subtitle = document.getElementById('modal-day-subtitle');
|
||||
|
||||
if (!modal || !body || !title || !subtitle) {
|
||||
console.error('openDayModal: Modal-Elemente nicht gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Modal sofort anzeigen mit Ladezustand
|
||||
title.textContent = 'Laden...';
|
||||
subtitle.innerHTML = '';
|
||||
body.innerHTML = '<div class="seelsorge-modal-loading"><div class="loading-spinner"></div></div>';
|
||||
modal.style.display = 'flex';
|
||||
|
||||
try {
|
||||
const response = await fetch('content/calendar_day.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'calendarDate=' + encodeURIComponent(calendarDate)
|
||||
});
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
if (response.ok) {
|
||||
body.innerHTML = html;
|
||||
|
||||
// Metadaten aus dem eingebetteten JSON lesen
|
||||
const metaEl = body.querySelector('#modal-meta');
|
||||
if (metaEl) {
|
||||
const meta = JSON.parse(metaEl.textContent);
|
||||
title.textContent = meta.title;
|
||||
|
||||
let chips = '<span class="seelsorge-modal-chip">KW ' + meta.kw + '</span>';
|
||||
if (meta.feiertag) {
|
||||
chips += '<span class="seelsorge-modal-chip seelsorge-chip-holiday">' + meta.feiertag + '</span>';
|
||||
}
|
||||
subtitle.innerHTML = chips;
|
||||
metaEl.remove();
|
||||
}
|
||||
} else {
|
||||
// PHP-Fehler direkt im Modal anzeigen
|
||||
title.textContent = 'Fehler ' + response.status;
|
||||
body.innerHTML = '<div style="padding:16px;font-family:monospace;font-size:0.85em;color:#d21400;white-space:pre-wrap;word-break:break-word;background:#fff5f5;border:1px solid #fcc;">'
|
||||
+ (html || 'Keine Antwort vom Server.') + '</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
body.innerHTML = '<div class="seelsorge-modal-empty">Fehler beim Laden der Daten: ' + error.message + '</div>';
|
||||
console.error('Fehler beim Laden der Tagesdetails:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Modal schliessen
|
||||
function closeDayModal() {
|
||||
const modal = document.getElementById('seelsorge-day-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// Backdrop-Klick schliesst Modal
|
||||
function closeDayModalOnBackdrop(event) {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeDayModal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Verfuegbarkeit im Modal togglen (Modal bleibt offen)
|
||||
async function toggleAvailabilityModal(date) {
|
||||
const btn = document.getElementById('modal-toggle-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gespeichert\u2026';
|
||||
|
||||
try {
|
||||
const response = await fetch("toggle_availability.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-type": "application/x-www-form-urlencoded" },
|
||||
body: "date=" + encodeURIComponent(date)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.ok) {
|
||||
// Modal-Inhalt neu laden um aktuellen Stand zu zeigen
|
||||
await openDayModal(date);
|
||||
|
||||
// Kalender-Tageszelle visuell aktualisieren
|
||||
updateCalendarCell(date, data.nowAvailable, data.availCount);
|
||||
} else {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
console.error("Fehler:", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
console.error("Fehler beim Aktualisieren der Verfuegbarkeit:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Kalender-Tageszelle visuell aktualisieren ohne Page-Reload
|
||||
function updateCalendarCell(date, nowAvailable, availCount) {
|
||||
var cells = document.querySelectorAll('#calendar_day_item');
|
||||
cells.forEach(function(cell) {
|
||||
var onclick = cell.getAttribute('onclick') || '';
|
||||
if (onclick.indexOf(date) === -1) return;
|
||||
|
||||
// Alle Verfuegbarkeits-Klassen entfernen
|
||||
cell.classList.remove('available_single', 'available_multiple', 'normalday');
|
||||
|
||||
if (availCount >= 2) {
|
||||
cell.classList.add('available_multiple');
|
||||
} else if (availCount === 1) {
|
||||
cell.classList.add('available_single');
|
||||
} else {
|
||||
cell.classList.add('normalday');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Profil eines Diakons aus dem Kalender-Modal oeffnen
|
||||
var _lastCalendarDate = null;
|
||||
|
||||
function openCalendarProfile(userId) {
|
||||
// Datum merken, damit wir beim Schliessen zurueckkehren koennen
|
||||
var metaEl = document.getElementById('modal-meta');
|
||||
var bodyEl = document.getElementById('modal-day-body');
|
||||
if (!_lastCalendarDate && bodyEl) {
|
||||
// Datum aus dem Toggle-Button extrahieren
|
||||
var toggleBtn = bodyEl.querySelector('#modal-toggle-btn');
|
||||
if (toggleBtn) {
|
||||
var onclickAttr = toggleBtn.getAttribute('onclick') || '';
|
||||
var match = onclickAttr.match(/'(\d{4}-\d{2}-\d{2})'/);
|
||||
if (match) _lastCalendarDate = match[1];
|
||||
}
|
||||
}
|
||||
closeDayModal();
|
||||
openCloseProfile(userId, 'profile_calendar', 'profile_calendar_overlay');
|
||||
}
|
||||
|
||||
// Globalen Listener fuer Kalender-Profil-Overlay: bei Schliessen Tagesmodal wieder oeffnen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// MutationObserver auf das Kalender-Profil-Overlay
|
||||
var calOverlay = document.getElementById('profile_calendar_overlay');
|
||||
if (calOverlay) {
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(m) {
|
||||
if (m.attributeName === 'style' && calOverlay.style.display === 'none' && _lastCalendarDate) {
|
||||
var date = _lastCalendarDate;
|
||||
_lastCalendarDate = null;
|
||||
openDayModal(date);
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(calOverlay, { attributes: true, attributeFilter: ['style'] });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Hilfe-Modal oeffnen/schliessen
|
||||
function openCalendarHelp() {
|
||||
var modal = document.getElementById('calendar-help-modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
}
|
||||
function closeCalendarHelp() {
|
||||
var modal = document.getElementById('calendar-help-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/* Zentrale Reiternavigation des Adminpanels.
|
||||
* Erwartet: $pdo (PDO) und optional $adminActiveTab.
|
||||
*/
|
||||
$adminActiveTab = $adminActiveTab ?? '';
|
||||
$adminInboxBadgeCount = 0;
|
||||
|
||||
try {
|
||||
$adminInboxBadgeCount = (int)$pdo->query("SELECT COUNT(*) FROM admin_messages WHERE status = 'pending'")->fetchColumn();
|
||||
} catch (Throwable $e) {
|
||||
// Die Navigation soll auch dann benutzbar bleiben, wenn das Postfach temporär nicht verfügbar ist.
|
||||
$adminInboxBadgeCount = 0;
|
||||
}
|
||||
|
||||
$adminTabs = [
|
||||
'users' => ['label' => 'Nutzerverwaltung', 'href' => 'admin.php'],
|
||||
'inbox' => ['label' => 'Postfach', 'href' => 'admin_inbox.php', 'badge' => $adminInboxBadgeCount],
|
||||
'kalli' => ['label' => 'Kalli', 'href' => 'admin_kalli.php'],
|
||||
'brandschutz' => ['label' => 'Brandschutz', 'href' => 'admin_brandschutz.php'],
|
||||
'notices' => ['label' => 'Meldungen', 'href' => 'admin_notices.php'],
|
||||
];
|
||||
?>
|
||||
<div class="content-tab-bar">
|
||||
<?php foreach ($adminTabs as $tabKey => $tab): ?>
|
||||
<a class="content-tab-bar__item<?= $adminActiveTab === $tabKey ? ' active' : '' ?>" href="<?= htmlspecialchars($tab['href'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<?= htmlspecialchars($tab['label'], ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php if (!empty($tab['badge'])): ?>
|
||||
<span class="content-tab-bar__badge"><?= (int)$tab['badge'] ?></span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,677 @@
|
||||
<?php /* Name: admin_brandschutz.php
|
||||
Beschreibung: Brandschutz-Schulungsverwaltung im Adminpanel
|
||||
- Übersicht aller Nutzer mit Brandschutz-Rolle
|
||||
- Fälligkeitsstatus und Details
|
||||
- Manuelle Anpassung des Schulungsdatums */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$isAdmin = $stmt->fetchColumn();
|
||||
|
||||
if ((int)$isAdmin !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Hilfsfunktionen
|
||||
function getProfileImageUrl_bs(array $row, string $g_profilePicturePath): string {
|
||||
$profileImg = glob($g_profilePicturePath."{$row['user_id']}-*.jpg");
|
||||
if (!empty($profileImg)) {
|
||||
usort($profileImg, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
|
||||
}
|
||||
return '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
}
|
||||
|
||||
function getDisplayName_bs(array $row): string {
|
||||
$fullName = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? ''));
|
||||
return $fullName !== '' ? $fullName : ($row['mail'] ?? 'Unbekannt');
|
||||
}
|
||||
|
||||
// Schulungsdatum manuell anpassen (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bs_update_date'])) {
|
||||
$editUserId = (int)($_POST['bs_user_id'] ?? 0);
|
||||
$newDate = $_POST['bs_new_date'] ?? '';
|
||||
|
||||
if ($editUserId > 0 && preg_match('/^\d{4}-\d{2}-\d{2}$/', $newDate)) {
|
||||
// Prüfen ob das neue Datum innerhalb der Frist liegt (365 Tage ab Datum > heute)
|
||||
$newDueDate = date('Y-m-d', strtotime($newDate . ' +365 days'));
|
||||
$isWithinDeadline = (strtotime($newDueDate) >= strtotime(date('Y-m-d')));
|
||||
|
||||
if ($isWithinDeadline) {
|
||||
// Datum in der Frist: Zähler zurücksetzen
|
||||
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date, reminder_count = 0, admin_reminder_count = 0");
|
||||
} else {
|
||||
// Datum außerhalb der Frist: Nur Datum aktualisieren, Zähler beibehalten
|
||||
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date");
|
||||
}
|
||||
$stmtUpsert->execute([$editUserId, $newDate]);
|
||||
|
||||
// Systemnachricht in erledigte Admin-Nachrichten
|
||||
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
if (empty($adminName)) $adminName = 'Ein Administrator';
|
||||
|
||||
$stmtUser = $pdo->prepare("SELECT first_name, last_name, mail FROM user_general_information WHERE user_id = ?");
|
||||
$stmtUser->execute([$editUserId]);
|
||||
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$editedName = trim(($editedUser['first_name'] ?? '') . ' ' . ($editedUser['last_name'] ?? ''));
|
||||
if (empty($editedName)) $editedName = $editedUser['mail'] ?? 'Unbekannt';
|
||||
|
||||
$formattedDate = date('d.m.Y', strtotime($newDate));
|
||||
$sysSubject = "Brandschutz-Schulungsdatum angepasst: {$editedName}";
|
||||
$sysMessage = "{$adminName} hat das Schulungsdatum von {$editedName} manuell auf den {$formattedDate} gesetzt.";
|
||||
|
||||
$stmtMsg = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$senderMail = $editedUser['mail'] ?? '';
|
||||
$stmtMsg->execute([$adminName, $senderMail, $sysSubject, $sysMessage]);
|
||||
|
||||
header("Location: admin_brandschutz.php?message=5");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Brandschutz-Nutzer laden
|
||||
$sql = "SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail,
|
||||
bt.last_training_date, bt.reminder_count, bt.admin_reminder_count,
|
||||
CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS IstRegistriert
|
||||
FROM user_permissions up
|
||||
INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id
|
||||
LEFT JOIN brandschutz_training bt ON ugi.user_id = bt.user_id
|
||||
LEFT JOIN user_login ul ON ugi.user_id = ul.user_id
|
||||
WHERE up.\"Brandschutz\" = 1
|
||||
ORDER BY bt.last_training_date ASC, ugi.last_name ASC, ugi.first_name ASC";
|
||||
$bsUsers = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<style>
|
||||
.admin-users-shell {
|
||||
padding: 18px;
|
||||
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
|
||||
min-height: 100%;
|
||||
}
|
||||
.admin-table-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(120, 140, 160, 0.22);
|
||||
background: rgba(255,255,255,0.86);
|
||||
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
|
||||
}
|
||||
.admin-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 18px 12px 18px;
|
||||
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
|
||||
}
|
||||
.admin-list-title {
|
||||
font-size: 1.05em;
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
}
|
||||
.admin-list-hint {
|
||||
font-size: 0.86em;
|
||||
color: hsl(210, 15%, 46%);
|
||||
}
|
||||
table.admin-user-table {
|
||||
display: table !important;
|
||||
width: calc(100% - 24px);
|
||||
margin: 8px 12px 14px 12px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
overflow: visible;
|
||||
}
|
||||
.admin-user-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 10px 14px;
|
||||
color: hsl(210, 16%, 46%);
|
||||
font-size: 0.78em;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 6px 18px rgba(31, 38, 46, 0.06);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row:hover {
|
||||
transform: translateY(-1px);
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 12px 26px rgba(31, 38, 46, 0.13);
|
||||
}
|
||||
.admin-user-table td {
|
||||
padding: 12px 14px !important;
|
||||
vertical-align: middle;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td:first-child {
|
||||
width: 82px;
|
||||
min-width: 82px;
|
||||
max-width: 82px;
|
||||
}
|
||||
.admin-profile-button {
|
||||
display: block;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(31, 38, 46, 0.18), 0 0 0 3px rgba(255,255,255,0.95);
|
||||
}
|
||||
.admin-profile-button img {
|
||||
display: block;
|
||||
width: 58px !important;
|
||||
height: 58px !important;
|
||||
object-fit: cover;
|
||||
border-radius: 12px !important;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row:hover .admin-profile-button img {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.admin-user-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
}
|
||||
.bs-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.78em;
|
||||
font-weight: 800;
|
||||
}
|
||||
.bs-status-ok {
|
||||
color: hsl(145, 50%, 28%);
|
||||
background: hsl(145, 50%, 93%);
|
||||
border: 1px solid hsl(145, 50%, 82%);
|
||||
}
|
||||
.bs-status-warning {
|
||||
color: hsl(35, 78%, 30%);
|
||||
background: hsl(42, 90%, 93%);
|
||||
border: 1px solid hsl(42, 90%, 78%);
|
||||
}
|
||||
.bs-status-overdue {
|
||||
color: hsl(0, 65%, 38%);
|
||||
background: hsl(0, 65%, 95%);
|
||||
border: 1px solid hsl(0, 65%, 82%);
|
||||
}
|
||||
.bs-status-critical {
|
||||
color: #FFFFFF;
|
||||
background: hsl(0, 65%, 45%);
|
||||
border: 1px solid hsl(0, 65%, 38%);
|
||||
}
|
||||
.bs-due-date {
|
||||
font-size: 0.9em;
|
||||
font-weight: 650;
|
||||
color: hsl(210, 14%, 35%);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.admin-modal-card {
|
||||
position: relative;
|
||||
width: min(720px, calc(100vw - 24px));
|
||||
max-height: min(760px, calc(100vh - 24px));
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
|
||||
}
|
||||
.admin-modal-hero {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 112px 1fr;
|
||||
gap: 18px;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
color: #FFFFFF;
|
||||
background:
|
||||
radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
|
||||
linear-gradient(135deg, hsl(25, 90%, 45%), hsl(25, 95%, 35%));
|
||||
}
|
||||
.admin-modal-profile-img {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 28px rgba(0,0,0,0.32), 0 0 0 4px rgba(255,255,255,0.18);
|
||||
}
|
||||
.admin-modal-title {
|
||||
margin: 0 42px 6px 0;
|
||||
font-size: 1.45em;
|
||||
font-weight: 850;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.admin-modal-subtitle {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
color: rgba(255,255,255,0.86);
|
||||
font-weight: 600;
|
||||
}
|
||||
.admin-modal-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 9px;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255,255,255,0.14);
|
||||
border: 1px solid rgba(255,255,255,0.18);
|
||||
font-size: 0.82em;
|
||||
}
|
||||
.admin-modal-body {
|
||||
padding: 22px 24px 24px 24px;
|
||||
}
|
||||
.admin-section-title {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 0.9em;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(210, 18%, 36%);
|
||||
}
|
||||
.admin-modal-card .exit {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid rgba(255,255,255,0.25);
|
||||
background: rgba(255,255,255,0.16);
|
||||
color: #FFFFFF;
|
||||
font-size: 1.35em;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bs-detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.bs-detail-card {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid hsl(210, 18%, 88%);
|
||||
background: linear-gradient(180deg, #FFFFFF, hsl(210, 12%, 98%));
|
||||
}
|
||||
.bs-detail-label {
|
||||
margin-bottom: 3px;
|
||||
color: hsl(210, 14%, 46%);
|
||||
font-size: 0.74em;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.bs-detail-value {
|
||||
color: hsl(210, 20%, 18%);
|
||||
font-size: 1.02em;
|
||||
font-weight: 850;
|
||||
}
|
||||
.bs-detail-value.overdue {
|
||||
color: hsl(0, 65%, 42%);
|
||||
}
|
||||
.bs-detail-value.ok {
|
||||
color: hsl(145, 50%, 32%);
|
||||
}
|
||||
.admin-modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
border-top: 1px solid hsl(210, 18%, 90%);
|
||||
padding-top: 16px;
|
||||
}
|
||||
.admin-modal-actions button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36px;
|
||||
padding: 8px 13px;
|
||||
border: 1px solid hsl(210, 18%, 84%);
|
||||
background: #FFFFFF;
|
||||
color: hsl(210, 20%, 22%);
|
||||
font-size: 0.86em;
|
||||
font-weight: 850;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
|
||||
}
|
||||
.admin-modal-actions button:hover {
|
||||
background: hsl(210, 57%, 96%);
|
||||
}
|
||||
.admin-save-button {
|
||||
color: #FFFFFF !important;
|
||||
background: linear-gradient(135deg, var(--nak-orange), hsl(25, 95%, 40%)) !important;
|
||||
border-color: hsl(25, 95%, 40%) !important;
|
||||
}
|
||||
.bs-date-input {
|
||||
padding: 10px 12px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-primary);
|
||||
border: 1px solid var(--gray-300);
|
||||
background: var(--gray-50);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-weight: 650;
|
||||
}
|
||||
.bs-date-input:focus {
|
||||
border-color: hsl(25, 80%, 55%);
|
||||
background: #FFFFFF;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 4px hsla(25, 80%, 55%, 0.14);
|
||||
}
|
||||
|
||||
@media (max-width: 780px) {
|
||||
.admin-users-shell { padding: 10px; }
|
||||
.admin-list-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
table.admin-user-table {
|
||||
display: block !important;
|
||||
width: auto;
|
||||
margin: 8px;
|
||||
}
|
||||
.admin-user-table thead { display: none; }
|
||||
.admin-user-table tbody {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
gap: 0 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(120, 140, 160, 0.16);
|
||||
box-shadow: 0 8px 22px rgba(31, 38, 46, 0.08);
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td {
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.admin-user-table tbody tr.admin-user-row td:first-child {
|
||||
grid-row: 1 / span 3;
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
max-width: 72px;
|
||||
}
|
||||
.admin-profile-button,
|
||||
.admin-profile-button img {
|
||||
width: 64px !important;
|
||||
height: 64px !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
.admin-modal-card {
|
||||
width: calc(100vw - 12px);
|
||||
max-height: calc(100dvh - 12px);
|
||||
}
|
||||
.admin-modal-hero {
|
||||
grid-template-columns: 78px minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
padding: 18px;
|
||||
}
|
||||
.admin-modal-profile-img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.admin-modal-title {
|
||||
font-size: 1.15em;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.admin-modal-body { padding: 18px; }
|
||||
.bs-detail-grid { grid-template-columns: 1fr; }
|
||||
.admin-modal-actions { flex-direction: column-reverse; }
|
||||
.admin-modal-actions button { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-admin">
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
|
||||
|
||||
<div class="header_title ht_admin text_white unselectable" id="header_title">
|
||||
<span class="header_title_text">Adminpanel</span>
|
||||
</div>
|
||||
|
||||
<div class="content" id="fullsize">
|
||||
<?php $adminActiveTab = 'brandschutz'; require __DIR__.'/_tabs.php'; ?>
|
||||
|
||||
<div class="admin-users-shell">
|
||||
<div class="admin-table-card">
|
||||
<div class="admin-list-header">
|
||||
<div>
|
||||
<span class="admin-list-title">Brandschutz-Schulungen</span>
|
||||
<div class="admin-list-hint"><?= count($bsUsers) ?> Nutzer mit Brandschutz-Rolle</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="admin-user-table" id="bsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Profil</th>
|
||||
<th>Name</th>
|
||||
<th>Nächste Fälligkeit</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($bsUsers as $bsUser): ?>
|
||||
<?php
|
||||
$nameOutput = getDisplayName_bs($bsUser);
|
||||
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
|
||||
|
||||
$lastTraining = $bsUser['last_training_date'];
|
||||
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
|
||||
|
||||
// Berechne Fälligkeit
|
||||
if (empty($lastTraining)) {
|
||||
$dueDate = null;
|
||||
$daysOverdue = null;
|
||||
$statusText = 'Nie geschult';
|
||||
$statusClass = 'bs-status-overdue';
|
||||
$dueDateDisplay = 'Sofort fällig';
|
||||
} else {
|
||||
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
|
||||
$today = date('Y-m-d');
|
||||
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
|
||||
|
||||
if ($diff > 30) {
|
||||
$statusText = 'Aktuell';
|
||||
$statusClass = 'bs-status-ok';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} elseif ($diff > 0) {
|
||||
$statusText = 'Bald fällig';
|
||||
$statusClass = 'bs-status-warning';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} elseif ($diff > -120) {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$statusText = "{$daysOverdue} Tage überfällig";
|
||||
$statusClass = 'bs-status-overdue';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
} else {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$statusText = "{$daysOverdue} Tage überfällig";
|
||||
$statusClass = 'bs-status-critical';
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
}
|
||||
}
|
||||
?>
|
||||
<tr class="admin-user-row" tabindex="0" role="button" aria-label="<?= htmlspecialchars($nameOutput) ?> Details" onclick="openBsModal(<?= (int)$bsUser['user_id'] ?>)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();openBsModal(<?= (int)$bsUser['user_id'] ?>)}">
|
||||
<td>
|
||||
<button class="admin-profile-button" type="button" tabindex="-1" aria-hidden="true">
|
||||
<img src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<span class="admin-user-name"><?= htmlspecialchars($nameOutput) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="bs-due-date"><?= $dueDateDisplay ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="bs-status-badge <?= $statusClass ?>"><?= $statusText ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($bsUsers)): ?>
|
||||
<tr><td colspan="4" style="padding:24px !important;text-align:center;color:hsl(210,20%,45%);background:#FFF !important;">Keine Nutzer mit der Rolle "Brandschutz" gefunden.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals für jeden Brandschutz-Nutzer -->
|
||||
<?php foreach ($bsUsers as $bsUser): ?>
|
||||
<?php
|
||||
$nameOutput = getDisplayName_bs($bsUser);
|
||||
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
|
||||
$lastTraining = $bsUser['last_training_date'];
|
||||
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
|
||||
|
||||
if (empty($lastTraining)) {
|
||||
$dueDate = null;
|
||||
$dueDateDisplay = 'Sofort fällig';
|
||||
$lastTrainingDisplay = 'Nie';
|
||||
$overdueText = 'Keine Schulung hinterlegt – sofort fällig';
|
||||
$overdueClass = 'overdue';
|
||||
} else {
|
||||
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
|
||||
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
|
||||
$lastTrainingDisplay = date('d.m.Y', strtotime($lastTraining));
|
||||
$today = date('Y-m-d');
|
||||
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
|
||||
|
||||
if ($diff > 0) {
|
||||
$overdueText = 'Nicht überfällig (' . (int)$diff . ' Tage verbleibend)';
|
||||
$overdueClass = 'ok';
|
||||
} else {
|
||||
$daysOverdue = abs((int)$diff);
|
||||
$overdueText = "{$daysOverdue} Tage überfällig";
|
||||
$overdueClass = 'overdue';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="standard_overlay" id="bsModal-<?= (int)$bsUser['user_id'] ?>" onclick="if(event.target.id==='bsModal-<?= (int)$bsUser['user_id'] ?>')closeBsModal(<?= (int)$bsUser['user_id'] ?>)">
|
||||
<div class="admin-modal-card">
|
||||
<button type="button" class="exit" aria-label="Schließen" onclick="closeBsModal(<?= (int)$bsUser['user_id'] ?>)">×</button>
|
||||
|
||||
<div class="admin-modal-hero">
|
||||
<img class="admin-modal-profile-img" src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
|
||||
<div>
|
||||
<div class="admin-modal-title"><?= htmlspecialchars($nameOutput) ?></div>
|
||||
<div class="admin-modal-subtitle">
|
||||
<span><?= htmlspecialchars($bsUser['mail'] ?? '') ?></span>
|
||||
<span class="admin-modal-chip">Brandschutz</span>
|
||||
</div>
|
||||
<div class="admin-modal-subtitle" style="margin-bottom:0;">Schulungsdetails & Fälligkeiten</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-modal-body">
|
||||
<div class="admin-section-title">Schulungsstatus</div>
|
||||
<div class="bs-detail-grid">
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Letzte Schulung</div>
|
||||
<div class="bs-detail-value"><?= $lastTrainingDisplay ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Nächste Fälligkeit</div>
|
||||
<div class="bs-detail-value"><?= $dueDateDisplay ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Überfälligkeit</div>
|
||||
<div class="bs-detail-value <?= $overdueClass ?>"><?= $overdueText ?></div>
|
||||
</div>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Erinnerungsmails gesendet</div>
|
||||
<div class="bs-detail-value"><?= $reminderCount ?></div>
|
||||
</div>
|
||||
<?php if ($adminReminderCount > 0): ?>
|
||||
<div class="bs-detail-card">
|
||||
<div class="bs-detail-label">Admin-Eskalationen</div>
|
||||
<div class="bs-detail-value overdue"><?= $adminReminderCount ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="admin-section-title">Schulungsdatum anpassen</div>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="bs_user_id" value="<?= (int)$bsUser['user_id'] ?>">
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;margin-bottom:8px;">
|
||||
<div style="flex:1;">
|
||||
<label style="display:block;font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);margin-bottom:4px;">Datum der letzten Schulung</label>
|
||||
<input type="date" name="bs_new_date" class="bs-date-input" value="<?= htmlspecialchars($lastTraining ?? '') ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin:0 0 16px 0;font-size:0.82em;color:hsl(210,14%,46%);font-weight:600;">Setzt das Schulungsdatum und alle Erinnerungszähler zurück.</p>
|
||||
|
||||
<div class="admin-modal-actions">
|
||||
<div>
|
||||
<button type="button" onclick="closeBsModal(<?= (int)$bsUser['user_id'] ?>)">Schließen</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" name="bs_update_date" value="1" class="admin-save-button">Datum speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div><!-- content -->
|
||||
|
||||
<script>
|
||||
function openBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'flex';
|
||||
}
|
||||
function closeBsModal(userId) {
|
||||
var modal = document.getElementById('bsModal-' + userId);
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.standard_overlay[id^="bsModal-"]').forEach(function(m) {
|
||||
m.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,564 @@
|
||||
<?php /* Name: admin_inbox.php
|
||||
Beschreibung: Admin-Nachrichteninbox
|
||||
- Registrierungsanfragen
|
||||
- Kontaktformular-Anfragen
|
||||
- Systemnachrichten
|
||||
- Seelsorge-Nachrichten
|
||||
- Info-Nachrichten */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$isAdmin = $stmt->fetchColumn();
|
||||
|
||||
if ((int)$isAdmin !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Typ-Konfiguration für Inbox
|
||||
$typeConfig = [
|
||||
'registration' => ['label' => 'Registrierung', 'class' => 'inbox-type-registration'],
|
||||
'contact' => ['label' => 'Kontakt', 'class' => 'inbox-type-contact'],
|
||||
'system' => ['label' => 'System', 'class' => 'inbox-type-system'],
|
||||
];
|
||||
|
||||
// Filter
|
||||
$activeFilter = $_GET['filter'] ?? 'all';
|
||||
$validFilters = array_merge(['all'], array_keys($typeConfig));
|
||||
if (!in_array($activeFilter, $validFilters, true)) $activeFilter = 'all';
|
||||
|
||||
function getTypeLabel($type, $typeConfig) {
|
||||
return $typeConfig[$type]['label'] ?? ucfirst($type);
|
||||
}
|
||||
function getTypeClass($type, $typeConfig) {
|
||||
return $typeConfig[$type]['class'] ?? 'inbox-type-system';
|
||||
}
|
||||
|
||||
// Kontaktnachricht als erledigt markieren
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["mark_done"])) {
|
||||
$msgId = (int)$_POST["mark_done"];
|
||||
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$msgId]);
|
||||
header("Location: admin_inbox.php?inbox_status=done");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Brandschutz-Eskalation: Admin bestätigt persönliche Erinnerung
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["bs_escalation_confirm"])) {
|
||||
$msgId = (int)$_POST["bs_escalation_msg_id"];
|
||||
$bsUserId = (int)$_POST["bs_escalation_user_id"];
|
||||
|
||||
// Nachricht als erledigt markieren
|
||||
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$msgId]);
|
||||
|
||||
// Log-Eintrag erstellen
|
||||
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
if (empty($adminName)) $adminName = 'Ein Administrator';
|
||||
|
||||
$stmtUser = $pdo->prepare("SELECT first_name, last_name, mail FROM user_general_information WHERE user_id = ?");
|
||||
$stmtUser->execute([$bsUserId]);
|
||||
$bsUserData = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$bsUserName = trim(($bsUserData['first_name'] ?? '') . ' ' . ($bsUserData['last_name'] ?? ''));
|
||||
if (empty($bsUserName)) $bsUserName = $bsUserData['mail'] ?? 'Unbekannt';
|
||||
|
||||
$logSubject = "Persönliche Brandschutz-Erinnerung bestätigt: {$bsUserName}";
|
||||
$logMessage = "{$adminName} hat bestätigt, {$bsUserName} persönlich an die ausstehende Brandschutzunterweisung erinnert zu haben.";
|
||||
|
||||
$stmtLog = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$senderMail = $bsUserData['mail'] ?? '';
|
||||
$stmtLog->execute([$adminName, $senderMail, $logSubject, $logMessage]);
|
||||
|
||||
header("Location: admin_inbox.php?inbox_status=done");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Nachrichten laden (mit optionalem Filter)
|
||||
$filterClause = '';
|
||||
if ($activeFilter !== 'all') {
|
||||
$filterClause = " AND type = '" . $activeFilter . "'";
|
||||
}
|
||||
$pendingMessages = $pdo->query("SELECT * FROM admin_messages WHERE status = 'pending'{$filterClause} ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$doneMessages = $pdo->query("SELECT * FROM admin_messages WHERE status = 'done'{$filterClause} ORDER BY processed_at DESC LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
// Rollen laden für Registrierungs-Modal
|
||||
$permissionRoles = [];
|
||||
$rolesResult = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'");
|
||||
while ($col = $rolesResult->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (!in_array($col['column_name'], ['p_id', 'user_id'], true)) {
|
||||
$permissionRoles[] = $col['column_name'];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<style>
|
||||
.inbox-shell {
|
||||
padding: 18px;
|
||||
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
|
||||
min-height: 100%;
|
||||
}
|
||||
.inbox-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(120, 140, 160, 0.22);
|
||||
background: rgba(255,255,255,0.86);
|
||||
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.inbox-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 18px 12px 18px;
|
||||
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
|
||||
}
|
||||
.inbox-title {
|
||||
font-size: 1.05em;
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
}
|
||||
.inbox-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
padding: 0 7px;
|
||||
background: hsl(0, 78%, 47%);
|
||||
color: #FFFFFF;
|
||||
font-size: 0.78em;
|
||||
font-weight: 850;
|
||||
}
|
||||
.inbox-item {
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid rgba(120, 140, 160, 0.1);
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
.inbox-item:last-child { border-bottom: none; }
|
||||
.inbox-item:hover { background: hsl(210, 57%, 97%); }
|
||||
.inbox-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.inbox-item-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.72em;
|
||||
font-weight: 800;
|
||||
}
|
||||
.inbox-type-registration {
|
||||
color: hsl(210, 57%, 34%);
|
||||
background: hsl(210, 57%, 94%);
|
||||
border: 1px solid hsl(210, 57%, 88%);
|
||||
}
|
||||
.inbox-type-contact {
|
||||
color: hsl(145, 50%, 32%);
|
||||
background: hsl(145, 50%, 93%);
|
||||
border: 1px solid hsl(145, 50%, 85%);
|
||||
}
|
||||
.inbox-type-system {
|
||||
color: hsl(30, 70%, 36%);
|
||||
background: hsl(30, 70%, 94%);
|
||||
border: 1px solid hsl(30, 70%, 85%);
|
||||
}
|
||||
|
||||
.inbox-item-date {
|
||||
font-size: 0.82em;
|
||||
color: hsl(210, 14%, 50%);
|
||||
font-weight: 600;
|
||||
}
|
||||
.inbox-item-subject {
|
||||
font-weight: 800;
|
||||
color: hsl(210, 20%, 15%);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.inbox-item-sender {
|
||||
font-size: 0.9em;
|
||||
color: hsl(210, 14%, 40%);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.inbox-item-message {
|
||||
font-size: 0.9em;
|
||||
color: hsl(210, 12%, 35%);
|
||||
white-space: pre-wrap;
|
||||
background: hsl(210, 12%, 98%);
|
||||
padding: 10px 12px;
|
||||
border: 1px solid hsl(210, 12%, 92%);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.inbox-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.inbox-actions form { display: inline; }
|
||||
.inbox-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 32px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid hsl(210, 18%, 84%);
|
||||
background: #FFFFFF;
|
||||
color: hsl(210, 20%, 22%);
|
||||
font-size: 0.82em;
|
||||
font-weight: 850;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
.inbox-btn:hover { background: hsl(210, 57%, 96%); }
|
||||
.inbox-btn-approve {
|
||||
color: #FFFFFF !important;
|
||||
background: linear-gradient(135deg, var(--nak-darkgreen), #005a10) !important;
|
||||
border-color: #005a10 !important;
|
||||
}
|
||||
.inbox-btn-reject {
|
||||
color: var(--nak-red) !important;
|
||||
border-color: hsl(352, 72%, 88%) !important;
|
||||
background: hsl(352, 72%, 97%) !important;
|
||||
}
|
||||
.inbox-btn-done {
|
||||
color: hsl(210, 14%, 46%) !important;
|
||||
background: hsl(210, 12%, 95%) !important;
|
||||
border-color: hsl(210, 12%, 88%) !important;
|
||||
}
|
||||
.inbox-empty {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: hsl(210, 14%, 50%);
|
||||
font-weight: 600;
|
||||
}
|
||||
.inbox-section-title {
|
||||
margin: 18px 0 10px 0;
|
||||
font-size: 0.88em;
|
||||
font-weight: 850;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: hsl(210, 14%, 46%);
|
||||
}
|
||||
.inbox-done-item {
|
||||
opacity: 0.6;
|
||||
}
|
||||
@media (max-width: 780px) {
|
||||
.inbox-shell { padding: 10px; }
|
||||
.inbox-item-header { flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
.inbox-filter-bar { gap: 6px; }
|
||||
.inbox-filter-btn { font-size: 0.72em; padding: 5px 10px; }
|
||||
}
|
||||
.inbox-filter-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.inbox-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid hsl(210, 18%, 84%);
|
||||
background: #FFF;
|
||||
color: hsl(210, 18%, 42%);
|
||||
font-size: 0.82em;
|
||||
font-weight: 750;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
.inbox-filter-btn:hover { background: hsl(210, 57%, 96%); }
|
||||
.inbox-filter-btn.active {
|
||||
background: hsl(210, 57%, 94%);
|
||||
border-color: hsl(210, 57%, 72%);
|
||||
color: hsl(210, 57%, 30%);
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-admin">
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
|
||||
|
||||
<?php if (isset($_GET["inbox_status"])): ?>
|
||||
<?php
|
||||
$inboxStatusMap = [
|
||||
'done' => ['type' => 'success', 'text' => 'Nachricht als erledigt markiert.'],
|
||||
'approved' => ['type' => 'success', 'text' => 'Registrierung freigegeben.'],
|
||||
'rejected' => ['type' => 'success', 'text' => 'Registrierung abgelehnt.'],
|
||||
'error' => ['type' => 'error', 'text' => 'Ein Fehler ist aufgetreten.'],
|
||||
];
|
||||
$statusKey = $_GET["inbox_status"];
|
||||
$statusInfo = $inboxStatusMap[$statusKey] ?? ['type' => 'success', 'text' => 'Aktion ausgeführt.'];
|
||||
?>
|
||||
<div class="nak-toast nak-toast--<?= $statusInfo['type'] ?>" id="nakToast">
|
||||
<div class="nak-toast__icon">
|
||||
<?php if ($statusInfo['type'] === 'success'): ?>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
<?php else: ?>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<span class="nak-toast__text"><?= htmlspecialchars($statusInfo['text']) ?></span>
|
||||
<button class="nak-toast__close" onclick="dismissNakToast()" aria-label="Schließen">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var toast = document.getElementById('nakToast');
|
||||
if (!toast) return;
|
||||
requestAnimationFrame(function() { toast.classList.add('nak-toast--visible'); });
|
||||
var timer = setTimeout(function() { dismissNakToast(); }, 4000);
|
||||
toast._timer = timer;
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState({}, '', 'admin_inbox.php');
|
||||
}
|
||||
})();
|
||||
function dismissNakToast() {
|
||||
var toast = document.getElementById('nakToast');
|
||||
if (!toast || toast.classList.contains('nak-toast--hiding')) return;
|
||||
clearTimeout(toast._timer);
|
||||
toast.classList.add('nak-toast--hiding');
|
||||
toast.classList.remove('nak-toast--visible');
|
||||
setTimeout(function() { toast.remove(); }, 350);
|
||||
}
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="header_title ht_admin text_white" id="header_title">
|
||||
<span class="header_title_text">Adminpanel</span>
|
||||
</div>
|
||||
|
||||
<div class="content" id="fullsize">
|
||||
<?php $adminActiveTab = 'inbox'; require __DIR__.'/_tabs.php'; ?>
|
||||
<div class="inbox-shell">
|
||||
|
||||
<!-- Filter-Leiste -->
|
||||
<div class="inbox-filter-bar">
|
||||
<a href="?filter=all" class="inbox-filter-btn <?= $activeFilter === 'all' ? 'active' : '' ?>">Alle</a>
|
||||
<?php foreach ($typeConfig as $typeKey => $typeCfg): ?>
|
||||
<a href="?filter=<?= $typeKey ?>" class="inbox-filter-btn <?= $activeFilter === $typeKey ? 'active' : '' ?>"><?= $typeCfg['label'] ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Offene Nachrichten -->
|
||||
<div class="inbox-card">
|
||||
<div class="inbox-header">
|
||||
<div class="inbox-title">Offene Anfragen</div>
|
||||
<?php if (count($pendingMessages) > 0): ?>
|
||||
<span class="inbox-count"><?= count($pendingMessages) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (count($pendingMessages) === 0): ?>
|
||||
<div class="inbox-empty">Keine offenen Anfragen vorhanden.</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($pendingMessages as $msg): ?>
|
||||
<div class="inbox-item">
|
||||
<div class="inbox-item-header">
|
||||
<div>
|
||||
<span class="inbox-item-type <?= getTypeClass($msg['type'], $typeConfig) ?>">
|
||||
<?= getTypeLabel($msg['type'], $typeConfig) ?>
|
||||
</span>
|
||||
</div>
|
||||
<span class="inbox-item-date"><?= date('d.m.Y, H:i', strtotime($msg['created_at'])) ?> Uhr</span>
|
||||
</div>
|
||||
<div class="inbox-item-subject"><?= htmlspecialchars($msg['subject'] ?? '') ?></div>
|
||||
<div class="inbox-item-sender">
|
||||
Von: <?= htmlspecialchars($msg['sender_name'] ?? 'Unbekannt') ?>
|
||||
(<?= htmlspecialchars($msg['sender_email'] ?? '') ?>)
|
||||
</div>
|
||||
<div class="inbox-item-message"><?= htmlspecialchars($msg['message'] ?? '') ?></div>
|
||||
|
||||
<div class="inbox-actions">
|
||||
<?php if ($msg['type'] === 'registration'): ?>
|
||||
<?php $data = json_decode($msg['data_json'], true); ?>
|
||||
<button type="button" class="inbox-btn inbox-btn-approve" onclick="openRegistrationModal(<?= (int)($data['pending_id'] ?? 0) ?>, <?= (int)$msg['id'] ?>, '<?= htmlspecialchars($msg['sender_name'] ?? '', ENT_QUOTES) ?>', '<?= htmlspecialchars($msg['sender_email'] ?? '', ENT_QUOTES) ?>')">Anfrage bearbeiten</button>
|
||||
<?php elseif ($msg['type'] === 'system' && !empty($msg['data_json'])): ?>
|
||||
<?php
|
||||
$data = json_decode($msg['data_json'], true);
|
||||
if (isset($data['action']) && $data['action'] === 'brandschutz_escalation'):
|
||||
?>
|
||||
<button type="button" class="inbox-btn inbox-btn-approve" style="background:linear-gradient(135deg,var(--nak-orange),hsl(25,95%,40%)) !important;border-color:hsl(25,95%,40%) !important;" onclick="openBsEscalationModal(<?= (int)$msg['id'] ?>, <?= (int)$data['user_id'] ?>, '<?= htmlspecialchars($data['user_name'] ?? '', ENT_QUOTES) ?>', <?= (int)($data['days_overdue'] ?? 0) ?>)">Persönliche Erinnerung bestätigen</button>
|
||||
<?php else: ?>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
|
||||
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
|
||||
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Erledigte Nachrichten -->
|
||||
<?php if (count($doneMessages) > 0): ?>
|
||||
<div class="inbox-section-title">Erledigte Anfragen (letzte 20)</div>
|
||||
<div class="inbox-card">
|
||||
<?php foreach ($doneMessages as $msg): ?>
|
||||
<div class="inbox-item inbox-done-item">
|
||||
<div class="inbox-item-header">
|
||||
<div>
|
||||
<span class="inbox-item-type <?= getTypeClass($msg['type'], $typeConfig) ?>">
|
||||
<?= getTypeLabel($msg['type'], $typeConfig) ?>
|
||||
</span>
|
||||
</div>
|
||||
<span class="inbox-item-date">Erledigt: <?= date('d.m.Y, H:i', strtotime($msg['processed_at'])) ?> Uhr</span>
|
||||
</div>
|
||||
<div class="inbox-item-subject"><?= htmlspecialchars($msg['subject'] ?? '') ?></div>
|
||||
<div class="inbox-item-sender">
|
||||
Von: <?= htmlspecialchars($msg['sender_name'] ?? 'Unbekannt') ?>
|
||||
(<?= htmlspecialchars($msg['sender_email'] ?? '') ?>)
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- inbox-shell -->
|
||||
</div><!-- content -->
|
||||
|
||||
<!-- Registrierungs-Modal -->
|
||||
<div class="standard_overlay" id="registrationModal" onclick="if(event.target.id==='registrationModal')closeRegistrationModal()">
|
||||
<div style="position:relative;width:min(560px,calc(100vw - 24px));max-height:min(700px,calc(100vh - 24px));overflow:auto;background:#FFF;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
|
||||
<button type="button" aria-label="Schließen" onclick="closeRegistrationModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
|
||||
|
||||
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,var(--gray-800),var(--gray-900));">
|
||||
<div style="font-size:1.3em;font-weight:850;">Registrierung bearbeiten</div>
|
||||
<div id="regModalUserInfo" style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;"></div>
|
||||
</div>
|
||||
|
||||
<div style="padding:22px 24px 24px 24px;">
|
||||
<form method="post" action="approve-registration.php" id="regModalForm">
|
||||
<input type="hidden" name="pending_id" id="regModalPendingId">
|
||||
<input type="hidden" name="message_id" id="regModalMessageId">
|
||||
<input type="hidden" name="action" id="regModalAction">
|
||||
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="display:block;margin-bottom:8px;font-weight:750;font-size:0.92em;color:hsl(210,18%,36%);">Rollen zuweisen:</label>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;">
|
||||
<?php foreach ($permissionRoles as $role): ?>
|
||||
<label style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid hsl(210,18%,84%);background:hsl(210,12%,98%);cursor:pointer;font-weight:650;font-size:0.88em;hyphens:auto;-webkit-hyphens:auto;overflow-wrap:break-word;">
|
||||
<input type="checkbox" name="roles[]" value="<?= htmlspecialchars($role) ?>" style="accent-color:hsl(210,57%,45%);flex-shrink:0;min-width:16px;min-height:16px;">
|
||||
<?= htmlspecialchars(genderRole($role)) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:20px;">
|
||||
<button type="button" onclick="submitRegistrationAction('reject')" class="inbox-btn inbox-btn-reject" style="padding:10px 18px;font-size:0.9em;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>Ablehnen</button>
|
||||
<button type="button" onclick="submitRegistrationAction('approve')" class="inbox-btn inbox-btn-approve" style="padding:10px 18px;font-size:0.9em;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>Freischalten</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openRegistrationModal(pendingId, messageId, name, email) {
|
||||
document.getElementById('regModalPendingId').value = pendingId;
|
||||
document.getElementById('regModalMessageId').value = messageId;
|
||||
document.getElementById('regModalUserInfo').textContent = name + ' (' + email + ')';
|
||||
document.getElementById('registrationModal').style.display = 'flex';
|
||||
}
|
||||
function closeRegistrationModal() {
|
||||
document.getElementById('registrationModal').style.display = 'none';
|
||||
}
|
||||
function submitRegistrationAction(action) {
|
||||
var confirmMsg = action === 'approve' ? 'Nutzer mit den gewählten Rollen freischalten?' : 'Registrierung ablehnen?';
|
||||
if (!confirm(confirmMsg)) return;
|
||||
document.getElementById('regModalAction').value = action;
|
||||
document.getElementById('regModalForm').submit();
|
||||
}
|
||||
|
||||
// Brandschutz Eskalation Modal
|
||||
function openBsEscalationModal(msgId, userId, userName, daysOverdue) {
|
||||
document.getElementById('bsEscMsgId').value = msgId;
|
||||
document.getElementById('bsEscUserId').value = userId;
|
||||
document.getElementById('bsEscUserName').textContent = userName;
|
||||
document.getElementById('bsEscDaysOverdue').textContent = daysOverdue + ' Tage';
|
||||
document.getElementById('bsEscalationModal').style.display = 'flex';
|
||||
}
|
||||
function closeBsEscalationModal() {
|
||||
document.getElementById('bsEscalationModal').style.display = 'none';
|
||||
}
|
||||
function submitBsEscalation() {
|
||||
document.getElementById('bsEscForm').submit();
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
if (document.getElementById('registrationModal').style.display === 'flex') {
|
||||
closeRegistrationModal();
|
||||
}
|
||||
if (document.getElementById('bsEscalationModal').style.display === 'flex') {
|
||||
closeBsEscalationModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Brandschutz Eskalation Modal -->
|
||||
<div class="standard_overlay" id="bsEscalationModal" onclick="if(event.target.id==='bsEscalationModal')closeBsEscalationModal()">
|
||||
<div style="position:relative;width:min(520px,calc(100vw - 24px));max-height:min(500px,calc(100vh - 24px));overflow:auto;background:#FFF;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
|
||||
<button type="button" aria-label="Schließen" onclick="closeBsEscalationModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
|
||||
|
||||
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,hsl(25,90%,45%),hsl(25,95%,35%));">
|
||||
<div style="font-size:1.3em;font-weight:850;">Persönliche Erinnerung bestätigen</div>
|
||||
<div style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;">Brandschutzunterweisung – Eskalation</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:22px 24px 24px 24px;">
|
||||
<p style="margin:0 0 14px 0;font-size:0.95em;color:hsl(210,20%,25%);line-height:1.6;">
|
||||
Bitte bestätigen Sie, dass Sie <strong id="bsEscUserName"></strong> persönlich an die ausstehende Brandschutzunterweisung erinnert haben.
|
||||
</p>
|
||||
<div style="padding:12px 14px;background:hsl(0,65%,95%);border:1px solid hsl(0,65%,85%);margin-bottom:18px;">
|
||||
<div style="font-size:0.78em;font-weight:850;text-transform:uppercase;letter-spacing:0.04em;color:hsl(0,65%,38%);margin-bottom:4px;">Überfälligkeit</div>
|
||||
<div style="font-size:1.1em;font-weight:850;color:hsl(0,65%,35%);" id="bsEscDaysOverdue"></div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="" id="bsEscForm">
|
||||
<input type="hidden" name="bs_escalation_msg_id" id="bsEscMsgId">
|
||||
<input type="hidden" name="bs_escalation_user_id" id="bsEscUserId">
|
||||
<input type="hidden" name="bs_escalation_confirm" value="1">
|
||||
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px;">
|
||||
<button type="button" onclick="closeBsEscalationModal()" class="inbox-btn" style="padding:10px 18px;font-size:0.9em;">Abbrechen</button>
|
||||
<button type="submit" class="inbox-btn inbox-btn-approve" style="padding:10px 18px;font-size:0.9em;background:linear-gradient(135deg,var(--nak-orange),hsl(25,95%,40%)) !important;border-color:hsl(25,95%,40%) !important;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
Bestätigen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
/* Name: admin_notices.php
|
||||
Beschreibung: Verwaltung der Meldungen auf dem Intranet-Hauptmenü */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare('SELECT "Admin" FROM user_permissions WHERE user_id = ?');
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$isAdmin = $stmt->fetchColumn();
|
||||
|
||||
if ((int)$isAdmin !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
const NOTICE_MAX_TITLE_LENGTH = 180;
|
||||
const NOTICE_MAX_MESSAGE_LENGTH = 4000;
|
||||
|
||||
$noticeTypes = [
|
||||
'info' => 'Information',
|
||||
'warning' => 'Warnung',
|
||||
'success' => 'Erfolg',
|
||||
'error' => 'Wichtig',
|
||||
];
|
||||
|
||||
if (empty($_SESSION['admin_notices_csrf'])) {
|
||||
$_SESSION['admin_notices_csrf'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
$csrfToken = $_SESSION['admin_notices_csrf'];
|
||||
|
||||
function noticeRedirect(string $status): void {
|
||||
header('Location: admin_notices.php?status=' . rawurlencode($status));
|
||||
exit();
|
||||
}
|
||||
|
||||
function noticeVerifyCsrf(): void {
|
||||
$postedToken = $_POST['csrf_token'] ?? '';
|
||||
$sessionToken = $_SESSION['admin_notices_csrf'] ?? '';
|
||||
if (!is_string($postedToken) || !is_string($sessionToken) || $sessionToken === '' || !hash_equals($sessionToken, $postedToken)) {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Anfrage. Bitte laden Sie die Seite neu.');
|
||||
}
|
||||
}
|
||||
|
||||
function noticeParseLocalDateTime(?string $value): ?string {
|
||||
$value = trim((string)$value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tz = new DateTimeZone('Europe/Berlin');
|
||||
$date = DateTimeImmutable::createFromFormat('!Y-m-d\TH:i', $value, $tz);
|
||||
$errors = DateTimeImmutable::getLastErrors();
|
||||
|
||||
if (!$date || ($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))) {
|
||||
throw new InvalidArgumentException('Ungültiges Datum.');
|
||||
}
|
||||
|
||||
// Mit explizitem Offset an PostgreSQL senden, damit TIMESTAMPTZ eindeutig ist.
|
||||
return $date->format('Y-m-d H:i:sP');
|
||||
}
|
||||
|
||||
function noticeFormatLocalDateTime(?string $value): string {
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return (new DateTimeImmutable($value))->setTimezone(new DateTimeZone('Europe/Berlin'))->format('Y-m-d\TH:i');
|
||||
} catch (Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function noticeDisplayDate(?string $value): string {
|
||||
if (empty($value)) {
|
||||
return 'unbegrenzt';
|
||||
}
|
||||
try {
|
||||
return (new DateTimeImmutable($value))->setTimezone(new DateTimeZone('Europe/Berlin'))->format('d.m.Y, H:i') . ' Uhr';
|
||||
} catch (Throwable $e) {
|
||||
return '–';
|
||||
}
|
||||
}
|
||||
|
||||
function noticeDbBool($value): bool {
|
||||
return $value === true || $value === 1 || $value === '1' || $value === 't' || $value === 'true';
|
||||
}
|
||||
|
||||
function noticeTextLength(string $value): int {
|
||||
return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
|
||||
}
|
||||
|
||||
function noticeValidateInput(array $noticeTypes): array {
|
||||
$title = trim((string)($_POST['title'] ?? ''));
|
||||
$message = trim((string)($_POST['message'] ?? ''));
|
||||
$type = (string)($_POST['type'] ?? 'info');
|
||||
$sortOrder = filter_var($_POST['sort_order'] ?? 0, FILTER_VALIDATE_INT);
|
||||
$isActive = isset($_POST['is_active']);
|
||||
|
||||
if ($title === '' || noticeTextLength($title) > NOTICE_MAX_TITLE_LENGTH) {
|
||||
throw new InvalidArgumentException('Titel fehlt oder ist zu lang.');
|
||||
}
|
||||
if ($message === '' || noticeTextLength($message) > NOTICE_MAX_MESSAGE_LENGTH) {
|
||||
throw new InvalidArgumentException('Meldung fehlt oder ist zu lang.');
|
||||
}
|
||||
if (!array_key_exists($type, $noticeTypes)) {
|
||||
throw new InvalidArgumentException('Ungültiger Meldungstyp.');
|
||||
}
|
||||
if ($sortOrder === false || $sortOrder < -9999 || $sortOrder > 9999) {
|
||||
throw new InvalidArgumentException('Ungültige Sortierung.');
|
||||
}
|
||||
|
||||
$startsAt = noticeParseLocalDateTime($_POST['starts_at'] ?? null);
|
||||
$endsAt = noticeParseLocalDateTime($_POST['ends_at'] ?? null);
|
||||
|
||||
if ($startsAt !== null && $endsAt !== null) {
|
||||
$start = new DateTimeImmutable($startsAt);
|
||||
$end = new DateTimeImmutable($endsAt);
|
||||
if ($end <= $start) {
|
||||
throw new InvalidArgumentException('Das Ende muss nach dem Beginn liegen.');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
'sort_order' => (int)$sortOrder,
|
||||
'is_active' => $isActive,
|
||||
'starts_at' => $startsAt,
|
||||
'ends_at' => $endsAt,
|
||||
];
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
noticeVerifyCsrf();
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
|
||||
try {
|
||||
if ($action === 'create') {
|
||||
$data = noticeValidateInput($noticeTypes);
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO mainpage_notices
|
||||
(title, message, type, is_active, starts_at, ends_at, sort_order, created_by, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())'
|
||||
);
|
||||
$stmt->execute([
|
||||
$data['title'], $data['message'], $data['type'], $data['is_active'] ? 'true' : 'false',
|
||||
$data['starts_at'], $data['ends_at'], $data['sort_order'], (int)$_SESSION['user_id'],
|
||||
]);
|
||||
noticeRedirect('created');
|
||||
}
|
||||
|
||||
if ($action === 'update') {
|
||||
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
|
||||
if (!$id || $id < 1) {
|
||||
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
|
||||
}
|
||||
$data = noticeValidateInput($noticeTypes);
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE mainpage_notices
|
||||
SET title = ?, message = ?, type = ?, is_active = ?, starts_at = ?, ends_at = ?, sort_order = ?, updated_at = NOW()
|
||||
WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([
|
||||
$data['title'], $data['message'], $data['type'], $data['is_active'] ? 'true' : 'false',
|
||||
$data['starts_at'], $data['ends_at'], $data['sort_order'], (int)$id,
|
||||
]);
|
||||
noticeRedirect('updated');
|
||||
}
|
||||
|
||||
if ($action === 'toggle') {
|
||||
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
|
||||
if (!$id || $id < 1) {
|
||||
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
|
||||
}
|
||||
$stmt = $pdo->prepare('UPDATE mainpage_notices SET is_active = NOT is_active, updated_at = NOW() WHERE id = ?');
|
||||
$stmt->execute([(int)$id]);
|
||||
noticeRedirect('toggled');
|
||||
}
|
||||
|
||||
if ($action === 'delete') {
|
||||
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
|
||||
if (!$id || $id < 1) {
|
||||
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
|
||||
}
|
||||
$stmt = $pdo->prepare('DELETE FROM mainpage_notices WHERE id = ?');
|
||||
$stmt->execute([(int)$id]);
|
||||
noticeRedirect('deleted');
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannte Aktion.');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$_SESSION['admin_notices_error'] = $e->getMessage();
|
||||
noticeRedirect('error');
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['admin_notices_error'] = 'Datenbankfehler. Prüfen Sie, ob die Tabelle mainpage_notices angelegt wurde.';
|
||||
noticeRedirect('error');
|
||||
}
|
||||
}
|
||||
|
||||
$noticeError = $_SESSION['admin_notices_error'] ?? '';
|
||||
unset($_SESSION['admin_notices_error']);
|
||||
$status = (string)($_GET['status'] ?? '');
|
||||
$statusMessages = [
|
||||
'created' => 'Meldung wurde angelegt.',
|
||||
'updated' => 'Meldung wurde gespeichert.',
|
||||
'toggled' => 'Status der Meldung wurde geändert.',
|
||||
'deleted' => 'Meldung wurde gelöscht.',
|
||||
];
|
||||
|
||||
$tableAvailable = true;
|
||||
$notices = [];
|
||||
try {
|
||||
$notices = $pdo->query(
|
||||
'SELECT id, title, message, type, is_active, starts_at, ends_at, sort_order, created_at, updated_at
|
||||
FROM mainpage_notices
|
||||
ORDER BY sort_order ASC, created_at DESC, id DESC'
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$tableAvailable = false;
|
||||
$noticeError = 'Die Tabelle mainpage_notices fehlt. Führen Sie zuerst general_files/ini/mainpage_notices.sql aus.';
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<style>
|
||||
.notice-admin-shell { padding: 18px; min-height: 100%; }
|
||||
.notice-admin-card { background:#fff; border:1px solid var(--gray-200); box-shadow:var(--shadow-md); margin-bottom:18px; }
|
||||
.notice-admin-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:16px 18px; border-bottom:1px solid var(--gray-200); }
|
||||
.notice-admin-title { font-size:1.05em; font-weight:800; color:var(--gray-900); }
|
||||
.notice-admin-hint { margin-top:3px; font-size:.86em; color:var(--gray-500); }
|
||||
.notice-admin-button { display:inline-flex; align-items:center; justify-content:center; min-height:36px; padding:7px 14px; border:1px solid var(--gray-800); background:var(--gray-900); color:#fff; font:inherit; font-size:.86em; font-weight:750; cursor:pointer; text-decoration:none; }
|
||||
.notice-admin-button:hover { color:#fff; background:#111; text-decoration:none; }
|
||||
.notice-admin-button--secondary { color:var(--gray-800); background:#fff; border-color:var(--gray-300); }
|
||||
.notice-admin-button--secondary:hover { color:var(--gray-900); background:var(--gray-50); }
|
||||
.notice-admin-button--danger { color:var(--nak-red); background:#fff; border-color:hsl(352,72%,84%); }
|
||||
.notice-admin-button--danger:hover { color:#fff; background:var(--nak-red); }
|
||||
.notice-admin-grid { display:grid; gap:12px; padding:16px 18px 18px; }
|
||||
.notice-admin-item { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:16px; padding:16px; border:1px solid var(--gray-200); background:var(--gray-50); }
|
||||
.notice-admin-item.is-disabled { opacity:.62; }
|
||||
.notice-admin-item-title { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:6px; font-weight:800; color:var(--gray-900); }
|
||||
.notice-admin-chip { display:inline-flex; align-items:center; padding:3px 8px; font-size:.72em; font-weight:800; border-left:3px solid currentColor; background:#fff; }
|
||||
.notice-admin-chip--info { color:var(--nak-blue); }
|
||||
.notice-admin-chip--warning { color:var(--nak-orange); }
|
||||
.notice-admin-chip--success { color:var(--nak-darkgreen); }
|
||||
.notice-admin-chip--error { color:var(--nak-red); }
|
||||
.notice-admin-message { white-space:pre-wrap; line-height:1.45; color:var(--gray-700); font-size:.92em; }
|
||||
.notice-admin-meta { display:flex; gap:14px; flex-wrap:wrap; margin-top:10px; color:var(--gray-500); font-size:.8em; }
|
||||
.notice-admin-actions { display:flex; align-items:flex-start; gap:7px; flex-wrap:wrap; justify-content:flex-end; }
|
||||
.notice-admin-actions form { margin:0; }
|
||||
.notice-admin-empty { padding:28px 18px; text-align:center; color:var(--gray-500); }
|
||||
.notice-admin-alert { margin:16px 18px 0; padding:11px 13px; border-left:4px solid var(--nak-darkgreen); background:hsl(145,50%,94%); color:hsl(145,50%,28%); font-size:.9em; }
|
||||
.notice-admin-alert--error { border-color:var(--nak-red); background:hsl(352,72%,96%); color:hsl(352,62%,35%); }
|
||||
|
||||
.notice-modal { position:fixed; inset:0; z-index:5000; display:none; align-items:center; justify-content:center; padding:20px; background:rgba(16,24,32,.62); }
|
||||
.notice-modal.open { display:flex; }
|
||||
.notice-modal-card { width:min(680px,100%); max-height:calc(100dvh - 40px); overflow:auto; background:#fff; box-shadow:var(--shadow-xl); }
|
||||
.notice-modal-header { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:18px 20px; border-bottom:1px solid var(--gray-200); }
|
||||
.notice-modal-title { font-size:1.08em; font-weight:800; color:var(--gray-900); }
|
||||
.notice-modal-close { border:0; background:transparent; font-size:1.5em; line-height:1; cursor:pointer; color:var(--gray-500); }
|
||||
.notice-modal-body { padding:20px; }
|
||||
.notice-form-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
|
||||
.notice-field { display:flex; flex-direction:column; gap:6px; }
|
||||
.notice-field--full { grid-column:1 / -1; }
|
||||
.notice-field label { font-size:.83em; font-weight:750; color:var(--gray-700); }
|
||||
.notice-field input, .notice-field select, .notice-field textarea { width:100%; box-sizing:border-box; padding:10px 11px; border:1px solid var(--gray-300); background:#fff; color:var(--gray-900); font:inherit; font-size:.9em; }
|
||||
.notice-field textarea { min-height:120px; resize:vertical; }
|
||||
.notice-checkbox { display:flex; align-items:center; gap:8px; min-height:40px; }
|
||||
.notice-checkbox input { width:auto; }
|
||||
.notice-form-note { margin-top:5px; font-size:.78em; color:var(--gray-500); }
|
||||
.notice-modal-actions { display:flex; justify-content:flex-end; gap:8px; padding:0 20px 20px; }
|
||||
|
||||
@media (max-width:760px) {
|
||||
.notice-admin-shell { padding:10px; }
|
||||
.notice-admin-header, .notice-admin-item { grid-template-columns:1fr; display:grid; }
|
||||
.notice-admin-actions { justify-content:flex-start; }
|
||||
.notice-form-grid { grid-template-columns:1fr; }
|
||||
.notice-field--full { grid-column:auto; }
|
||||
.content-tab-bar { overflow-x:auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-admin">
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
|
||||
|
||||
<div class="header_title ht_admin text_white unselectable" id="header_title">
|
||||
<span class="header_title_text">Adminpanel</span>
|
||||
</div>
|
||||
|
||||
<div class="content" id="fullsize">
|
||||
<?php $adminActiveTab = 'notices'; require __DIR__.'/_tabs.php'; ?>
|
||||
|
||||
<div class="notice-admin-shell">
|
||||
<div class="notice-admin-card">
|
||||
<div class="notice-admin-header">
|
||||
<div>
|
||||
<div class="notice-admin-title">Meldungen im Hauptmenü</div>
|
||||
<div class="notice-admin-hint">Meldungen erscheinen ausschließlich oberhalb der Kacheln auf der Intranet-Hauptseite.</div>
|
||||
</div>
|
||||
<?php if ($tableAvailable): ?>
|
||||
<button type="button" class="notice-admin-button" onclick="openNoticeModal()">+ Neue Meldung</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($status === 'error' && $noticeError !== ''): ?>
|
||||
<div class="notice-admin-alert notice-admin-alert--error"><?= htmlspecialchars($noticeError, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php elseif (!$tableAvailable && $noticeError !== ''): ?>
|
||||
<div class="notice-admin-alert notice-admin-alert--error"><?= htmlspecialchars($noticeError, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php elseif (isset($statusMessages[$status])): ?>
|
||||
<div class="notice-admin-alert"><?= htmlspecialchars($statusMessages[$status], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($tableAvailable): ?>
|
||||
<?php if (empty($notices)): ?>
|
||||
<div class="notice-admin-empty">Noch keine Meldungen vorhanden.</div>
|
||||
<?php else: ?>
|
||||
<div class="notice-admin-grid">
|
||||
<?php foreach ($notices as $notice): ?>
|
||||
<?php
|
||||
$noticeId = (int)$notice['id'];
|
||||
$noticeType = array_key_exists($notice['type'], $noticeTypes) ? $notice['type'] : 'info';
|
||||
$editPayload = [
|
||||
'id' => $noticeId,
|
||||
'title' => $notice['title'],
|
||||
'message' => $notice['message'],
|
||||
'type' => $noticeType,
|
||||
'is_active' => noticeDbBool($notice['is_active']),
|
||||
'starts_at' => noticeFormatLocalDateTime($notice['starts_at']),
|
||||
'ends_at' => noticeFormatLocalDateTime($notice['ends_at']),
|
||||
'sort_order' => (int)$notice['sort_order'],
|
||||
];
|
||||
?>
|
||||
<div class="notice-admin-item<?= noticeDbBool($notice['is_active']) ? '' : ' is-disabled' ?>">
|
||||
<div>
|
||||
<div class="notice-admin-item-title">
|
||||
<?= htmlspecialchars($notice['title'], ENT_QUOTES, 'UTF-8') ?>
|
||||
<span class="notice-admin-chip notice-admin-chip--<?= htmlspecialchars($noticeType, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($noticeTypes[$noticeType], ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php if (!noticeDbBool($notice['is_active'])): ?><span class="notice-admin-chip">Deaktiviert</span><?php endif; ?>
|
||||
</div>
|
||||
<div class="notice-admin-message"><?= htmlspecialchars($notice['message'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div class="notice-admin-meta">
|
||||
<span>Von: <?= htmlspecialchars(noticeDisplayDate($notice['starts_at']), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span>Bis: <?= htmlspecialchars(noticeDisplayDate($notice['ends_at']), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span>Sortierung: <?= (int)$notice['sort_order'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice-admin-actions">
|
||||
<button type="button" class="notice-admin-button notice-admin-button--secondary" onclick='openNoticeModal(<?= json_encode($editPayload, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>)'>Bearbeiten</button>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="action" value="toggle">
|
||||
<input type="hidden" name="id" value="<?= $noticeId ?>">
|
||||
<button type="submit" class="notice-admin-button notice-admin-button--secondary"><?= noticeDbBool($notice['is_active']) ? 'Deaktivieren' : 'Aktivieren' ?></button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('Meldung wirklich dauerhaft löschen?');">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="id" value="<?= $noticeId ?>">
|
||||
<button type="submit" class="notice-admin-button notice-admin-button--danger">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notice-modal" id="noticeModal" aria-hidden="true">
|
||||
<div class="notice-modal-card" role="dialog" aria-modal="true" aria-labelledby="noticeModalTitle">
|
||||
<form method="post" id="noticeForm">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="action" id="noticeAction" value="create">
|
||||
<input type="hidden" name="id" id="noticeId" value="">
|
||||
|
||||
<div class="notice-modal-header">
|
||||
<div class="notice-modal-title" id="noticeModalTitle">Neue Meldung</div>
|
||||
<button type="button" class="notice-modal-close" onclick="closeNoticeModal()" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="notice-modal-body">
|
||||
<div class="notice-form-grid">
|
||||
<div class="notice-field notice-field--full">
|
||||
<label for="noticeTitle">Titel</label>
|
||||
<input id="noticeTitle" name="title" type="text" maxlength="180" required>
|
||||
</div>
|
||||
<div class="notice-field notice-field--full">
|
||||
<label for="noticeMessage">Meldung</label>
|
||||
<textarea id="noticeMessage" name="message" maxlength="4000" required></textarea>
|
||||
</div>
|
||||
<div class="notice-field">
|
||||
<label for="noticeType">Typ</label>
|
||||
<select id="noticeType" name="type">
|
||||
<?php foreach ($noticeTypes as $typeKey => $typeLabel): ?>
|
||||
<option value="<?= htmlspecialchars($typeKey, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($typeLabel, ENT_QUOTES, 'UTF-8') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="notice-field">
|
||||
<label for="noticeSortOrder">Sortierung</label>
|
||||
<input id="noticeSortOrder" name="sort_order" type="number" min="-9999" max="9999" value="0" required>
|
||||
<div class="notice-form-note">Kleinere Zahlen erscheinen zuerst.</div>
|
||||
</div>
|
||||
<div class="notice-field">
|
||||
<label for="noticeStartsAt">Anzeigen ab</label>
|
||||
<input id="noticeStartsAt" name="starts_at" type="datetime-local">
|
||||
<div class="notice-form-note">Leer = sofort.</div>
|
||||
</div>
|
||||
<div class="notice-field">
|
||||
<label for="noticeEndsAt">Anzeigen bis</label>
|
||||
<input id="noticeEndsAt" name="ends_at" type="datetime-local">
|
||||
<div class="notice-form-note">Leer = ohne Ablaufdatum.</div>
|
||||
</div>
|
||||
<div class="notice-field notice-field--full">
|
||||
<label class="notice-checkbox">
|
||||
<input id="noticeIsActive" name="is_active" type="checkbox" value="1" checked>
|
||||
Meldung aktiv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice-modal-actions">
|
||||
<button type="button" class="notice-admin-button notice-admin-button--secondary" onclick="closeNoticeModal()">Abbrechen</button>
|
||||
<button type="submit" class="notice-admin-button">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openNoticeModal(data) {
|
||||
data = data || null;
|
||||
var modal = document.getElementById('noticeModal');
|
||||
document.getElementById('noticeAction').value = data ? 'update' : 'create';
|
||||
document.getElementById('noticeId').value = data ? data.id : '';
|
||||
document.getElementById('noticeTitle').value = data ? data.title : '';
|
||||
document.getElementById('noticeMessage').value = data ? data.message : '';
|
||||
document.getElementById('noticeType').value = data ? data.type : 'warning';
|
||||
document.getElementById('noticeSortOrder').value = data ? data.sort_order : 0;
|
||||
document.getElementById('noticeStartsAt').value = data ? data.starts_at : '';
|
||||
document.getElementById('noticeEndsAt').value = data ? data.ends_at : '';
|
||||
document.getElementById('noticeIsActive').checked = data ? !!data.is_active : true;
|
||||
document.getElementById('noticeModalTitle').textContent = data ? 'Meldung bearbeiten' : 'Neue Meldung';
|
||||
modal.classList.add('open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.getElementById('noticeTitle').focus();
|
||||
}
|
||||
|
||||
function closeNoticeModal() {
|
||||
var modal = document.getElementById('noticeModal');
|
||||
modal.classList.remove('open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
document.getElementById('noticeModal').addEventListener('click', function(event) {
|
||||
if (event.target === this) closeNoticeModal();
|
||||
});
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') closeNoticeModal();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php /* Name: admin_profiles.php
|
||||
Beschreibung: Administratoren-Seite zur Nutzerverwaltung */
|
||||
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
</head>
|
||||
<body class="theme-admin">
|
||||
|
||||
<?php /* Besondere Admin-Rechte checken */
|
||||
// Fetch user details
|
||||
$stmt = $pdo->prepare("SELECT * FROM user_permissions up INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id WHERE up.user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$isAdmin = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Check if the user has admin access (Admin = 1)
|
||||
if ($isAdmin["Admin"] == 1) {
|
||||
$sql_groups = "SELECT * FROM user_permissions";
|
||||
$stmt = $pdo->prepare($sql_groups);
|
||||
$stmt->execute([]);
|
||||
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} else {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
<?php // Es folgen ab hier die HTML-Ausgaben beginnend mit dem HTML-Header: ?>
|
||||
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
|
||||
<?php // Header (einfach): Titel der Seite ?>
|
||||
<div class="header_title ht_admin text_white" id="header_title">
|
||||
<div class="flexrow fullwidth" style="justify-content:space-between !important;">
|
||||
<div class="header_title_text">Nutzerverwaltung</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php // Profilanzeige
|
||||
require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/intranet/show_profile.php"); ?>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php /* Name: approve-registration.php
|
||||
Beschreibung: Admin genehmigt oder lehnt eine Registrierungsanfrage ab.
|
||||
- Setzt Rollen bei Freischaltung
|
||||
- Sendet Mails an Nutzer UND Admins
|
||||
POST-Parameter: action (approve/reject), pending_id, message_id, roles[] */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
// Admin-Check
|
||||
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$isAdmin = $stmt->fetchColumn();
|
||||
|
||||
if ((int)$isAdmin !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
header("Location: admin_inbox.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Admin-Name ermitteln
|
||||
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
if (empty($adminName)) $adminName = 'Ein Administrator';
|
||||
|
||||
$action = $_POST["action"] ?? '';
|
||||
$pending_id = (int)($_POST["pending_id"] ?? 0);
|
||||
$message_id = (int)($_POST["message_id"] ?? 0);
|
||||
$selectedRoles = $_POST["roles"] ?? [];
|
||||
if (!is_array($selectedRoles)) $selectedRoles = [];
|
||||
|
||||
if ($pending_id === 0) {
|
||||
header("Location: admin_inbox.php?inbox_status=error");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Pending Registration laden
|
||||
$stmt = $pdo->prepare("SELECT * FROM pending_registrations WHERE id = ? AND status = 'pending'");
|
||||
$stmt->execute([$pending_id]);
|
||||
$pending = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$pending) {
|
||||
header("Location: admin_inbox.php?inbox_status=error");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Rollen-Spalten laden
|
||||
$permissionRoles = [];
|
||||
$rolesResult = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'");
|
||||
while ($col = $rolesResult->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (!in_array($col['column_name'], ['p_id', 'user_id'], true)) {
|
||||
$permissionRoles[] = $col['column_name'];
|
||||
}
|
||||
}
|
||||
|
||||
$loginUrl = "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/authorization/login.php";
|
||||
|
||||
if ($action === 'approve') {
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
// Nutzer in user_general_information anlegen
|
||||
$stmt = $pdo->prepare("INSERT INTO user_general_information (mail, first_name, last_name, phone) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$pending['email'], $pending['first_name'], $pending['last_name'], $pending['phone']]);
|
||||
$newUserId = $pdo->lastInsertId();
|
||||
|
||||
if (!$newUserId) {
|
||||
$checkStmt = $pdo->prepare("SELECT user_id FROM user_general_information WHERE mail = ?");
|
||||
$checkStmt->execute([$pending['email']]);
|
||||
$newUserId = $checkStmt->fetchColumn();
|
||||
}
|
||||
|
||||
// Login-Daten anlegen
|
||||
$stmt = $pdo->prepare("INSERT INTO user_login (user_id, password_hash) VALUES (?, ?)");
|
||||
$stmt->execute([$newUserId, $pending['password_hash']]);
|
||||
|
||||
// Berechtigungen mit gewählten Rollen anlegen
|
||||
$roleColumns = array_map(function($r) { return '"' . str_replace('"', '""', $r) . '"'; }, $permissionRoles);
|
||||
$columns = array_merge(['"user_id"'], $roleColumns);
|
||||
$placeholders = array_fill(0, count($columns), '?');
|
||||
$roleValues = [];
|
||||
foreach ($permissionRoles as $role) {
|
||||
$roleValues[] = in_array($role, $selectedRoles, true) ? 1 : 0;
|
||||
}
|
||||
$params = array_merge([(int)$newUserId], $roleValues);
|
||||
|
||||
$insertSql = "INSERT INTO user_permissions (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||
$stmt = $pdo->prepare($insertSql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Pending-Status aktualisieren
|
||||
$stmt = $pdo->prepare("UPDATE pending_registrations SET status = 'approved' WHERE id = ?");
|
||||
$stmt->execute([$pending_id]);
|
||||
|
||||
// Inbox-Nachricht als erledigt markieren
|
||||
if ($message_id > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$message_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// Zugewiesene Rollen als Text
|
||||
$assignedRoles = !empty($selectedRoles) ? implode(', ', $selectedRoles) : 'Keine besonderen Rollen';
|
||||
|
||||
// Mail an den Nutzer
|
||||
$userBetreff = "Registrierung freigeschaltet";
|
||||
$userNachricht = "Hallo {$pending['first_name']},\n\n";
|
||||
$userNachricht .= "Ihr Account für das Intranet der NAK Reinickendorf wurde freigeschaltet!\n\n";
|
||||
$userNachricht .= "Sie können sich ab sofort mit Ihrer E-Mail-Adresse und dem von Ihnen gewählten Passwort anmelden.";
|
||||
|
||||
send_intranet_mail($pending['email'], $userBetreff, $userNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt anmelden", $loginUrl);
|
||||
|
||||
// Mail an Admin-Team
|
||||
$adminBetreff = "Registrierung freigegeben: {$pending['first_name']} {$pending['last_name']}";
|
||||
$adminNachricht = "{$adminName} hat die Registrierung bearbeitet:\n\n";
|
||||
$adminNachricht .= "Nutzer: {$pending['first_name']} {$pending['last_name']}\n";
|
||||
$adminNachricht .= "E-Mail: {$pending['email']}\n";
|
||||
$adminNachricht .= "Aktion: Freigeschaltet\n";
|
||||
$adminNachricht .= "Zugewiesene Rollen: {$assignedRoles}";
|
||||
|
||||
send_admin_team_mail($pdo, $adminBetreff, $adminNachricht);
|
||||
|
||||
// Systemnachricht im Posteingang
|
||||
$sysSubject = "Nutzer freigeschaltet: {$pending['first_name']} {$pending['last_name']}";
|
||||
$sysMessage = "{$adminName} hat {$pending['first_name']} {$pending['last_name']} ({$pending['email']}) freigeschaltet.\nZugewiesene Rollen: {$assignedRoles}";
|
||||
$stmtSys = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$stmtSys->execute([$adminName, $pending['email'], $sysSubject, $sysMessage]);
|
||||
|
||||
header("Location: admin_inbox.php?inbox_status=approved");
|
||||
exit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
error_log("approve-registration error: " . $e->getMessage());
|
||||
header("Location: admin_inbox.php?inbox_status=error");
|
||||
exit();
|
||||
}
|
||||
|
||||
} elseif ($action === 'reject') {
|
||||
// Ablehnen
|
||||
$stmt = $pdo->prepare("UPDATE pending_registrations SET status = 'rejected' WHERE id = ?");
|
||||
$stmt->execute([$pending_id]);
|
||||
|
||||
// Inbox-Nachricht als erledigt markieren
|
||||
if ($message_id > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$message_id]);
|
||||
}
|
||||
|
||||
// Mail an den Nutzer
|
||||
$userBetreff = "Registrierung abgelehnt";
|
||||
$userNachricht = "Hallo {$pending['first_name']},\n\n";
|
||||
$userNachricht .= "Ihre Registrierungsanfrage für das Intranet der NAK Reinickendorf wurde leider abgelehnt.\n\n";
|
||||
$userNachricht .= "Bei Fragen wenden Sie sich bitte an den Administrator über das Kontaktformular auf der Anmeldeseite.";
|
||||
|
||||
send_intranet_mail($pending['email'], $userBetreff, $userNachricht);
|
||||
|
||||
// Mail an Admin-Team
|
||||
$adminBetreff = "Registrierung abgelehnt: {$pending['first_name']} {$pending['last_name']}";
|
||||
$adminNachricht = "{$adminName} hat die Registrierung bearbeitet:\n\n";
|
||||
$adminNachricht .= "Nutzer: {$pending['first_name']} {$pending['last_name']}\n";
|
||||
$adminNachricht .= "E-Mail: {$pending['email']}\n";
|
||||
$adminNachricht .= "Aktion: Abgelehnt";
|
||||
|
||||
send_admin_team_mail($pdo, $adminBetreff, $adminNachricht);
|
||||
|
||||
// Systemnachricht im Posteingang
|
||||
$sysSubject = "Registrierung abgelehnt: {$pending['first_name']} {$pending['last_name']}";
|
||||
$sysMessage = "{$adminName} hat die Registrierung von {$pending['first_name']} {$pending['last_name']} ({$pending['email']}) abgelehnt.";
|
||||
$stmtSys = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$stmtSys->execute([$adminName, $pending['email'], $sysSubject, $sysMessage]);
|
||||
|
||||
header("Location: admin_inbox.php?inbox_status=rejected");
|
||||
exit();
|
||||
}
|
||||
|
||||
header("Location: admin_inbox.php");
|
||||
exit();
|
||||
?>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php /* Name: process_contact.php
|
||||
Beschreibung: Verarbeitung von Kontaktformular-Anfragen
|
||||
- Speichert in admin_messages
|
||||
- Sendet Mail an technik@nak-reinickendorf.de */
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'error' => 'Methode nicht erlaubt']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Felder auslesen
|
||||
$name = trim($_POST["contact_name"] ?? '');
|
||||
$email = trim($_POST["contact_email"] ?? '');
|
||||
$subject = trim($_POST["contact_subject"] ?? 'Kontaktanfrage');
|
||||
$message = trim($_POST["contact_message"] ?? '');
|
||||
|
||||
// Validierung
|
||||
if (empty($name) || empty($email) || empty($message)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Bitte alle Felder ausfüllen.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Ungültige E-Mail-Adresse.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// In admin_messages speichern
|
||||
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message) VALUES ('contact', ?, ?, ?, ?)");
|
||||
$stmt->execute([$name, $email, $subject, $message]);
|
||||
|
||||
// Mail an Admin-Team (Admin + Technik)
|
||||
$mail_betreff = "Kontaktanfrage: {$subject}";
|
||||
$mail_nachricht = "Neue Kontaktanfrage über das Intranet:\n\n";
|
||||
$mail_nachricht .= "Name: {$name}\n";
|
||||
$mail_nachricht .= "E-Mail: {$email}\n";
|
||||
$mail_nachricht .= "Betreff: {$subject}\n\n";
|
||||
$mail_nachricht .= "Nachricht:\n{$message}";
|
||||
|
||||
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht);
|
||||
|
||||
// JSON-Antwort für AJAX
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Nachricht wurde gesendet.']);
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php /* Name: login.php
|
||||
Beschreibung: Login-Seite (Authorisierung) */
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
|
||||
$sql = "SELECT
|
||||
u.*,
|
||||
l.password_hash AS password_hash
|
||||
FROM
|
||||
user_general_information AS u
|
||||
INNER JOIN
|
||||
user_login AS l ON u.user_id = l.user_id
|
||||
WHERE
|
||||
u.mail = ?";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$_POST["email"]]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user) {
|
||||
|
||||
if (isset($user['password_hash']) && password_verify($_POST["password"], $user["password_hash"])) {
|
||||
|
||||
session_start();
|
||||
session_regenerate_id();
|
||||
$_SESSION["user_id"] = $user["user_id"];
|
||||
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$is_invalid = true;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
|
||||
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js" defer></script>
|
||||
<script src="validation.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login_panel">
|
||||
|
||||
<?php if (isset($is_invalid) && $is_invalid): ?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastLogin">
|
||||
<div class="nak-toast__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
</div>
|
||||
<span class="nak-toast__text">Ungültiger Login!</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastLogin').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET["message"]) && $_GET["message"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastPw">
|
||||
<div class="nak-toast__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
</div>
|
||||
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastPw').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
<div class="login_content">
|
||||
<div class="nak-logo-link auth-logo">
|
||||
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
|
||||
<div class="auth-logo__text">
|
||||
<div class="auth-logo__name">Neuapostolische Kirche</div>
|
||||
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Anmelden</h2>
|
||||
|
||||
<form method="post">
|
||||
<div class="flexcolumn_center">
|
||||
<input class="login_field" placeholder="E-Mailadresse" type="email" name="email" id="email"
|
||||
value="<?= htmlspecialchars($_POST["email"] ?? "") ?>">
|
||||
<input class="login_field" placeholder="Passwort" type="password" name="password" id="password">
|
||||
<input class="login_button" type="submit" name="submit" value="Anmelden">
|
||||
</div>
|
||||
</form>
|
||||
<div class="small_text" style="text-align:center;margin-top:24px;color:var(--gray-600);">
|
||||
Sie haben noch keinen Account für das Intranet?<br>
|
||||
<a href="signup.php">Registrieren</a><br>
|
||||
<br>
|
||||
Passwort vergessen?<br>
|
||||
<a href="reset-password.php">Passwort zurücksetzen</a><br>
|
||||
<br>
|
||||
Probleme bei der Anmeldung?<br>
|
||||
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
|
||||
</div>
|
||||
</div><!-- EOD: login_content -->
|
||||
</div><!-- EOD: login_panel -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php /* Name: loginout.php
|
||||
Beschreibung: Logout-Seite */
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
|
||||
|
||||
session_destroy();
|
||||
|
||||
header("Location: {$g_httpLoginPath}");
|
||||
exit();
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php /* Name: reset-password-process.php
|
||||
Beschreibung: Verarbeitung des Passwort-Resets (3 Schritte)
|
||||
- Schritt 1: OTP generieren und per SMS + Mail senden
|
||||
- Schritt 2: OTP verifizieren → Reset-Token erzeugen
|
||||
- Schritt 3: Neues Passwort setzen mit Reset-Token */
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
// PostgreSQL-Zeitzone auf PHP-Zeitzone setzen
|
||||
$pdo->exec("SET timezone = 'Europe/Berlin'");
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
header("Location: reset-password.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $_POST["action"] ?? '';
|
||||
|
||||
// =============================================
|
||||
// SCHRITT 1: OTP anfordern
|
||||
// =============================================
|
||||
if ($action === "request_otp") {
|
||||
$email = trim($_POST["email"] ?? '');
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
header("Location: reset-password.php?error=1");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Nutzer suchen (muss registriert sein = user_login existiert)
|
||||
$stmt = $pdo->prepare("SELECT ugi.user_id, ugi.phone, ugi.first_name FROM user_general_information ugi INNER JOIN user_login ul ON ugi.user_id = ul.user_id WHERE ugi.mail = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
header("Location: reset-password.php?error=1");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($user['phone'])) {
|
||||
header("Location: reset-password.php?error=2");
|
||||
exit;
|
||||
}
|
||||
|
||||
// OTP generieren (6-stellig)
|
||||
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
|
||||
// Ablaufzeit als UNIX-Timestamp speichern (vermeidet Timezone-Probleme)
|
||||
$expires_timestamp = time() + 600; // 10 Minuten
|
||||
$expires_at = date('Y-m-d H:i:s', $expires_timestamp);
|
||||
|
||||
// Alte OTPs für diesen Nutzer invalidieren
|
||||
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0");
|
||||
$stmt->execute([$user['user_id']]);
|
||||
|
||||
// Neues OTP speichern
|
||||
$stmt = $pdo->prepare("INSERT INTO password_reset_otp (user_id, otp_code, expires_at) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$user['user_id'], $otp, $expires_at]);
|
||||
|
||||
// SMS senden (wie im Seelsorgeplaner)
|
||||
$phone_full = "+49" . $user['phone'];
|
||||
$messageText = "Intranet NAK Reinickendorf\n\nIhr Bestaetigungscode: {$otp}\n\nDer Code ist 10 Minuten gueltig.";
|
||||
|
||||
$smsData = array(
|
||||
"phone" => $phone_full,
|
||||
"message" => $messageText
|
||||
);
|
||||
|
||||
$options = array(
|
||||
'http' => array(
|
||||
'header' => "Content-Type: application/json; charset=utf-8",
|
||||
'method' => 'POST',
|
||||
'content' => json_encode($smsData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'timeout' => 10,
|
||||
),
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$smsResult = @file_get_contents(rtrim(intranet_env('SMS_GATEWAY_URL'), '/') . '/send_sms', false, $context);
|
||||
|
||||
if ($smsResult === FALSE) {
|
||||
header("Location: reset-password.php?error=4");
|
||||
exit;
|
||||
}
|
||||
|
||||
// OTP zusätzlich per Mail senden
|
||||
$greeting = !empty($user['first_name']) ? "Hallo {$user['first_name']}" : "Hallo";
|
||||
$mailBetreff = "Ihr Bestätigungscode";
|
||||
$mailNachricht = "{$greeting},\n\n";
|
||||
$mailNachricht .= "Sie haben das Zurücksetzen Ihres Passworts angefordert.\n\n";
|
||||
$mailNachricht .= "Ihr Bestätigungscode lautet: {$otp}\n\n";
|
||||
$mailNachricht .= "Der Code ist 10 Minuten gültig.\n\n";
|
||||
$mailNachricht .= "Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.";
|
||||
|
||||
send_intranet_mail($email, $mailBetreff, $mailNachricht);
|
||||
|
||||
// Telefonnummer maskieren für Anzeige (nur letzte 3 Ziffern zeigen)
|
||||
$phone_masked = str_repeat('*', max(0, strlen($user['phone']) - 3)) . substr($user['phone'], -3);
|
||||
|
||||
header("Location: reset-password.php?step=verify&uid={$user['user_id']}&phone=" . urlencode($phone_masked));
|
||||
exit;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// SCHRITT 2: OTP verifizieren → Reset-Token erzeugen
|
||||
// =============================================
|
||||
if ($action === "verify_otp") {
|
||||
$user_id = (int)($_POST["user_id"] ?? 0);
|
||||
$otp_code = trim($_POST["otp_code"] ?? '');
|
||||
|
||||
if ($user_id === 0 || strlen($otp_code) !== 6) {
|
||||
header("Location: reset-password.php?error=3");
|
||||
exit;
|
||||
}
|
||||
|
||||
// OTP prüfen – Vergleich mit PHP-Zeit statt DB NOW()
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$stmt = $pdo->prepare("SELECT id FROM password_reset_otp WHERE user_id = ? AND otp_code = ? AND used = 0 AND expires_at > ?");
|
||||
$stmt->execute([$user_id, $otp_code, $now]);
|
||||
$otpRecord = $stmt->fetch();
|
||||
|
||||
if (!$otpRecord) {
|
||||
header("Location: reset-password.php?step=verify&uid={$user_id}&error=3");
|
||||
exit;
|
||||
}
|
||||
|
||||
// OTP als verwendet markieren
|
||||
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE id = ?");
|
||||
$stmt->execute([$otpRecord['id']]);
|
||||
|
||||
// Einmal-Token für Passwort-Änderung generieren (in Session speichern)
|
||||
$reset_token = bin2hex(random_bytes(32));
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$_SESSION['reset_token'] = $reset_token;
|
||||
$_SESSION['reset_user_id'] = $user_id;
|
||||
$_SESSION['reset_token_expires'] = time() + 600; // 10 Minuten
|
||||
|
||||
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token));
|
||||
exit;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// SCHRITT 3: Neues Passwort setzen (nach OTP-Verifizierung)
|
||||
// =============================================
|
||||
if ($action === "set_password") {
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$reset_token = $_POST["reset_token"] ?? '';
|
||||
$new_password = $_POST["new_password"] ?? '';
|
||||
$new_password_confirm = $_POST["new_password_confirm"] ?? '';
|
||||
|
||||
// Token validieren
|
||||
if (empty($reset_token)
|
||||
|| !isset($_SESSION['reset_token'])
|
||||
|| $reset_token !== $_SESSION['reset_token']
|
||||
|| !isset($_SESSION['reset_token_expires'])
|
||||
|| $_SESSION['reset_token_expires'] < time()) {
|
||||
// Session-Daten aufräumen
|
||||
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
|
||||
header("Location: reset-password.php?error=7");
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = (int)$_SESSION['reset_user_id'];
|
||||
|
||||
// Passwort-Validierung
|
||||
if (strlen($new_password) < 8 || !preg_match("/[a-z]/i", $new_password) || !preg_match("/[0-9]/", $new_password)) {
|
||||
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=5");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($new_password !== $new_password_confirm) {
|
||||
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=6");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Neues Passwort setzen
|
||||
$password_hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE user_login SET password_hash = ? WHERE user_id = ?");
|
||||
$stmt->execute([$password_hash, $user_id]);
|
||||
|
||||
// User-Daten laden für Mail und Vermerk
|
||||
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$resetUserData = $stmt->fetch();
|
||||
|
||||
if ($resetUserData) {
|
||||
$loginUrl = "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/authorization/login.php";
|
||||
$greeting = !empty($resetUserData['first_name']) ? "Hallo {$resetUserData['first_name']}" : "Hallo";
|
||||
|
||||
$mailBetreff = "Passwort erfolgreich geändert";
|
||||
$mailNachricht = "{$greeting},\n\n";
|
||||
$mailNachricht .= "Ihr Passwort für das Intranet der NAK Reinickendorf wurde erfolgreich geändert.\n\n";
|
||||
$mailNachricht .= "Sie können sich ab sofort mit Ihrem neuen Passwort anmelden.";
|
||||
|
||||
send_intranet_mail($resetUserData['mail'], $mailBetreff, $mailNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt anmelden", $loginUrl);
|
||||
|
||||
// Vermerk in erledigte Tasks
|
||||
$userName = trim(($resetUserData['first_name'] ?? '') . ' ' . ($resetUserData['last_name'] ?? ''));
|
||||
if (empty($userName)) $userName = $resetUserData['mail'];
|
||||
|
||||
$taskSubject = "Passwort selbst zurückgesetzt: {$userName}";
|
||||
$taskMessage = "{$userName} ({$resetUserData['mail']}) hat das eigene Passwort erfolgreich zurückgesetzt.";
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$stmt->execute([$userName, $resetUserData['mail'], $taskSubject, $taskMessage]);
|
||||
}
|
||||
|
||||
// Session-Daten aufräumen
|
||||
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
|
||||
|
||||
header("Location: login.php?message=1");
|
||||
exit;
|
||||
}
|
||||
|
||||
header("Location: reset-password.php");
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php /* Name: reset-password.php
|
||||
Beschreibung: Passwort-Reset per SMS OTP (3 Schritte)
|
||||
- Schritt 1: E-Mail eingeben → OTP per SMS
|
||||
- Schritt 2: OTP-Code verifizieren
|
||||
- Schritt 3: Neues Passwort setzen */
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
|
||||
|
||||
$step = $_GET["step"] ?? "email";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<style>
|
||||
.login_button { position: relative; }
|
||||
.login_button.is-loading {
|
||||
color: transparent !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.login_button.is-loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin: -11px 0 0 -11px;
|
||||
border: 3px solid rgba(255,255,255,0.35);
|
||||
border-top-color: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.7s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login_panel">
|
||||
|
||||
<?php if (isset($_GET["error"])): ?>
|
||||
<?php
|
||||
$errorMessages = [
|
||||
1 => "E-Mail-Adresse nicht gefunden oder nicht registriert.",
|
||||
2 => "Keine Telefonnummer hinterlegt. Bitte wenden Sie sich an den Administrator.",
|
||||
3 => "Ungültiger oder abgelaufener Code.",
|
||||
4 => "SMS konnte nicht gesendet werden. Bitte versuchen Sie es erneut.",
|
||||
5 => "Passwort muss mindestens 8 Zeichen, einen Buchstaben und eine Zahl enthalten.",
|
||||
6 => "Passwörter stimmen nicht überein.",
|
||||
7 => "Sitzung abgelaufen. Bitte starten Sie den Vorgang erneut."
|
||||
];
|
||||
$errorNum = (int)$_GET["error"];
|
||||
$errorText = $errorMessages[$errorNum] ?? "Unbekannter Fehler.";
|
||||
?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||
<span class="nak-toast__text"><?= htmlspecialchars($errorText) ?></span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
|
||||
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="login_content">
|
||||
<div class="nak-logo-link auth-logo">
|
||||
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
|
||||
<div class="auth-logo__text">
|
||||
<div class="auth-logo__name">Neuapostolische Kirche</div>
|
||||
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Passwort zurücksetzen</h2>
|
||||
|
||||
<?php if ($step === "email"): ?>
|
||||
<!-- SCHRITT 1: E-Mail eingeben -->
|
||||
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Geben Sie Ihre E-Mail-Adresse ein. Ein Bestätigungscode wird an Ihre hinterlegte Telefonnummer und per Mail gesendet.</p>
|
||||
|
||||
<form action="reset-password-process.php" method="post" id="otpRequestForm">
|
||||
<input type="hidden" name="action" value="request_otp">
|
||||
<label for="email">E-Mailadresse</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<button class="login_button" type="submit" id="requestBtn">Code anfordern</button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('otpRequestForm').addEventListener('submit', function() {
|
||||
var btn = document.getElementById('requestBtn');
|
||||
btn.classList.add('is-loading');
|
||||
btn.disabled = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php elseif ($step === "verify"): ?>
|
||||
<!-- SCHRITT 2: Nur OTP-Code eingeben -->
|
||||
<?php $phone_masked = htmlspecialchars($_GET["phone"] ?? "***"); ?>
|
||||
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ein 6-stelliger Code wurde an die Nummer +49 <?= $phone_masked ?> und an Ihre E-Mail-Adresse gesendet.</p>
|
||||
|
||||
<form action="reset-password-process.php" method="post">
|
||||
<input type="hidden" name="action" value="verify_otp">
|
||||
<input type="hidden" name="user_id" value="<?= (int)($_GET["uid"] ?? 0) ?>">
|
||||
|
||||
<label for="otp_code">Bestätigungscode</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="text" id="otp_code" name="otp_code" maxlength="6" inputmode="numeric" pattern="[0-9]{6}" placeholder="000000" required autocomplete="one-time-code" style="text-align:center;font-size:1.4em;letter-spacing:8px;">
|
||||
</div>
|
||||
|
||||
<input class="login_button" type="submit" value="Code bestätigen">
|
||||
</form>
|
||||
|
||||
<?php elseif ($step === "newpassword"): ?>
|
||||
<!-- SCHRITT 3: Neues Passwort setzen (nur nach verifiziertem OTP) -->
|
||||
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ihr Code wurde bestätigt. Geben Sie nun Ihr neues Passwort ein.</p>
|
||||
|
||||
<form action="reset-password-process.php" method="post">
|
||||
<input type="hidden" name="action" value="set_password">
|
||||
<input type="hidden" name="reset_token" value="<?= htmlspecialchars($_GET["token"] ?? '') ?>">
|
||||
|
||||
<label for="new_password">Neues Passwort</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="new_password" name="new_password" required>
|
||||
</div>
|
||||
|
||||
<label for="new_password_confirm">Neues Passwort wiederholen</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="new_password_confirm" name="new_password_confirm" required>
|
||||
</div>
|
||||
|
||||
<input class="login_button" type="submit" value="Passwort ändern">
|
||||
</form>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="small_text" align="center">
|
||||
<br>
|
||||
<a href="login.php">Zurück zur Anmeldung</a><br>
|
||||
<br>
|
||||
Probleme beim Zurücksetzen?<br>
|
||||
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
|
||||
</div>
|
||||
</div><!-- EOD: login_content -->
|
||||
</div><!-- EOD: login_panel -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php /* Name: self-signup-process.php
|
||||
Beschreibung: Verarbeitung der Selbstregistrierung
|
||||
- Validierung der Eingaben
|
||||
- Speicherung in pending_registrations
|
||||
- Mail an Admin (samuel.bias@aol.de)
|
||||
- Eintrag in admin_messages */
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
// Validierung
|
||||
if (empty($_POST["first_name"])) {
|
||||
die("Vorname ist erforderlich.");
|
||||
}
|
||||
if (empty($_POST["last_name"])) {
|
||||
die("Nachname ist erforderlich.");
|
||||
}
|
||||
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
|
||||
die("Gültige E-Mail-Adresse erforderlich.");
|
||||
}
|
||||
if (strlen($_POST["password"]) < 8) {
|
||||
die("Passwort muss mindestens 8 Zeichen lang sein.");
|
||||
}
|
||||
if (!preg_match("/[a-z]/i", $_POST["password"])) {
|
||||
die("Passwort muss mindestens einen Buchstaben enthalten.");
|
||||
}
|
||||
if (!preg_match("/[0-9]/", $_POST["password"])) {
|
||||
die("Passwort muss mindestens eine Zahl enthalten.");
|
||||
}
|
||||
if ($_POST["password"] !== $_POST["password_confirmation"]) {
|
||||
die("Passwörter stimmen nicht überein.");
|
||||
}
|
||||
|
||||
$email = trim($_POST["email"]);
|
||||
$first_name = trim($_POST["first_name"]);
|
||||
$last_name = trim($_POST["last_name"]);
|
||||
|
||||
// Prüfen ob E-Mail bereits existiert (in user_general_information oder pending_registrations)
|
||||
$checkSql = "SELECT COUNT(*) FROM user_general_information WHERE mail = ?";
|
||||
$stmt = $pdo->prepare($checkSql);
|
||||
$stmt->execute([$email]);
|
||||
$count1 = $stmt->fetchColumn();
|
||||
|
||||
$checkSql2 = "SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'";
|
||||
$stmt2 = $pdo->prepare($checkSql2);
|
||||
$stmt2->execute([$email]);
|
||||
$count2 = $stmt2->fetchColumn();
|
||||
|
||||
if ($count1 > 0 || $count2 > 0) {
|
||||
header("Location: self-signup.php?error=2");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Passwort hashen und Token generieren
|
||||
$password_hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
|
||||
$token = bin2hex(random_bytes(32));
|
||||
|
||||
// In pending_registrations speichern
|
||||
$insertSql = "INSERT INTO pending_registrations (first_name, last_name, email, password_hash, token) VALUES (?, ?, ?, ?, ?)";
|
||||
$stmt = $pdo->prepare($insertSql);
|
||||
if (!$stmt) {
|
||||
header("Location: self-signup.php?error=1");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$stmt->execute([$first_name, $last_name, $email, $password_hash, $token])) {
|
||||
header("Location: self-signup.php?error=1");
|
||||
exit;
|
||||
}
|
||||
$pending_id = $pdo->lastInsertId();
|
||||
|
||||
// Admin-Nachricht in Inbox speichern
|
||||
$subject = "Neue Registrierungsanfrage: {$first_name} {$last_name}";
|
||||
$message_text = "Neue Selbstregistrierung:\n\nName: {$first_name} {$last_name}\nE-Mail: {$email}\n\nBitte im Adminpanel freischalten.";
|
||||
$data_json = json_encode(['pending_id' => $pending_id, 'token' => $token]);
|
||||
|
||||
$inboxSql = "INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, data_json) VALUES ('registration', ?, ?, ?, ?, ?)";
|
||||
$stmt = $pdo->prepare($inboxSql);
|
||||
$sender_name = "{$first_name} {$last_name}";
|
||||
$stmt->execute([$sender_name, $email, $subject, $message_text, $data_json]);
|
||||
|
||||
// Mail an Admin-Team (Admin + Technik)
|
||||
$admin_email = ADMIN_EMAIL;
|
||||
$mail_betreff = "Intranet: Neue Registrierungsanfrage von {$first_name} {$last_name}";
|
||||
$mail_nachricht = "Hallo Admin,\n\n";
|
||||
$mail_nachricht .= "Es liegt eine neue Registrierungsanfrage vor:\n\n";
|
||||
$mail_nachricht .= "Name: {$first_name} {$last_name}\n";
|
||||
$mail_nachricht .= "E-Mail: {$email}\n\n";
|
||||
$mail_nachricht .= "Bitte melden Sie sich im Adminpanel an, um die Anfrage zu bearbeiten.\n\n";
|
||||
$mail_nachricht .= "-- Intranet NAK Reinickendorf";
|
||||
|
||||
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht);
|
||||
|
||||
// Erfolg
|
||||
header("Location: self-signup.php?success=1");
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php /* Name: self-signup.php
|
||||
Beschreibung: Selbstregistrierung - Nutzer kann sich eigenständig registrieren.
|
||||
Admin erhält Mail + Inbox-Nachricht zur Freischaltung. */
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
|
||||
<script src="just-validate.js" defer></script>
|
||||
<script src="validation.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login_panel">
|
||||
|
||||
<?php if (isset($_GET["error"])): ?>
|
||||
<?php if ($_GET["error"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||
<span class="nak-toast__text">Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
|
||||
<?php elseif ($_GET["error"] == 2): ?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||
<span class="nak-toast__text">Diese E-Mailadresse ist bereits registriert oder es liegt bereits eine Anfrage vor.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
|
||||
<span class="nak-toast__text">Ihre Registrierungsanfrage wurde gesendet. Sie erhalten eine Benachrichtigung, sobald Ihr Konto freigeschaltet wurde.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="login_content">
|
||||
<div class="nak-logo-link" style="display:flex;align-items:flex-end;gap:12px;text-decoration:none;margin-bottom:24px;">
|
||||
<img src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo" style="width:52px;height:52px;flex-shrink:0;">
|
||||
<div style="display:flex;flex-direction:column;justify-content:flex-end;line-height:1.2;">
|
||||
<div style="font-family:var(--font-primary);font-weight:500;font-size:0.875rem;color:var(--gray-900);white-space:nowrap;">Neuapostolische Kirche</div>
|
||||
<div style="font-family:var(--font-primary);font-weight:300;font-size:0.75rem;color:var(--gray-600);white-space:nowrap;">Berlin-Reinickendorf</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Registrierung beantragen</h2>
|
||||
<h3 class="no_margins" align="center">Ihr Antrag wird von einem Administrator geprüft und freigeschaltet.</h3>
|
||||
|
||||
<form action="self-signup-process.php" method="post" id="signup" novalidate>
|
||||
<label for="first_name"><br>Vorname</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="text" id="first_name" name="first_name" required>
|
||||
</div>
|
||||
|
||||
<label for="last_name">Nachname</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="text" id="last_name" name="last_name" required>
|
||||
</div>
|
||||
|
||||
<label for="email">E-Mailadresse</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="email" id="email" name="email" required>
|
||||
</div>
|
||||
|
||||
<label for="password">Passwort</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<label for="password_confirmation">Passwort wiederholen</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="password_confirmation" name="password_confirmation" required>
|
||||
</div>
|
||||
|
||||
<input class="login_button" type="submit" value="Registrierung beantragen">
|
||||
</form>
|
||||
<div class="small_text" align="center">
|
||||
Sie haben bereits einen Account?
|
||||
<br>
|
||||
<a href="login.php">Anmelden</a>
|
||||
<br><br>
|
||||
Sie wurden bereits vom Admin vorregistriert?
|
||||
<br>
|
||||
<a href="signup.php">Direkt registrieren</a>
|
||||
</div>
|
||||
</div><!-- EOD: login_content -->
|
||||
</div><!-- EOD: login_panel -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php /* Name: signup-process.php
|
||||
Beschreibung: Vereinte Registrierungs-Verarbeitung
|
||||
- Prüft ob Nutzer vorregistriert ist → direkte Registrierung
|
||||
- Wenn nicht → automatisch Antrag in pending_registrations + Mail an Admin */
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
// === Validierung ===
|
||||
if (empty($_POST["first_name"])) {
|
||||
die("Vorname ist erforderlich.");
|
||||
}
|
||||
if (empty($_POST["last_name"])) {
|
||||
die("Nachname ist erforderlich.");
|
||||
}
|
||||
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
|
||||
die("Gültige E-Mail-Adresse erforderlich.");
|
||||
}
|
||||
if (empty($_POST["phone"])) {
|
||||
die("Telefonnummer ist erforderlich.");
|
||||
}
|
||||
if (strlen($_POST["password"]) < 8) {
|
||||
die("Passwort muss mindestens 8 Zeichen lang sein.");
|
||||
}
|
||||
if (!preg_match("/[a-z]/i", $_POST["password"])) {
|
||||
die("Passwort muss mindestens einen Buchstaben enthalten.");
|
||||
}
|
||||
if (!preg_match("/[0-9]/", $_POST["password"])) {
|
||||
die("Passwort muss mindestens eine Zahl enthalten.");
|
||||
}
|
||||
if ($_POST["password"] !== $_POST["password_confirmation"]) {
|
||||
die("Passwörter stimmen nicht überein.");
|
||||
}
|
||||
|
||||
$email = trim($_POST["email"]);
|
||||
$first_name = trim($_POST["first_name"]);
|
||||
$last_name = trim($_POST["last_name"]);
|
||||
$phone = preg_replace('/[^0-9]/', '', trim($_POST["phone"]));
|
||||
$password_hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
|
||||
|
||||
// === Prüfung 1: Ist der Nutzer vom Admin vorregistriert? ===
|
||||
$stmt = $pdo->prepare("SELECT user_id FROM user_general_information WHERE mail = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user_id = $stmt->fetchColumn();
|
||||
|
||||
if ($user_id) {
|
||||
// Nutzer ist vorregistriert – prüfen ob bereits Login existiert
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM user_login WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$loginExists = $stmt->fetchColumn();
|
||||
|
||||
if ($loginExists > 0) {
|
||||
// Bereits vollständig registriert
|
||||
header("Location: signup.php?error=2");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Direkte Registrierung durchführen
|
||||
$stmt = $pdo->prepare("INSERT INTO user_login (user_id, password_hash) VALUES (?, ?)");
|
||||
|
||||
if ($stmt->execute([$user_id, $password_hash])) {
|
||||
|
||||
// Namen und Telefonnummer aktualisieren
|
||||
$stmt = $pdo->prepare("UPDATE user_general_information SET first_name=?, last_name=?, phone=? WHERE user_id=?");
|
||||
$stmt->execute([$first_name, $last_name, $phone, $user_id]);
|
||||
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
} else {
|
||||
header("Location: signup.php?error=1");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// === Prüfung 2: Liegt bereits ein Antrag vor? ===
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'");
|
||||
$stmt->execute([$email]);
|
||||
$pendingCount = $stmt->fetchColumn();
|
||||
|
||||
if ($pendingCount > 0) {
|
||||
header("Location: signup.php?error=2");
|
||||
exit;
|
||||
}
|
||||
|
||||
// === Antrag stellen (Nutzer nicht vorregistriert) ===
|
||||
$token = bin2hex(random_bytes(32));
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO pending_registrations (first_name, last_name, email, phone, password_hash, token) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
|
||||
if (!$stmt->execute([$first_name, $last_name, $email, $phone, $password_hash, $token])) {
|
||||
header("Location: signup.php?error=1");
|
||||
exit;
|
||||
}
|
||||
$pending_id = $pdo->lastInsertId();
|
||||
|
||||
// Admin-Nachricht in Inbox speichern
|
||||
$subject = "Neue Registrierungsanfrage: {$first_name} {$last_name}";
|
||||
$message_text = "Neue Registrierungsanfrage:\n\nName: {$first_name} {$last_name}\nE-Mail: {$email}\n\nBitte im Adminpanel freischalten.";
|
||||
$data_json = json_encode(['pending_id' => $pending_id, 'token' => $token]);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, data_json) VALUES ('registration', ?, ?, ?, ?, ?)");
|
||||
$sender_name = "{$first_name} {$last_name}";
|
||||
$stmt->execute([$sender_name, $email, $subject, $message_text, $data_json]);
|
||||
|
||||
// Mail an Admin-Team (Admin + Technik)
|
||||
$mail_betreff = "Neue Registrierungsanfrage von {$first_name} {$last_name}";
|
||||
$mail_nachricht = "Es liegt eine neue Registrierungsanfrage vor:\n\n";
|
||||
$mail_nachricht .= "Name: {$first_name} {$last_name}\n";
|
||||
$mail_nachricht .= "E-Mail: {$email}\n";
|
||||
$mail_nachricht .= "Telefon: {$phone}\n\n";
|
||||
$mail_nachricht .= "Bitte melden Sie sich im Adminpanel an, um die Anfrage zu bearbeiten.";
|
||||
|
||||
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht, "Im Adminpanel bearbeiten", "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/adminpanel/admin_inbox.php");
|
||||
|
||||
// Erfolg – gleiche Meldung wie bei direkter Registrierung (Nutzer merkt keinen Unterschied)
|
||||
header("Location: signup.php?success=1");
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php /* Name: signup.php
|
||||
Beschreibung: Registrierungsformular (vereint)
|
||||
- Wenn Nutzer vorregistriert: direkte Registrierung
|
||||
- Wenn nicht: Antrag wird automatisch gestellt
|
||||
- Verwendet signup-process.php */
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
|
||||
<!-- Quelle für just-validate.js: https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js -->
|
||||
<script src="just-validate.js" defer></script>
|
||||
<script src="validation.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login_panel">
|
||||
|
||||
<?php if (isset($_GET["error"])): ?>
|
||||
<?php if ($_GET["error"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||
<span class="nak-toast__text">Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
|
||||
<?php elseif ($_GET["error"] == 2): ?>
|
||||
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||
<span class="nak-toast__text">Diese E-Mailadresse ist bereits registriert oder es liegt bereits eine Anfrage vor.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
|
||||
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
|
||||
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
|
||||
<span class="nak-toast__text">Ihre Registrierung wurde beantragt. Sie erhalten eine Benachrichtigung, sobald Ihr Konto freigeschaltet wurde.</span>
|
||||
</div>
|
||||
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="login_content">
|
||||
<div class="nak-logo-link auth-logo">
|
||||
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
|
||||
<div class="auth-logo__text">
|
||||
<div class="auth-logo__name">Neuapostolische Kirche</div>
|
||||
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Registrieren</h2>
|
||||
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Erstellen Sie Ihren persönlichen Zugang zum Intranet.</p>
|
||||
|
||||
<form action="signup-process.php" method="post" id="signup" novalidate>
|
||||
<label for="first_name">Vorname</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="text" id="first_name" name="first_name">
|
||||
</div>
|
||||
|
||||
<label for="last_name">Nachname</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="text" id="last_name" name="last_name">
|
||||
</div>
|
||||
|
||||
<label for="email">E-Mailadresse</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="email" id="email" name="email">
|
||||
</div>
|
||||
|
||||
<label for="phone">Telefonnummer</label>
|
||||
<div class="item">
|
||||
<div style="display:flex;align-items:stretch;width:100%;max-width:100%;margin-bottom:10px;box-sizing:border-box;">
|
||||
<div class="input-prefix" style="border-radius:0;border:1px solid var(--gray-300);border-right:none;margin:0;padding:14px 12px;font-weight:500;box-shadow:none;width:auto;background:var(--gray-200);color:var(--gray-700);display:flex;align-items:center;flex-shrink:0;">+49</div>
|
||||
<input class="login_field" type="tel" id="phone" name="phone" style="border-radius:0;border-left:0;margin:0;flex:1;width:auto;min-width:0;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="password">Passwort</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="password" name="password">
|
||||
</div>
|
||||
|
||||
<label for="password_confirmation">Passwort wiederholen</label>
|
||||
<div class="item">
|
||||
<input class="login_field" type="password" id="password_confirmation" name="password_confirmation">
|
||||
</div>
|
||||
|
||||
<input class="login_button" type="submit" value="Registrieren">
|
||||
</form>
|
||||
<div class="small_text" align="center">
|
||||
Sie haben bereits einen Account?
|
||||
<br>
|
||||
<a href="login.php">Anmelden</a><br>
|
||||
<br>
|
||||
Probleme bei der Registrierung?<br>
|
||||
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
|
||||
</div>
|
||||
</div><!-- EOD: login_content -->
|
||||
</div><!-- EOD: login_panel -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php /* Name: validate-email.php
|
||||
Beschreibung: Validierung der E-Mailadresse
|
||||
- Wird innerhalb von validation.js aufgerufen
|
||||
- Prüft ob E-Mail bereits vollständig registriert oder
|
||||
ein offener Antrag vorliegt */
|
||||
|
||||
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
|
||||
header("Content-Type: application/json");
|
||||
|
||||
if (!isset($_GET["email"]) || empty(trim($_GET["email"]))) {
|
||||
echo json_encode(["available" => false]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$email = trim($_GET["email"]);
|
||||
$is_available = true;
|
||||
|
||||
// Prüfung 1: Gibt es einen vollständigen Login für diese E-Mail?
|
||||
$stmt = $pdo->prepare("SELECT u.user_id FROM user_general_information u INNER JOIN user_login l ON u.user_id = l.user_id WHERE u.mail = ?");
|
||||
$stmt->execute([$email]);
|
||||
|
||||
if ($stmt->fetch()) {
|
||||
$is_available = false;
|
||||
}
|
||||
|
||||
// Prüfung 2: Liegt bereits ein offener Antrag vor?
|
||||
if ($is_available) {
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'");
|
||||
$stmt->execute([$email]);
|
||||
$pendingCount = $stmt->fetchColumn();
|
||||
|
||||
if ($pendingCount > 0) {
|
||||
$is_available = false;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(["available" => $is_available]);
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,74 @@
|
||||
const validation = new JustValidate("#signup");
|
||||
validation
|
||||
.addField("#first_name", [
|
||||
{
|
||||
rule: "required",
|
||||
errorMessage: "Pflichtfeld"
|
||||
}
|
||||
])
|
||||
.addField("#last_name", [
|
||||
{
|
||||
rule: "required",
|
||||
errorMessage: "Pflichtfeld"
|
||||
}
|
||||
])
|
||||
.addField("#email", [
|
||||
{
|
||||
rule: "required",
|
||||
errorMessage: "Pflichtfeld"
|
||||
},
|
||||
{
|
||||
rule: "email",
|
||||
errorMessage: "Ungültig"
|
||||
},
|
||||
{
|
||||
validator: (value) => () => {
|
||||
return fetch("validate-email.php?email=" + encodeURIComponent(value))
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(json) {
|
||||
return json.available;
|
||||
});
|
||||
},
|
||||
errorMessage: "Bereits registriert"
|
||||
}
|
||||
])
|
||||
.addField("#phone", [
|
||||
{
|
||||
rule: "required",
|
||||
errorMessage: "Pflichtfeld"
|
||||
},
|
||||
{
|
||||
rule: "minLength",
|
||||
value: 6,
|
||||
errorMessage: "Ungültige Telefonnummer"
|
||||
},
|
||||
{
|
||||
validator: (value) => {
|
||||
return /^[1-9][0-9\s\-\/()]{4,}$/.test(value);
|
||||
},
|
||||
errorMessage: "Ohne führende 0, nur Ziffern"
|
||||
}
|
||||
])
|
||||
.addField("#password", [
|
||||
{
|
||||
rule: "required",
|
||||
errorMessage: "Pflichtfeld"
|
||||
},
|
||||
{
|
||||
rule: "password",
|
||||
errorMessage: "Zu leicht"
|
||||
}
|
||||
])
|
||||
.addField("#password_confirmation", [
|
||||
{
|
||||
validator: (value, fields) => {
|
||||
return value === fields["#password"].elem.value;
|
||||
},
|
||||
errorMessage: "Passwörter müssen übereinstimmen"
|
||||
}
|
||||
])
|
||||
.onSuccess((event) => {
|
||||
document.getElementById("signup").submit();
|
||||
});
|
||||
|
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/* Name: brandschutz_cron.php
|
||||
Beschreibung: Monatlicher Cron-Job (am 1. des Monats) für Brandschutz-Schulungserinnerungen
|
||||
|
||||
Aufgaben:
|
||||
1. Einen Monat vor Ablauf: Erste Erinnerungsmail an den Nutzer
|
||||
2. Nach Ablauf der Frist: Monatliche Erinnerungsmail an den Nutzer
|
||||
3. 4+ Monate nach Ablauf: Eskalation an Admins (aktive Systemnachricht + Mail)
|
||||
|
||||
Aufruf: php brandschutz_cron.php (via Cron: 0 8 1 * *)
|
||||
*/
|
||||
|
||||
// Initialisierung
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
$_SERVER['DOCUMENT_ROOT'] = '/var/www/html';
|
||||
$_SERVER['HTTP_HOST'] = getenv('INTRANET_FQDN') ?: 'localhost';
|
||||
|
||||
$pdo = require $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/database.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$log = [];
|
||||
|
||||
// Alle Nutzer mit Brandschutz-Rolle laden
|
||||
$sql = "SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail,
|
||||
bt.last_training_date, bt.reminder_count, bt.admin_reminder_count
|
||||
FROM user_permissions up
|
||||
INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id
|
||||
LEFT JOIN brandschutz_training bt ON ugi.user_id = bt.user_id
|
||||
WHERE up.\"Brandschutz\" = 1";
|
||||
$result = $pdo->query($sql);
|
||||
$users = $result->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($users as $bsUser) {
|
||||
$userId = (int)$bsUser['user_id'];
|
||||
$firstName = $bsUser['first_name'] ?? '';
|
||||
$lastName = $bsUser['last_name'] ?? '';
|
||||
$mail = $bsUser['mail'] ?? '';
|
||||
$fullName = trim("{$firstName} {$lastName}");
|
||||
if (empty($fullName)) $fullName = $mail;
|
||||
$greeting = !empty($firstName) ? "Hallo {$firstName}" : "Hallo";
|
||||
|
||||
$lastTraining = $bsUser['last_training_date'];
|
||||
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
|
||||
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
|
||||
|
||||
// Sicherstellen dass ein Tracking-Eintrag existiert
|
||||
$stmtEnsure = $pdo->prepare("INSERT INTO brandschutz_training (user_id) VALUES (?) ON CONFLICT (user_id) DO NOTHING");
|
||||
$stmtEnsure->execute([$userId]);
|
||||
|
||||
// Fälligkeit berechnen
|
||||
if (empty($lastTraining)) {
|
||||
// Nie geschult = sofort fällig
|
||||
$dueDate = $today;
|
||||
$daysUntilDue = -1; // Bereits fällig
|
||||
$daysOverdue = 1;
|
||||
} else {
|
||||
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
|
||||
$daysUntilDue = (int)((strtotime($dueDate) - strtotime($today)) / 86400);
|
||||
$daysOverdue = $daysUntilDue < 0 ? abs($daysUntilDue) : 0;
|
||||
}
|
||||
|
||||
// Szenario 1: Einen Monat vor Ablauf (0-30 Tage verbleibend) - Erste Erinnerung
|
||||
if ($daysUntilDue >= 0 && $daysUntilDue <= 30 && $reminderCount === 0) {
|
||||
$dueDateFormatted = date('d.m.Y', strtotime($dueDate));
|
||||
$subject = "Erinnerung: Brandschutzunterweisung bis {$dueDateFormatted} fällig";
|
||||
$message = "{$greeting},\n\nIhre Brandschutzunterweisung läuft am {$dueDateFormatted} ab.\n\n";
|
||||
$message .= "Die Schulung dauert nur etwa 20 Minuten und kann bequem vom Handy oder PC aus durchgeführt werden.\n\n";
|
||||
$message .= "Bitte absolvieren Sie die Unterweisung innerhalb der nächsten zwei Monate über das Intranet.\n\n";
|
||||
$message .= "Vielen Dank!";
|
||||
|
||||
$brandschutzUrl = "https://" . $_SERVER['HTTP_HOST'] . "/intranet_repo/projects/brandschutz/brandschutz.php";
|
||||
send_intranet_mail($mail, $subject, $message, null, "intranet@nak-reinickendorf.de", "Zur Brandschutzunterweisung", $brandschutzUrl);
|
||||
|
||||
// Zähler erhöhen
|
||||
$stmtUpdate = $pdo->prepare("UPDATE brandschutz_training SET reminder_count = reminder_count + 1 WHERE user_id = ?");
|
||||
$stmtUpdate->execute([$userId]);
|
||||
|
||||
// Log in erledigte Nachrichten
|
||||
$sysSubject = "Brandschutz-Erinnerung gesendet: {$fullName}";
|
||||
$sysMessage = "Automatische Erinnerungsmail an {$fullName} ({$mail}) gesendet. Schulung fällig am {$dueDateFormatted}.";
|
||||
$stmtLog = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', 'System', ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$stmtLog->execute([$mail, $sysSubject, $sysMessage]);
|
||||
|
||||
$log[] = "Erste Erinnerung an {$fullName} gesendet (fällig am {$dueDateFormatted}).";
|
||||
}
|
||||
|
||||
// Szenario 2: Frist überschritten - Monatliche Erinnerung an Nutzer
|
||||
if ($daysOverdue > 0) {
|
||||
$dueDateFormatted = !empty($lastTraining) ? date('d.m.Y', strtotime($dueDate)) : 'sofort';
|
||||
$monthsOverdue = (int)ceil($daysOverdue / 30);
|
||||
|
||||
$subject = "Überfällig: Brandschutzunterweisung ({$daysOverdue} Tage)";
|
||||
$message = "{$greeting},\n\nIhre Brandschutzunterweisung ist seit {$daysOverdue} Tagen überfällig";
|
||||
if ($dueDateFormatted !== 'sofort') {
|
||||
$message .= " (Frist war der {$dueDateFormatted})";
|
||||
}
|
||||
$message .= ".\n\nDie Schulung dauert nur etwa 20 Minuten und kann bequem vom Handy oder PC aus durchgeführt werden.\n\n";
|
||||
$message .= "Bitte absolvieren Sie die Unterweisung umgehend über das Intranet.\n\n";
|
||||
$message .= "Dies ist die {$reminderCount}. Erinnerung zu dieser Schulung.";
|
||||
|
||||
$brandschutzUrl = "https://" . $_SERVER['HTTP_HOST'] . "/intranet_repo/projects/brandschutz/brandschutz.php";
|
||||
send_intranet_mail($mail, $subject, $message, null, "intranet@nak-reinickendorf.de", "Zur Brandschutzunterweisung", $brandschutzUrl);
|
||||
|
||||
// Zähler erhöhen
|
||||
$stmtUpdate = $pdo->prepare("UPDATE brandschutz_training SET reminder_count = reminder_count + 1 WHERE user_id = ?");
|
||||
$stmtUpdate->execute([$userId]);
|
||||
|
||||
// Log in erledigte Nachrichten
|
||||
$sysSubject = "Brandschutz-Erinnerung gesendet: {$fullName} ({$daysOverdue} Tage überfällig)";
|
||||
$sysMessage = "Automatische Erinnerungsmail (Nr. " . ($reminderCount + 1) . ") an {$fullName} ({$mail}) gesendet. Schulung seit {$daysOverdue} Tagen überfällig.";
|
||||
$stmtLog = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', 'System', ?, ?, ?, 'done', NOW(), NOW())");
|
||||
$stmtLog->execute([$mail, $sysSubject, $sysMessage]);
|
||||
|
||||
$log[] = "Erinnerung Nr." . ($reminderCount + 1) . " an {$fullName} ({$daysOverdue} Tage überfällig).";
|
||||
|
||||
// Szenario 3: 4+ Monate überfällig - Eskalation an Brandschutzbeauftragte + Admins
|
||||
if ($daysOverdue >= 120) {
|
||||
$adminSubject = "ESKALATION: Brandschutzunterweisung überfällig – {$fullName}";
|
||||
$adminMessage = "Die Brandschutzunterweisung von {$fullName} ({$mail}) ist seit {$daysOverdue} Tagen überfällig (mehr als 4 Monate).\n\n";
|
||||
$adminMessage .= "Bitte erinnern Sie die Person persönlich an die ausstehende Schulung.\n\n";
|
||||
$adminMessage .= "Bisherige Erinnerungsmails an den Nutzer: " . ($reminderCount + 1) . "\n";
|
||||
$adminMessage .= "Bisherige Admin-Eskalationen: " . ($adminReminderCount + 1);
|
||||
|
||||
// Mail an Brandschutzbeauftragte
|
||||
send_brandschutzbeauftragter_mail($pdo, $adminSubject, $adminMessage);
|
||||
|
||||
// Aktive (pending) Systemnachricht im Postfach
|
||||
$dataJson = json_encode([
|
||||
'action' => 'brandschutz_escalation',
|
||||
'user_id' => $userId,
|
||||
'user_name' => $fullName,
|
||||
'days_overdue' => $daysOverdue
|
||||
]);
|
||||
$stmtAdmin = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, created_at, data_json) VALUES ('system', 'System', ?, ?, ?, 'pending', NOW(), ?)");
|
||||
$stmtAdmin->execute([$mail, $adminSubject, $adminMessage, $dataJson]);
|
||||
|
||||
// Admin-Zähler erhöhen
|
||||
$stmtAdminCount = $pdo->prepare("UPDATE brandschutz_training SET admin_reminder_count = admin_reminder_count + 1 WHERE user_id = ?");
|
||||
$stmtAdminCount->execute([$userId]);
|
||||
|
||||
$log[] = "ESKALATION an Brandschutzbeauftragte für {$fullName} ({$daysOverdue} Tage überfällig).";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo = null;
|
||||
|
||||
// Ausgabe/Logging
|
||||
$output = "Brandschutz-Cron ausgeführt am " . date('d.m.Y H:i:s') . "\n";
|
||||
$output .= count($users) . " Nutzer mit Brandschutz-Rolle geprüft.\n";
|
||||
if (!empty($log)) {
|
||||
$output .= "\nAktionen:\n" . implode("\n", $log);
|
||||
} else {
|
||||
$output .= "\nKeine Aktionen erforderlich.";
|
||||
}
|
||||
|
||||
echo $output . "\n";
|
||||
|
After Width: | Height: | Size: 830 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 510 KiB |
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 240 KiB |
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
// Prüft, ob ein Basisname dem von der Anwendung erzeugten Format entspricht.
|
||||
function isValidNewsFileBase(string $fileBase): bool
|
||||
{
|
||||
return preg_match(
|
||||
'/^\d{4}-\d{2}-\d{2}-[A-Za-z0-9_-]+$/D',
|
||||
$fileBase
|
||||
) === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Löscht eine News vollständig:
|
||||
*
|
||||
* - Original-PDF
|
||||
* - alle daraus erzeugten PNG-Seiten
|
||||
*/
|
||||
function deleteNewsFiles(string $fileBase): bool
|
||||
{
|
||||
if (!isValidNewsFileBase($fileBase)) {
|
||||
error_log('Ungültiger News-Dateiname beim Löschen: ' . $fileBase);
|
||||
return false;
|
||||
}
|
||||
|
||||
$newsDirectory = __DIR__ . '/news';
|
||||
$pngDirectory = $newsDirectory . '/png';
|
||||
|
||||
$success = true;
|
||||
|
||||
// Alle erzeugten PNG-Seiten löschen
|
||||
$pngFiles = glob(
|
||||
$pngDirectory . '/' . $fileBase . '_page_*.png'
|
||||
) ?: [];
|
||||
|
||||
foreach ($pngFiles as $pngFile) {
|
||||
if (is_file($pngFile) && !unlink($pngFile)) {
|
||||
error_log('News-PNG konnte nicht gelöscht werden: ' . $pngFile);
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Original-PDF löschen
|
||||
$pdfFile = $newsDirectory . '/' . $fileBase . '.pdf';
|
||||
|
||||
if (is_file($pdfFile) && !unlink($pdfFile)) {
|
||||
error_log('News-PDF konnte nicht gelöscht werden: ' . $pdfFile);
|
||||
$success = false;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Löscht alle abgelaufenen News.
|
||||
*
|
||||
* Wichtig:
|
||||
* Es werden sowohl vorhandene PDFs als auch page_1-PNGs betrachtet.
|
||||
* Dadurch werden auch verwaiste PDFs ohne PNG zuverlässig gefunden.
|
||||
*/
|
||||
function cleanupExpiredNews(): void
|
||||
{
|
||||
$newsDirectory = __DIR__ . '/news';
|
||||
$pngDirectory = $newsDirectory . '/png';
|
||||
|
||||
$fileBases = [];
|
||||
|
||||
// PDFs erfassen
|
||||
foreach (glob($newsDirectory . '/*.pdf') ?: [] as $pdfFile) {
|
||||
$fileBase = pathinfo($pdfFile, PATHINFO_FILENAME);
|
||||
|
||||
if (isValidNewsFileBase($fileBase)) {
|
||||
$fileBases[$fileBase] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Zusätzlich PNGs erfassen.
|
||||
// Damit funktioniert die Bereinigung auch, falls die PDF bereits fehlt.
|
||||
foreach (glob($pngDirectory . '/*_page_1.png') ?: [] as $pngFile) {
|
||||
$fileName = basename($pngFile);
|
||||
$suffix = '_page_1.png';
|
||||
|
||||
if (!str_ends_with($fileName, $suffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileBase = substr($fileName, 0, -strlen($suffix));
|
||||
|
||||
if (isValidNewsFileBase($fileBase)) {
|
||||
$fileBases[$fileBase] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
foreach (array_keys($fileBases) as $fileBase) {
|
||||
// YYYY-MM-DD aus dem Dateinamen holen
|
||||
$expiryDateString = substr($fileBase, 0, 10);
|
||||
|
||||
$expiryDate = DateTimeImmutable::createFromFormat(
|
||||
'!Y-m-d',
|
||||
$expiryDateString
|
||||
);
|
||||
|
||||
// Zusätzlich prüfen, ob wirklich ein gültiges Datum entstanden ist
|
||||
if (
|
||||
$expiryDate === false ||
|
||||
$expiryDate->format('Y-m-d') !== $expiryDateString
|
||||
) {
|
||||
error_log(
|
||||
'Ungültiges Ablaufdatum bei News-Datei: ' . $fileBase
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aushang bleibt den gesamten angegebenen Tag sichtbar.
|
||||
* Beispiel Ablaufdatum 2026-09-05:
|
||||
* Löschung ab 2026-09-06 00:00 Uhr.
|
||||
*/
|
||||
$deleteFrom = $expiryDate->modify('+1 day');
|
||||
|
||||
if ($now >= $deleteFrom) {
|
||||
if (!deleteNewsFiles($fileBase)) {
|
||||
error_log(
|
||||
'Abgelaufene News konnte nicht vollständig gelöscht werden: '
|
||||
. $fileBase
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 394 KiB |
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../general_files/ini/runtime_env.php';
|
||||
define('HA_BASE_URL', rtrim(intranet_env('HA_BASE_URL'), '/'));
|
||||
define('HA_TOKEN', intranet_env('HA_TOKEN'));
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
require_once __DIR__ . '/../contentpanel/insertion/news_cleanup.php';
|
||||
|
||||
// DB-Verbindung zentral laden (liefert PDO-Objekt zurück)
|
||||
$pdo = require __DIR__ . '/../../general_files/ini/database.php';
|
||||
$openWeatherApiKey = intranet_env('OPENWEATHER_API_KEY');
|
||||
|
||||
$heute = date('Y-m-d');
|
||||
|
||||
// --- Gottesdienst (Bibelwort & Eingangslied) ---
|
||||
$sql = "SELECT bibelwort, bibelvers, eingang_nummer, eingang_titel
|
||||
FROM gottesdienst
|
||||
WHERE bibeldatum = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$heute]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$bibelwort = $row['bibelwort'];
|
||||
$bibelvers = $row['bibelvers'];
|
||||
$eingangNummer = $row['eingang_nummer'];
|
||||
$eingangTitel = $row['eingang_titel'];
|
||||
} else {
|
||||
$bibelwort = "Bibelwort nicht eingetragen";
|
||||
$bibelvers = "Bibelvers nicht gefunden.";
|
||||
$eingangNummer = "N/A";
|
||||
$eingangTitel = "Eingangslied nicht gefunden";
|
||||
}
|
||||
|
||||
// --- Ansagen ---
|
||||
$sqlAnsagen = "SELECT ansage_datum_von, ansage_datum_bis, ansage_uhrzeit, ansage_ort, ansage_titel, ansage_untertitel
|
||||
FROM ansagen
|
||||
WHERE ansage_datum_von >= ? OR ansage_datum_bis >= ?
|
||||
ORDER BY ansage_datum_von ASC";
|
||||
$stmtAnsagen = $pdo->prepare($sqlAnsagen);
|
||||
$stmtAnsagen->execute([$heute, $heute]);
|
||||
$ansagenResult = $stmtAnsagen->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Verbindung schließen (optional, aber sauber)
|
||||
$pdo = null;
|
||||
|
||||
// --- Aushänge ---
|
||||
$pngDirectoryUrl = '../contentpanel/insertion/news/png/';
|
||||
|
||||
// Abgelaufene Aushänge einschließlich Original-PDF bereinigen
|
||||
cleanupExpiredNews();
|
||||
|
||||
$pngDirectoryFs = realpath(
|
||||
__DIR__ . '/../contentpanel/insertion/news/png'
|
||||
);
|
||||
|
||||
$pngFiles = ($pngDirectoryFs && is_dir($pngDirectoryFs))
|
||||
? scandir($pngDirectoryFs)
|
||||
: [];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NAK Reinickendorf</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/intranet_repo/projects/_central/img/nak-logo.svg">
|
||||
<link rel="shortcut icon" href="/intranet_repo/projects/_central/img/favicon.ico">
|
||||
<link rel="stylesheet" href="stylo.css?v=<?php echo time(); ?>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=SUSE:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Anton&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-board">
|
||||
<div class="content-column-right">
|
||||
<div class="content-column-right-upper-box">
|
||||
<div class="content-column-right-upper-box-right">
|
||||
<div class="content-item-time">
|
||||
<div class="time">
|
||||
<div id="uhrzeit"></div>
|
||||
</div>
|
||||
<div class="date">
|
||||
<center><?php
|
||||
$months = ["UNDEFINED", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
|
||||
echo date("d. ") . $months[intval(date("m"))];
|
||||
?></center>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-item-daily">
|
||||
<div class="header-sticky-note">
|
||||
<div>Heutige Ansagen</div>
|
||||
</div>
|
||||
<div class="news-container">
|
||||
<div class="news-scroll">
|
||||
<?php foreach ($ansagenResult as $ansage) : ?>
|
||||
<div class="news-box">
|
||||
<div class="news-date">
|
||||
<?php
|
||||
$wochentage = [
|
||||
'Mon' => 'Mo','Tue' => 'Di','Wed' => 'Mi','Thu' => 'Do',
|
||||
'Fri' => 'Fr','Sat' => 'Sa','Sun' => 'So'
|
||||
];
|
||||
$datum_von_timestamp = strtotime($ansage['ansage_datum_von']);
|
||||
$heute = date("Y-m-d");
|
||||
$morgen = date("Y-m-d", strtotime('+1 day'));
|
||||
$datum_von_format = date("Y-m-d", $datum_von_timestamp);
|
||||
|
||||
if ($datum_von_format === $heute) {
|
||||
echo "Heute";
|
||||
} elseif ($datum_von_format === $morgen) {
|
||||
echo "Morgen";
|
||||
} else {
|
||||
$datum_von = date("d.m.", $datum_von_timestamp);
|
||||
$wochentag_englisch = date("D", $datum_von_timestamp);
|
||||
$wochentag = $wochentage[$wochentag_englisch] ?? $wochentag_englisch;
|
||||
echo $wochentag . ' ' . $datum_von;
|
||||
}
|
||||
if (!empty($ansage['ansage_datum_bis'])) {
|
||||
$datum_bis = date("d.m.", strtotime($ansage['ansage_datum_bis']));
|
||||
echo "<br> bis " . $datum_bis;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div>
|
||||
<?php echo htmlspecialchars($ansage['ansage_titel']); ?>
|
||||
<?php if (!empty($ansage['ansage_untertitel'])) : ?>
|
||||
<div class="news-subtitle"><?php echo htmlspecialchars($ansage['ansage_untertitel']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($ansage['ansage_uhrzeit']) && !empty($ansage['ansage_ort'])) : ?>
|
||||
<div class="news-subtitle"><i><?php echo date("H:i", strtotime($ansage['ansage_uhrzeit'])) . ' Uhr'; ?>, <?php echo htmlspecialchars($ansage['ansage_ort']); ?></i></div>
|
||||
<?php elseif (!empty($ansage['ansage_uhrzeit'])) : ?>
|
||||
<div class="news-subtitle"><i><?php echo date("H:i", strtotime($ansage['ansage_uhrzeit'])) . ' Uhr'; ?></i></div>
|
||||
<?php elseif (!empty($ansage['ansage_ort'])) : ?>
|
||||
<div class="news-subtitle"><i><?php echo htmlspecialchars($ansage['ansage_ort']); ?></i></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-column-right-upper-box-left">
|
||||
<div class="content-item-middle-top">
|
||||
<div class="header-sticky-note">
|
||||
<div>Bibelwort</div>
|
||||
</div>
|
||||
<div class="bible"><?php echo $bibelwort; ?></div>
|
||||
<div class="verse"><?php echo $bibelvers; ?></div>
|
||||
</div>
|
||||
|
||||
<div class="content-column-double-item">
|
||||
<div class="content-item">
|
||||
<div class="header-sticky-note">
|
||||
<div>Eingangslied</div>
|
||||
</div>
|
||||
<div class="GB"><?php echo $eingangNummer; ?></div>
|
||||
<div class="GB-title"><?php echo $eingangTitel; ?></div>
|
||||
</div>
|
||||
|
||||
<div class="content-item">
|
||||
<div class="header-sticky-note">
|
||||
<div>Temperatur</div>
|
||||
</div>
|
||||
<div class="temperature">
|
||||
<div class="container">
|
||||
<div id="wetter"></div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 60 60" width="64px" height="64px" baseProfile="basic">
|
||||
<polygon fill="#66e1ff" points="46,22.11 46,24.42 43,26.15 43,34.24 25,44.63 14,38.28 14,35.97 12,34.81 10,35.97 7,34.24 7,20.38 4,20.96 4,18.65 22,8.26 34,5.95"/>
|
||||
<polygon fill="#4292a6" points="25,30.774 28,34.815 43,26.155 43,34.238 25,44.63"/>
|
||||
<polygon fill="#ff5757" points="16,18.649 4,20.959 4,18.649 16,16.34 28,32.506 28,34.815"/>
|
||||
<polygon fill="#4292a6" points="34,8.257 22,10.566 22,8.257 34,5.947 46,22.113 46,24.423"/>
|
||||
<polygon fill="#e64e4e" points="4,18.649 22,8.257 34,5.947 16,16.34"/>
|
||||
<polygon fill="#cc4545" points="28,32.506 46,22.113 34,5.947 16,16.34"/>
|
||||
<polygon fill="#b33d3d" points="28,32.506 46,22.113 46,24.423 28,34.815"/>
|
||||
<polygon fill="#4292a6" points="17,34.238 22,37.124 22,31.351 17,28.464"/>
|
||||
<polygon fill="#4292a6" points="10,35.97 12,34.815 12,27.887 10,26.732"/>
|
||||
<polygon fill="#4ad8ff" points="19,33.083 22,34.815 22,31.351 19,29.619"/>
|
||||
<polygon fill="#52b1cc" points="17,34.238 22,37.124 22,34.815 19,33.083"/>
|
||||
<polygon fill="#ff5757" points="12,34.815 14,35.97 14,29.041 12,27.887"/>
|
||||
</svg>
|
||||
<div class="text-indoor" id='temperature_txt'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /left upper box -->
|
||||
</div> <!-- /right upper -->
|
||||
|
||||
<div class="content-item-bottom-box">
|
||||
<div class="header-sticky-note">
|
||||
<div>Aushänge</div>
|
||||
</div>
|
||||
<div class="aushang-flex">
|
||||
<?php
|
||||
$hasPng = false;
|
||||
foreach ($pngFiles as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'png') {
|
||||
$hasPng = true;
|
||||
echo '<img src="' . $pngDirectoryUrl . $file . '" alt="Aushang" height="95%">';
|
||||
}
|
||||
}
|
||||
if (!$hasPng) {
|
||||
echo '<p style="text-align: left;">Keine Aushänge vorgeplant.</p>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /right column -->
|
||||
|
||||
<div class="content-column-left">
|
||||
<div class="content-item-video">
|
||||
<!-- leerer Container, kein Video/HLS -->
|
||||
<div class="video-container" aria-label="Leer"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-item-month">
|
||||
<div class="header-sticky-note">
|
||||
<div>Monatsplan</div>
|
||||
</div>
|
||||
<div class="content-item-month-cell">
|
||||
<img src="month/terminuebersicht-1.png?v=<?php echo time(); ?>" alt="image">
|
||||
<img src="month/terminuebersicht-2.png?v=<?php echo time(); ?>" alt="image">
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /left column -->
|
||||
</div> <!-- /content-board -->
|
||||
|
||||
<script>
|
||||
function updateClock() {
|
||||
var now = new Date();
|
||||
var uhrzeitElement = document.getElementById("uhrzeit");
|
||||
var stunden = now.getHours();
|
||||
var minuten = now.getMinutes();
|
||||
uhrzeitElement.innerHTML = + padZero(stunden) + ':' + padZero(minuten);
|
||||
}
|
||||
function padZero(number) { return number < 10 ? '0' + number : number; }
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
|
||||
// OpenWeather (Außentemperatur+Icon)
|
||||
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=52.571291&lon=13.310422&appid=<?= rawurlencode($openWeatherApiKey) ?>&units=metric&language=german`;
|
||||
// Wetter-Icons
|
||||
const cloudy = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<defs>
|
||||
<filter id="blur" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="2.5"/>
|
||||
<feOffset dx="0" dy="2" result="off"/>
|
||||
<feComponentTransfer><feFuncA type="linear" slope="0.15"/></feComponentTransfer>
|
||||
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#blur)">
|
||||
<path d="M22 46h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 20 29a9 9 0 0 0 2 17z" fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M18 46h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 17 31a8 8 0 0 0 1 15z" fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const sun = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="32" cy="32" r="10" fill="#FFD34D" stroke="#fff"/>
|
||||
<path d="M32 6v8M32 50v8M6 32h8M50 32h8M13.5 13.5l5.7 5.7M44.8 44.8l5.7 5.7M50.5 13.5l-5.7 5.7M19.2 44.8l-5.7 5.7"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const moon = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<path d="M41 8a22 22 0 1 0 15 35A18 18 0 0 1 41 8z" fill="#DDE7FF" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>`;
|
||||
|
||||
const cloudy_day = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<g transform="translate(4,0)">
|
||||
<circle cx="22" cy="22" r="9" fill="#FFD34D" stroke="#fff" stroke-width="2"/>
|
||||
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M22 6v6M22 32v6M6 22h6M32 22h6M10.5 10.5l4.2 4.2M29.3 29.3l4.2 4.2M33.5 10.5l-4.2 4.2M14.7 29.3l-4.2 4.2"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(8,10)">
|
||||
<path d="M18 36h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 16 19a9 9 0 0 0 2 17z"
|
||||
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M22 36h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 21 21a8 8 0 0 0 1 15z"
|
||||
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const cloudy_night = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<path d="M38 10a16 16 0 1 0 11 26A13 13 0 0 1 38 10z" fill="#DDE7FF" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<g transform="translate(10,18)">
|
||||
<path d="M14 28h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 12 11a9 9 0 0 0 2 17z"
|
||||
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M18 28h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 17 13a8 8 0 0 0 1 15z"
|
||||
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const rain_shower = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<path d="M20 36h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 19a9 9 0 0 0 2 17z"
|
||||
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<g stroke="#fff" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M24 42l-3 6" />
|
||||
<path d="M34 42l-3 6" />
|
||||
<path d="M44 42l-3 6" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const rain = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<path d="M20 34h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 17a9 9 0 0 0 2 17z"
|
||||
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<g stroke="#fff" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M22 40l-3 8" />
|
||||
<path d="M32 40l-3 8" />
|
||||
<path d="M42 40l-3 8" />
|
||||
<path d="M52 40l-3 8" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const thunder = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<path d="M18 34h28a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 16 17a9 9 0 0 0 2 17z"
|
||||
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M32 36l-6 12h7l-3 10 12-16h-7l3-6z"
|
||||
fill="#FFD34D" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const snowy = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g>
|
||||
<path d="M20 34h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 17a9 9 0 0 0 2 17z"
|
||||
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<g fill="#fff">
|
||||
<circle cx="24" cy="44" r="2"/>
|
||||
<circle cx="34" cy="44" r="2"/>
|
||||
<circle cx="44" cy="44" r="2"/>
|
||||
<circle cx="29" cy="52" r="2"/>
|
||||
<circle cx="39" cy="52" r="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
const mist = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
|
||||
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" opacity="0.95">
|
||||
<path d="M14 24h36"/>
|
||||
<path d="M10 32h44"/>
|
||||
<path d="M14 40h36"/>
|
||||
<path d="M18 48h28"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
|
||||
async function getWeather() {
|
||||
try {
|
||||
var response = await fetch(apiUrl);
|
||||
var data = await response.json();
|
||||
if (data.cod === 200) {
|
||||
var weatherIcon = data.weather[0].icon;
|
||||
var temperature = data.main.temp;
|
||||
var weatherElement = document.getElementById('wetter');
|
||||
if (["04d","04n","03d","03n"].includes(weatherIcon)) {
|
||||
weatherElement.innerHTML = '<div>' + cloudy + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (weatherIcon === "02d") {
|
||||
weatherElement.innerHTML = '<div>' + cloudy_day + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (weatherIcon === "02n") {
|
||||
weatherElement.innerHTML = '<div>' + cloudy_night + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (weatherIcon === "01d") {
|
||||
weatherElement.innerHTML = '<div>' + sun + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (weatherIcon === "01n") {
|
||||
weatherElement.innerHTML = '<div>' + moon + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (["09d","09n","10n"].includes(weatherIcon)) {
|
||||
weatherElement.innerHTML = '<div>' + rain_shower + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (weatherIcon === "10d") {
|
||||
weatherElement.innerHTML = '<div>' + rain + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (["11d","11n"].includes(weatherIcon)) {
|
||||
weatherElement.innerHTML = '<div>' + thunder + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (["13d","13n"].includes(weatherIcon)) {
|
||||
weatherElement.innerHTML = '<div>' + snowy + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
} else if (["50d","50n"].includes(weatherIcon)) {
|
||||
weatherElement.innerHTML = '<div>' + mist + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
|
||||
}
|
||||
} else {
|
||||
console.error('Fehler beim Abrufen des Wetters:', data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen des Wetters:', error.message);
|
||||
}
|
||||
}
|
||||
getWeather();
|
||||
setInterval(getWeather, 30000);
|
||||
|
||||
// INNENTEMPERATUR: jetzt über Home Assistant (Server-Proxy)
|
||||
function updateTemperature() {
|
||||
const entityId = 'sensor.temp_temperatur';
|
||||
fetch('ha_temperature.php?entity_id=' + encodeURIComponent(entityId))
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
console.error('HA Fehler:', data);
|
||||
return;
|
||||
}
|
||||
document.getElementById('temperature_txt').innerText = `${data.value}${data.unit || '°C'}`;
|
||||
})
|
||||
.catch(err => console.error('Fetch Fehler:', err));
|
||||
}
|
||||
updateTemperature();
|
||||
setInterval(updateTemperature, 10000);
|
||||
|
||||
function updateContent() {
|
||||
fetch('update_content.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
console.error('Fehler:', data.error);
|
||||
return;
|
||||
}
|
||||
document.querySelector('.bible').innerText = data.bibelwort;
|
||||
document.querySelector('.verse').innerText = data.bibelvers;
|
||||
document.querySelector('.GB').innerText = data.eingang_nummer;
|
||||
document.querySelector('.GB-title').innerText = data.eingang_titel;
|
||||
setupScrolling();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Abrufen der Daten:', error);
|
||||
});
|
||||
}
|
||||
updateContent();
|
||||
setInterval(updateContent, 30000);
|
||||
|
||||
function setupScrolling() {
|
||||
const SPEED_V = 7; // News vertikal
|
||||
const SPEED_H = 10; // Aushänge horizontal
|
||||
|
||||
const containerFlex = document.querySelector('.aushang-flex');
|
||||
if (containerFlex) {
|
||||
const images = containerFlex.querySelectorAll('img');
|
||||
const measureAndApplyH = () => {
|
||||
let totalWidth = 0;
|
||||
images.forEach(img => {
|
||||
const styles = window.getComputedStyle(img);
|
||||
const mr = parseFloat(styles.marginRight) || 0;
|
||||
totalWidth += (img.offsetWidth || 0) + mr;
|
||||
});
|
||||
const containerWidth = containerFlex.offsetWidth || 0;
|
||||
const scrollDistanceH = Math.max(0, totalWidth - containerWidth);
|
||||
containerFlex.style.setProperty('--scroll-distance-h', `-${scrollDistanceH}px`);
|
||||
const durationH = Math.max(1, scrollDistanceH / SPEED_H);
|
||||
if (scrollDistanceH === 0) {
|
||||
containerFlex.style.animation = 'none';
|
||||
} else {
|
||||
containerFlex.style.animationName = 'scroll-h';
|
||||
containerFlex.style.animationDuration = `${durationH}s`;
|
||||
containerFlex.style.animationTimingFunction = 'linear';
|
||||
containerFlex.style.animationIterationCount = 'infinite';
|
||||
}
|
||||
};
|
||||
let pending = 0;
|
||||
images.forEach(img => {
|
||||
if (!img.complete || img.naturalWidth === 0) {
|
||||
pending++;
|
||||
img.addEventListener('load', () => {
|
||||
pending--;
|
||||
if (pending === 0) measureAndApplyH();
|
||||
}, { once: true });
|
||||
}
|
||||
});
|
||||
if (pending === 0) measureAndApplyH();
|
||||
}
|
||||
|
||||
const containerNews = document.querySelector('.news-container');
|
||||
const scrollBox = document.querySelector('.news-scroll');
|
||||
if (containerNews && scrollBox) {
|
||||
const totalHeight = scrollBox.scrollHeight || 0;
|
||||
const visibleHeight = containerNews.offsetHeight || 0;
|
||||
const scrollDistanceV = Math.max(0, totalHeight - visibleHeight);
|
||||
scrollBox.style.setProperty('--scroll-distance-v', `-${scrollDistanceV}px`);
|
||||
const durationV = Math.max(1, scrollDistanceV / SPEED_V);
|
||||
if (scrollDistanceV === 0) {
|
||||
scrollBox.style.animation = 'none';
|
||||
} else {
|
||||
scrollBox.style.animationName = 'scroll-v';
|
||||
scrollBox.style.animationDuration = `${durationV}s`;
|
||||
scrollBox.style.animationTimingFunction = 'linear';
|
||||
scrollBox.style.animationIterationCount = 'infinite';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const news = document.querySelector('.news-scroll');
|
||||
const aushang = document.querySelector('.aushang-flex');
|
||||
if (news) news.style.animation = 'none';
|
||||
if (aushang) aushang.style.animation = 'none';
|
||||
setTimeout(setupScrolling, 50);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
const news = document.querySelector('.news-scroll');
|
||||
const aushang = document.querySelector('.aushang-flex');
|
||||
if (news) news.style.animation = 'none';
|
||||
if (aushang) aushang.style.animation = 'none';
|
||||
setTimeout(setupScrolling, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Token/URL aus externer Config laden
|
||||
require_once __DIR__ . '/config.local.php';
|
||||
|
||||
// Entity-ID (Standard: dein Sensor aus der Liste)
|
||||
$entityId = isset($_GET['entity_id']) ? $_GET['entity_id'] : 'sensor.temp_temperatur';
|
||||
|
||||
$url = rtrim(HA_BASE_URL, '/') . '/api/states/' . urlencode($entityId);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . HA_TOKEN,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_CONNECTTIMEOUT => 3,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $httpCode < 200 || $httpCode >= 300) {
|
||||
http_response_code(502);
|
||||
echo json_encode([
|
||||
'error' => 'Home Assistant request failed',
|
||||
'details' => $error ?: ('HTTP ' . $httpCode)
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (!is_array($data) || !isset($data['state'])) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Unexpected HA response']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$val = $data['state'];
|
||||
$unit = $data['attributes']['unit_of_measurement'] ?? '°C';
|
||||
|
||||
$CALIBRATION_OFFSET = +0;
|
||||
|
||||
if (is_numeric($val)) {
|
||||
$number = round((float)$val + $CALIBRATION_OFFSET, 1);
|
||||
} else {
|
||||
$number = $val;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'entity_id' => $entityId,
|
||||
'value' => $number,
|
||||
'unit' => $unit,
|
||||
'friendly_name' => $data['attributes']['friendly_name'] ?? null,
|
||||
'last_changed' => $data['last_changed'] ?? null,
|
||||
]);
|
||||
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 126 KiB |
@@ -0,0 +1,340 @@
|
||||
/* Gesamtes Seiten-Layout */
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: 'SUSE', sans-serif;
|
||||
font-size: 20px;
|
||||
background-image: url('cloudy.jpg');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Content Board Layout */
|
||||
.content-board {
|
||||
background-color: #a8a6a7a7;
|
||||
width: 99%;
|
||||
height: 97%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
padding: 0 6px 7px 3px;
|
||||
}
|
||||
|
||||
.content-column-left {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-column-right {
|
||||
width: 58%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.temperature {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 5px 0 0 0;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.verse {
|
||||
text-align: justify;
|
||||
color: rgb(67, 67, 67);
|
||||
padding: 5px 15px 5px 15px;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: rgb(67, 67, 67);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content-column-right-upper-box-left {
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-column-right-upper-box-right {
|
||||
width: 39%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-column-right-upper-box {
|
||||
width: 100%;
|
||||
height: 51%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-item-time {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #ffffffab;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-item-news {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #ffffffab;
|
||||
width: 100%;
|
||||
height: 63%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-item-video {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #cbc5bc;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-item-middle-top {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #cbc5bc;
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Pseudoelement für den animierten border-bottom Effekt */
|
||||
.content-item-middle-top::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 15px;
|
||||
background: linear-gradient(to bottom, transparent, #cbc5bc);
|
||||
}
|
||||
|
||||
/* Monatsplan: jetzt mit weißem Hintergrund */
|
||||
.content-item-month {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #cbc5bc7d;
|
||||
width: 100%;
|
||||
height: 56%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.content-item-month-cell {
|
||||
display: flex;
|
||||
background-color: #ffffffe5;
|
||||
justify-content: center; /* Zentriere die Bilder horizontal */
|
||||
height: 94%;
|
||||
align-items: center; /* Zentriere die Bilder vertikal */
|
||||
}
|
||||
|
||||
.content-item-month-cell img {
|
||||
max-width: 100%; /* Verhindert, dass das Bild größer als der Container wird */
|
||||
max-height: 100%; /* Verhindert Verzerrungen, indem es die maximale Höhe auf die Containerhöhe beschränkt */
|
||||
object-fit: contain; /* Stellt sicher, dass das Bild im Container bleibt, ohne gestreckt zu werden */
|
||||
}
|
||||
|
||||
.content-item-daily {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #edebe8;
|
||||
width: 100%;
|
||||
height: 63%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-sticky-note {
|
||||
font-size: 25px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
padding: 5px 0 15px 0;
|
||||
border-bottom: 2px solid #38312430;
|
||||
background-color: #cbc5bc;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 80px;
|
||||
text-align: center;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.bible {
|
||||
text-align: center;
|
||||
font-size: 60px;
|
||||
padding: 10px 0 10px 0;
|
||||
border-bottom: 2px solid #38312456;
|
||||
background-color: #edebe8;
|
||||
}
|
||||
|
||||
.GB {
|
||||
text-align: center;
|
||||
font-size: 60px;
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.GB-title {
|
||||
text-align: center;
|
||||
color: rgb(67, 67, 67);
|
||||
}
|
||||
|
||||
.content-column-double-item {
|
||||
width: 100%;
|
||||
height: 33%;
|
||||
margin: 15px 0 15px 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Elemente innerhalb des Content Boards */
|
||||
.content-item {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #edebe8;
|
||||
height: 100%;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.content-item-bottom-box {
|
||||
border-radius: 0;
|
||||
box-shadow: 5px 5px 2px rgba(0, 0, 0, 0.16);
|
||||
background-color: #edebe8;
|
||||
margin: 6px 0 0 0;
|
||||
height: 47.5%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 5px solid #cbc5bc;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-container video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scale(1.2);
|
||||
object-fit: cover;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40%;
|
||||
background-color: #38312431;
|
||||
border-radius: 0;
|
||||
border: 2px solid #38312440;
|
||||
}
|
||||
|
||||
.text-outdoor {
|
||||
font-weight: bold;
|
||||
padding: 9px 0 0 0;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.text-indoor {
|
||||
font-weight: bold;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.news-box {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 15px 5px 15px;
|
||||
border-bottom: 2px solid #38312456;
|
||||
background-color: #edebe8;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.news-date {
|
||||
font-weight: bold;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.news-subtitle {
|
||||
color: rgba(67, 67, 67, 0.657);
|
||||
}
|
||||
|
||||
.news-container {
|
||||
height: 290px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.news-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation-name: scroll-v;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
--scroll-distance-v: 0px;
|
||||
}
|
||||
@keyframes scroll-v {
|
||||
0% { transform: translateY(0); }
|
||||
100% { transform: translateY(var(--scroll-distance-v)); }
|
||||
}
|
||||
|
||||
.aushang-flex img {
|
||||
border-radius: 0;
|
||||
margin: 0 10px 0 0;
|
||||
border: 2px solid #38312440;
|
||||
box-shadow: 4px 4px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.aushang-flex {
|
||||
padding: 0 5px 0 5px;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
height: 90.5%;
|
||||
border-radius: 0;
|
||||
|
||||
animation-name: scroll-h;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
--scroll-distance-h: 0px;
|
||||
}
|
||||
@keyframes scroll-h {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(var(--scroll-distance-h)); }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
// DB-Verbindung zentral laden
|
||||
$pdo = require __DIR__ . '/../../general_files/ini/database.php';
|
||||
|
||||
$heute = date('Y-m-d');
|
||||
|
||||
try {
|
||||
// --- Bibelwort, Bibelvers und Eingangslied ---
|
||||
$sql = "SELECT bibelwort, bibelvers, eingang_nummer, eingang_titel
|
||||
FROM gottesdienst
|
||||
WHERE bibeldatum = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$heute]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$bibelwort = $row['bibelwort'];
|
||||
$bibelvers = $row['bibelvers'];
|
||||
$eingangNummer = $row['eingang_nummer'];
|
||||
$eingangTitel = $row['eingang_titel'];
|
||||
} else {
|
||||
$bibelwort = "Bibelwort nicht eingetragen";
|
||||
$bibelvers = "Bibelvers nicht gefunden.";
|
||||
$eingangNummer = "N/A";
|
||||
$eingangTitel = "Eingangslied nicht gefunden";
|
||||
}
|
||||
|
||||
// --- Ansagen ---
|
||||
$sqlAnsagen = "SELECT ansage_datum_von, ansage_datum_bis, ansage_uhrzeit, ansage_ort, ansage_titel, ansage_untertitel
|
||||
FROM ansagen
|
||||
WHERE ansage_datum_von >= ? OR ansage_datum_bis >= ?
|
||||
ORDER BY ansage_datum_von ASC";
|
||||
$stmtAnsagen = $pdo->prepare($sqlAnsagen);
|
||||
$stmtAnsagen->execute([$heute, $heute]);
|
||||
$ansagenResult = $stmtAnsagen->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Rückgabe als JSON
|
||||
echo json_encode([
|
||||
'bibelwort' => $bibelwort,
|
||||
'bibelvers' => $bibelvers,
|
||||
'eingang_nummer' => $eingangNummer,
|
||||
'eingang_titel' => $eingangTitel,
|
||||
'ansagen' => $ansagenResult
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Optional: error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Fehler bei der Verbindung zur Datenbank'], JSON_UNESCAPED_UNICODE);
|
||||
} finally {
|
||||
$pdo = null;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php /* Name: developer.php
|
||||
Beschreibung: Entwicklungsprozess (Bearbeitungsseite)
|
||||
- inkl. Aufruf von button_edit.php */
|
||||
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
</head>
|
||||
<body class="theme-green">
|
||||
|
||||
<?php /* Besondere Entwickler-Rechte checken */
|
||||
// Fetch user details
|
||||
$sql = "SELECT * FROM user_permissions up INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id WHERE up.user_id = {$_SESSION["user_id"]}";
|
||||
$result = $pdo->query($sql);
|
||||
$isDeveloper = $result->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Check if the user has developer access (Entwickler = 1)
|
||||
if ($isDeveloper["Entwickler"] == 1) {
|
||||
$sql_groups = "SELECT * FROM user_permissions";
|
||||
$stmt = $pdo->prepare($sql_groups);
|
||||
$stmt->execute();
|
||||
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} else {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
<?php // Es folgen ab hier die HTML-Ausgaben beginnend mit dem HTML-Header: ?>
|
||||
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
|
||||
<?php // Header (erweitert): Titel der Seite mit Funktionsaufruf ?>
|
||||
<div class="header_title ht_dev text_white" id="header_title">
|
||||
<div class="flexrow fullwidth" style="justify-content:space-between !important;">
|
||||
<div class="header_title_text">Entwicklungsprozess</div>
|
||||
|
||||
<div class="small_text" style="margin-right:8px;margin-left:8px;align-self:center;">
|
||||
<?php echo "$p_nac_base // $p_version ($p_status)" ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php // Die oberen Standard-Overlays müssen vor dem Content-Bereich platziert sein. ?>
|
||||
|
||||
|
||||
<div class="content" id="base">
|
||||
|
||||
<div class="content_flexcontainer">
|
||||
<div class="flexright">
|
||||
|
||||
<?php // Info-Panel einbinden
|
||||
require_once 'developer_info.php'; ?>
|
||||
|
||||
<?php // Info-Panel einbinden
|
||||
//require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/_templates/playground/table_openClose.php'; ?>
|
||||
</div>
|
||||
|
||||
<div class="flexleft">
|
||||
<?php // TODO: Durch die Developer-Textpanels iterieren
|
||||
// TODO: Dev-Texte werden später noch in die Datenbank integriert
|
||||
$edit_id = 1;
|
||||
$filename = "work/todo.txt";
|
||||
$filecontent = file_get_contents($filename);
|
||||
$filecontent_output = markdownFormat($filecontent);
|
||||
|
||||
if (isset($_POST['speichern'])) {
|
||||
$filecontent = $_POST['filecontent'];
|
||||
|
||||
file_put_contents($filename, $filecontent); //Speichern + Bearbeitungsmodus verlassen
|
||||
header("Location: {$g_httpCurrentCleanUrl}");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
<div class="content_panel" id="1">
|
||||
<div class="flexrow_right fullwidth">
|
||||
<?php require $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/snippets/button_edit.php'; ?>
|
||||
</div>
|
||||
|
||||
<?php if ( (isset($_GET['action']) && $_GET['action'] == 'edit') &&
|
||||
(isset($edit_id) && $edit_id == 1) ) { ?>
|
||||
|
||||
<form class="display_within_dev" action="" method="post" id="<?php echo $edit_id; ?>">
|
||||
<textarea id="filecontent" name="filecontent" autofocus><?php echo $filecontent; ?></textarea>
|
||||
<div>
|
||||
<input class="button" type="submit" value="Speichern" name="speichern">
|
||||
<input class="button" type="button" value="Abbrechen" onclick="window.location.href=window.location.href.split('?')[0];">
|
||||
</div>
|
||||
</form>
|
||||
<?php } else { ?>
|
||||
<div class="content_panel_output"><?php echo $filecontent_output; ?></div>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- EOD: content -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php /* Name: developer_info.php
|
||||
Beschreibung: Serverinformationen und Entwicklungsübersicht */
|
||||
?>
|
||||
|
||||
|
||||
<?php
|
||||
$label1 = "Allgemein";
|
||||
$label_class1 = "text_green";
|
||||
$array_admin1 = [
|
||||
"Name" => "Max Mustermann",
|
||||
"Alter" => 30,
|
||||
"Beruf" => "Softwareentwickler",
|
||||
// Weitere Daten ...
|
||||
];
|
||||
?>
|
||||
<?php // Info-Panel einbinden
|
||||
// require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/_templates/playground/table_openClose.php'; ?>
|
||||
|
||||
|
||||
<?php
|
||||
$label2 = "Kundendaten";
|
||||
$label_class2 = "text_blue";
|
||||
$array_admin2 = [
|
||||
"Kundennummer" => "12345",
|
||||
"Name" => "Erika Musterfrau",
|
||||
"E-Mail" => "erika@example.com",
|
||||
// Weitere Daten ...
|
||||
];
|
||||
?>
|
||||
|
||||
<?php // Info-Panel einbinden
|
||||
// require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/_templates/playground/table_openClose.php'; ?>
|
||||
|
||||
<div class="content_panel" id="data">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
<label class="text_green">Allgemeines</label>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($array_admin as $key => $value): ?>
|
||||
<tr>
|
||||
<td><?= $key ?></td>
|
||||
<td><?= nvl($value, '---') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
<label class="text_green">Serverinformationen</label>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($array_dev as $key => $value): ?>
|
||||
<tr>
|
||||
<td><?= $key ?></td>
|
||||
<td><?= nvl($value, '---') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<h2>Grafiken</h2>
|
||||
|
||||
**SVG**
|
||||
https://www.svgrepo.com/
|
||||
|
||||
- Iconsax Bold Oval Icons Collection
|
||||
-- https://www.svgrepo.com/collection/iconsax-bold-oval-icons/
|
||||
|
||||
- Lightning Design Utility Icons Collection
|
||||
-- https://www.svgrepo.com/collection/lightning-design-utility-icons/
|
||||
|
||||
- Bootstrap UI Icons Collection
|
||||
-- https://www.svgrepo.com/collection/bootstrap-ui-icons/
|
||||
|
||||
- Zest Interface Icons Collection
|
||||
-- https://www.svgrepo.com/collection/zest-interface-icons/
|
||||
@@ -0,0 +1,159 @@
|
||||
<h2>Relaunch: Einführung (Aufgaben Samu)</h2>
|
||||
**Umstrukturierung: SeelsorgePlaner**
|
||||
|
||||
- projects\seelsorge\besuchsstellen.php (und die eingebundenen Unterseiten):
|
||||
:: Fortsetzung Technik:
|
||||
:::: 1. Bei Klicken auf Buttons und Bilder auf der aktuellen Ansicht verbleiben, anstelle auf die Startseite (active) zu wechseln (AJAX Funktion).
|
||||
:::: 2. Sicherung: Aufruf aller Seiten zu Seelsorge nur für Amtsgeschwister !!!
|
||||
- Alle Objekte die über den Browser theoretisch aufgerufen werden können: -> Sicherheit, dass kein Quellcode gelesen werden kann. Routing ggf. auf LogIn-Seite.
|
||||
- Web-Server: Kein Laden eines Objektordners. Damit keine Dateien gesichtet oder explizit heruntergeladen werden können.
|
||||
- Analyse: Mehrfach-Codes -> Optimierung: Auslagern in separate Files (snippets-Ordner)
|
||||
|
||||
*Admin-Seite*
|
||||
- Profil als Pop-Up (analog Besuchsstellen-Seite)
|
||||
|
||||
*Seelsorge-Seiten*
|
||||
- Workaround zu openCloseProfile ggf. optimieren oder besser neue Lösung schaffen.
|
||||
|
||||
*Prüfung*
|
||||
- Alle Buttons prüfen. Ist die Textgröße in den Buttons für Bildschirm und Handy okay?
|
||||
|
||||
**Fehlerbehebung (SAMU) **
|
||||
- Bugfix: *visit.php*
|
||||
-- Nach einer Aktion (Erneuern/Priorisieren) wird die Seite neu geladen für den Fokus auf Datensatz. Das funktioniert aktuell nicht wg. Seitenverlinkung auf seelsorge.php. Hier brauchen wir einen neuen Denkansatz. Wie gestalten wir das? Ist ggf. doch eine Aufteilung der Seitenbereiche zurück auf zwei extra Seiten notwendig oder gibt es die Möglichkeit dies per Javascript zu steuern, indem beim Neuladen der Seite die zu aktivierenden Elemente bekannt sind und ein-/ausgeblendet werden? Kann auf ein Neuladen der Seite verzichtet werden, indem per AJAX nur der Inhalt aktualisiert wird?
|
||||
- *calender_edit.php*
|
||||
-- Datum in der Vergangenheit sind anwählbar im Edit-Modus. Erst ab dem aktuellen Tag darf das möglich sein.
|
||||
|
||||
** Datenbankanbindung der Verfügbarkeiten (Samu) --> "greenbox" auf Kalender-Item **
|
||||
- Datenabfrage: bei abgelaufenen Tagen keine Prüfung auf Verfügbarkeit nötig -> ohne "greenbox"
|
||||
|
||||
** Fehlerbereinigung Browser: Alle bekannten Browser checken**
|
||||
- Style-Fehler/-Optimierung melden und ggf. von Markus korrigieren lassen
|
||||
|
||||
**Bug auf Developer-Seite: (Hilfe durch Samu)**
|
||||
- Wenn Cookie *modus=edit* und Seite verlassen wird, bleibt das Cookie erhalten.
|
||||
-- Reset des Cookies beim Verlassen der Seite. Allerdings muss das Cookie bestehen bleiben, wenn weiterhin bearbeitet werden soll. Andere Cookies müssen ebenfalls resettet werden, die nichts mit anderen Seiten zu tun haben. Können wir dazu bestimmte Modi einsetzen? z. B. "modus=dev_edit" oder Kombi-Prüfung auf pageFile+modus?
|
||||
-- Fehler beim Speichern beheben
|
||||
-- Welche Lösungsansätze haben wir, um die einzelnen Aufgaben der ToDo-Liste zentral (und nur online) zu pflegen?
|
||||
:: Einzelne Datensätze?
|
||||
:: Einzelne Textdateien in einem geschützten Dateiordner?
|
||||
|
||||
** Seite Kalender: (Mitarbeit durch Samu)**
|
||||
-- **Absprache:** Ist das Kalenderblatt final?
|
||||
-- Was wollen wir ggf. in der zweiten Übersicht der Verfügbarkeiten oder einer zusätzlichen Sidebar auflisten? (Feiertage, besondere Termine aus dem CKalender)
|
||||
:: >> Welche Informationen sind wichtig / welche sind nicht erforderlich?
|
||||
:: >> Was erledigen wir im nächsten Release?
|
||||
:: **Auflistung aller Besuchsstellen mit:**
|
||||
-- Speicherung von "Letzter Besuch"?
|
||||
|
||||
|
||||
<h2>Weiterentwicklung (Markus)</h2>
|
||||
** Layout verfeinern **
|
||||
- admin.php << Adminseite sowie seelsorge.php << Besuchsstellen-Seite:
|
||||
-- Tabelle, bei Cursor-Tasten drücken: rauf/runter -> Markierung der Tabellenzeile wechseln
|
||||
- Farben über Farbdefinitionen (var)
|
||||
- Farben vereinheitlichen z.b. #00000 -> black-variable
|
||||
|
||||
** Kleine Optimierung:**
|
||||
- Wiederholte absolute Pfade in Variablen speichern:
|
||||
-- ../general_files
|
||||
-- ../_central
|
||||
-- ../intranet
|
||||
-- ../kalli
|
||||
-- ../seelsorge
|
||||
-- ../authorization
|
||||
- Wiederholte Codes in extra Code-Snippets auslagern:
|
||||
-- z. B. admin.php: Bildausgabe
|
||||
- Anstelle von "exit;" schreiben wir "exit();"
|
||||
|
||||
**Kalender:**
|
||||
- Aufnahme Ferienzeiten Berlin/Brandenburg
|
||||
|
||||
<HR>
|
||||
|
||||
<h2>2. Release: Nice-to-have´s und Umsetzung von neuen Anforderungen</h2>
|
||||
** Neue Seite: Chat**
|
||||
- Implementierung des Chats
|
||||
- Chatverlauf mit Datum, Uhrzeit, Nachricht, Autor, Profilbild und Möglichkeit zur Texteingabe
|
||||
- Chatmöglichkeit mit jedem User oder nur Funktionsträger oder auschließlich nur für Amtsgeschwister?
|
||||
|
||||
** Kalli **
|
||||
- Datum und Uhrzeit Felder direkt anwählbar? Denn es muss zweimal in das Feld geklickt werden.
|
||||
- Bei nicht Media Felder nebeneinander darstellen? Ggf. Padding in Statusmeldung-Feld erhöhen?
|
||||
|
||||
** Seite Kalender:**
|
||||
- CKalender-Einbindung: Welche Infos zur entsprechenden Rolle (Chorsänger, Putzgruppe, Technik) wollen wir zur Ausgabe bringen?
|
||||
- Neben Kalenderblatt: Sidebar mit nächsten Feiertagen, Ferien, besondere Termine als Vorschau (mit welchem Zeitraum)
|
||||
- Bessere Darstellung der Feiertage bei @media?
|
||||
-- Zum Beispiel: Bei Media ausblenden der Feiertage und kleinen rosa Punkt anzeigen. Feiertag in Verfügbarkeit anzeigen.
|
||||
|
||||
** Seite Besuchsstellen (bzw. erweiterte Adminseite) **
|
||||
- Sollen Administratoren Datensätze löschen und bearbeiten? Betrifft alle Daten: Priesterbezirk, Adresse, Kommunikationsdaten und Hinweis
|
||||
-- für Priesterbezirk benötigen wir eine Auswahlfunktion, um den neuen Priester einzusetzen.
|
||||
- Sollen Hinweistexte verkürtzt dargestellt und ausklappbar sein? Button mit Pfeil nach unten/oben.
|
||||
-- Umsetzungsmöglichkeit: Button über Wechsel der Klasse mit enthaltenen bgimage. Text: Mit Display none, opacity 0. wird dann zu Display Block mit opacity 1.
|
||||
- Aktions-Buttons nicht mit Text sondern Symbole und Toolbartext einblenden für eine kompaktere Darstellung? Oder dies bei @media für eine bessere Übersichtlichkeit?
|
||||
|
||||
** Seitenbereich Kalendertag **
|
||||
- Macht eine Sidebar-Darstellung des Kalenderblatts Sinn und wäre es damit komfortabler? --> Mini-Kalender zum betreffenden Monat und Anklick-Möglichkeit zum Wechsel des Kalendertages in einer kleineren kompakten Ansicht. Oder sogar wählbar mit Vergrößern/Verkleinern-Button?
|
||||
|
||||
** Datenausgabe formatiert anzeigen**
|
||||
- z. B. Telefonnummern. Was ist dafür notwendig, gibt es dazu verfügbare Funktionen (oder reguläre Ausdrücke)? Eventuell gibt es etwas externes, was wir einbinden können?
|
||||
|
||||
** Optimierung profile.php**
|
||||
- Nach Bestätigung der Änderung Statuszeile/Overlay einblenden (ggf. nach ein paar Sekunden wegblenden)
|
||||
|
||||
** Style- Optimierung **
|
||||
- Dunkelmodus anbieten und für jeden User speichern, ob hell/dunkel
|
||||
- Profilfotos in thumbnails dublizieren. Nur in der maximal notwendigen Auflösung werden für die Ausgabe verwendet. Reichen 100x100px aus?
|
||||
- Content-Bereich vergrößern/verschieben indem das Menü ausgeblendet wird. Beim Zeigen mit der Maus auf den Bereich soll es wieder angezeigt werden, Sliden. Oder durch Klicken kann der Bereich rein und raus geschoben werden, wobei sich der Content-Bereich anpasst.
|
||||
- Entweder beim Scrollen innerhalb von FORM (das mit content_fullsize umschlossen ist) und mindestens die Seitengröße hat (ggf. ist ein Scrollbalken vorhanden) Schaltfläche mit scrollToTop oder ähnliches einblenden. Alternativ festen Button einsetzen.
|
||||
- Layout: Farben: Prüfung/Korrektur auf richtigen Farbcode bzw. Farbverbesserung
|
||||
- overlay_message.php: OK-Button oder ähnliches anstelle des Exit-Buttons?
|
||||
-- Oder automatisches Ausblenden z. B. nach 2 bis 4 Sekunden (ggf. Message links unten kurz ein und wieder ausblenden - evntl. bei kleinen Bildschirmen anders formatiert anzeigen) - aös animierte Darstellung mit grünem oder weißen Haken.
|
||||
|
||||
** Seite Admin **
|
||||
- Vorschlag: Nutzer als Blöcke untereinander bei Media. Onclick Element um Berechtigungen als Liste einzublenden. Bearbeitungs-Button um Einträge zu ändern, Toogle Button zum Bedienen. Entweder sofortige Speicherung oder Bestätigungs-Button?
|
||||
|
||||
** Seite Developer **
|
||||
- Farbe azure harmony einsetzen für Schattierungen anstelle von #DDDDDD.
|
||||
|
||||
<h2>3. Release: - IDEE - Eigenständiges KALLI und Ablösung von CKalender</H2>
|
||||
- Alle CKalender Funktionen werden adaptiert.
|
||||
- Generischer Aufbau der HTML-Ausgabe anhand der Datenbankinhalte.
|
||||
-- Online-Verwaltung von Terminarten
|
||||
-- Online-Verwaltung von Nutzergruppen
|
||||
-- Nutzerverwaltung: Ansicht und Bedienung vereinfachen und je nach Medium anders darstellen -> One-Button-Mechanik...
|
||||
-- Aufteilung der Administration in mehrere Ebenen und ansprechendes Seitenmenü.
|
||||
- Profil, eigenes: An/abwahl von Kontaktmedien: WhatsApp, Signal etc. -> Button mit Hintergrund mit Hovertext / oder als Text mit Tooglebar?
|
||||
- Admin . Tabelle nach Namen sortieren
|
||||
- w3schools.com CSS Templates einsetzen
|
||||
- Kalendarium: Brauchen wir einen Countdown zum Reload? Wenn nein, Funktionen, Content und SVG löschen
|
||||
- Seelsorge Menü - Javascript: auch bei Rezise nachjustieren
|
||||
-- Bottom von Indikatorstreifen an menu-item ausrichten, zumindest bei @media
|
||||
|
||||
**Alle Codes optmieren**
|
||||
- Feiertage: Berliner und regionale Feiertage vervollständigen. Ggf. andere API nutzen.
|
||||
- Profilseite, persönliche: Alle Amtsgeschwister haben die Möglichkeit Ihre Zeitpräferenzen in Wochentagen (Buttons) und Uhrzeit von/bis (normale Uhrzeit-Felder) einzutragen - hier: Erweitern der Datenbank
|
||||
|
||||
**Anfragemöglichkeit für Amtsgeschwister**
|
||||
-- Routenplaner: Eigene Adresse -> Amtsgeschwister-Adresse -> Besuchsstelle
|
||||
... Kontaktaufnahem über Chat ...
|
||||
|
||||
- **profile.php**
|
||||
- Änderung des Passwortes ermöglichen?
|
||||
|
||||
<h2>Ideen</h2>
|
||||
**Feedback-Formular am Seitenrand**
|
||||
- Jeder Nutzer kann den Entwicklern eine Info senden. Besonders am Anfang können Fehler auftreten. Beim Senden werden alle Entwickler per E-Mail und/oder Push-Notifikation informiert.
|
||||
- Ggf. Hilfe und Feedback mit Fragezeichen-Button innerhalb des Profilmenüs
|
||||
- Menü: Zurück-Button rechts oder links anbieten im Menütitel?
|
||||
|
||||
** Suchfunktion **
|
||||
- Suche oder Filtermöglichkeit in den Tabellen von Besuchsstellen und/oder Admin anbieten?
|
||||
|
||||
** Sidbar **
|
||||
Wenn mehr Seiten hinzu kommen werden: Header Menü als SideMenü. Menü-Symbol (3 Linien), beim Klicken Menü nach rechts anzeigen, Animation (rotieren). Bei erneuten klicken Pfeil nach links. Hoch runter Buttons bei möglichen Unterseiten mit Animation (rotieren).
|
||||
|
||||
** Darstellung von Buttons für Aktionen **
|
||||
Wenn Anzahl der Buttons/Links auf einer Seite mehr werden, dann zumindest bei Media Aktionen und Hyperlinks nur als Symbol anzeigen und bei Hover Text einblenden.
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php /* Name: index.php
|
||||
Beschreibung: Intranet: Hauptseite */
|
||||
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
</head>
|
||||
<body class="theme-darkblue">
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
|
||||
<div class="header_title ht_darkblue text_white" id="header_title">
|
||||
<span class="header_title_text">Hauptmenü</span>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_GET["kalli_success"]) && $_GET["kalli_success"] == 1): ?>
|
||||
<div style="position:fixed;top:calc(var(--header-height) + 8px);left:50%;transform:translateX(-50%);z-index:2000;padding:12px 28px;background:hsl(145,50%,93%);color:hsl(145,50%,28%);font-weight:600;font-size:var(--font-size-sm);box-shadow:var(--shadow-md);white-space:nowrap;" id="kalliSuccessMsg">
|
||||
Eintragung erfolgreich! Kalli wird Ihren Termin direkt vermerken.
|
||||
</div>
|
||||
<script>setTimeout(function(){var el=document.getElementById('kalliSuccessMsg');if(el)el.style.display='none';},5000);</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mainpage" id="mainpage">
|
||||
<div class="mainpage_container">
|
||||
|
||||
<?php
|
||||
$mainpageNotices = [];
|
||||
try {
|
||||
$noticeStmt = $pdo->prepare(
|
||||
"SELECT id, title, message, type
|
||||
FROM mainpage_notices
|
||||
WHERE is_active = TRUE
|
||||
AND (starts_at IS NULL OR starts_at <= CURRENT_TIMESTAMP)
|
||||
AND (ends_at IS NULL OR ends_at > CURRENT_TIMESTAMP)
|
||||
ORDER BY sort_order ASC, created_at ASC, id ASC"
|
||||
);
|
||||
$noticeStmt->execute();
|
||||
$mainpageNotices = $noticeStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$mainpageNotices = [];
|
||||
}
|
||||
|
||||
$allowedNoticeTypes = ['info', 'warning', 'success', 'error'];
|
||||
foreach ($mainpageNotices as $mainpageNotice):
|
||||
$noticeType = in_array($mainpageNotice['type'], $allowedNoticeTypes, true) ? $mainpageNotice['type'] : 'info';
|
||||
?>
|
||||
<div class="mainpage_notice mainpage_notice--<?= htmlspecialchars($noticeType, ENT_QUOTES, 'UTF-8') ?>" role="status">
|
||||
<div class="mainpage_notice_title"><?= htmlspecialchars($mainpageNotice['title'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div class="mainpage_notice_text"><?= nl2br(htmlspecialchars($mainpageNotice['message'], ENT_QUOTES, 'UTF-8')) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (isset($groups["Brandschutz"]) && $groups["Brandschutz"] == 1) {
|
||||
// Prüfen ob Schulung überfällig ist
|
||||
$bsOverdue = false;
|
||||
$bsStmt = $pdo->prepare("SELECT last_training_date FROM brandschutz_training WHERE user_id = ?");
|
||||
$bsStmt->execute([$_SESSION['user_id']]);
|
||||
$bsLastTraining = $bsStmt->fetchColumn();
|
||||
if ($bsLastTraining === false) {
|
||||
$bsOverdue = true;
|
||||
} else {
|
||||
if (empty($bsLastTraining) || strtotime($bsLastTraining . ' +365 days') < time()) {
|
||||
$bsOverdue = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($bsOverdue): ?>
|
||||
<div class="mainpage_notice mainpage_notice--warning" role="status">
|
||||
<div class="mainpage_notice_title">Brandschutzunterweisung fällig!</div>
|
||||
<div class="mainpage_notice_text">Bitte führen Sie zeitnah die <strong><a href="window.location.href='/intranet_repo/projects/brandschutz/brandschutz.php'">Online-Brandschutzunterweisung</a></strong> durch. Die Schulung dauert nur ca. 15 bis 20 Minuten. Eine regelmäßige Durchführung ist gesetzlich vorgeschrieben. Vielen Dank!</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$kalliRoles = ["Amtsgeschwister", "Allgemein", "Musik", "Kinderunterrichte", "Jugend", "Technik", "Brandschutz", "Abwesenheiten"];
|
||||
$hasKalliRole = false;
|
||||
foreach ($kalliRoles as $role) {
|
||||
if (isset($groups[$role]) && $groups[$role] == 1) { $hasKalliRole = true; break; }
|
||||
}
|
||||
if ($hasKalliRole): ?>
|
||||
<button class="button_mainpage unselectable bg_blue" onclick="window.location.href='/intranet_repo/projects/kalli/kalli.php'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>
|
||||
<div class="text">Kalli</div>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="button_mainpage unselectable bg_darkblue" onclick="window.open('https://nak-reinickendorf.de/pdf/terminuebersicht.pdf', '_blank');">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2z"/></svg>
|
||||
<div class="text">Monatsplan</div>
|
||||
</button>
|
||||
|
||||
<?php if ($groups["Amtsgeschwister"] == 1) { ?>
|
||||
<button class="button_mainpage unselectable bg_purple" onclick="window.location.href='/intranet_repo/projects/seelsorge/seelsorge.php'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
||||
<div class="text">Seelsorge</div>
|
||||
</button>
|
||||
<?php } ?>
|
||||
|
||||
<button class="button_mainpage unselectable bg_orange" onclick="window.location.href='/intranet_repo/projects/brandschutz/brandschutz.php';" style="position:relative;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M12 12.9l-2.13 2.09C9.31 15.55 9 16.28 9 17.06 9 18.68 10.35 20 12 20s3-1.32 3-2.94c0-.78-.31-1.52-.87-2.07L12 12.9z M16 6l-.44.55C14.38 8.02 12 7.19 12 5.3V2S4 6 4 13c0 4.42 3.58 8 8 8s8-3.58 8-8c0-2.96-1.61-5.62-4-7z"/></svg>
|
||||
<div class="text">Brandschutz</div>
|
||||
<?php if ($bsOverdue): ?>
|
||||
<span style="position:absolute;top:6px;right:6px;min-width:26px;height:26px;padding:0 6px;border-radius:9999px;background:var(--nak-red);color:#FFF;font-size:0.85em;font-weight:700;display:flex;align-items:center;justify-content:center;">!</span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php } ?>
|
||||
|
||||
<button class="button_mainpage unselectable bg_teal" onclick="window.location.href='/intranet_repo/projects/intranet/profile.php'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
||||
<div class="text">Konto</div>
|
||||
</button>
|
||||
|
||||
<?php if (isset($groups["Allgemein"]) && $groups["Allgemein"] == 1) { ?>
|
||||
<button class="button_mainpage unselectable bg_raspberry" onclick="window.location.href='/intranet_repo/projects/contentpanel/insertion/insert.php'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
|
||||
<div class="text">Schwarzes Brett</div>
|
||||
</button>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($groups["Admin"] == 1) {
|
||||
// Unbearbeitete Nachrichten zählen
|
||||
$badgeStmt = $pdo->prepare("SELECT COUNT(*) FROM admin_messages WHERE status = 'pending'");
|
||||
$badgeStmt->execute();
|
||||
$unreadCount = $badgeStmt->fetchColumn();
|
||||
?>
|
||||
<button class="button_mainpage unselectable bg_black" id="spin" onclick="window.location.href='/intranet_repo/projects/adminpanel/admin.php'" style="position:relative;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
|
||||
<div class="text">Admin</div>
|
||||
<?php if ($unreadCount > 0): ?>
|
||||
<span style="position:absolute;top:6px;right:6px;min-width:26px;height:26px;padding:0 6px;border-radius:9999px;background:var(--nak-red);color:#FFF;font-size:0.85em;font-weight:700;display:flex;align-items:center;justify-content:center;"><?= $unreadCount ?></span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($groups["Entwickler"] == 1) { ?>
|
||||
<button class="button_mainpage unselectable bg_green" id="spin" onclick="window.location.href='/intranet_repo/projects/developerpanel/developer.php'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="32" height="32"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||
<div class="text">Entwickler</div>
|
||||
</button>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php /* Name: profile.php
|
||||
Beschreibung: Profil des angemeldeten Nutzers */
|
||||
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
</head>
|
||||
<body class="theme-teal">
|
||||
|
||||
<?php // Durchführung der Aktionen
|
||||
|
||||
if (isset($_SESSION["user_id"])) {
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
|
||||
// Passwort-Reset-Aktion
|
||||
if (isset($_POST["action"]) && $_POST["action"] === "change_password") {
|
||||
$current_password = $_POST["current_password"] ?? '';
|
||||
$new_password = $_POST["new_password"] ?? '';
|
||||
$new_password_confirm = $_POST["new_password_confirm"] ?? '';
|
||||
|
||||
// Aktuelles Passwort prüfen
|
||||
$stmt = $pdo->prepare("SELECT password_hash FROM user_login WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
$pw_result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$pw_result || !password_verify($current_password, $pw_result['password_hash'])) {
|
||||
$pw_error = "Das aktuelle Passwort ist falsch.";
|
||||
} elseif (strlen($new_password) < 8 || !preg_match("/[a-z]/i", $new_password) || !preg_match("/[0-9]/", $new_password)) {
|
||||
$pw_error = "Das neue Passwort muss mindestens 8 Zeichen, einen Buchstaben und eine Zahl enthalten.";
|
||||
} elseif ($new_password !== $new_password_confirm) {
|
||||
$pw_error = "Die neuen Passwörter stimmen nicht überein.";
|
||||
} else {
|
||||
$password_hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE user_login SET password_hash = ? WHERE user_id = ?");
|
||||
$stmt->execute([$password_hash, $_SESSION["user_id"]]);
|
||||
$pw_success = "Passwort erfolgreich geändert.";
|
||||
}
|
||||
} else {
|
||||
// Profildaten-Update
|
||||
$mail = $_POST["mail"];
|
||||
$phone = !empty($_POST["phone"]) ? $_POST["phone"] : null; // Wert auf NULL setzen, wenn das Feld leer ist
|
||||
|
||||
// Profilbild hochladen, wenn ein Bild ausgewählt wurde
|
||||
if ($_FILES["profile_picture"]["error"] === UPLOAD_ERR_OK) {
|
||||
$upload_dir = $_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/";
|
||||
|
||||
// Lösche vorhandene Dateien mit übereinstimmender Benutzer-ID
|
||||
$files = glob($upload_dir . $_SESSION["user_id"] . "-*.jpg");
|
||||
foreach ($files as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
// Generiere einen eindeutigen Dateinamen basierend auf Benutzer-ID und Datum/Uhrzeit
|
||||
$filename = $_SESSION["user_id"] . "-" . date("YmdHis") . ".jpg";
|
||||
$upload_file = $upload_dir . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES["profile_picture"]["tmp_name"], $upload_file)) {
|
||||
$message = "Profilbild erfolgreich hochgeladen.";
|
||||
} else {
|
||||
$message = "Beim Hochladen des Profilbildes ist ein Fehler aufgetreten.";
|
||||
}
|
||||
}
|
||||
|
||||
// Benutzerdaten aktualisieren
|
||||
$sql_update = "UPDATE user_general_information
|
||||
SET mail=?, phone=?
|
||||
WHERE user_id=?";
|
||||
$stmt = $pdo->prepare($sql_update);
|
||||
$stmt->execute([$mail, $phone, $_SESSION["user_id"]]);
|
||||
|
||||
// Weiterleitung ohne Nachricht
|
||||
header("Location: {$_SERVER['PHP_SELF']}");
|
||||
exit();
|
||||
} // Ende else (Profildaten-Update)
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM user_general_information WHERE user_id = {$_SESSION["user_id"]}";
|
||||
$result = $pdo->query($sql);
|
||||
$user = $result->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$sql = "SELECT * FROM user_permissions WHERE user_id = {$_SESSION["user_id"]}";
|
||||
$result = $pdo->query($sql);
|
||||
$groups = $result->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
|
||||
header("Location: {$g_httpLoginPath}");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ACHTUNG: Die PHP-Header Weiterleitungen müssen hier abgeschlossen sein.
|
||||
?>
|
||||
|
||||
|
||||
<?php // Es folgen ab hier die HTML-Ausgaben beginnend mit dem HTML-Header: ?>
|
||||
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
|
||||
<?php // Laden der Standard-Overlays: ?>
|
||||
|
||||
<?php // Header (erweitert): Titel der Seite mit Funktionsaufruf ?>
|
||||
<div class="header_title ht_teal" id="header_title">
|
||||
<span class="header_title_text">Kontoverwaltung</span>
|
||||
</div>
|
||||
|
||||
|
||||
<?php // Die oberen Standard-Overlays müssen vor dem Content-Bereich platziert sein. ?>
|
||||
|
||||
|
||||
<div class="content" id="base">
|
||||
<div class="content-tab-bar" id="profile-tabs">
|
||||
<div class="content-tab-bar__item active" data-target="profile-tab-info">Informationen</div>
|
||||
<div class="content-tab-bar__item" data-target="profile-tab-security">Sicherheit</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-tab-content active" id="profile-tab-info">
|
||||
<div class="profile">
|
||||
<div class="content_panel">
|
||||
|
||||
<?php
|
||||
// Generiere den Dateinamen basierend auf Benutzer-ID und Datum/Uhrzeit
|
||||
$profileImg = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$_SESSION["user_id"]}-*.jpg");
|
||||
|
||||
// Überprüfe, ob eine passende Datei existiert
|
||||
if (!empty($profileImg)) {
|
||||
// Sortiere die Dateien nach Datum/Uhrzeit und nimm die neueste
|
||||
usort($profileImg, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
$latestProfileImg = $profileImg[0];
|
||||
|
||||
// Entferne den absoluten Pfad
|
||||
$relativePath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $latestProfileImg);
|
||||
|
||||
// Bild anzeigen
|
||||
echo "<img class='unselectable' src='$relativePath' alt='Profil'>";
|
||||
} else {
|
||||
// Standardbild anzeigen, wenn keine passende Datei gefunden wurde
|
||||
echo "<img class='unselectable' src='/intranet_repo/general_files/profile_pictures/default.jpg' alt='Profil'>";
|
||||
}
|
||||
?>
|
||||
|
||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" enctype="multipart/form-data">
|
||||
|
||||
<h2><?php echo $user["first_name"]; ?> <?php echo $user["last_name"]; ?></h2>
|
||||
|
||||
<div class="eventbox_mini">
|
||||
<div class="eventbox_item">
|
||||
<label class="text_black left" for="mail"><strong>E-Mailadresse</strong></label>
|
||||
<input class="field_fullwidth left" type="email" id="mail" name="mail" value="<?php echo $user["mail"]; ?>" required>
|
||||
|
||||
<label class="text_black left" for="phone"><strong>Telefon</strong></label>
|
||||
<div class="flexrow_left fullwidth">
|
||||
<div class="input-prefix">+49</div>
|
||||
|
||||
<?php if ($user["phone"] === null): ?>
|
||||
<input class="phone-input left" type="tel" id="phone" name="phone">
|
||||
<?php else: ?>
|
||||
<input class="phone-input left" type="tel" id="phone" name="phone"
|
||||
value="<?php echo $user["phone"]; ?>">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="eventbox_item">
|
||||
<div class="flexcolumn_left">
|
||||
<label><strong>Profilbild ändern</strong></label>
|
||||
<input style="padding:5px 0;" type="file" id="profile_picture" name="profile_picture" accept="image/jpeg, image/png">
|
||||
</div>
|
||||
</div>
|
||||
<input class="button_fullwidth" type="submit" value="Profil aktualisieren">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p><strong>Gruppen:</strong><br>
|
||||
<?php
|
||||
$responsibilities = [];
|
||||
foreach ($groups as $key => $value) {
|
||||
if ($value == 1) {
|
||||
$responsibilities[] = $key;
|
||||
}
|
||||
}
|
||||
echo implode(", ", $responsibilities);
|
||||
?>
|
||||
</p>
|
||||
|
||||
</div><!-- EOD: content_panel -->
|
||||
</div><!-- EOD: profile -->
|
||||
</div><!-- EOD: profile-tab-info -->
|
||||
|
||||
<div class="profile-tab-content" id="profile-tab-security">
|
||||
<div class="profile">
|
||||
<div class="content_panel">
|
||||
|
||||
<h2 style="margin-bottom: var(--space-lg);">Passwort ändern</h2>
|
||||
|
||||
<?php if (!empty($pw_error)): ?>
|
||||
<div style="padding: 10px 14px; margin-bottom: var(--space-md); background: hsl(0,78%,95%); color: hsl(0,78%,40%); font-weight: 500; font-size: var(--font-size-sm);">
|
||||
<?= htmlspecialchars($pw_error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($pw_success)): ?>
|
||||
<div style="padding: 10px 14px; margin-bottom: var(--space-md); background: hsl(145,50%,93%); color: hsl(145,50%,28%); font-weight: 500; font-size: var(--font-size-sm);">
|
||||
<?= htmlspecialchars($pw_success) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
<div class="eventbox_mini">
|
||||
<div class="eventbox_item">
|
||||
<label class="text_black left" for="current_password"><strong>Aktuelles Passwort</strong></label>
|
||||
<input class="field_fullwidth left" type="password" id="current_password" name="current_password" required>
|
||||
|
||||
<label class="text_black left" for="new_password"><strong>Neues Passwort</strong></label>
|
||||
<input class="field_fullwidth left" type="password" id="new_password" name="new_password" required>
|
||||
|
||||
<label class="text_black left" for="new_password_confirm"><strong>Neues Passwort wiederholen</strong></label>
|
||||
<input class="field_fullwidth left" type="password" id="new_password_confirm" name="new_password_confirm" required>
|
||||
</div>
|
||||
<input class="button_fullwidth" type="submit" value="Passwort ändern">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p style="font-size: var(--font-size-sm); color: var(--gray-500); margin-top: var(--space-sm);">
|
||||
Passwort vergessen? <a href="/intranet_repo/projects/authorization/reset-password.php">Per SMS/Mail zurücksetzen</a>
|
||||
</p>
|
||||
|
||||
</div><!-- EOD: content_panel -->
|
||||
</div><!-- EOD: profile -->
|
||||
</div><!-- EOD: profile-tab-security -->
|
||||
</div><!-- EOD: content base -->
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('#profile-tabs .content-tab-bar__item').forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelectorAll('#profile-tabs .content-tab-bar__item').forEach(function(t) { t.classList.remove('active'); });
|
||||
document.querySelectorAll('.profile-tab-content').forEach(function(c) { c.classList.remove('active'); });
|
||||
tab.classList.add('active');
|
||||
var target = document.getElementById(tab.getAttribute('data-target'));
|
||||
if (target) target.classList.add('active');
|
||||
});
|
||||
});
|
||||
<?php if (!empty($pw_error) || !empty($pw_success)): ?>
|
||||
// Auto-switch to security tab if password action result
|
||||
document.querySelector('[data-target="profile-tab-security"]').click();
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php /* Name: show_profile.php
|
||||
Beschreibung: Profil eines ausgewählten Nutzers (Abschnitt zur Einbindung) */
|
||||
|
||||
// Initialisierung
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
|
||||
|
||||
<?php
|
||||
if (isset($_SESSION["profile_id"])) {
|
||||
$pdo = require($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
|
||||
|
||||
$sql = "SELECT * FROM user_general_information
|
||||
WHERE user_id = {$_SESSION["profile_id"]}";
|
||||
$result = $pdo->query($sql);
|
||||
$partner = $result->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($partner["mail"])) {
|
||||
$sql_groups = "SELECT * FROM user_permissions WHERE user_id = ?";
|
||||
$stmt = $pdo->prepare($sql_groups);
|
||||
$stmt->execute([$_SESSION["profile_id"]]);
|
||||
$groups = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
else {
|
||||
header("Location:{$g_httpLoginPath}");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="content" id="base">
|
||||
<div class="profile">
|
||||
<div class="content_panel">
|
||||
|
||||
<?php
|
||||
// Generiere den Dateinamen basierend auf Benutzer-ID und Datum/Uhrzeit
|
||||
$profileImg = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$_SESSION["profile_id"]}-*.jpg");
|
||||
|
||||
// Überprüfe, ob eine passende Datei existiert
|
||||
if (!empty($profileImg)) {
|
||||
// Sortiere die Dateien nach Datum/Uhrzeit und nimm die neueste
|
||||
usort($profileImg, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
$latestProfileImg = $profileImg[0];
|
||||
|
||||
// Entferne den absoluten Pfad
|
||||
$relativePath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $latestProfileImg);
|
||||
|
||||
// Bild anzeigen
|
||||
echo "<img src='$relativePath' alt='Profil'>";
|
||||
} else {
|
||||
// Standardbild anzeigen, wenn keine passende Datei gefunden wurde
|
||||
echo "<img src='/intranet_repo/general_files/profile_pictures/default.jpg' alt='Profil'>";
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($partner["first_name"] === null): ?>
|
||||
<h3 style="color:var(--nak-red);">Dieser Nutzer ist noch nicht registriert.</h3>
|
||||
<?php else: ?>
|
||||
<h2><?php echo $partner["first_name"]; ?> <?php echo $partner["last_name"]; ?></h2>
|
||||
<?php endif; ?>
|
||||
<p><strong>E-Mail:</strong><br><a href="mailto:<?php echo $partner["mail"]; ?>"><?php echo $partner["mail"]; ?></a></p>
|
||||
<?php if ($partner["phone"] != null): ?>
|
||||
<p><strong>Telefon:</strong><br>
|
||||
<a href="tel:0<?php echo $partner["phone"]; ?>">0<?php echo $partner["phone"]; ?></a></p>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<p><strong>Gruppen:</strong><br>
|
||||
<?php
|
||||
$responsibilities = [];
|
||||
foreach ($groups as $key => $value) {
|
||||
if ($value == 1) {
|
||||
$responsibilities[] = $key;
|
||||
}
|
||||
}
|
||||
echo implode(", ", $responsibilities);
|
||||
?>
|
||||
</p>
|
||||
</div><!-- EOD: content_panel -->
|
||||
</div><!-- EOD: profile -->
|
||||
</div><!-- EOD: content base -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,780 @@
|
||||
<?php /* Name: kalli.php
|
||||
Beschreibung: Kalli: Hauptseite */
|
||||
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
</head>
|
||||
<body class="theme-blue">
|
||||
|
||||
<?php // Stelle sicher, dass die Autoload-Datei von PHPSpreadsheet geladen wird
|
||||
require 'vendor/autoload.php'; ?>
|
||||
|
||||
<?php // Durchführung der Aktionen
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
|
||||
if(isset($_POST['submit'])) {
|
||||
// if no errors, carry on
|
||||
if(!isset($error)) {
|
||||
$name = htmlspecialchars($user["first_name"]) . " " . htmlspecialchars($user["last_name"]);
|
||||
$info = "Von " . $name . " eingetragen (Eintragungsassistent)";
|
||||
// Create a new PHPExcel object
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
// Set the header row
|
||||
$sheet->setCellValue('A1', 'ID');
|
||||
$sheet->setCellValue('B1', 'Lang-ID');
|
||||
$sheet->setCellValue('C1', 'Termin-Typ');
|
||||
$sheet->setCellValue('D1', 'Von-Datum');
|
||||
$sheet->setCellValue('E1', 'Von-Zeit');
|
||||
$sheet->setCellValue('F1', 'Bis-Datum');
|
||||
$sheet->setCellValue('G1', 'Bis-Zeit');
|
||||
$sheet->setCellValue('H1', 'Zeitzusatz');
|
||||
$sheet->setCellValue('I1', 'Titel');
|
||||
$sheet->setCellValue('J1', 'Text');
|
||||
$sheet->setCellValue('K1', 'Ort');
|
||||
$sheet->setCellValue('L1', 'Planungsstatus');
|
||||
$sheet->setCellValue('M1', 'Anzeigeziel');
|
||||
$sheet->setCellValue('N1', 'Gruppenzuordnung');
|
||||
$sheet->setCellValue('O1', 'Zyklusart');
|
||||
$sheet->setCellValue('P1', 'Zyklusintervall');
|
||||
$sheet->setCellValue('Q1', 'Zyklusdetail');
|
||||
$sheet->setCellValue('R1', 'Notiz');
|
||||
$sheet->setCellValue('S1', 'Verantwortlich');
|
||||
$sheet->setCellValue('T1', 'Hintergrundfarbe');
|
||||
$sheet->setCellValue('U1', 'BucherID');
|
||||
$sheet->setCellValue('V1', 'Benutzerdefinierte Felder');
|
||||
$sheet->setCellValue('W1', 'Dokument-Titel');
|
||||
$sheet->setCellValue('X1', 'Anzeigeziel Ort');
|
||||
$sheet->setCellValue('Y1', 'Anzeigeziel Notiz');
|
||||
$sheet->setCellValue('Z1', 'Anzeigeziel Verantwortlicher');
|
||||
$sheet->setCellValue('AA1', 'Erzeugt');
|
||||
$sheet->setCellValue('AB1', 'Aktualisiert');
|
||||
|
||||
// Set the data row
|
||||
// Titel
|
||||
$selectedGroup = isset($_POST['group']) ? $_POST['group'] : '';
|
||||
$sheet->setCellValue('I2', $selectedGroup);
|
||||
// Datum
|
||||
$date_s = isset($_POST['date_s']) ? $_POST['date_s'] : '';
|
||||
|
||||
$sheet->setCellValue('D2', date('d.m.Y', strtotime($date_s)));
|
||||
|
||||
$date_e = isset($_POST['date_e']) ? $_POST['date_e'] : '';
|
||||
if (date('d.m.Y', strtotime($date_e)) != "01.01.1970"){
|
||||
$sheet->setCellValue('F2', date('d.m.Y', strtotime($date_e)));
|
||||
}
|
||||
// Zeit
|
||||
$time_s = isset($_POST['time_s']) ? $_POST['time_s'] : '';
|
||||
if ($time_s !== '' && preg_match('/^\d{2}:\d{2}$/', $time_s)) {
|
||||
$sheet->setCellValue('E2', $time_s);
|
||||
} elseif ($time_s !== '') {
|
||||
$error[] = "Ungültiges Zeitformat. Verwenden Sie das Format hh:mm.";
|
||||
}
|
||||
$time_e = isset($_POST['time_e']) ? $_POST['time_e'] : '';
|
||||
if ($time_e !== '' && preg_match('/^\d{2}:\d{2}$/', $time_e)) {
|
||||
$sheet->setCellValue('G2', $time_e);
|
||||
} elseif ($time_e !== '') {
|
||||
$error[] = "Ungültiges Zeitformat. Verwenden Sie das Format hh:mm.";
|
||||
}
|
||||
// FIX
|
||||
$sheet->setCellValue('C2', 'Einzel');
|
||||
$sheet->setCellValue('O2', 'einmalig');
|
||||
|
||||
// Setze den Ort in Zelle K2
|
||||
$location = isset($_POST['location']) ? htmlspecialchars($_POST['location']) : '';
|
||||
$sheet->setCellValue('K2', $location);
|
||||
|
||||
// Setze den Text in Zelle J2
|
||||
$further_text = isset($_POST['further_text']) ? htmlspecialchars($_POST['further_text']) : '';
|
||||
$sheet->setCellValue('J2', $further_text);
|
||||
$sheet->setCellValue('R2', $info);
|
||||
$responsibility = isset($_POST['responsibility']) ? htmlspecialchars($_POST['responsibility']) : '';
|
||||
$sheet->setCellValue('S2', $responsibility);
|
||||
|
||||
$userInput = isset($_POST['userinput']) ? $_POST['userinput'] : '';
|
||||
$userInput2 = isset($_POST['userinput2']) ? $_POST['userinput2'] : '';
|
||||
|
||||
// DB-Lookup: Preset-Werte für die gewählte Terminart laden
|
||||
$presetStmt = $pdo->prepare("SELECT planungsstatus, anzeigeziel, gruppenzuordnung, anzeigeziel_ort, anzeigeziel_notiz, anzeigeziel_verantwortlicher, default_text FROM kalli_appointment_types WHERE name = ? LIMIT 1");
|
||||
$presetStmt->execute([$selectedGroup]);
|
||||
$preset = $presetStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($preset) {
|
||||
$sheet->setCellValue('L2', $preset['planungsstatus']);
|
||||
$sheet->setCellValue('M2', $preset['anzeigeziel']);
|
||||
$sheet->setCellValue('N2', $preset['gruppenzuordnung']);
|
||||
$sheet->setCellValue('X2', $preset['anzeigeziel_ort']);
|
||||
$sheet->setCellValue('Y2', $preset['anzeigeziel_notiz']);
|
||||
$sheet->setCellValue('Z2', $preset['anzeigeziel_verantwortlicher']);
|
||||
if (!empty($preset['default_text']) && empty($further_text)) {
|
||||
$sheet->setCellValue('J2', $preset['default_text']);
|
||||
}
|
||||
} elseif ($userInput != "" && $selectedGroup == "oeffentlich") {
|
||||
$sheet->setCellValue('I2', $userInput);
|
||||
$sheet->setCellValue('L2', 'fix');
|
||||
$sheet->setCellValue('M2', 'öffentlich');
|
||||
$sheet->setCellValue('N2', 'AN ALLE');
|
||||
$sheet->setCellValue('X2', 'öffentlich');
|
||||
$sheet->setCellValue('Y2', 'intern');
|
||||
$sheet->setCellValue('Z2', 'intern');
|
||||
} elseif ($userInput != "" && $selectedGroup == "intern") {
|
||||
$sheet->setCellValue('I2', $userInput);
|
||||
$sheet->setCellValue('L2', 'fix');
|
||||
$sheet->setCellValue('M2', 'intern');
|
||||
$sheet->setCellValue('N2', 'AN ALLE');
|
||||
$sheet->setCellValue('X2', 'intern');
|
||||
$sheet->setCellValue('Y2', 'intern');
|
||||
$sheet->setCellValue('Z2', 'intern');
|
||||
}
|
||||
|
||||
// Save the file on the server
|
||||
$xlsxDir = rtrim(intranet_env('CKAL_XLSX_DIR', '/data/ckal-xlsx'), '/');
|
||||
if (!is_dir($xlsxDir) && !mkdir($xlsxDir, 0770, true) && !is_dir($xlsxDir)) {
|
||||
throw new RuntimeException("CKalender-XLSX-Verzeichnis kann nicht angelegt werden: {$xlsxDir}");
|
||||
}
|
||||
$FileName = $xlsxDir . "/" . $name . "-" . date("Ymd-His") . ".xlsx";
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save($FileName);
|
||||
|
||||
// Redirect to prevent form resubmission on page reload
|
||||
print "<script language='javascript'>window.location.href='/intranet_repo/projects/intranet/index.php?kalli_success=1';</script>";
|
||||
//header("Location: kalli.php");
|
||||
//exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ACHTUNG: Die PHP-Header Weiterleitungen müssen hier abgeschlossen sein.
|
||||
?>
|
||||
|
||||
|
||||
<?php// Es folgen ab hier die HTML-Ausgaben beginnend mit dem HTML-Header:?>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
|
||||
|
||||
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
|
||||
|
||||
<div class="header_title ht_kalli text_white" id="header_title">
|
||||
<span class="header_title_text">Kalli: Eintragungsassistent</span>
|
||||
</div>
|
||||
|
||||
<div class="content" id="base">
|
||||
|
||||
<?php if (isset($_SESSION["user_id"])): ?>
|
||||
|
||||
<?php
|
||||
if (isset($_GET["error"])) {
|
||||
$error = $_GET["error"];
|
||||
$isSuccess = in_array($error, [3]);
|
||||
$toastType = $isSuccess ? 'success' : 'error';
|
||||
$toastIcon = $isSuccess
|
||||
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>'
|
||||
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>';
|
||||
$toastText = ($error == 3) ? 'Eintragung erfolgreich! Kalli wird Ihren Termin direkt vermerken.' : 'Es gab einen Fehler. Bitte kontaktieren Sie uns!';
|
||||
echo "<div class='nak-toast nak-toast--{$toastType}' id='nakToast'>";
|
||||
echo "<div class='nak-toast__icon'>{$toastIcon}</div>";
|
||||
echo "<span class='nak-toast__text'>" . htmlspecialchars($toastText) . "</span>";
|
||||
echo "<button class='nak-toast__close' onclick='dismissNakToast()' aria-label='Schließen'><svg viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='square' stroke-linejoin='miter'><line x1='18' y1='6' x2='6' y2='18'/><line x1='6' y1='6' x2='18' y2='18'/></svg></button>";
|
||||
echo "</div>";
|
||||
echo "<script>
|
||||
requestAnimationFrame(function(){document.getElementById('nakToast').classList.add('nak-toast--visible');});
|
||||
setTimeout(function(){dismissNakToast();},4000);
|
||||
function dismissNakToast(){var t=document.getElementById('nakToast');if(!t||t.classList.contains('nak-toast--hiding'))return;t.classList.add('nak-toast--hiding');t.classList.remove('nak-toast--visible');setTimeout(function(){t.remove();},350);}
|
||||
</script>";
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* ── Kalli Page Wizard ── */
|
||||
.kalli-wizard {
|
||||
max-width: 1400px;
|
||||
width: 90%;
|
||||
margin: var(--space-xl) auto;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Progress Bar ── */
|
||||
.kalli-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: 20px 28px 16px;
|
||||
background: var(--gray-50);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
.kalli-progress-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.kalli-progress-circle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--gray-500);
|
||||
background: var(--gray-200);
|
||||
border: 2px solid var(--gray-300);
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
.kalli-progress-step.active .kalli-progress-circle {
|
||||
background: var(--nak-blue);
|
||||
border-color: var(--nak-blue);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(39,134,207,0.35);
|
||||
}
|
||||
.kalli-progress-step.done .kalli-progress-circle {
|
||||
background: #ffffff;
|
||||
border-color: var(--nak-blue);
|
||||
color: var(--nak-blue);
|
||||
}
|
||||
.kalli-progress-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
color: var(--gray-500);
|
||||
white-space: nowrap;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
.kalli-progress-step.active .kalli-progress-label,
|
||||
.kalli-progress-step.done .kalli-progress-label {
|
||||
color: var(--nak-blue);
|
||||
}
|
||||
.kalli-progress-line {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 60px;
|
||||
background: var(--gray-300);
|
||||
margin: 0 6px;
|
||||
margin-bottom: 20px;
|
||||
transition: background 250ms ease;
|
||||
}
|
||||
.kalli-progress-line.done {
|
||||
background: var(--nak-blue);
|
||||
}
|
||||
|
||||
/* ── Body ── */
|
||||
.kalli-modal-body {
|
||||
padding: 22px 28px 16px;
|
||||
}
|
||||
.kalli-section-title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* ── Category Grid (Step 1) ── */
|
||||
.kalli-category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
.kalli-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.kalli-category-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--gray-700);
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
label.kalli-radio-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap:8px;
|
||||
padding: 0px 5px;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 300;
|
||||
color: var(--gray-600);
|
||||
border: 1px solid transparent;
|
||||
transition: background 150ms ease, border-color 150ms ease;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
label.kalli-radio-item input[type="radio"],
|
||||
label.kalli-radio-item input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--nak-blue);
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
label.kalli-radio-item span {
|
||||
hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
label.kalli-radio-item:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-200);
|
||||
}
|
||||
.kalli-custom-section {
|
||||
margin-top: var(--space-lg);
|
||||
padding: var(--space-md);
|
||||
background: hsl(0,60%,98%);
|
||||
border: 1px solid hsl(0,60%,90%);
|
||||
}
|
||||
|
||||
/* ── Field Grid (Steps 2 & 3) ── */
|
||||
.kalli-field-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
.kalli-field-grid-single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.kalli-field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.kalli-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
.kalli-field {
|
||||
padding: 12px 14px;
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-primary);
|
||||
font-weight: 300;
|
||||
border: 1px solid var(--gray-300);
|
||||
background: var(--gray-50);
|
||||
outline: none;
|
||||
transition: border-color 150ms ease, box-shadow 150ms ease;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.kalli-field:focus {
|
||||
border-color: var(--nak-blue);
|
||||
box-shadow: 0 0 0 3px rgba(39,134,207,0.2);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* ── Summary (Step 4) ── */
|
||||
.kalli-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
.kalli-summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
}
|
||||
.kalli-summary-row:last-child { border-bottom: none; }
|
||||
.kalli-summary-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
.kalli-summary-value {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-900);
|
||||
text-align: right;
|
||||
}
|
||||
.kalli-validation-error {
|
||||
margin-top: var(--space-md);
|
||||
padding: 10px 14px;
|
||||
background: hsl(0,78%,95%);
|
||||
color: hsl(0,78%,40%);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ── Actions ── */
|
||||
.kalli-modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 28px 20px;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
}
|
||||
.kalli-modal-actions-left,
|
||||
.kalli-modal-actions-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.kalli-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 38px;
|
||||
padding: 8px 18px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
font-family: var(--font-primary);
|
||||
border: 1px solid var(--gray-300);
|
||||
background: #ffffff;
|
||||
color: var(--gray-800);
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, transform 150ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.kalli-btn:hover {
|
||||
background: var(--gray-100);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.kalli-btn-primary {
|
||||
background: var(--nak-blue);
|
||||
border-color: var(--nak-blue);
|
||||
color: #ffffff;
|
||||
}
|
||||
.kalli-btn-primary:hover {
|
||||
background: var(--nak-blue-hover);
|
||||
border-color: var(--nak-blue-hover);
|
||||
}
|
||||
.kalli-btn-submit {
|
||||
background: linear-gradient(135deg, var(--nak-blue), var(--nak-blue-hover));
|
||||
border-color: var(--nak-blue-hover);
|
||||
color: #ffffff;
|
||||
padding: 10px 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.kalli-btn-submit:hover {
|
||||
background: linear-gradient(135deg, var(--nak-blue-hover), #185d8f);
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 600px) {
|
||||
.kalli-wizard { margin: 0; width: 100%; }
|
||||
.kalli-progress { padding: 14px 12px 10px; }
|
||||
.kalli-progress-label { font-size: 0.65rem; }
|
||||
.kalli-progress-circle { width: 26px; height: 26px; font-size: 0.7rem; }
|
||||
.kalli-progress-line { min-width: 12px; margin-bottom: 16px; }
|
||||
.kalli-modal-body { padding: 16px; }
|
||||
.kalli-category-grid { grid-template-columns: 1fr; }
|
||||
.kalli-field-grid { grid-template-columns: 1fr; }
|
||||
.kalli-modal-actions { padding: 12px 16px 16px; flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="kalli-wizard">
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="kalli-progress">
|
||||
<div class="kalli-progress-step active" data-step="1">
|
||||
<div class="kalli-progress-circle">1</div>
|
||||
<span class="kalli-progress-label">Terminart</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="2">
|
||||
<div class="kalli-progress-circle">2</div>
|
||||
<span class="kalli-progress-label">Zeitraum</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="3">
|
||||
<div class="kalli-progress-circle">3</div>
|
||||
<span class="kalli-progress-label">Informationen</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="4">
|
||||
<div class="kalli-progress-circle">4</div>
|
||||
<span class="kalli-progress-label">Übersicht</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form wraps all steps -->
|
||||
<form id="kalliForm" action="" method="post">
|
||||
|
||||
<!-- STEP 1: Terminart -->
|
||||
<div class="kalli-step" id="kalliStep1">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Terminart auswählen</div>
|
||||
<div class="kalli-category-grid">
|
||||
<?php
|
||||
// Kategorien und Terminarten aus der Datenbank laden
|
||||
$catQuery = $pdo->query("SELECT * FROM kalli_categories ORDER BY sort_order, name");
|
||||
$kalliCategories = [];
|
||||
while ($cat = $catQuery->fetch(PDO::FETCH_ASSOC)) {
|
||||
$kalliCategories[] = $cat;
|
||||
}
|
||||
$typeQuery = $pdo->query("SELECT * FROM kalli_appointment_types ORDER BY sort_order, name");
|
||||
$kalliTypesByCategory = [];
|
||||
while ($type = $typeQuery->fetch(PDO::FETCH_ASSOC)) {
|
||||
$kalliTypesByCategory[$type['category_id']][] = $type;
|
||||
}
|
||||
|
||||
// Alle verwendeten Rollen sammeln für die "Terminart nicht aufgelistet"-Sektion
|
||||
$kalliAllRoles = [];
|
||||
$isAdminUser = (isset($groups["Admin"]) && $groups["Admin"] == 1);
|
||||
|
||||
foreach ($kalliCategories as $cat):
|
||||
$role = $cat['permission_role'];
|
||||
$kalliAllRoles[$role] = 1;
|
||||
if (!$isAdminUser && !(isset($groups[$role]) && $groups[$role] == 1)) continue;
|
||||
$types = $kalliTypesByCategory[$cat['id']] ?? [];
|
||||
if (empty($types)) continue;
|
||||
?>
|
||||
<div class="kalli-category">
|
||||
<div class="kalli-category-label"><?= htmlspecialchars($cat['name']) ?></div>
|
||||
<?php foreach ($types as $type):
|
||||
$displayLabel = !empty($type['display_label']) ? $type['display_label'] : $type['name'];
|
||||
?>
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="<?= htmlspecialchars($type['name']) ?>"><span><?= htmlspecialchars($displayLabel) ?></span></label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$hasAnyKalliRole = $isAdminUser || count(array_intersect_key($groups, $kalliAllRoles)) > 0;
|
||||
if ($hasAnyKalliRole): ?>
|
||||
<div class="kalli-custom-section">
|
||||
<div class="kalli-category-label" style="color:var(--nak-red);">Terminart nicht aufgelistet?</div>
|
||||
<p style="margin:0 0 var(--space-sm) 0;font-size:var(--font-size-sm);color:var(--gray-600);">Nur ausfüllen, wenn die Terminart oben nicht vorhanden ist.</p>
|
||||
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap;margin-bottom:var(--space-sm);">
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="oeffentlich"><span>Öffentlich</span></label>
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="intern"><span>Intern</span></label>
|
||||
</div>
|
||||
<input class="kalli-field" type="text" name="userinput" id="kalliUserInput" placeholder="Titel (nur für benutzerdefinierte Termine)">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="kalli-modal-actions">
|
||||
<div></div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(2)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 2: Zeitraum -->
|
||||
<div class="kalli-step" id="kalliStep2" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Zeitraum festlegen</div>
|
||||
<div class="kalli-field-grid">
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Datum <span style="color:var(--nak-red);">*</span></label>
|
||||
<input class="kalli-field" type="date" name="date_s" id="kalliDateS" required>
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Enddatum <span style="font-weight:300;color:var(--gray-500);">(falls mehrtägig)</span></label>
|
||||
<input class="kalli-field" type="date" name="date_e" id="kalliDateE">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Zeit von <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="time" name="time_s" id="kalliTimeS">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Zeit bis <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="time" name="time_e" id="kalliTimeE">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(1)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(3)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 3: Informationen -->
|
||||
<div class="kalli-step" id="kalliStep3" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Zusätzliche Informationen</div>
|
||||
<div class="kalli-field-grid kalli-field-grid-single">
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Textfeld <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="further_text" id="kalliFurtherText" placeholder="z.B. betreffende Person, etc.">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Ort <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="location" id="kalliLocation">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Verantwortliche/r <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="responsibility" id="kalliResponsibility">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(2)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(4)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 4: Übersicht -->
|
||||
<div class="kalli-step" id="kalliStep4" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Übersicht – Bitte prüfen Sie Ihre Eingaben</div>
|
||||
<div class="kalli-summary">
|
||||
<div class="kalli-summary-row">
|
||||
<span class="kalli-summary-label">Terminart</span>
|
||||
<span class="kalli-summary-value" id="sumGroup">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row">
|
||||
<span class="kalli-summary-label">Datum</span>
|
||||
<span class="kalli-summary-value" id="sumDateS">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumDateERow" style="display:none;">
|
||||
<span class="kalli-summary-label">Enddatum</span>
|
||||
<span class="kalli-summary-value" id="sumDateE">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTimeSRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Zeit von</span>
|
||||
<span class="kalli-summary-value" id="sumTimeS">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTimeERow" style="display:none;">
|
||||
<span class="kalli-summary-label">Zeit bis</span>
|
||||
<span class="kalli-summary-value" id="sumTimeE">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTextRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Textfeld</span>
|
||||
<span class="kalli-summary-value" id="sumText">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumLocRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Ort</span>
|
||||
<span class="kalli-summary-value" id="sumLoc">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumRespRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Verantwortlich</span>
|
||||
<span class="kalli-summary-value" id="sumResp">–</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="kalliValidationError" class="kalli-validation-error" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(3)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="submit" name="submit" class="kalli-btn kalli-btn-submit">Eintragung absenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var kalliCurrentStep = 1;
|
||||
|
||||
function kalliGoToStep(step) {
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
document.getElementById('kalliStep' + i).style.display = (i === step) ? '' : 'none';
|
||||
}
|
||||
kalliCurrentStep = step;
|
||||
kalliUpdateProgress(step);
|
||||
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
|
||||
// Header wieder einblenden, falls er durch Scrollen ausgeblendet war
|
||||
var headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 64;
|
||||
var elemHeaderMenue = document.getElementById('header_menue');
|
||||
var elemHeaderTitle = document.getElementById('header_title');
|
||||
if (elemHeaderMenue) { elemHeaderMenue.style.top = '0'; }
|
||||
if (elemHeaderTitle) { elemHeaderTitle.style.top = headerHeight + 'px'; }
|
||||
}
|
||||
|
||||
function kalliUpdateProgress(step) {
|
||||
var steps = document.querySelectorAll('.kalli-progress-step');
|
||||
var lines = document.querySelectorAll('.kalli-progress-line');
|
||||
steps.forEach(function(el, idx) {
|
||||
var s = idx + 1;
|
||||
el.classList.remove('active', 'done');
|
||||
if (s === step) el.classList.add('active');
|
||||
else if (s < step) el.classList.add('done');
|
||||
});
|
||||
lines.forEach(function(el, idx) {
|
||||
el.classList.toggle('done', idx < step - 1);
|
||||
});
|
||||
}
|
||||
|
||||
function kalliNextStep(step) {
|
||||
var errEl = document.getElementById('kalliValidationError');
|
||||
if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; }
|
||||
|
||||
if (step === 2) {
|
||||
var sel = document.querySelector('#kalliForm input[name="group"]:checked');
|
||||
if (!sel) { alert('Bitte wählen Sie eine Terminart aus.'); return; }
|
||||
if ((sel.value === 'oeffentlich' || sel.value === 'intern') && !document.getElementById('kalliUserInput').value.trim()) {
|
||||
alert('Bitte geben Sie einen Titel für den benutzerdefinierten Termin ein.'); return;
|
||||
}
|
||||
}
|
||||
if (step === 3) {
|
||||
if (!document.getElementById('kalliDateS').value) { alert('Bitte geben Sie ein Datum ein.'); return; }
|
||||
}
|
||||
if (step === 4) { kalliBuildSummary(); }
|
||||
kalliGoToStep(step);
|
||||
}
|
||||
|
||||
function kalliPrevStep(step) { kalliGoToStep(step); }
|
||||
|
||||
function kalliBuildSummary() {
|
||||
var grp = document.querySelector('#kalliForm input[name="group"]:checked');
|
||||
var grpVal = grp ? grp.value : '–';
|
||||
if ((grpVal === 'oeffentlich' || grpVal === 'intern') && document.getElementById('kalliUserInput')) {
|
||||
var custom = document.getElementById('kalliUserInput').value.trim();
|
||||
grpVal = custom ? custom + ' (' + (grpVal === 'oeffentlich' ? 'Öffentlich' : 'Intern') + ')' : grpVal;
|
||||
}
|
||||
document.getElementById('sumGroup').textContent = grpVal;
|
||||
document.getElementById('sumDateS').textContent = kalliFormatDate(document.getElementById('kalliDateS').value);
|
||||
kalliShowSummaryRow('sumDateERow', document.getElementById('kalliDateE').value, 'sumDateE', kalliFormatDate(document.getElementById('kalliDateE').value));
|
||||
kalliShowSummaryRow('sumTimeSRow', document.getElementById('kalliTimeS').value, 'sumTimeS', document.getElementById('kalliTimeS').value);
|
||||
kalliShowSummaryRow('sumTimeERow', document.getElementById('kalliTimeE').value, 'sumTimeE', document.getElementById('kalliTimeE').value);
|
||||
kalliShowSummaryRow('sumTextRow', document.getElementById('kalliFurtherText').value.trim(), 'sumText', document.getElementById('kalliFurtherText').value.trim());
|
||||
kalliShowSummaryRow('sumLocRow', document.getElementById('kalliLocation').value.trim(), 'sumLoc', document.getElementById('kalliLocation').value.trim());
|
||||
kalliShowSummaryRow('sumRespRow', document.getElementById('kalliResponsibility').value.trim(), 'sumResp', document.getElementById('kalliResponsibility').value.trim());
|
||||
}
|
||||
|
||||
function kalliShowSummaryRow(rowId, value, valId, displayVal) {
|
||||
var row = document.getElementById(rowId);
|
||||
if (value) { row.style.display = ''; document.getElementById(valId).textContent = displayVal; }
|
||||
else { row.style.display = 'none'; }
|
||||
}
|
||||
|
||||
function kalliFormatDate(iso) {
|
||||
if (!iso) return '–';
|
||||
var parts = iso.split('-');
|
||||
return parts.length === 3 ? parts[2] + '.' + parts[1] + '.' + parts[0] : iso;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- content -->
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,746 @@
|
||||
<!-- Kalli Eintragungsassistent – Mehrstufiges Modal -->
|
||||
<div class="standard_overlay" id="kalliModal" onclick="if(event.target.id==='kalliModal')closeKalliModal();" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;justify-content:center;align-items:center;background-color:hsla(210,57%,25%,0.8);z-index:9999;">
|
||||
<div class="kalli-modal-card">
|
||||
|
||||
<!-- Close Button -->
|
||||
<button type="button" class="kalli-modal-exit" onclick="closeKalliModal()" aria-label="Schließen">×</button>
|
||||
|
||||
<!-- Hero Header -->
|
||||
<div class="kalli-modal-hero">
|
||||
<div>
|
||||
<div class="kalli-modal-title">Kalli – Eintragungsassistent</div>
|
||||
<div class="kalli-modal-subtitle">Neuen Termin im CKalender eintragen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="kalli-progress">
|
||||
<div class="kalli-progress-step active" data-step="1">
|
||||
<div class="kalli-progress-circle">1</div>
|
||||
<span class="kalli-progress-label">Terminart</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="2">
|
||||
<div class="kalli-progress-circle">2</div>
|
||||
<span class="kalli-progress-label">Zeitraum</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="3">
|
||||
<div class="kalli-progress-circle">3</div>
|
||||
<span class="kalli-progress-label">Informationen</span>
|
||||
</div>
|
||||
<div class="kalli-progress-line"></div>
|
||||
<div class="kalli-progress-step" data-step="4">
|
||||
<div class="kalli-progress-circle">4</div>
|
||||
<span class="kalli-progress-label">Übersicht</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form wraps all steps -->
|
||||
<form id="kalliForm" action="/intranet_repo/projects/kalli/kalli.php" method="post">
|
||||
|
||||
<!-- STEP 1: Terminart -->
|
||||
<div class="kalli-step" id="kalliStep1">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Terminart auswählen</div>
|
||||
|
||||
<div class="kalli-category-grid">
|
||||
<?php
|
||||
$isAdminUser = (isset($groups["Admin"]) && $groups["Admin"] == 1);
|
||||
|
||||
// Kategorien und Terminarten aus der Datenbank laden
|
||||
$catQueryModal = $pdo->query("SELECT * FROM kalli_categories ORDER BY sort_order, name");
|
||||
$kalliCategoriesModal = [];
|
||||
while ($catM = $catQueryModal->fetch(PDO::FETCH_ASSOC)) {
|
||||
$kalliCategoriesModal[] = $catM;
|
||||
}
|
||||
$typeQueryModal = $pdo->query("SELECT * FROM kalli_appointment_types ORDER BY sort_order, name");
|
||||
$kalliTypesByCategoryModal = [];
|
||||
while ($typeM = $typeQueryModal->fetch(PDO::FETCH_ASSOC)) {
|
||||
$kalliTypesByCategoryModal[$typeM['category_id']][] = $typeM;
|
||||
}
|
||||
|
||||
$kalliAllRolesModal = [];
|
||||
foreach ($kalliCategoriesModal as $catM):
|
||||
$roleM = $catM['permission_role'];
|
||||
$kalliAllRolesModal[$roleM] = 1;
|
||||
if (!$isAdminUser && !(isset($groups[$roleM]) && $groups[$roleM] == 1)) continue;
|
||||
$typesM = $kalliTypesByCategoryModal[$catM['id']] ?? [];
|
||||
if (empty($typesM)) continue;
|
||||
?>
|
||||
<div class="kalli-category">
|
||||
<div class="kalli-category-label"><?= htmlspecialchars($catM['name']) ?></div>
|
||||
<?php foreach ($typesM as $typeM):
|
||||
$displayLabelM = !empty($typeM['display_label']) ? $typeM['display_label'] : $typeM['name'];
|
||||
?>
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="<?= htmlspecialchars($typeM['name']) ?>"><span><?= htmlspecialchars($displayLabelM) ?></span></label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$hasAnyKalliRoleModal = $isAdminUser || count(array_intersect_key($groups, $kalliAllRolesModal)) > 0;
|
||||
if ($hasAnyKalliRoleModal): ?>
|
||||
<div class="kalli-custom-section">
|
||||
<div class="kalli-category-label" style="color:var(--nak-red);">Terminart nicht aufgelistet?</div>
|
||||
<p style="margin:0 0 var(--space-sm) 0;font-size:var(--font-size-sm);color:var(--gray-600);">Nur ausfüllen, wenn die Terminart oben nicht vorhanden ist.</p>
|
||||
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap;margin-bottom:var(--space-sm);">
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="oeffentlich"><span>Öffentlich</span></label>
|
||||
<label class="kalli-radio-item"><input type="radio" name="group" value="intern"><span>Intern</span></label>
|
||||
</div>
|
||||
<input class="kalli-field" type="text" name="userinput" id="kalliUserInput" placeholder="Titel (nur für benutzerdefinierte Termine)">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="kalli-modal-actions">
|
||||
<div></div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn" onclick="closeKalliModal()">Abbrechen</button>
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(2)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 2: Zeitraum -->
|
||||
<div class="kalli-step" id="kalliStep2" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Zeitraum festlegen</div>
|
||||
|
||||
<div class="kalli-field-grid">
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Datum <span style="color:var(--nak-red);">*</span></label>
|
||||
<input class="kalli-field" type="date" name="date_s" id="kalliDateS" required>
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Enddatum <span style="font-weight:300;color:var(--gray-500);">(falls mehrtägig)</span></label>
|
||||
<input class="kalli-field" type="date" name="date_e" id="kalliDateE">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Zeit von <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="time" name="time_s" id="kalliTimeS">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Zeit bis <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="time" name="time_e" id="kalliTimeE">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(1)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(3)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 3: Informationen -->
|
||||
<div class="kalli-step" id="kalliStep3" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Zusätzliche Informationen</div>
|
||||
|
||||
<div class="kalli-field-grid kalli-field-grid-single">
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Textfeld <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="further_text" id="kalliFurtherText" placeholder="z.B. betreffende Person, etc.">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Ort <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="location" id="kalliLocation">
|
||||
</div>
|
||||
<div class="kalli-field-group">
|
||||
<label class="kalli-label">Verantwortliche/r <span style="font-weight:300;color:var(--gray-500);">(optional)</span></label>
|
||||
<input class="kalli-field" type="text" name="responsibility" id="kalliResponsibility">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(2)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="button" class="kalli-btn kalli-btn-primary" onclick="kalliNextStep(4)">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 4: Übersicht -->
|
||||
<div class="kalli-step" id="kalliStep4" style="display:none;">
|
||||
<div class="kalli-modal-body">
|
||||
<div class="kalli-section-title">Übersicht – Bitte prüfen Sie Ihre Eingaben</div>
|
||||
|
||||
<div class="kalli-summary">
|
||||
<div class="kalli-summary-row">
|
||||
<span class="kalli-summary-label">Terminart</span>
|
||||
<span class="kalli-summary-value" id="sumGroup">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row">
|
||||
<span class="kalli-summary-label">Datum</span>
|
||||
<span class="kalli-summary-value" id="sumDateS">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumDateERow" style="display:none;">
|
||||
<span class="kalli-summary-label">Enddatum</span>
|
||||
<span class="kalli-summary-value" id="sumDateE">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTimeSRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Zeit von</span>
|
||||
<span class="kalli-summary-value" id="sumTimeS">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTimeERow" style="display:none;">
|
||||
<span class="kalli-summary-label">Zeit bis</span>
|
||||
<span class="kalli-summary-value" id="sumTimeE">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumTextRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Textfeld</span>
|
||||
<span class="kalli-summary-value" id="sumText">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumLocRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Ort</span>
|
||||
<span class="kalli-summary-value" id="sumLoc">–</span>
|
||||
</div>
|
||||
<div class="kalli-summary-row" id="sumRespRow" style="display:none;">
|
||||
<span class="kalli-summary-label">Verantwortlich</span>
|
||||
<span class="kalli-summary-value" id="sumResp">–</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kalliValidationError" class="kalli-validation-error" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="kalli-modal-actions">
|
||||
<div class="kalli-modal-actions-left">
|
||||
<button type="button" class="kalli-btn" onclick="kalliPrevStep(3)">Zurück</button>
|
||||
</div>
|
||||
<div class="kalli-modal-actions-right">
|
||||
<button type="submit" name="submit" class="kalli-btn kalli-btn-submit">Eintragung absenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* ── Kalli Modal ── */
|
||||
.kalli-modal-card {
|
||||
position: relative;
|
||||
width: min(720px, calc(100vw - 24px));
|
||||
max-height: min(90vh, calc(100vh - 24px));
|
||||
overflow-y: auto;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
|
||||
}
|
||||
|
||||
.kalli-modal-exit {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid rgba(255,255,255,0.25);
|
||||
background: rgba(255,255,255,0.16);
|
||||
color: #ffffff;
|
||||
font-size: 1.35em;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.kalli-modal-hero {
|
||||
padding: 24px 28px;
|
||||
color: #ffffff;
|
||||
background:
|
||||
radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
|
||||
linear-gradient(135deg, var(--nak-blue), var(--nak-blue-hover));
|
||||
}
|
||||
|
||||
.kalli-modal-title {
|
||||
font-size: 1.35em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.kalli-modal-subtitle {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 300;
|
||||
color: rgba(255,255,255,0.8);
|
||||
}
|
||||
|
||||
/* ── Progress Bar ── */
|
||||
.kalli-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: 20px 28px 16px;
|
||||
background: var(--gray-50);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.kalli-progress-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.kalli-progress-circle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--gray-500);
|
||||
background: var(--gray-200);
|
||||
border: 2px solid var(--gray-300);
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
.kalli-progress-step.active .kalli-progress-circle {
|
||||
background: var(--nak-blue);
|
||||
border-color: var(--nak-blue);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(39,134,207,0.35);
|
||||
}
|
||||
|
||||
.kalli-progress-step.done .kalli-progress-circle {
|
||||
background: #ffffff;
|
||||
border-color: var(--nak-blue);
|
||||
color: var(--nak-blue);
|
||||
}
|
||||
|
||||
.kalli-progress-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
color: var(--gray-500);
|
||||
white-space: nowrap;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
.kalli-progress-step.active .kalli-progress-label,
|
||||
.kalli-progress-step.done .kalli-progress-label {
|
||||
color: var(--nak-blue);
|
||||
}
|
||||
|
||||
.kalli-progress-line {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
min-width: 20px;
|
||||
max-width: 60px;
|
||||
background: var(--gray-300);
|
||||
margin: 0 6px;
|
||||
margin-bottom: 20px;
|
||||
transition: background 250ms ease;
|
||||
}
|
||||
|
||||
.kalli-progress-line.done {
|
||||
background: var(--nak-blue);
|
||||
}
|
||||
|
||||
/* ── Modal Body ── */
|
||||
.kalli-modal-body {
|
||||
padding: 22px 28px 16px;
|
||||
}
|
||||
|
||||
.kalli-section-title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* ── Category Grid (Step 1) ── */
|
||||
.kalli-category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.kalli-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.kalli-category-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--gray-700);
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
label.kalli-radio-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
color: var(--gray-800);
|
||||
border: 1px solid transparent;
|
||||
transition: background 150ms ease, border-color 150ms ease;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
label.kalli-radio-item input[type="radio"],
|
||||
label.kalli-radio-item input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--nak-blue);
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
label.kalli-radio-item span {
|
||||
hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
label.kalli-radio-item:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-200);
|
||||
}
|
||||
|
||||
.kalli-custom-section {
|
||||
margin-top: var(--space-lg);
|
||||
padding: var(--space-md);
|
||||
background: hsl(0,60%,98%);
|
||||
border: 1px solid hsl(0,60%,90%);
|
||||
}
|
||||
|
||||
/* ── Field Grid (Steps 2 & 3) ── */
|
||||
.kalli-field-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.kalli-field-grid-single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.kalli-field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kalli-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.kalli-field {
|
||||
padding: 12px 14px;
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-primary);
|
||||
font-weight: 300;
|
||||
border: 1px solid var(--gray-300);
|
||||
background: var(--gray-50);
|
||||
outline: none;
|
||||
transition: border-color 150ms ease, box-shadow 150ms ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kalli-field:focus {
|
||||
border-color: var(--nak-blue);
|
||||
box-shadow: 0 0 0 3px var(--nak-blue-20);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* ── Summary (Step 4) ── */
|
||||
.kalli-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.kalli-summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.kalli-summary-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.kalli-summary-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.kalli-summary-value {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--gray-900);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.kalli-validation-error {
|
||||
margin-top: var(--space-md);
|
||||
padding: 10px 14px;
|
||||
background: hsl(0,78%,95%);
|
||||
color: hsl(0,78%,40%);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ── Actions ── */
|
||||
.kalli-modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 28px 20px;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.kalli-modal-actions-left,
|
||||
.kalli-modal-actions-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.kalli-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 38px;
|
||||
padding: 8px 18px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
font-family: var(--font-primary);
|
||||
border: 1px solid var(--gray-300);
|
||||
background: #ffffff;
|
||||
color: var(--gray-800);
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, transform 150ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kalli-btn:hover {
|
||||
background: var(--gray-100);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.kalli-btn-primary {
|
||||
background: var(--nak-blue);
|
||||
border-color: var(--nak-blue);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.kalli-btn-primary:hover {
|
||||
background: var(--nak-blue-hover);
|
||||
border-color: var(--nak-blue-hover);
|
||||
}
|
||||
|
||||
.kalli-btn-submit {
|
||||
background: linear-gradient(135deg, var(--nak-blue), var(--nak-blue-hover));
|
||||
border-color: var(--nak-blue-hover);
|
||||
color: #ffffff;
|
||||
padding: 10px 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.kalli-btn-submit:hover {
|
||||
background: linear-gradient(135deg, var(--nak-blue-hover), #185d8f);
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 600px) {
|
||||
.kalli-modal-card {
|
||||
width: 100vw;
|
||||
max-height: 100vh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
.kalli-modal-hero {
|
||||
padding: 18px 16px;
|
||||
}
|
||||
|
||||
.kalli-modal-title {
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
.kalli-progress {
|
||||
padding: 14px 12px 10px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.kalli-progress-label {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.kalli-progress-circle {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.kalli-progress-line {
|
||||
min-width: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kalli-modal-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.kalli-category-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.kalli-field-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.kalli-modal-actions {
|
||||
padding: 12px 16px 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var kalliCurrentStep = 1;
|
||||
|
||||
function openKalliModal() {
|
||||
document.getElementById('kalliModal').style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
kalliGoToStep(1);
|
||||
}
|
||||
|
||||
function closeKalliModal() {
|
||||
document.getElementById('kalliModal').style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function kalliGoToStep(step) {
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
document.getElementById('kalliStep' + i).style.display = (i === step) ? '' : 'none';
|
||||
}
|
||||
kalliCurrentStep = step;
|
||||
kalliUpdateProgress(step);
|
||||
// Scroll modal body to top
|
||||
document.querySelector('.kalli-modal-card').scrollTop = 0;
|
||||
}
|
||||
|
||||
function kalliUpdateProgress(step) {
|
||||
var steps = document.querySelectorAll('.kalli-progress-step');
|
||||
var lines = document.querySelectorAll('.kalli-progress-line');
|
||||
steps.forEach(function(el, idx) {
|
||||
var s = idx + 1;
|
||||
el.classList.remove('active', 'done');
|
||||
if (s === step) el.classList.add('active');
|
||||
else if (s < step) el.classList.add('done');
|
||||
});
|
||||
lines.forEach(function(el, idx) {
|
||||
el.classList.toggle('done', idx < step - 1);
|
||||
});
|
||||
}
|
||||
|
||||
function kalliNextStep(step) {
|
||||
var errEl = document.getElementById('kalliValidationError');
|
||||
if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; }
|
||||
|
||||
if (step === 2) {
|
||||
// Validate step 1: a group must be selected
|
||||
var sel = document.querySelector('#kalliForm input[name="group"]:checked');
|
||||
if (!sel) {
|
||||
alert('Bitte wählen Sie eine Terminart aus.');
|
||||
return;
|
||||
}
|
||||
// If custom group, require title
|
||||
if ((sel.value === 'oeffentlich' || sel.value === 'intern') && !document.getElementById('kalliUserInput').value.trim()) {
|
||||
alert('Bitte geben Sie einen Titel für den benutzerdefinierten Termin ein.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (step === 3) {
|
||||
// Validate step 2: date is required
|
||||
if (!document.getElementById('kalliDateS').value) {
|
||||
alert('Bitte geben Sie ein Datum ein.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (step === 4) {
|
||||
kalliBuildSummary();
|
||||
}
|
||||
kalliGoToStep(step);
|
||||
}
|
||||
|
||||
function kalliPrevStep(step) {
|
||||
kalliGoToStep(step);
|
||||
}
|
||||
|
||||
function kalliBuildSummary() {
|
||||
var grp = document.querySelector('#kalliForm input[name="group"]:checked');
|
||||
var grpVal = grp ? grp.value : '–';
|
||||
if ((grpVal === 'oeffentlich' || grpVal === 'intern') && document.getElementById('kalliUserInput')) {
|
||||
var custom = document.getElementById('kalliUserInput').value.trim();
|
||||
grpVal = custom ? custom + ' (' + (grpVal === 'oeffentlich' ? 'Öffentlich' : 'Intern') + ')' : grpVal;
|
||||
}
|
||||
document.getElementById('sumGroup').textContent = grpVal;
|
||||
|
||||
var dateS = document.getElementById('kalliDateS').value;
|
||||
document.getElementById('sumDateS').textContent = dateS ? kalliFormatDate(dateS) : '–';
|
||||
|
||||
var dateE = document.getElementById('kalliDateE').value;
|
||||
kalliShowSummaryRow('sumDateERow', dateE, 'sumDateE', kalliFormatDate(dateE));
|
||||
|
||||
var timeS = document.getElementById('kalliTimeS').value;
|
||||
kalliShowSummaryRow('sumTimeSRow', timeS, 'sumTimeS', timeS);
|
||||
|
||||
var timeE = document.getElementById('kalliTimeE').value;
|
||||
kalliShowSummaryRow('sumTimeERow', timeE, 'sumTimeE', timeE);
|
||||
|
||||
var text = document.getElementById('kalliFurtherText').value.trim();
|
||||
kalliShowSummaryRow('sumTextRow', text, 'sumText', text);
|
||||
|
||||
var loc = document.getElementById('kalliLocation').value.trim();
|
||||
kalliShowSummaryRow('sumLocRow', loc, 'sumLoc', loc);
|
||||
|
||||
var resp = document.getElementById('kalliResponsibility').value.trim();
|
||||
kalliShowSummaryRow('sumRespRow', resp, 'sumResp', resp);
|
||||
}
|
||||
|
||||
function kalliShowSummaryRow(rowId, value, valId, displayVal) {
|
||||
var row = document.getElementById(rowId);
|
||||
if (value) {
|
||||
row.style.display = '';
|
||||
document.getElementById(valId).textContent = displayVal;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function kalliFormatDate(iso) {
|
||||
if (!iso) return '–';
|
||||
var parts = iso.split('-');
|
||||
if (parts.length === 3) return parts[2] + '.' + parts[1] + '.' + parts[0];
|
||||
return iso;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit2e312969af0145e24c15300d55587a02::getLoader();
|
||||
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
|
||||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
||||
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
|
||||
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
|
||||
'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'),
|
||||
'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'),
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit2e312969af0145e24c15300d55587a02
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit2e312969af0145e24c15300d55587a02', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit2e312969af0145e24c15300d55587a02', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit2e312969af0145e24c15300d55587a02::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit2e312969af0145e24c15300d55587a02::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
||||
require $file;
|
||||
}
|
||||
}, null, null);
|
||||
foreach ($filesToLoad as $fileIdentifier => $file) {
|
||||
$requireFile($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit2e312969af0145e24c15300d55587a02
|
||||
{
|
||||
public static $files = array (
|
||||
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'Z' =>
|
||||
array (
|
||||
'ZipStream\\' => 10,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\SimpleCache\\' => 16,
|
||||
'Psr\\Http\\Message\\' => 17,
|
||||
'Psr\\Http\\Client\\' => 16,
|
||||
'PhpOffice\\PhpSpreadsheet\\' => 25,
|
||||
'PHPMailer\\PHPMailer\\' => 20,
|
||||
),
|
||||
'M' =>
|
||||
array (
|
||||
'Matrix\\' => 7,
|
||||
),
|
||||
'C' =>
|
||||
array (
|
||||
'Complex\\' => 8,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'ZipStream\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
|
||||
),
|
||||
'Psr\\SimpleCache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-factory/src',
|
||||
1 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
),
|
||||
'Psr\\Http\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-client/src',
|
||||
),
|
||||
'PhpOffice\\PhpSpreadsheet\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
|
||||
),
|
||||
'PHPMailer\\PHPMailer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
|
||||
),
|
||||
'Matrix\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src',
|
||||
),
|
||||
'Complex\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/markbaker/complex/classes/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
'H' =>
|
||||
array (
|
||||
'HTMLPurifier' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit2e312969af0145e24c15300d55587a02::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit2e312969af0145e24c15300d55587a02::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInit2e312969af0145e24c15300d55587a02::$prefixesPsr0;
|
||||
$loader->classMap = ComposerStaticInit2e312969af0145e24c15300d55587a02::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||