Compare commits
1031 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42816cd450 | |||
| 85cf357846 | |||
| 4c4a93865d | |||
| 0a87e4d9eb | |||
| 240296f89b | |||
| 56909742bc | |||
| bbf4b0646e | |||
| 9f4343cc53 | |||
| a5998cffca | |||
| 153a612ad6 | |||
| 437da98add | |||
| a4b0dfe7ec | |||
| 3a819d6ed2 | |||
| 4f4ad9599d | |||
| d9da018ac2 | |||
| 2148ba75f3 | |||
| c4ffc1c797 | |||
| 96f38ce72e | |||
| b81f0c9e5c | |||
| f8aed4283e | |||
| a785d862d8 | |||
| a728a8a556 | |||
| ae34afda15 | |||
| 59e0b9f111 | |||
| cd50840b19 | |||
| 3bce65a1a5 | |||
| 50b29c96d6 | |||
| d5fc5f3065 | |||
| 0427d52a58 | |||
| cc804757ff | |||
| b55a6592f0 | |||
| e12a98cc98 | |||
| 320141855a | |||
| 12801344f1 | |||
| 83387de103 | |||
| dfb8e1dc89 | |||
| bdbc38a339 | |||
| e6356525b9 | |||
| ca74690952 | |||
| 75f9fdbeb8 | |||
| 81a27135d5 | |||
| a58576637a | |||
| 997957a5d3 | |||
| ec794db706 | |||
| ba749bca68 | |||
| 12a6af55b0 | |||
| 400f018104 | |||
| 80f4b623f9 | |||
| 6546d264b0 | |||
| e7e120e5b8 | |||
| ec66889a1f | |||
| f1703a476e | |||
| bac296b58a | |||
| bfb2d2b0d8 | |||
| ad9a00fd49 | |||
| 42faab17e8 | |||
| 1da4842847 | |||
| afd90086d8 | |||
| a7a54f877c | |||
| 9b9f71d21c | |||
| 4b84f0ab09 | |||
| 8c8b148bf4 | |||
| 6bf335ce46 | |||
| 144a1015f1 | |||
| c0a94b2eee | |||
| f1a32120d6 | |||
| 97fa1de999 | |||
| 7341fe7733 | |||
| 5b0528a914 | |||
| ac64e65dd3 | |||
| 96ffa3288e | |||
| 0c6cf13efa | |||
| 3867458050 | |||
| f752150188 | |||
| 325df51812 | |||
| 97afd1ea06 | |||
| e3903b4f3e | |||
| 031f2afd25 | |||
| d3884d2fc1 | |||
| 1575f4fa24 | |||
| 0aca76d2f4 | |||
| eca6646071 | |||
| 7189133861 | |||
| 8c961a56ce | |||
| 4f95a3d423 | |||
| 35e5006063 | |||
| fcb9289d80 | |||
| 6595a3d78d | |||
| 41d8ac6721 | |||
| 63c213bae7 | |||
| 61d72436c9 | |||
| 721bc8d833 | |||
| 2666d2d14f | |||
| de1ba88860 | |||
| e0fb04794d | |||
| 5310083e11 | |||
| 6897a29574 | |||
| b564306fb3 | |||
| 8836034670 | |||
| 98b088afc0 | |||
| 51c850d122 | |||
| d590561ade | |||
| ba11c216bd | |||
| 71a1b029cb | |||
| 2126d076d5 | |||
| 7b7208ad6b | |||
| 2ecd446dd0 | |||
| 97302f1bb3 | |||
| 35d4425086 | |||
| d3435d4cde | |||
| f61c548b9e | |||
| 101a2a247b | |||
| 423941e7ed | |||
| a8e76c782d | |||
| 3f9c048493 | |||
| fee5a701df | |||
| d341312192 | |||
| 4dadfb8863 | |||
| ada97ec538 | |||
| 0b4c5f988d | |||
| 59eeb05e4b | |||
| 5d8239822a | |||
| 31585511d0 | |||
| 2ec1b70c91 | |||
| 8f8b25249c | |||
| 8be6e6d61c | |||
| bad0c08e6f | |||
| 55695f3f82 | |||
| 74cbc60cbe | |||
| c09c7ecfa3 | |||
| 944e332b49 | |||
| 4306785be9 | |||
| 266b70a094 | |||
| bd8593d212 | |||
| 0d009262b6 | |||
| bb2c368b1f | |||
| a712f5a993 | |||
| 53bee08833 | |||
| 7a2a3ac1e9 | |||
| a5f02c9a6c | |||
| f77f09ec6f | |||
| 535df85cff | |||
| 82e8c8a1a3 | |||
| c0d8fdede6 | |||
| 958ee1c1f5 | |||
| 1dd128d480 | |||
| dca716ddc5 | |||
| c1dd4fce2b | |||
| 16cfa92721 | |||
| 3b2ed78c1c | |||
| cebbe8869a | |||
| e07051b356 | |||
| 2d2bbeb39f | |||
| c00443b6fa | |||
| 1d541e285c | |||
| b96c588697 | |||
| fde9c59f91 | |||
| 72c7363a07 | |||
| 0b36050558 | |||
| 00dbf83da4 | |||
| c3e060b963 | |||
| a382d9254e | |||
| 2b7f44b817 | |||
| b5e4f44252 | |||
| 8561b02a08 | |||
| 01f4ff2d28 | |||
| b7e4387373 | |||
| e9a92eaec7 | |||
| 6aec353d50 | |||
| 7a4f2d738f | |||
| a0a469a9df | |||
| 54209929fe | |||
| 5321534d71 | |||
| 7cf542c270 | |||
| c45d23a6b3 | |||
| ab094429b7 | |||
| cc5f46c00e | |||
| 1b2ab1cccb | |||
| adbf488e01 | |||
| 15ea5ddbfb | |||
| b93ca321a2 | |||
| 97847c76c4 | |||
| 3dae5ee720 | |||
| cbb662d159 | |||
| c5e7439541 | |||
| a748d94305 | |||
| 6c1a893f80 | |||
| 788e803e84 | |||
| 4d0f1de01c | |||
| e71ee8300e | |||
| f39885da10 | |||
| b7ca5bcfe2 | |||
| 57b0782577 | |||
| 4f524db221 | |||
| c2b7462307 | |||
| cdbf81878b | |||
| 68ec6dedd7 | |||
| 12782463d7 | |||
| 21a935edf3 | |||
| 3d65f79d2c | |||
| ed31210e3a | |||
| 5030caa029 | |||
| 4a87152531 | |||
| 1401c3afe7 | |||
| d5a3f4298f | |||
| b4a5c9c020 | |||
| 6d6f1f31b0 | |||
| cafffba921 | |||
| 8fa758d825 | |||
| 17034c928b | |||
| 395041fd44 | |||
| c117c7e641 | |||
| 01fc8537cf | |||
| 12ad9759cf | |||
| 057639be7c | |||
| 87e7be1c10 | |||
| 1db77346d0 | |||
| 1e6ac0fcd9 | |||
| b3c74c2172 | |||
| 0805958033 | |||
| 5c0e978c93 | |||
| 520298bae4 | |||
| 969adc4836 | |||
| d9eca06197 | |||
| 48f2e90e49 | |||
| 358247a2a1 | |||
| de4d2fe5c0 | |||
| e1102eb7db | |||
| a8df9590fd | |||
| 3e453e22ec | |||
| beeab0ec90 | |||
| 9353a7eaaa | |||
| cac8f3046d | |||
| 66eeac372c | |||
| 4a6a3cf708 | |||
| cb44c3f5a8 | |||
| eb0a34e959 | |||
| 8d683cc1a0 | |||
| ee41dd4cfe | |||
| 3d50e6e3b3 | |||
| 4f8d11881d | |||
| 5c924d3277 | |||
| 7f703a8386 | |||
| f063ea4741 | |||
| ca643dd298 | |||
| e19225e49e | |||
| 9ffbca679e | |||
| d90a10e462 | |||
| b6f25d041b | |||
| 4321c4bee5 | |||
| d1b3641754 | |||
| 5281c1bc7d | |||
| aae78bf479 | |||
| af17764346 | |||
| a8d321609d | |||
| 23ccbe2d02 | |||
| 47239016c5 | |||
| f4f6960b9d | |||
| c4c299eed6 | |||
| e20b59b43d | |||
| 901232b683 | |||
| 40c5fa69f5 | |||
| 7694396454 | |||
| dafc149bba | |||
| f23ec27247 | |||
| efb179dec2 | |||
| 86222aa92e | |||
| a5fb5cd360 | |||
| 6f6e53a483 | |||
| f13a2c6f1e | |||
| 59012cacfb | |||
| 21c5471d73 | |||
| e1ee70b46f | |||
| eb56b8d6a0 | |||
| d597046f41 | |||
| ae1c873536 | |||
| 1f7a2a77f6 | |||
| 9269465f6b | |||
| 883adfd242 | |||
| 1e890f9a28 | |||
| 2bb13d2e8c | |||
| cb02e05d55 | |||
| 05777f6109 | |||
| f3f042b3b9 | |||
| dfb13b1f96 | |||
| 6fa5038589 | |||
| 55f53e3346 | |||
| f36037d36d | |||
| 500bca80c1 | |||
| fc0361ed3a | |||
| df1485ce49 | |||
| b8bceb31c6 | |||
| f0308c3505 | |||
| e7ece1ed13 | |||
| 8ed7f7ec8f | |||
| feaef4a624 | |||
| 7d9aae2a59 | |||
| a88f8ca638 | |||
| be6a5f9add | |||
| ed4498578d | |||
| 118be50b3c | |||
| 1e2184ae20 | |||
| 8c1e1a64bf | |||
| b9c28968fe | |||
| 703d8d7519 | |||
| 8e52d99ba4 | |||
| d4020671c1 | |||
| 5c432e5801 | |||
| aac13297de | |||
| f60a4a4f60 | |||
| 0ed4de74b0 | |||
| 48bd26342a | |||
| b8e884a5aa | |||
| 1dbef5ca4b | |||
| e43ae7f96a | |||
| eab256d17f | |||
| bbdcbbf0ce | |||
| ec9bc3fe1f | |||
| 80f92a384e | |||
| 95251d23ad | |||
| c16bf574ce | |||
| 0e44d92baa | |||
| f9c1c67a38 | |||
| 286e2b6237 | |||
| fb78794261 | |||
| 371a65d361 | |||
| 7bd3c8dfd1 | |||
| e5aa6ea25d | |||
| de9afd3275 | |||
| 79e9f17f70 | |||
| 12cc3c5d5b | |||
| 404cea74ff | |||
| 16e94947f2 | |||
| aff0b0560f | |||
| de19a6fc65 | |||
| 1f669aadfc | |||
| 33f428559c | |||
| 0c3b9eb250 | |||
| c654fab94a | |||
| f2ef3c08a7 | |||
| 8a66e8af3f | |||
| 41d1cc6013 | |||
| c9c6a4ae81 | |||
| a5131e33dc | |||
| a19d1db990 | |||
| b6e7da6808 | |||
| f21fab937f | |||
| 30fccfe69b | |||
| b692d76f7d | |||
| 56533d4a16 | |||
| 8a22b475f9 | |||
| ae3a1be5e1 | |||
| 8199afc88f | |||
| 2d7ef8fc1d | |||
| d75d78ccb3 | |||
| 42489f50b6 | |||
| 52fde19380 | |||
| b1fb3e5baa | |||
| 5f3dae64c1 | |||
| b31ce66ef9 | |||
| 93bd561ff9 | |||
| d0d93be96b | |||
| 327a05cf63 | |||
| 1394467abe | |||
| ace73f15ef | |||
| a5e1fb4c38 | |||
| ac2451fe64 | |||
| 5f9789a884 | |||
| e0fbd220f0 | |||
| a0932fed04 | |||
| 42aa908f1c | |||
| da9dc0e8fb | |||
| 36f6396148 | |||
| 12facddcd5 | |||
| 429ecb4e0e | |||
| 8019d9c99a | |||
| 9f9c92385d | |||
| 9ecfc3b6f8 | |||
| d00f2d9d53 | |||
| 37a032aa9f | |||
| fd8e13eaa9 | |||
| b5d1e08d22 | |||
| 23321f90c2 | |||
| 90c6f67c24 | |||
| 7e2b29bfb0 | |||
| cd6aa80f59 | |||
| d5fdc9383f | |||
| 53865efc78 | |||
| 8cbd442371 | |||
| f54cf58dc2 | |||
| ede8e1cb21 | |||
| aaff15ee8c | |||
| bb7c85d770 | |||
| b7160f4df6 | |||
| a6b57bc352 | |||
| 70f7e05fe1 | |||
| 15f17b7af4 | |||
| a5650f2720 | |||
| 4758366063 | |||
| e7a5c2b352 | |||
| 3c14df129d | |||
| 5660f8d59d | |||
| 36ec2a539c | |||
| 339e51cca7 | |||
| 0726ba214c | |||
| 0e032ae029 | |||
| b9dead1c8b | |||
| 9010b68a39 | |||
| 252c14a377 | |||
| 4abfe801ec | |||
| c23a10eb6e | |||
| 11b55008ea | |||
| 46dcc16c59 | |||
| d42dff1165 | |||
| 11da418f60 | |||
| 981e78ff7e | |||
| c798bbd230 | |||
| e22d97e15a | |||
| c662369259 | |||
| 11b7be4ca0 | |||
| a029314bc9 | |||
| 8dd2d4f4ea | |||
| a4efbd4f0b | |||
| f63b8c6506 | |||
| 198fefbdf5 | |||
| 135bd7b9cd | |||
| 4cd0383322 | |||
| caa9a9ff8f | |||
| 524dc2a399 | |||
| 793d9632b2 | |||
| 5892a875c8 | |||
| b130bf9e19 | |||
| 1314362df9 | |||
| 5a1c032f1b | |||
| fae4bc86ad | |||
| 664092d52f | |||
| 22b1dd827d | |||
| 50df844e23 | |||
| 45b6b41d60 | |||
| a0a79e0191 | |||
| 46def40a14 | |||
| a76113dcaf | |||
| f56a04c3ab | |||
| 7f891b6ebf | |||
| 1343496a12 | |||
| 1735dd28a2 | |||
| bfc9ee4d01 | |||
| 9321d09489 | |||
| 7269ea03db | |||
| 2a3a15c92d | |||
| a7f1729e6e | |||
| cdccb0bfe8 | |||
| 48501a9069 | |||
| a175765e33 | |||
| 3d57c2e482 | |||
| bac4334fb8 | |||
| aa3e7c2f36 | |||
| 46cd524f70 | |||
| 86fb37abc4 | |||
| 9349b91926 | |||
| b9c86e2074 | |||
| 68672474e4 | |||
| a08197cc10 | |||
| e28620d9fb | |||
| ded98ed757 | |||
| 18fe69cbcd | |||
| e35d07ebc3 | |||
| e772a8b57f | |||
| 9c3ebc9877 | |||
| 3d13c3b8c8 | |||
| 6c3e788f13 | |||
| aceac0bf8b | |||
| 78d04f1715 | |||
| 1d773ea4e6 | |||
| a595567db1 | |||
| 945d3e1c1f | |||
| d58ea953af | |||
| b9475a045f | |||
| 32c2a1a3d8 | |||
| fc93f2b3f0 | |||
| f33778f97a | |||
| 1c72a03f26 | |||
| 3dcbdbf23c | |||
| 8143d501fa | |||
| 3471eedf4a | |||
| b6a3dad2ab | |||
| 375e6d9625 | |||
| 4d144ec704 | |||
| 92574a0f76 | |||
| ade73f3105 | |||
| ee75620cd8 | |||
| 94486e2b0a | |||
| a26e985edf | |||
| cf9ac795eb | |||
| 370c047c16 | |||
| a562a67923 | |||
| 078dbd8901 | |||
| 64d624a6df | |||
| 90ab57e4cb | |||
| a00ddf18e6 | |||
| 5048dc2466 | |||
| 7209fe3e20 | |||
| 4e35e67ef1 | |||
| 830b57d245 | |||
| 06930644a6 | |||
| a519387773 | |||
| ed40596389 | |||
| 4c855c05ed | |||
| e7189245d5 | |||
| 709a4ba83c | |||
| fb54ba2462 | |||
| c6b55030cf | |||
| af04557f58 | |||
| 2c100bddf2 | |||
| 8e3642c668 | |||
| 87d78b749b | |||
| 604dbeca93 | |||
| 8e9e7d65d0 | |||
| e2c317e507 | |||
| 5a7fa35465 | |||
| 2601a1c1ca | |||
| 0e26679f24 | |||
| d5ab659217 | |||
| 2d5919d572 | |||
| f56a074a3c | |||
| 24dc432f01 | |||
| 016193de07 | |||
| f04d8fecd3 | |||
| 36276c7c88 | |||
| 4a2cf0b55b | |||
| 400436a73f | |||
| 4c378dfba6 | |||
| c3a9de71f4 | |||
| ecf2e9ef57 | |||
| 45ac70eba2 | |||
| 3b66c6aa3a | |||
| e37b882481 | |||
| 5c7a47e002 | |||
| bbaa73dc1a | |||
| 9f5806f019 | |||
| 6de86c3143 | |||
| c9c0de6c3c | |||
| 4f827ed3a8 | |||
| aedac79743 | |||
| 84a39bf0d4 | |||
| 75c6d099f5 | |||
| 319957c01e | |||
| 26dc33ea2f | |||
| 7597eddd7c | |||
| 0441b4e56f | |||
| cbff21136c | |||
| 03f0d19b01 | |||
| ca65c6c6e1 | |||
| 2b095f16bf | |||
| 930bf99fa1 | |||
| 366527e8e9 | |||
| 4e06240307 | |||
| 38608a5278 | |||
| b19f30cca4 | |||
| b206e809ac | |||
| e88f741fa7 | |||
| 6958e71bfb | |||
| c6ad0b2a59 | |||
| da9ea4f413 | |||
| 5703c0a6dd | |||
| b70943dd35 | |||
| 0f7099be81 | |||
| 9cbc1637b4 | |||
| d01be7d84c | |||
| 28511e0cc4 | |||
| 3774a2caee | |||
| 70277d3712 | |||
| 18cb092d06 | |||
| 417f34e35f | |||
| c62ff5b2c0 | |||
| ad422a134d | |||
| 9fce9ec8dd | |||
| a329137de2 | |||
| b328c2613c | |||
| 62a7b6773c | |||
| 625dd35b9e | |||
| f99b7bc8ab | |||
| e779162ec2 | |||
| 17345345b5 | |||
| 3df526d5b5 | |||
| d5f30176c4 | |||
| 308e5af6ff | |||
| 268fe6a6b9 | |||
| a24914126f | |||
| 1ca27bc072 | |||
| c96d93c2ce | |||
| cd38af22db | |||
| c440c25e41 | |||
| e105ab61b7 | |||
| 10b058afa0 | |||
| d18f33a87a | |||
| 62190fa821 | |||
| a6210c9d6e | |||
| fdece531af | |||
| 8073235466 | |||
| 7dc3c3b988 | |||
| 21793dffd7 | |||
| ca337c4fac | |||
| 76beb8d62e | |||
| c5bfd02f09 | |||
| 9f4ca058bd | |||
| 603c208ae4 | |||
| 754777d69e | |||
| 829a0e49d8 | |||
| f12babf2b1 | |||
| d6249169c6 | |||
| 6aa1cd9a63 | |||
| 09795c30dd | |||
| 59268dc102 | |||
| 979378d142 | |||
| 6ce7f0ef22 | |||
| 71a8dd5f17 | |||
| e86fef1766 | |||
| 3cb63f791a | |||
| f96415bfe6 | |||
| 365f70af98 | |||
| c1ddbfdf48 | |||
| e575e3dfa8 | |||
| 560f53cfb6 | |||
| d8c350c20f | |||
| 5b718f24ed | |||
| 6634bee77b | |||
| 2a3e73d409 | |||
| 21bdb0d66d | |||
| 5db4ad3e63 | |||
| 2ce13cef72 | |||
| 02551049e0 | |||
| 55e9fa6a20 | |||
| c82378baa8 | |||
| 541d5b4c5e | |||
| f9ab3f8db2 | |||
| 6c5047be30 | |||
| 25f6d2bf4a | |||
| 2cd68873de | |||
| d987ca271f | |||
| 0a37dd4b8b | |||
| b4ca25db3d | |||
| 738b36c406 | |||
| c785363384 | |||
| 32cb8a9d9e | |||
| 29e23a8d3b | |||
| 1a6403963a | |||
| e096774233 | |||
| 222759a1bc | |||
| 7a3043f644 | |||
| f1986bc3f7 | |||
| 75d2f17a3f | |||
| 317f4c0ce9 | |||
| 4a7bfa9567 | |||
| fec3cbb550 | |||
| 497ce5bdb4 | |||
| 53b593b380 | |||
| 2d03b1f051 | |||
| d3c190096a | |||
| 2998a7bf22 | |||
| 43bd452a30 | |||
| dfdcc99111 | |||
| ea409dae07 | |||
| 2810bfdce2 | |||
| ff23e6c9a2 | |||
| 88c369de32 | |||
| 52a34cd9b4 | |||
| 46eb99b534 | |||
| 0eba5f875a | |||
| b93692c160 | |||
| 00f9f9cd91 | |||
| 2c0d52a3d5 | |||
| 4a67932002 | |||
| 339e3025b8 | |||
| 4d88605192 | |||
| bd3d81e80c | |||
| 98683197e0 | |||
| 2ded6300ab | |||
| 79efee0979 | |||
| 9db31d4b8c | |||
| d7c7ef1041 | |||
| 483dd4182e | |||
| f63a2be01a | |||
| 838b13e39f | |||
| 7b4abe32a7 | |||
| 26889a8b59 | |||
| 60302e02b6 | |||
| 72023a6634 | |||
| dd1194a1c7 | |||
| d5fbfc950f | |||
| d4fa588c8f | |||
| 2a5ff14cb5 | |||
| df17478d72 | |||
| 38fba55798 | |||
| 5b3e7bf426 | |||
| cb07a4c80f | |||
| 85512e61a2 | |||
| 380736ce26 | |||
| 9eb6feb589 | |||
| 23eee02208 | |||
| ca4dfd03e4 | |||
| 5c613a82c0 | |||
| 34ffad38a6 | |||
| b131523bf3 | |||
| db2bd7e195 | |||
| 51ccad038d | |||
| 18810dcfe2 | |||
| ed1815b78e | |||
| 246a2a856c | |||
| 77b3916985 | |||
| 298a9b406a | |||
| e5d6291aac | |||
| 667d009cf8 | |||
| 3edc689e5f | |||
| 938cca55fe | |||
| d5e177654f | |||
| 59bbbc6bf4 | |||
| ec814c9026 | |||
| b390437790 | |||
| d08e56bb5a | |||
| ccff6ab4cb | |||
| 04c1cb00c5 | |||
| 1f2be8b8ee | |||
| f2c0b4e4b5 | |||
| 77fd9af112 | |||
| 7ea3ce66ce | |||
| a91869bf47 | |||
| 9ae1cd4c34 | |||
| 475fb00001 | |||
| 168cf68a70 | |||
| 36d4ac0681 | |||
| 1afc7b480a | |||
| b88046e228 | |||
| 4f00859d1b | |||
| 1b68f037ae | |||
| 7067303758 | |||
| e499ffba22 | |||
| c4e7ca3ac2 | |||
| 55367963fb | |||
| 5522f4fc04 | |||
| 332507d433 | |||
| fe703d4e89 | |||
| ef6d6d9a91 | |||
| 128b191af5 | |||
| 5612bfe0b7 | |||
| d0f9a97394 | |||
| 67ecf6e870 | |||
| fd27577407 | |||
| 83ccf84da3 | |||
| 968534d28e | |||
| cd60423f1a | |||
| 9bb2587c49 | |||
| ef94a5873c | |||
| 8dbbe9eaf9 | |||
| 3afac85222 | |||
| 9eae4985d3 | |||
| 63c7bb8848 | |||
| 28ef21aa52 | |||
| 6a6ab16e09 | |||
| 5130a638b9 | |||
| bc9b5effb2 | |||
| 5ee412b050 | |||
| 65412e6df9 | |||
| 22ca680f3c | |||
| 83268d3a67 | |||
| d24bd0aa8b | |||
| c52834aa0d | |||
| 306b9e8152 | |||
| 30bb0d4657 | |||
| fdd7408f53 | |||
| 7f77297100 | |||
| e8693f282f | |||
| 8477462e07 | |||
| c28268faf3 | |||
| 24a759c61a | |||
| 0d0a7dba90 | |||
| 6de87c9e6c | |||
| 104f265e9b | |||
| f21d819c77 | |||
| ac1828b5f9 | |||
| 22bb2de043 | |||
| 664ad37181 | |||
| c2c98d12bc | |||
| cdc8b85f3c | |||
| c0d9949037 | |||
| 5dbe7a99b9 | |||
| 086596f68c | |||
| 7a206674ab | |||
| 9698512ccf | |||
| f4d144980d | |||
| 1513cbda7f | |||
| 1becb82b1b | |||
| 91880b163d | |||
| 81dd452f81 | |||
| 24642ad83e | |||
| 9b8f4036a9 | |||
| 86395f0727 | |||
| e5b617dcba | |||
| 581c012a72 | |||
| 4d8f338649 | |||
| e5eaaf857e | |||
| e0835b70df | |||
| ed471cdf94 | |||
| f825537b52 | |||
| 485e08e6c6 | |||
| 7ff02a6a94 | |||
| ec0148bb86 | |||
| 9aa0793ab4 | |||
| 54220682cf | |||
| b13cff14dc | |||
| f86bdfd2ea | |||
| 3933b9b55c | |||
| 7a784c8243 | |||
| 10ddf8bf7f | |||
| 0779a2c7d4 | |||
| 053ae0b52f | |||
| be8b6372db | |||
| 6068417fc5 | |||
| 14d0b23d06 | |||
| 1f6ddcd1fd | |||
| 701fda5ab5 | |||
| 47731a8d46 | |||
| 0ade795415 | |||
| 2a9a60ae84 | |||
| efdc9b4175 | |||
| 6076903b42 | |||
| d43218fdb2 | |||
| 554dbd677a | |||
| 6a7e961cf6 | |||
| e49e614958 | |||
| 70fe6ff807 | |||
| f6932afcb8 | |||
| e3901265d8 | |||
| 368a4948ec | |||
| 6c07502866 | |||
| c1c8bc47d7 | |||
| 8466a857c3 | |||
| 68d9224ea3 | |||
| aac26b0e50 | |||
| a1a9fa9d3e | |||
| 5625326f38 | |||
| 8841cffb25 | |||
| d72bf8c3e2 | |||
| 04195f5d92 | |||
| 24a3ad0639 | |||
| 72097f891d | |||
| 0686e98384 | |||
| 7d9bef932f | |||
| e69cb50a9c | |||
| 890187cff2 | |||
| 46a307a5b0 | |||
| 7706c7dfbf | |||
| a946cee891 | |||
| f2a9d8d1b3 | |||
| e128a31a34 | |||
| d7acca36fd | |||
| d28560b77e | |||
| ee6dd0ddae | |||
| feb64eb769 | |||
| ecc76e453e | |||
| 949ca542ab | |||
| b090195d9f | |||
| 1f1e7dc814 | |||
| 3606a750be | |||
| 8762bc1b07 | |||
| c7f279b7e9 | |||
| 92f1f077d2 | |||
| 50120f974d | |||
| 713a258d48 | |||
| 0a7e6df734 | |||
| 8c769ba280 | |||
| 5bcb64f7f8 | |||
| 944f40d89f | |||
| b15421efee | |||
| 3ddd66dcbb | |||
| 8af0367edf | |||
| b95fc1adbe | |||
| 63f400995b | |||
| 8aa657ed99 | |||
| 627c72bb7f | |||
| a8a75d90ac | |||
| 0b1b704115 | |||
| 0babc42359 | |||
| 4a80d42dda | |||
| a2daded6b3 | |||
| d3169eba18 | |||
| 2d967c200c | |||
| 2044853f94 | |||
| 9a3e4cc7dc | |||
| f508a222c6 | |||
| 193e526e67 | |||
| f77643af44 | |||
| 9e900a3436 | |||
| 75988ffe71 | |||
| cb1f3f0d07 | |||
| 4d8017aa2b | |||
| 365f37dcea | |||
| 2472143146 | |||
| a1184299b1 | |||
| 4d4b8aeb99 | |||
| c41302d046 | |||
| aaefd2917e | |||
| ee7986bef3 | |||
| 8de1b1bdb5 | |||
| 4a663391ef | |||
| 4cd877ea4c | |||
| 0f573d30ae | |||
| fdd56c10b8 | |||
| 46daf72a14 | |||
| 896e6abcd8 | |||
| 95e5cf0908 | |||
| be35a00cef | |||
| b616251511 | |||
| 3f5313dd00 | |||
| f909d0453b | |||
| d5f4f2b8a5 | |||
| 87a8e9b7d2 | |||
| 729b0f08a1 | |||
| 2907bcdd67 | |||
| 65935c644b | |||
| 8645900035 | |||
| 49a8d49296 | |||
| bc1051e4a2 | |||
| d8f8fbbdd4 | |||
| baf3f88e1e | |||
| 480b6cd4ae | |||
| 29b588babb | |||
| a29e48efad | |||
| 84a9f7063a | |||
| 58f7be9d52 | |||
| 8fa91e74da | |||
| a33824a935 | |||
| bf06df461d | |||
| 0f529bae05 | |||
| 526ae0cf17 | |||
| cdd3a8a5a6 | |||
| 48c855de7b | |||
| 51d38b1d42 | |||
| e61cb0dc1f | |||
| fccd00e959 | |||
| facddf3e42 | |||
| a43dc50cf1 | |||
| 143b78b45d | |||
| 4aef921299 | |||
| 1db44ade6f | |||
| a76cb0bb0a | |||
| e3aeeb7fdd | |||
| 1fb181d83c | |||
| 8d3a18ee86 | |||
| 76fac30a5f | |||
| 882cfb28d2 | |||
| 8e3d11a607 | |||
| 1fc687ffc9 | |||
| bde79ace3a | |||
| 299140da94 | |||
| f7ddde6860 | |||
| bde258859c | |||
| cdc7611dc5 | |||
| 160150d2fe | |||
| 012b21449b | |||
| 70b1191eda | |||
| 5c0b9b4d44 | |||
| 0b518db97f | |||
| eb87cfa2fa | |||
| 279a5329cf | |||
| 2252ec2646 | |||
| b2c79c636a | |||
| f97f345939 | |||
| 673f171e8b | |||
| 5c4a1d1d91 | |||
| 76928e78e7 | |||
| e6412ef2cd | |||
| d577bd11f1 | |||
| 793f1e0e96 | |||
| 78f669e857 | |||
| 99374dae3c | |||
| a1aa191cf7 | |||
| 615330e8e6 | |||
| 6fec6ebfa3 | |||
| 9a4d25776c | |||
| b1705e5497 | |||
| 132f7866d1 | |||
| 532ec22811 | |||
| 59ef5b8bc2 | |||
| 7940cc3b3c | |||
| 2a8a801749 | |||
| ea02066644 | |||
| 1171fed5cd | |||
| 36cb2c03f1 | |||
| 44f647c60b | |||
| 7e09860826 | |||
| 2d9b146517 | |||
| bbc7e7297e | |||
| c7f2288f8e | |||
| e9d1d017e1 | |||
| bf2aff1f77 | |||
| f4fcf7b387 | |||
| d37e6460ab | |||
| 3e03f0aa90 | |||
| 4809a10327 | |||
| df316263eb | |||
| b9eb97a842 | |||
| 86daa175fa | |||
| 134b439fd3 | |||
| d3303de510 | |||
| b13b713e43 | |||
| f687347e11 | |||
| fa70550ca2 | |||
| 2ff947e62e | |||
| 7267ad4498 | |||
| 5381368af0 | |||
| 6c4041ba16 | |||
| 99f874820f | |||
| 46a609e4b7 | |||
| f5c8f4adab | |||
| 314fd8e26e | |||
| a1b61ddc47 | |||
| 5b0fa08c81 | |||
| 044f2f8248 | |||
| 333d19e3e2 | |||
| f4171c69ea | |||
| 4a9c4ae462 | |||
| 37b0376a38 | |||
| f4156d16c5 | |||
| cd74f7a963 | |||
| 49ed423eaf | |||
| 1b7802a2ac | |||
| 6e449912ab | |||
| 8fe32f6245 | |||
| f52022d098 | |||
| e67a350857 |
Generated
+18
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
|
||||||
|
<data-source name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
|
||||||
|
<database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51">
|
||||||
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<auth-provider>no-auth</auth-provider>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema" qname="@" />
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/articles</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
<DataSourcesHistory>
|
||||||
|
<DataSourceFromHistory isRemovedFromProject="true">
|
||||||
|
<data-source source="LOCAL" name="articles" uuid="a0abcd0a-1d6f-40e4-88be-f442bcb431ba">
|
||||||
|
<database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51">
|
||||||
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/articles</jdbc-url>
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<auth-provider>no-auth</auth-provider>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema" qname="@" />
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</DataSourceFromHistory>
|
||||||
|
<DataSourceFromHistory isRemovedFromProject="false">
|
||||||
|
<data-source source="LOCAL" name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
|
||||||
|
<database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51">
|
||||||
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/articles</jdbc-url>
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<auth-provider>no-auth</auth-provider>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema" qname="@" />
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</DataSourceFromHistory>
|
||||||
|
</DataSourcesHistory>
|
||||||
Generated
+1
@@ -10,6 +10,7 @@
|
|||||||
<option name="highlightLevel" value="WARNING" />
|
<option name="highlightLevel" value="WARNING" />
|
||||||
<option name="transferred" value="true" />
|
<option name="transferred" value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.5" />
|
||||||
<component name="PhpStanOptionsConfiguration">
|
<component name="PhpStanOptionsConfiguration">
|
||||||
<option name="transferred" value="true" />
|
<option name="transferred" value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
Team Tag-Buchstabe: DI-14-C-Schulte-Eilers-Ortmann
|
# Webprogrammierung: Gruppe DI-14-C
|
||||||
Namen der Studierenden:
|
|
||||||
- Name 1 Caroline Schulte
|
|
||||||
- Name 2 Jacques Eilers
|
|
||||||
- Name 3 Niklas Ortmann
|
|
||||||
Abgabe zu Aufgabenblatt 1
|
|
||||||
|
|
||||||
Hinweise:
|
## Beteiligte
|
||||||
index.html wurde zu einer index.php umbenannt.
|
- Caroline Schulte
|
||||||
webhook.php bitte ignorieren. Diese dient nur als zukünftige Automatisierung des Repo-Pulls (s. https://docs.gitea.com/usage/webhooks)
|
- Jacques Eilers
|
||||||
|
- Niklas Ortmann
|
||||||
|
|
||||||
|
## Login-Informationen für Dummy-User
|
||||||
|
`Anmeldename, Passwort, Mailadresse`:
|
||||||
|
- `max.mustermann, test12345, max.mustermann@web.de`
|
||||||
|
|
||||||
|
## Weitere Voraussetzungen zur Nutzung
|
||||||
|
- Per Klick auf das Logo gelangt man auf die Home-Seite.
|
||||||
|
|
||||||
|
## Ausgelassene Teilaufgaben
|
||||||
|
- Das Anerkennen der Datenschutzerklärung und das Einhalten der
|
||||||
|
Nutzungsbedingungen (bspw. was Rechte an eingestellten Texten und
|
||||||
|
hochgeladenen Fotos angeht) muss ein Nutzer bei der Registrierung via einer
|
||||||
|
Checkbox und dem Opt-In-Verfahren fehlt noch.
|
||||||
|
|
||||||
|
## Bekannte Fehler und Mängel
|
||||||
|
- Bitte auf die gesetzten TODO's achten. Wenn Inhalte fehlen, sind sie i.d.R. als TODO kommentiert.
|
||||||
|
- Die Kategorieseite listet momentan alle passenden Beiträge untereinander. Später sollen mit einem Paginator die neusten
|
||||||
|
Beiträge nacheinander aufgelistet werden (ähnlich wie bei der Suche, wenn nach Fach gefiltert wird).
|
||||||
|
- Wenn ein Bild aus einem Beitrag entfernt wird, dann wird noch nicht die Datei im Pfad /uploads gelöscht.
|
||||||
|
- id in showArticle-controller.php und updateArticle-controller.php wird nicht als gültige numerische ID geprüft.
|
||||||
|
- Bilder im Beitragseditor sollen zukünftig eine Bildunterschrift bekommen und größenverstellbar sein.
|
||||||
|
- Die Elemente eines Contents im Beitrag werden momentan stumpf untereinander aufgelistet. Soll später
|
||||||
|
sich responisve auch nebeneinander orientieren usw.
|
||||||
|
- Die Datenbank ist noch in die verschiedenen Manager unterteilt. Zukünftig werden alle Daten in einer Datenbank gespeichert.
|
||||||
|
|
||||||
|
## Besonderheiten des Projektes
|
||||||
|
- Es wurde AJAX verwendet, um asynchrone Erstellung von Kommentaren zu implementieren. Es ermöglicht dem Nutzer, einen
|
||||||
|
Kommentar abzusenden, ohne dass die gesamte Webseite neu geladen werden muss.
|
||||||
|
- Mit JavaScript werden auch clientseitig die Kommentare visuell hinzugefügt und die Kommentarbäume aufgebaut.
|
||||||
|
- JavaScript wird verwendet, um im erweitertem Beitragseditor clientseitig einzelne Content-Boxen erstellen und löschen
|
||||||
|
zu können.
|
||||||
|
- JavaScript wird ebenfalls verwendet, um in die Suchergebnisse clientseitig zu sortieren.
|
||||||
|
- Die Kommentarfunktion ist zusätzlich auch ohne JavaScript nutzbar. Kommentare und Antworten können auch ohne JavaScript erstellt werden.
|
||||||
|
- Eigene Kommentare und Antworten können bearbeitet und gelöscht werden. Die Bearbeitung und das Löschen funktionieren ebenfalls ohne JavaScript über normale Formulare und serverseitige Verarbeitung.
|
||||||
|
|
||||||
|
|
||||||
|
## Sonstiges
|
||||||
|
- Das Datenschema befindet sich unter /planung/Datenschema.pdf
|
||||||
|
- Im Impressum, sowie der Datenschutzerklaerung wurden Dummy-Daten verwendet
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<!--
|
|
||||||
Artikel: Datenschutz vs Datensicherheit
|
|
||||||
Funktion: Inhalt zum Fach Informatik
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Datenschutz vs Datensicherheit</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Datenschutz ist in unserer digital vernetzten Welt allgegenwärtig – ob in sozialen Netzwerken, beim
|
|
||||||
Online-Shopping oder dem Einsatz von Bonuskarten, bei der Auswertung von Konsumverhalten und selbstverständlich
|
|
||||||
auch im schulischen Kontext.
|
|
||||||
</p>
|
|
||||||
<h2>
|
|
||||||
Was ist Datenschutz?
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
Datenschutz bedeutet, dass persönliche Daten nur gesammelt, gespeichert und verwendet werden dürfen, wenn
|
|
||||||
es wirklich nötig ist – und nur für klar festgelegte Zwecke. Es geht darum, die Privatsphäre von Menschen
|
|
||||||
zu schützen.
|
|
||||||
</p>
|
|
||||||
<h2>
|
|
||||||
Was ist Datenschutz?
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
Datensicherheit umfasst alle technischen und organisatorischen Maßnahmen, die verhindern sollen, dass Daten
|
|
||||||
verloren gehen, beschädigt oder unbemerkt verändert werden. Sie schützt Informationen vor Missbrauch.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<!--
|
|
||||||
Artikel: Satz des Pythagoras
|
|
||||||
Funktion: Inhalt zum Fach Mathe
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Satz des Pythagoras</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Der Satz des Pythagoras wurde von dem griechischen Philosophen Pythagoras von Samos formuliert und im
|
|
||||||
dritten Jahrhundert vor Christus veröffentlicht.
|
|
||||||
</p>
|
|
||||||
<figure>
|
|
||||||
<img src="https://cdn8.picryl.com/photo/2016/05/14/pythagoras-e9560b-1024.jpg" alt="Trulli" style="width:10%">
|
|
||||||
<figcaption><a href="https://cdn8.picryl.com/photo/2016/05/14/pythagoras-e9560b-1024.jpg">Quelle</a></figcaption>
|
|
||||||
</figure>
|
|
||||||
<p>
|
|
||||||
In der beigefügten Abbildung sehen wir ein rechtwinkliges Dreieck, dessen drei Seiten die Längen a, b und c
|
|
||||||
besitzen. Auf jeder Seite ist ein Quadrat konstruiert. Das Quadrat auf der Seite a hat die Fläche a2, das
|
|
||||||
Quadrat auf der Seite b hat die Fläche b2 und das Quadrat auf der Seite c hat die Fläche c2. Der Satz des
|
|
||||||
Pythagoras besagt, dass die Summe der Flächen der beiden kleineren Quadrate gleich der Fläche des größten
|
|
||||||
Quadrats ist. Das bedeutet, dass a^2+b^2=c^2 (Satz des Pythagoras).
|
|
||||||
</p>
|
|
||||||
<a href="../docs/Uebungsaufgaben_Pythagoras.pdf">Lernzettel-Download</a>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<!--
|
|
||||||
Artikel: Tunneleffekt
|
|
||||||
Funktion: Inhalt zum Fach Physik
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Tunneleffekt</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Der Tunneleffekt ist ein quantenmechanisches Phänomen, bei dem Teilchen – vor allem Elektronen – eine
|
|
||||||
Energiebarriere überwinden können, obwohl sie nach den Regeln der klassischen Physik nicht genügend Energie
|
|
||||||
dafür besitzen. In der klassischen Vorstellung müsste ein Elektron entweder genügend Energie haben, um über
|
|
||||||
eine Barriere zu „springen“, oder es würde vollständig zurückgeworfen. In der Quantenphysik wird ein Elektron
|
|
||||||
jedoch nicht als punktförmiges Teilchen beschrieben, sondern als Wellenfunktion, die sich räumlich ausdehnt.
|
|
||||||
Dadurch besteht eine endliche Wahrscheinlichkeit, dass sich das Elektron auf der anderen Seite einer Barriere
|
|
||||||
befindet (Griffiths & Schroeter, 2018).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
<!--
|
|
||||||
Form: Login-Bereich
|
|
||||||
Funktion: Benutzerauthentifizierung und Zugang zum eigenen Profil, Erstellen von Beiträgen, etc.
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="login-page">
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="logo-wrapper">
|
|
||||||
<img src="../images/logo.png" alt="Logo" class="login-logo">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>Bitte anmelden</h1>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<label id ="inputEmail" class="screenreader-only">E-Mail Adresse / </label>
|
|
||||||
|
|
||||||
<p class="input-label">Benutzername/E-Mail-Adresse:</p>
|
|
||||||
<input type="email" name="email" class="login-input" placeholder="E-Mail-Adresse" required autofocus>
|
|
||||||
|
|
||||||
<p class="input-label">Passwort:</p>
|
|
||||||
<input type="password" name="password" class="login-input" placeholder="Passwort" required>
|
|
||||||
|
|
||||||
<div class="checkbox-wrapper">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="remember-me" value="1"> angemeldet bleiben
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" value="anmelden" name="loginSubmit" class="login-button">anmelden</button>
|
|
||||||
|
|
||||||
<div class="register-link">
|
|
||||||
<a href="register.php">Noch keinen Account? Jetzt hier registrieren!</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
<!--
|
|
||||||
Form: Registrierung
|
|
||||||
Funktion: Erstellung neuer Benutzerkonten
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="login-page">
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="logo-wrapper">
|
|
||||||
<img src="../images/logo.png" alt="Logo" class="login-logo">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>Jetzt Registrieren!</h1>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<p class="input-label">Email:</p>
|
|
||||||
<input type="email" name="email" class="login-input" placeholder="mustermann@web.de" required>
|
|
||||||
|
|
||||||
<p class="input-label">Vorname:</p>
|
|
||||||
<input type="text" name="vorname" class="login-input" placeholder="Max" required>
|
|
||||||
|
|
||||||
<p class="input-label">Nachname:</p>
|
|
||||||
<input type="text" name="nachname" class="login-input" placeholder="Mustermann" required>
|
|
||||||
|
|
||||||
<p class="input-label">Passwort:</p>
|
|
||||||
<input type="password" name="password" class="login-input" placeholder="Passwort" required>
|
|
||||||
|
|
||||||
<div class="checkbox-wrapper">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" value="remember-me"> angemeldet bleiben
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" value="anmelden" name="loginSubmit" class="login-button">kostenlos registrieren</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!--
|
||||||
|
Seite: 404 Seite nicht gefunden
|
||||||
|
Funktion: Wenn eine Seite nicht gefunden wurde oder noch fehlt, dann wird hierauf verwiesen.
|
||||||
|
-->
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
|
<?php
|
||||||
|
unset($_SESSION["message"]);
|
||||||
|
?>
|
||||||
|
<h1>404 - Seite nicht vorhanden</h1>
|
||||||
|
<p>
|
||||||
|
Die Seite, nach der du suchst, existiert hier leider nicht. Entweder haben wir sie verlegt, ein hungriger Bug hat sie gefressen, oder du hast dich beim Tippen vertippt. (Wir schieben die Schuld einfach mal ganz unauffällig auf dich.)
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<!--
|
||||||
|
Seite: Barrierefreiheitserklärung
|
||||||
|
Inhalt: Erklärung zur Barrierefreiheit der Plattform
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="EduForge">
|
||||||
|
<meta name="author" content="Niklas Ortmann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/x-icon" href="../images/logos/logo_icon.ico">
|
||||||
|
<link rel="stylesheet" href="../css/main.css">
|
||||||
|
<link rel="stylesheet" href="../css/navbar.css">
|
||||||
|
<link rel="stylesheet" href="../css/footer.css">
|
||||||
|
<title>EduForge</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include_once 'includes/navbar.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<h1>Barrierefreiheitserklärung</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Wir bemühen uns, die Plattform EduForge möglichst barrierefrei zugänglich zu gestalten.
|
||||||
|
Die Website wurde im Rahmen eines Hochschulprojekts erstellt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Stand der Vereinbarkeit</h2>
|
||||||
|
<p>
|
||||||
|
Diese Website ist teilweise barrierefrei. Einzelne Funktionen oder Inhalte können
|
||||||
|
noch nicht vollständig barrierefrei nutzbar sein.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Nicht barrierefreie Inhalte</h2>
|
||||||
|
<p>
|
||||||
|
Einzelne Bilder verfügen möglicherweise noch nicht über vollständige Alternativtexte.
|
||||||
|
Außerdem kann die Bedienung mit Tastatur oder Screenreader in einzelnen Bereichen
|
||||||
|
eingeschränkt sein.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Feedback und Kontakt</h2>
|
||||||
|
<p>
|
||||||
|
Sollten Ihnen Barrieren auf unserer Website auffallen, kontaktieren Sie uns bitte
|
||||||
|
über die im Impressum angegebenen Kontaktdaten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Erstellungsdatum</h2>
|
||||||
|
<p>
|
||||||
|
Diese Erklärung wurde im Rahmen eines Hochschulprojekts erstellt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include_once 'includes/footer.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/** @var string $title */
|
||||||
|
/** @var string $message */
|
||||||
|
/** @var string $link */
|
||||||
|
/** @var bool $isSuccess */
|
||||||
|
|
||||||
|
$title = $title ?? "Passwort zurücksetzen";
|
||||||
|
$message = $message ?? "Der Bestätigungslink konnte nicht verarbeitet werden.";
|
||||||
|
$link = $link ?? "";
|
||||||
|
$isSuccess = $isSuccess ?? false;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ansicht: Passwortbestätigung
|
||||||
|
Funktion: Zeigt das Ergebnis der Passwortänderung an.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1><?php echo htmlspecialchars($title); ?></h1>
|
||||||
|
|
||||||
|
<p class="alert-message <?php echo $isSuccess ? 'is-success' : 'is-error'; ?> confirm-message">
|
||||||
|
<?php echo htmlspecialchars($message); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php echo $link; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
/** @var string $title */
|
||||||
|
/** @var string $message */
|
||||||
|
/** @var string $link */
|
||||||
|
/** @var bool $isSuccess */
|
||||||
|
|
||||||
|
$title = $title ?? "Registrierung";
|
||||||
|
$message = $message ?? "Der Registrierungslink konnte nicht verarbeitet werden.";
|
||||||
|
$link = $link ?? "";
|
||||||
|
$isSuccess = $isSuccess ?? false;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ansicht: Registrierungsbestätigung
|
||||||
|
Funktion: Zeigt das Ergebnis der Registrierungsbestätigung an.
|
||||||
|
-->
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1><?php echo htmlspecialchars($title); ?></h1>
|
||||||
|
|
||||||
|
<p class="alert-message <?php echo $isSuccess ? 'is-success' : 'is-error'; ?> confirm-message">
|
||||||
|
<?php echo htmlspecialchars($message); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php echo $link; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
<?php
|
||||||
|
$user = $user ?? null;
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Aktuelle Blockliste ermitteln: Session-Fallback (nach Validierungsfehler
|
||||||
|
// oder Zwischen-Submit "add_block"/"delete_block") oder leeres Array (Start).
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
$rawContent = '[]';
|
||||||
|
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])) {
|
||||||
|
$rawContent = $_SESSION['old_content'];
|
||||||
|
unset($_SESSION['old_content']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocks = json_decode($rawContent, true);
|
||||||
|
if (!is_array($blocks)) {
|
||||||
|
$blocks = [];
|
||||||
|
}
|
||||||
|
$blocks = array_values($blocks); // sequentielle Indizes 0..n-1 sicherstellen
|
||||||
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Beitrag erstellen
|
||||||
|
Inhalt: Formular für die Erstellung eines neuen Beitrags
|
||||||
|
-->
|
||||||
|
<form method="post" action="php/controller/createArticle-controller.php" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<main class="editor-main">
|
||||||
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
|
|
||||||
|
<input type="text" id="title" name="title"
|
||||||
|
value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>"
|
||||||
|
placeholder="Titel hier eingeben" required>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Content-Blöcke: werden serverseitig als echte, benannte Formularfelder gerendert
|
||||||
|
(blocks[i][type], blocks[i][text] bzw. blocks[i][image]). Dadurch funktioniert das
|
||||||
|
Hinzufügen/Entfernen von Blöcken und der Bild-Upload auch ganz ohne JavaScript über
|
||||||
|
einen normalen Formular-Submit. Ist JavaScript aktiv, fängt js/editor.js diese
|
||||||
|
Submits ab und erledigt dieselbe Änderung lokal im DOM, ohne den Server zu belasten.
|
||||||
|
-->
|
||||||
|
<div id="block-container">
|
||||||
|
<?php foreach ($blocks as $i => $block): ?>
|
||||||
|
<?php
|
||||||
|
$blockType = $block['type'] ?? '';
|
||||||
|
if ($blockType !== 'text' && $blockType !== 'image') {
|
||||||
|
continue; // unbekannter/kaputter Block wird übersprungen
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="editor-block article-editor-scope" data-index="<?php echo (int)$i; ?>">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][type]" value="<?php echo htmlspecialchars($blockType); ?>">
|
||||||
|
|
||||||
|
<?php if ($blockType === 'text'): ?>
|
||||||
|
<textarea name="blocks[<?php echo (int)$i; ?>][text]"
|
||||||
|
placeholder="Schreibe deinen Textblock..."><?php echo htmlspecialchars($block['value'] ?? ''); ?></textarea>
|
||||||
|
<?php else: /* image */ ?>
|
||||||
|
<?php if (!empty($block['value'])): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($block['value']); ?>"
|
||||||
|
class="block-image-preview"
|
||||||
|
style="max-width:200px;display:block;margin-top:10px;">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][existing_image]" value="<?php echo htmlspecialchars($block['value']); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" name="blocks[<?php echo (int)$i; ?>][image]" accept="image/*">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button type="submit" name="editor_action" value="delete_block:<?php echo (int)$i; ?>" class="delete-block-btn">✕</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="add-block-control" class="article-editor-scope add-block-control">
|
||||||
|
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
||||||
|
<div id="block-popup" class="article-editor-scope block-popup">
|
||||||
|
<button type="submit" name="editor_action" value="add_text" data-type="text">Textblock</button>
|
||||||
|
<button type="submit" name="editor_action" value="add_image" data-type="image">Bild einfügen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Seitenleiste -->
|
||||||
|
<aside class="editor-sidebar">
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<button type="submit" id="editor-button" class="button">Veröffentlichen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<label for="category">Kategorie <span class="required">*</span></label>
|
||||||
|
<select id="category" name="category" required>
|
||||||
|
<option disabled <?php
|
||||||
|
if ((!isset($_SESSION['old_category']) || empty($_SESSION['old_category'])) && (!isset($category) || empty($category))) {
|
||||||
|
echo 'selected';
|
||||||
|
}
|
||||||
|
?>>Kategorie wählen...</option>
|
||||||
|
|
||||||
|
<optgroup label="Sprachen">
|
||||||
|
<option value="deutsch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'deutsch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'deutsch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Deutsch</option>
|
||||||
|
<option value="englisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'englisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'englisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Englisch</option>
|
||||||
|
<option value="franzoesisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'franzoesisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'franzoesisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Französisch</option>
|
||||||
|
<option value="latein" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'latein') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'latein') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Latein</option>
|
||||||
|
<option value="literatur" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'literatur') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'literatur') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Literatur</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="MINT">
|
||||||
|
<option value="mathe" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'mathe') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'mathe') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Mathematik</option>
|
||||||
|
<option value="biologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'biologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'biologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Biologie</option>
|
||||||
|
<option value="chemie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'chemie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'chemie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Chemie</option>
|
||||||
|
<option value="physik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'physik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'physik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Physik</option>
|
||||||
|
<option value="informatik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'informatik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'informatik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Informatik</option>
|
||||||
|
<option value="astronomie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'astronomie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'astronomie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Astronomie</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="Gesellschaft & Werte">
|
||||||
|
<option value="geschichte" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'geschichte') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'geschichte') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Geschichte</option>
|
||||||
|
<option value="erdkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'erdkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'erdkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Erdkunde</option>
|
||||||
|
<option value="sozialkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sozialkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sozialkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sozialkunde</option>
|
||||||
|
<option value="wirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'wirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'wirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Wirtschaftskunde</option>
|
||||||
|
<option value="religion" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'religion') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'religion') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Religion</option>
|
||||||
|
<option value="ethik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'ethik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'ethik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Ethikunterricht</option>
|
||||||
|
<option value="philosophie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'philosophie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'philosophie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Philosophie</option>
|
||||||
|
<option value="psychologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'psychologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'psychologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Psychologie</option>
|
||||||
|
<option value="kunst" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'kunst') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'kunst') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Kunst</option>
|
||||||
|
<option value="musik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'musik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'musik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Musik</option>
|
||||||
|
<option value="theater" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'theater') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'theater') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Theater</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="Technik & Praxis">
|
||||||
|
<option value="technik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'technik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'technik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Technik</option>
|
||||||
|
<option value="werken" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'werken') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'werken') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Werken</option>
|
||||||
|
<option value="hauswirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Hauswirtschaft</option>
|
||||||
|
<option value="sport" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sport') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sport') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sport</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['old_category'])) {
|
||||||
|
unset($_SESSION['old_category']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<label for="tags">Schlagwörter</label>
|
||||||
|
<input type="text" id="tags" name="tags"
|
||||||
|
value="<?php echo htmlspecialchars($_SESSION['old_tags'] ?? ''); unset($_SESSION['old_tags']); ?>"
|
||||||
|
placeholder="z.B. Technik, IT (mit Komma trennen)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<!--
|
||||||
|
Seite: Datenschutzerklärung
|
||||||
|
Inhalt: Informationen zur Datenverarbeitung gemäß DSGVO
|
||||||
|
-->
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<h1>Datenschutzerklärung</h1>
|
||||||
|
|
||||||
|
<h2>1. Datenschutz auf einen Blick</h2>
|
||||||
|
|
||||||
|
<h3>Allgemeine Hinweise</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die folgenden Hinweise geben einen einfachen Überblick darüber,
|
||||||
|
was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen.
|
||||||
|
Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.
|
||||||
|
Ausführliche Informationen zum Datenschutz finden Sie in dieser Datenschutzerklärung.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Datenerfassung auf dieser Website</h3>
|
||||||
|
|
||||||
|
<h4>Wer ist verantwortlich für die Datenerfassung?</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die Datenverarbeitung erfolgt durch den Betreiber dieser Website.
|
||||||
|
Die Kontaktdaten finden Sie im Abschnitt
|
||||||
|
„Hinweis zur verantwortlichen Stelle“.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Wie erfassen wir Ihre Daten?</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Personenbezogene Daten werden erhoben, wenn Sie diese freiwillig
|
||||||
|
im Rahmen der Nutzung der Plattform angeben.
|
||||||
|
Dies betrifft insbesondere die Registrierung eines Benutzerkontos,
|
||||||
|
Änderungen am Benutzerprofil sowie das Veröffentlichen von Beiträgen
|
||||||
|
und Kommentaren.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Darüber hinaus werden beim Besuch der Website automatisch technische
|
||||||
|
Informationen wie Browsertyp, Betriebssystem oder Zeitpunkt des Seitenaufrufs
|
||||||
|
verarbeitet. Diese Daten dienen ausschließlich der technischen Bereitstellung
|
||||||
|
und Sicherheit der Website.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Wofür nutzen wir Ihre Daten?</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die erhobenen Daten werden verwendet, um Ihr Benutzerkonto bereitzustellen,
|
||||||
|
die Funktionen der Plattform zu ermöglichen sowie Beiträge und Kommentare
|
||||||
|
den jeweiligen Benutzerkonten zuzuordnen.
|
||||||
|
Außerdem werden technische Daten verarbeitet, um einen sicheren und
|
||||||
|
fehlerfreien Betrieb der Website zu gewährleisten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Welche Rechte haben Sie bezüglich Ihrer Daten?</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sie haben jederzeit das Recht auf Auskunft über Ihre gespeicherten
|
||||||
|
personenbezogenen Daten sowie auf Berichtigung oder Löschung dieser Daten.
|
||||||
|
Darüber hinaus können Sie eine erteilte Einwilligung jederzeit mit Wirkung
|
||||||
|
für die Zukunft widerrufen. Außerdem steht Ihnen ein Beschwerderecht bei der
|
||||||
|
zuständigen Datenschutzaufsichtsbehörde zu.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>2. Hosting</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Diese Website wird auf einem Server der Carl von Ossietzky Universität
|
||||||
|
Oldenburg im Rahmen eines Studienprojekts betrieben.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Hosting</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Zur Bereitstellung der Website werden technisch notwendige Daten,
|
||||||
|
beispielsweise IP-Adressen oder Server-Logdaten, verarbeitet.
|
||||||
|
Diese Daten dienen ausschließlich dem sicheren Betrieb der Website.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>3. Allgemeine Hinweise und Pflichtinformationen</h2>
|
||||||
|
|
||||||
|
<h3>Datenschutz</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die Betreiber dieser Website behandeln personenbezogene Daten vertraulich
|
||||||
|
und entsprechend der Datenschutz-Grundverordnung (DSGVO) sowie den
|
||||||
|
geltenden gesetzlichen Datenschutzvorschriften.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Personenbezogene Daten werden nur in dem Umfang verarbeitet,
|
||||||
|
wie dies für die Bereitstellung der Plattform und ihrer Funktionen
|
||||||
|
erforderlich ist.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Registrierung und Benutzerkonto</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bei der Registrierung werden personenbezogene Daten wie
|
||||||
|
Vorname, Nachname, E-Mail-Adresse und ein verschlüsseltes Passwort
|
||||||
|
verarbeitet.
|
||||||
|
Diese Daten dienen ausschließlich der Bereitstellung und Verwaltung
|
||||||
|
Ihres Benutzerkontos.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Beiträge und Kommentare</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Wenn Sie Beiträge oder Kommentare veröffentlichen,
|
||||||
|
werden diese gemeinsam mit Ihrem Benutzerkonto gespeichert
|
||||||
|
und auf der Plattform angezeigt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Hinweis zur verantwortlichen Stelle</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Verantwortlich für die Datenverarbeitung ist:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Carl von Ossietzky Universität Oldenburg<br>
|
||||||
|
Ammerländer Heerstraße 114–118<br>
|
||||||
|
E-Mail: GruppeC@uol.de
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Verantwortliche Stelle ist die natürliche oder juristische Person,
|
||||||
|
die über Zwecke und Mittel der Verarbeitung personenbezogener Daten entscheidet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Speicherdauer</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Personenbezogene Daten werden nur solange gespeichert,
|
||||||
|
wie dies zur Erfüllung des jeweiligen Zwecks erforderlich ist
|
||||||
|
oder gesetzliche Aufbewahrungspflichten bestehen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die Verarbeitung personenbezogener Daten erfolgt entsprechend den
|
||||||
|
gesetzlichen Vorgaben der DSGVO.
|
||||||
|
Je nach Verarbeitung erfolgt diese insbesondere auf Grundlage
|
||||||
|
von Art. 6 Abs. 1 lit. a, b, c oder f DSGVO.
|
||||||
|
</p>
|
||||||
|
<h3>Empfänger von personenbezogenen Daten</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Personenbezogene Daten werden grundsätzlich nicht an Dritte weitergegeben,
|
||||||
|
sofern hierfür keine gesetzliche Verpflichtung besteht oder dies zur
|
||||||
|
Bereitstellung der Plattform erforderlich ist.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sofern die Verarbeitung personenbezogener Daten auf Ihrer Einwilligung
|
||||||
|
beruht, können Sie diese Einwilligung jederzeit mit Wirkung für die Zukunft
|
||||||
|
widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Verarbeitung
|
||||||
|
bleibt hiervon unberührt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Widerspruchsrecht nach Art. 21 DSGVO</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Werden personenbezogene Daten auf Grundlage von Art. 6 Abs. 1 lit. e oder
|
||||||
|
lit. f DSGVO verarbeitet, haben Sie das Recht, aus Gründen, die sich aus
|
||||||
|
Ihrer besonderen Situation ergeben, jederzeit Widerspruch gegen diese
|
||||||
|
Verarbeitung einzulegen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Beschwerderecht bei der zuständigen Aufsichtsbehörde</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Im Falle datenschutzrechtlicher Verstöße haben Betroffene das Recht,
|
||||||
|
Beschwerde bei einer zuständigen Datenschutzaufsichtsbehörde einzulegen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Recht auf Datenübertragbarkeit</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sie haben das Recht, personenbezogene Daten, die wir auf Grundlage Ihrer
|
||||||
|
Einwilligung oder zur Vertragserfüllung automatisiert verarbeiten,
|
||||||
|
in einem gängigen, maschinenlesbaren Format zu erhalten oder an einen
|
||||||
|
anderen Verantwortlichen übertragen zu lassen, soweit dies technisch
|
||||||
|
möglich ist.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Auskunft, Berichtigung und Löschung</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sie haben jederzeit das Recht auf unentgeltliche Auskunft über Ihre
|
||||||
|
gespeicherten personenbezogenen Daten sowie auf Berichtigung,
|
||||||
|
Einschränkung der Verarbeitung oder Löschung dieser Daten,
|
||||||
|
soweit dem keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Recht auf Einschränkung der Verarbeitung</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Unter den gesetzlichen Voraussetzungen können Sie die Einschränkung der
|
||||||
|
Verarbeitung Ihrer personenbezogenen Daten verlangen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Cookies</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Diese Website verwendet ausschließlich technisch notwendige Cookies.
|
||||||
|
Diese dienen beispielsweise der Sitzungsverwaltung nach der Anmeldung
|
||||||
|
sowie der sicheren und fehlerfreien Bereitstellung der Website.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Es werden keine Analyse-, Marketing- oder Tracking-Cookies eingesetzt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sie können Ihren Browser so konfigurieren, dass Sie über das Setzen von
|
||||||
|
Cookies informiert werden oder Cookies vollständig ablehnen.
|
||||||
|
Dadurch können einzelne Funktionen der Website eingeschränkt sein.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Änderungen dieser Datenschutzerklärung</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Der Betreiber behält sich vor, diese Datenschutzerklärung anzupassen,
|
||||||
|
wenn dies aufgrund technischer oder rechtlicher Änderungen erforderlich
|
||||||
|
wird.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</main>
|
||||||
+36
-27
@@ -1,40 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
include_once 'php/controller/home-controller.php';
|
||||||
|
?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Seite: Home
|
Seite: Home
|
||||||
Inhalt: Beinhaltet den Inhalt der Startseite
|
Inhalt: Beinhaltet den Inhalt der Startseite
|
||||||
-->
|
-->
|
||||||
<main>
|
<main>
|
||||||
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
|
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<p>
|
<p>
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
Schön, dass du da bist. EduForge ist deine digitale Werkstatt für Lernerfolg. Wir unterstützen Schüler und
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
Studierende dabei, die passenden Lernmaterialien zu finden, Wissenslücken zu schließen und Gelerntes gezielt
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
zu vertiefen.
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Flexbox -->
|
<!-- Flexbox für aktive Kategorien -->
|
||||||
<div class="flexbox">
|
<div class="flexbox">
|
||||||
<div class="container">
|
<?php
|
||||||
<a href="content/informatik.php" class="category-link">Informatik</a>
|
if (!empty($categoriesWithArticles) && is_array($categoriesWithArticles)):
|
||||||
<div class="article-link">
|
foreach ($categoriesWithArticles as $category):
|
||||||
<a href="articles/datenschutz.php">Datenschutz vs. Datensicherheit</a>
|
?>
|
||||||
</div>
|
<div class="container">
|
||||||
</div>
|
<a href="index.php?pfad=showCategory&category=<?php echo htmlspecialchars($category['slug']); ?>" class="category-link">
|
||||||
|
<?php echo htmlspecialchars($category['name']); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="container">
|
<!-- die 5 neuesten Beiträge der Kategorie -->
|
||||||
<a href="content/mathe.php" class="category-link">Mathe</a>
|
<div class="article-links-wrapper">
|
||||||
<div class="article-link">
|
<?php foreach ($category['articles'] as $article): ?>
|
||||||
<a href="articles/pythagoras.php">Satz des Pythagoras</a>
|
<div class="article-link">
|
||||||
</div>
|
<a href="index.php?pfad=showArticle&id=<?php echo htmlspecialchars($article->getId()); ?>">
|
||||||
</div>
|
<?php echo htmlspecialchars($article->getTitle()); ?>
|
||||||
|
</a>
|
||||||
<div class="container">
|
</div>
|
||||||
<a href="content/physik.php" class="category-link">Physik</a>
|
<?php endforeach; ?>
|
||||||
<div class="article-link">
|
</div>
|
||||||
<a href="articles/tunneleffekt.php">Der Tunneleffekt</a>
|
</div>
|
||||||
</div>
|
<?php
|
||||||
</div>
|
endforeach;
|
||||||
|
else:
|
||||||
|
?>
|
||||||
|
<p>Aktuell sind keine Beiträge in den Kategorien vorhanden.</p>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<!--
|
||||||
|
Seite: Impressum
|
||||||
|
Inhalt: wer wie was auf der Webseite
|
||||||
|
-->
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<h1>Impressum</h1>
|
||||||
|
<p>Gruppe C<br />
|
||||||
|
Ammerlaender Heerstrasse, 114-118<br />
|
||||||
|
26129 Oldenburg</p>
|
||||||
|
<h2>Kontakt</h2>
|
||||||
|
<p>Telefon: 0123456789<br />
|
||||||
|
E-Mail: GruppeC@uni-oldenburg.de</p>
|
||||||
|
<p>Quelle: <a href="https://www.e-recht24.de/impressum-generator.html">https://www.e-recht24.de/impressum-generator.html</a></p>
|
||||||
|
|
||||||
|
</main>
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Informatik
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Informatik</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="../articles/datenschutz.php">Datenschutz vs. Datensicherheit</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
$error = $error ?? null;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Form: Login-Bereich
|
||||||
|
Funktion: Benutzerauthentifizierung und Zugang zum eigenen Profil, Erstellen von Beiträgen, etc.
|
||||||
|
-->
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1>Bitte anmelden</h1>
|
||||||
|
|
||||||
|
<?php include_once "includes/alertMessages.php"; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="index.php?pfad=login">
|
||||||
|
|
||||||
|
<p class="input-label">Benutzername/E-Mail-Adresse:</p>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="E-Mail-Adresse"
|
||||||
|
required
|
||||||
|
autofocus>
|
||||||
|
|
||||||
|
<p class="input-label">Passwort:</p>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Passwort"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
value="anmelden"
|
||||||
|
name="loginSubmit"
|
||||||
|
class="button">
|
||||||
|
anmelden
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="register-link">
|
||||||
|
<a href="index.php?pfad=password-forgotten">
|
||||||
|
Passwort vergessen?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="register-link">
|
||||||
|
<a href="index.php?pfad=register">
|
||||||
|
Noch keinen Account? Jetzt hier registrieren!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Mathe
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Mathe</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="../articles/pythagoras.php">Satz des Pythagoras</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<!--
|
||||||
|
Seite: Nutzungsbedingungen
|
||||||
|
Inhalt: Regelungen für die Nutzung der Plattform
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="EduForge">
|
||||||
|
<meta name="author" content="Niklas Ortmann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/x-icon" href="../images/logos/logo_icon.ico">
|
||||||
|
<link rel="stylesheet" href="../css/main.css">
|
||||||
|
<link rel="stylesheet" href="../css/navbar.css">
|
||||||
|
<link rel="stylesheet" href="../css/footer.css">
|
||||||
|
<title>EduForge</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include_once 'includes/navbar.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<h1>Nutzungsbedingungen</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Diese Nutzungsbedingungen regeln die Nutzung der Plattform EduForge.
|
||||||
|
Mit der Registrierung und Nutzung der Website erkennen Nutzerinnen und Nutzer
|
||||||
|
die nachfolgenden Bedingungen an.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>1. Nutzung der Plattform</h2>
|
||||||
|
<p>
|
||||||
|
EduForge dient dem Austausch von Lerninhalten, Beiträgen und Kommentaren.
|
||||||
|
Die Nutzung der Plattform darf ausschließlich im Rahmen der geltenden Gesetze erfolgen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>2. Registrierung und Benutzerkonto</h2>
|
||||||
|
<p>
|
||||||
|
Für die Nutzung bestimmter Funktionen ist eine Registrierung erforderlich.
|
||||||
|
Nutzerinnen und Nutzer verpflichten sich, bei der Registrierung
|
||||||
|
wahrheitsgemäße Angaben zu machen und ihre Zugangsdaten vertraulich zu behandeln.
|
||||||
|
Änderungen der Profildaten können jederzeit über das Benutzerprofil vorgenommen werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>3. Verhalten der Nutzerinnen und Nutzer</h2>
|
||||||
|
<p>
|
||||||
|
Nutzerinnen und Nutzer verpflichten sich zu einem respektvollen Umgang miteinander.
|
||||||
|
Das Veröffentlichen von beleidigenden, diskriminierenden, rechtswidrigen oder
|
||||||
|
anderweitig unangemessenen Inhalten ist untersagt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>4. Veröffentlichung von Inhalten</h2>
|
||||||
|
<p>
|
||||||
|
Nutzerinnen und Nutzer sind für die von ihnen veröffentlichten Inhalte selbst verantwortlich.
|
||||||
|
Dies gilt insbesondere für Beiträge, Kommentare, Bilder und sonstige hochgeladene Dateien.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>5. Benutzerkonto löschen</h2>
|
||||||
|
<p>
|
||||||
|
Nutzerinnen und Nutzer können ihr Benutzerkonto jederzeit über die entsprechende
|
||||||
|
Funktion der Plattform löschen. Personenbezogene Daten werden anschließend gemäß
|
||||||
|
der Datenschutzerklärung verarbeitet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>6. Passwort und Passwort-Reset</h2>
|
||||||
|
<p>
|
||||||
|
Nutzerinnen und Nutzer sind verpflichtet, ihr Passwort geheim zu halten und
|
||||||
|
nicht an Dritte weiterzugeben. Bei Verlust oder Vergessen des Passworts kann
|
||||||
|
die bereitgestellte Passwort-Reset-Funktion genutzt werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>7. Moderation und Entfernung von Inhalten</h2>
|
||||||
|
<p>
|
||||||
|
Der Betreiber behält sich vor, Beiträge, Kommentare oder Benutzerkonten zu sperren
|
||||||
|
oder Inhalte zu entfernen, wenn diese gegen geltendes Recht oder diese
|
||||||
|
Nutzungsbedingungen verstoßen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>8. Urheberrechte</h2>
|
||||||
|
<p>
|
||||||
|
Es dürfen ausschließlich Inhalte veröffentlicht werden, an denen die Nutzerinnen
|
||||||
|
und Nutzer die erforderlichen Rechte besitzen. Durch das Einstellen von Inhalten
|
||||||
|
versichern sie, keine Rechte Dritter zu verletzen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>9. Änderungen der Nutzungsbedingungen</h2>
|
||||||
|
<p>
|
||||||
|
Der Betreiber behält sich vor, diese Nutzungsbedingungen anzupassen, sofern dies
|
||||||
|
für den Betrieb oder die Weiterentwicklung der Plattform erforderlich ist.
|
||||||
|
Über wesentliche Änderungen werden die Nutzerinnen und Nutzer informiert.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include_once 'includes/footer.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
$error = $error ?? null;
|
||||||
|
$success = $success ?? null;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1>Passwort vergessen</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success)): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
<?php echo $success; ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="index.php?pfad=password-forgotten">
|
||||||
|
|
||||||
|
<p class="input-label">E-Mail-Adresse:</p>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="E-Mail-Adresse"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<p class="input-label">Neues Passwort:</p>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Neues Passwort"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
name="passwordForgottenSubmit"
|
||||||
|
class="button">
|
||||||
|
Passwort zurücksetzen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="register-link">
|
||||||
|
<a href="index.php?pfad=login">
|
||||||
|
Zurück zum Login
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Physik
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Physik</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="../articles/tunneleffekt.php">Der Tunneleffekt</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
+215
-78
@@ -1,99 +1,236 @@
|
|||||||
<!--
|
<?php
|
||||||
Seite: Profil
|
include_once 'php/controller/profile-controller.php';
|
||||||
Inhalt: Das eigene Profil, wenn man angemeldet ist. Dort hat man die Möglichkeit seine Angaben zu ändern.
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
$user = $user ?? null;
|
||||||
<meta charset="utf-8">
|
$isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
||||||
<meta name="description" content="EduForge">
|
?>
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<main class="form-page">
|
||||||
|
<div class="flexbox">
|
||||||
|
<div class="container">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
<?php if (!empty($error)): ?>
|
||||||
<div style="float:left; text-align: center;">
|
<p class="alert-message is-error">
|
||||||
<a href="../index.php">
|
<?php echo htmlspecialchars($error); ?>
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
</p>
|
||||||
</a>
|
<?php endif; ?>
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
<form method="post" action="index.php?pfad=profile">
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="form-page">
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
<div class="form-container">
|
<label class="input-label">Vorname</label>
|
||||||
|
<input type="text"
|
||||||
|
name="vorname"
|
||||||
|
class="login-input"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["vorname"] ?? $user["vorname"] ?? ""); ?>"
|
||||||
|
<?php echo $isEditMode ? "" : "readonly"; ?>
|
||||||
|
required>
|
||||||
|
|
||||||
<div class="logo-wrapper">
|
<label class="input-label">Nachname</label>
|
||||||
<h1>Benutzerdaten ändern</h1>
|
<input type="text"
|
||||||
<img src="../images/logo.png" alt="Logo" class="login-logo">
|
name="nachname"
|
||||||
</div>
|
class="login-input"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["nachname"] ?? $user["nachname"] ?? ""); ?>"
|
||||||
|
<?php echo $isEditMode ? "" : "readonly"; ?>
|
||||||
|
required>
|
||||||
|
|
||||||
<form>
|
<label class="input-label">Email-Adresse</label>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
class="login-input"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["email"] ?? $user["email"] ?? ""); ?>"
|
||||||
|
<?php echo $isEditMode ? "" : "readonly"; ?>
|
||||||
|
required>
|
||||||
|
|
||||||
<label class="input-label">Vorname</label>
|
<label class="input-label">
|
||||||
|
<?php echo $isEditMode ? "Neues Passwort" : "Passwort"; ?>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" name="Vorname" class="login-input" required
|
<?php if ($isEditMode): ?>
|
||||||
placeholder="Vorname"
|
<input type="password"
|
||||||
value="<?php //DB-Daten ?>">
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Leer lassen, wenn es gleich bleiben soll">
|
||||||
|
<?php else: ?>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
value="********"
|
||||||
|
readonly>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<label class="input-label">Nachname</label>
|
<br><br>
|
||||||
<input type="text" name="Nachname" class="login-input" required
|
|
||||||
placeholder="Nachname"
|
|
||||||
value="<?php //DB-Daten ?>">
|
|
||||||
|
|
||||||
<label class="input-label">Email-Adresse</label>
|
<?php if ($isEditMode): ?>
|
||||||
<input type="email" name="Email" class="login-input" required
|
<button type="submit"
|
||||||
placeholder="mustermann@web.de"
|
name="saveProfile"
|
||||||
value="<?php //DB-Daten ?>">
|
class="button">
|
||||||
|
|
||||||
<label class="input-label">Passwort</label>
|
|
||||||
<input type="password" name="Passwort" class="login-input"
|
|
||||||
required placeholder="Passwort">
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<button type="submit" class="login-button">
|
|
||||||
Speichern
|
Speichern
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<br><br>
|
<a href="index.php?pfad=profile" class="button">
|
||||||
|
Abbrechen
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="index.php?pfad=profile&edit=1" class="button">
|
||||||
|
Bearbeiten
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<button type="button" class="login-button">
|
</form>
|
||||||
Account löschen
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<form action="php/controller/deleteAccount-controller.php" method="POST">
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
<button type="submit"
|
||||||
|
class="button"
|
||||||
|
onclick="return confirm('Möchtest du deinen Account und alle deine Beiträge wirklich unwiderruflich löschen?');">
|
||||||
|
Account löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h2 class="section-title">Meine Beiträge</h2>
|
||||||
|
|
||||||
|
<div class="articles-list">
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
||||||
|
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Es ist ein interner Fehler aufgetreten. Bitte versuche es erneut.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php elseif (isset($userArticles) && count($userArticles) > 0): ?>
|
||||||
|
|
||||||
|
<?php foreach ($userArticles as $userArticle): ?>
|
||||||
|
<div class="article-item">
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="article-date">
|
||||||
|
<?php echo htmlspecialchars($userArticle->getCreationDate()); ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="article-category">
|
||||||
|
<?php echo htmlspecialchars($userArticle->getCategory()); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="article-title">
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo $userArticle->getID(); ?>">
|
||||||
|
<?php echo htmlspecialchars($userArticle->getTitle()); ?>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tags = $userArticle->getTags();
|
||||||
|
if (isset($tags) && !empty($tags)):
|
||||||
|
?>
|
||||||
|
<div class="article-view-bottom-section">
|
||||||
|
<div class="article-view-tags-label">Tags:</div>
|
||||||
|
|
||||||
|
<div class="article-view-tags-list">
|
||||||
|
<?php
|
||||||
|
$tagArray = is_array($tags) ? $tags : explode(',', $tags);
|
||||||
|
|
||||||
|
foreach ($tagArray as $tag):
|
||||||
|
$trimmedTag = trim($tag);
|
||||||
|
|
||||||
|
if (!empty($trimmedTag)):
|
||||||
|
?>
|
||||||
|
<span class="article-view-tag-item">
|
||||||
|
<?php echo htmlspecialchars($trimmedTag); ?>
|
||||||
|
</span>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=updateArticle&id=<?php echo $userArticle->getID(); ?>"
|
||||||
|
class="edit-link-button">
|
||||||
|
Bearbeiten
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<form action="php/controller/deleteArticle-controller.php" method="POST">
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
<input type="hidden"
|
||||||
|
name="id"
|
||||||
|
value="<?php echo htmlspecialchars($userArticle->getID()); ?>">
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="button"
|
||||||
|
onclick="return confirm('Möchtest du diesen Artikel wirklich löschen?');">
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>Du hast noch keine Beiträge erstellt.</p>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="button"
|
||||||
|
onclick="window.location.href='index.php?pfad=createArticle';">
|
||||||
|
Beitrag erstellen!
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</form>
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="comments-section">
|
||||||
|
|
||||||
|
<h2 class="section-title">Meine Kommentare</h2>
|
||||||
|
|
||||||
|
<!-- Liste aller Kommentare des Nutzers -->
|
||||||
|
<div class="comments-list">
|
||||||
|
|
||||||
|
<?php if (isset($userComments) && count($userComments) > 0): ?>
|
||||||
|
|
||||||
|
<?php foreach ($userComments as $comment): ?>
|
||||||
|
<div class="article-item">
|
||||||
|
|
||||||
|
<!-- Erstellungsdatum des Kommentars -->
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="article-date">
|
||||||
|
<?php echo htmlspecialchars($comment->getCreated()); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inhalt des Kommentars -->
|
||||||
|
<p>
|
||||||
|
<?php echo nl2br(htmlspecialchars($comment->getContent())); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Link zum Beitrag, unter dem der Kommentar geschrieben wurde -->
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo htmlspecialchars($comment->getArticleId()); ?>"
|
||||||
|
class="edit-link-button">
|
||||||
|
Zum Beitrag
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>Du hast noch keine Kommentare geschrieben.</p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
|
<?php unset($_SESSION["message"]); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
</main>
|
||||||
<a href="../legal/impressum.php">Impressum</a>
|
|
||||||
<a href="../legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="../legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
$error = $error ?? null;
|
||||||
|
$success = $success ?? null;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Form: Registrierung
|
||||||
|
Funktion: Erstellung neuer Benutzerkonten
|
||||||
|
-->
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1>Jetzt Registrieren!</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success)): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
<?php echo $success; ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="index.php?pfad=register">
|
||||||
|
|
||||||
|
<p class="input-label">Email:</p>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="mustermann@web.de"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["email"] ?? ""); ?>"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<p class="input-label">Vorname:</p>
|
||||||
|
<input type="text"
|
||||||
|
name="vorname"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Max"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["vorname"] ?? ""); ?>"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<p class="input-label">Nachname:</p>
|
||||||
|
<input type="text"
|
||||||
|
name="nachname"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Mustermann"
|
||||||
|
value="<?php echo htmlspecialchars($_POST["nachname"] ?? ""); ?>"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<p class="input-label">Passwort:</p>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Passwort"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="termsAccepted"
|
||||||
|
value="1"
|
||||||
|
<?php echo isset($_POST["termsAccepted"]) ? "checked" : ""; ?>
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
Ich habe die
|
||||||
|
<a
|
||||||
|
href="index.php?pfad=datenschutz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Datenschutzerklärung
|
||||||
|
</a>
|
||||||
|
und die
|
||||||
|
<a
|
||||||
|
href="index.php?pfad=nutzungsbedingungen"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Nutzungsbedingungen
|
||||||
|
</a>
|
||||||
|
gelesen und akzeptiere sie.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
value="register"
|
||||||
|
name="registerSubmit"
|
||||||
|
class="button">
|
||||||
|
kostenlos registrieren
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION["search_results"] ?? [];
|
||||||
|
$query = $_SESSION["search_query"] ?? "";
|
||||||
|
|
||||||
|
// Sortierung / Kategorie / Limit: GET-Parameter haben Vorrang, sonst
|
||||||
|
// Rückfall auf die zuletzt in der Session gemerkten Werte. So bleibt
|
||||||
|
// z.B. ein reiner Pagination-Link (?page=2) bei der aktuellen
|
||||||
|
// Sortierung/Filterung.
|
||||||
|
$currentSort = $_GET['sort'] ?? ($_SESSION['search_sort'] ?? 'alphabet');
|
||||||
|
if (!in_array($currentSort, ['alphabet', 'likes', 'newest', 'oldest'])) {
|
||||||
|
$currentSort = 'alphabet';
|
||||||
|
}
|
||||||
|
$_SESSION['search_sort'] = $currentSort;
|
||||||
|
|
||||||
|
$currentCategory = strtolower($_GET['category'] ?? ($_SESSION['search_category'] ?? 'all'));
|
||||||
|
$_SESSION['search_category'] = $currentCategory;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : ($_SESSION['search_limit'] ?? 10);
|
||||||
|
if (!in_array($limit, [10, 20, 50, 100])) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['search_limit'] = $limit;
|
||||||
|
|
||||||
|
// Serverseitig sortieren + filtern
|
||||||
|
$filteredResults = sortSearchResults($rawResults, $currentSort);
|
||||||
|
$filteredResults = filterSearchResultsByCategory($filteredResults, $currentCategory);
|
||||||
|
|
||||||
|
$totalResultsCount = count($filteredResults);
|
||||||
|
$totalPages = max(1, (int)ceil($totalResultsCount / $limit));
|
||||||
|
|
||||||
|
$currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
if ($currentPage < 1) {
|
||||||
|
$currentPage = 1;
|
||||||
|
} elseif ($currentPage > $totalPages) {
|
||||||
|
$currentPage = $totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = ($currentPage - 1) * $limit;
|
||||||
|
|
||||||
|
// Dies ist die No-JS-Basisversion: nur die aktuelle Seite wird gerendert.
|
||||||
|
// Bei aktiviertem JS lädt js/results-app.js zusätzlich den kompletten
|
||||||
|
// Datensatz nach (ein einziger Request) und übernimmt Sortierung, Filter
|
||||||
|
// und Pagination danach komplett im Browser, ohne weitere Serveranfragen.
|
||||||
|
$results = array_slice($filteredResults, $offset, $limit);
|
||||||
|
$resultCount = count($results);
|
||||||
|
|
||||||
|
// Kategorien für das Auswahlfeld (Wert => Label)
|
||||||
|
$categories = [
|
||||||
|
'Deutsch' => 'Deutsch', 'Englisch' => 'Englisch', 'Franzoesisch' => 'Französisch',
|
||||||
|
'Latein' => 'Latein', 'Literatur' => 'Literatur', 'Mathematik' => 'Mathematik',
|
||||||
|
'Biologie' => 'Biologie', 'Informatik' => 'Informatik', 'Chemie' => 'Chemie',
|
||||||
|
'Physik' => 'Physik', 'Astronomie' => 'Astronomie', 'Geschichte' => 'Geschichte',
|
||||||
|
'Erdkunde' => 'Erdkunde', 'Sozialkunde' => 'Sozialkunde', 'Wirtschaftskunde' => 'Wirtschaftskunde',
|
||||||
|
'Religion' => 'Religion', 'Ethikunterricht' => 'Ethikunterricht', 'Philosophie' => 'Philosophie',
|
||||||
|
'Psychologie' => 'Psychologie', 'Kunst' => 'Kunst', 'Musik' => 'Musik', 'Theater' => 'Theater',
|
||||||
|
'Technik' => 'Technik', 'Werken' => 'Werken', 'Hauswirtschaft' => 'Hauswirtschaft', 'Sport' => 'Sport',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Suchergebnisse
|
||||||
|
Inhalt: Zeigt die Ergebnisse einer Suche an.
|
||||||
|
Funktioniert ohne JavaScript (serverseitige Sortierung/Filter/Pagination
|
||||||
|
über echte Links + Formulare) und wird bei aktiviertem JS von
|
||||||
|
js/search-results.js vollständig client-seitig übernommen.
|
||||||
|
-->
|
||||||
|
<div class="s-res-layout-grid">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
|
<!-- Links: Seitenleiste für Filter und Suche -->
|
||||||
|
<aside class="s-res-sidebar">
|
||||||
|
|
||||||
|
<!-- Formular 1: startet eine NEUE Suche (löst immer einen Server-Request aus) -->
|
||||||
|
<form action="php/controller/search-results-controller.php" method="GET" id="search-form-id" class="s-res-sidebar-form">
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Suche anpassen</h3>
|
||||||
|
<input type="search" id="site-search" name="q" placeholder="Suchen..." class="nav__search" value="<?php echo htmlspecialchars($query); ?>" maxlength="50" required>
|
||||||
|
<button type="submit" class="nav__search-button">Suchen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Formular 2: sortiert/filtert die BEREITS gefundenen Ergebnisse.
|
||||||
|
Ohne JS: echter Reload dieser Seite, aber ohne erneute Datenbank-Suche.
|
||||||
|
Mit JS: search-results.js übernimmt das live im Browser, der Submit-Button
|
||||||
|
wird dafür nicht benötigt und über <noscript> versteckt. -->
|
||||||
|
<form action="index.php" method="GET" id="s-res-filter-form" class="s-res-sidebar-form">
|
||||||
|
<input type="hidden" name="pfad" value="search-results">
|
||||||
|
<input type="hidden" name="q" value="<?php echo htmlspecialchars($query); ?>">
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Sortierung</h3>
|
||||||
|
<div class="s-res-filter-group">
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="alphabet" class="sort-radio" <?php echo $currentSort === 'alphabet' ? 'checked' : ''; ?>>
|
||||||
|
<span>Alphabetisch</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="likes" class="sort-radio" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?>>
|
||||||
|
<span>Beliebtheit (Likes)</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="newest" class="sort-radio" <?php echo $currentSort === 'newest' ? 'checked' : ''; ?>>
|
||||||
|
<span>Neueste Beiträge</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="oldest" class="sort-radio" <?php echo $currentSort === 'oldest' ? 'checked' : ''; ?>>
|
||||||
|
<span>Älteste Beiträge</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Kategorie filtern</h3>
|
||||||
|
<select id="category-filter" name="category" class="s-res-limit-select" style="width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #cbd5e1;">
|
||||||
|
<option value="all" <?php echo $currentCategory === 'all' ? 'selected' : ''; ?>>Alle Kategorien</option>
|
||||||
|
<?php foreach ($categories as $value => $label): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($value); ?>" <?php echo $currentCategory === strtolower($value) ? 'selected' : ''; ?>><?php echo htmlspecialchars($label); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="nav__search-button">Filter anwenden</button>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="s-res-main-content">
|
||||||
|
|
||||||
|
<div class="s-res-header">
|
||||||
|
<h1 class="s-res-main-title">Suchergebnisse</h1>
|
||||||
|
<p class="s-res-meta"><span id="s-res-result-count"><?php echo $totalResultsCount; ?></span> Treffer für Ihre Suchanfrage "<?php echo htmlspecialchars($query); ?>"</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnisliste -->
|
||||||
|
<div class="s-res-list">
|
||||||
|
<?php if (!empty($results)): ?>
|
||||||
|
|
||||||
|
<?php foreach ($results as $item):
|
||||||
|
$likesCount = getLikeCount($item);
|
||||||
|
?>
|
||||||
|
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
|
||||||
|
<div class="s-res-content">
|
||||||
|
<h2 class="s-res-item-title">
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
|
||||||
|
<?php echo htmlspecialchars($item['title']); ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="s-res-meta-row">
|
||||||
|
<p class="s-res-author">Von: <span class="s-res-author-name"><?php echo htmlspecialchars($item['author']); ?></span></p>
|
||||||
|
|
||||||
|
<span class="s-res-likes">
|
||||||
|
❤️ <?php echo $likesCount; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-res-arrow">→</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php elseif (isset($_SESSION["message"]) && $_SESSION["message"] === "invalid_search_query"): ?>
|
||||||
|
<p>Unzulässige Suchanfrage</p>
|
||||||
|
|
||||||
|
<?php elseif ($query !== "" && $totalResultsCount === 0 && count($rawResults) > 0): ?>
|
||||||
|
<p>Keine Beiträge in dieser Kategorie gefunden.</p>
|
||||||
|
|
||||||
|
<?php elseif ($query !== "" && $totalResultsCount === 0): ?>
|
||||||
|
<p>Keine Beiträge zu diesem Suchbegriff gefunden.</p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php unset($_SESSION["message"]); ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-pagination-footer">
|
||||||
|
|
||||||
|
<!-- Auswahl der Ergebnisse pro Seite. Gehört per form="..." logisch
|
||||||
|
zum Filterformular oben -->
|
||||||
|
<div class="s-res-limit-selector">
|
||||||
|
<label for="s-res-per-page" class="s-res-limit-label">Ergebnisse pro Seite:</label>
|
||||||
|
<select id="s-res-per-page" name="limit" form="s-res-filter-form" class="s-res-limit-select">
|
||||||
|
<option value="10" <?php echo $limit === 10 ? 'selected' : ''; ?>>10</option>
|
||||||
|
<option value="20" <?php echo $limit === 20 ? 'selected' : ''; ?>>20</option>
|
||||||
|
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>50</option>
|
||||||
|
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>100</option>
|
||||||
|
</select>
|
||||||
|
<noscript><button type="submit" form="s-res-filter-form" class="nav__search-button">Übernehmen</button></noscript>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="s-res-page-navigation" aria-label="Seitennavigation">
|
||||||
|
<!-- No-JS-Fallback: -->
|
||||||
|
<noscript>
|
||||||
|
<?php echo renderNoJsPagination($currentPage, $totalPages, $query, $currentSort, $currentCategory, $limit); ?>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- JS-Version: wird per search-results.js befüllt/eingeblendet: -->
|
||||||
|
<div id="js-page-navigation" style="display:none;">
|
||||||
|
<button type="button" class="s-res-page-btn" id="prev-page-btn">«</button>
|
||||||
|
<span id="dynamic-page-numbers"></span>
|
||||||
|
<button type="button" class="s-res-page-btn" id="next-page-btn">»</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zeigt den Inhalt einer simulierten E-Mail an.
|
||||||
|
* Die Datei wird über einen zufällig erzeugten Token geladen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = __DIR__ . "/../data/mails/" . $token . ".html";
|
||||||
|
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
echo "<p>Datei nicht gefunden.</p>";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
<h1>Simulierte E-Mail</h1>
|
||||||
|
|
||||||
|
<?php include $file; ?>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
<?php
|
||||||
|
$comments = [];
|
||||||
|
$mainComments = [];
|
||||||
|
$repliesByParent = [];
|
||||||
|
$articleObj = null;
|
||||||
|
|
||||||
|
include_once 'php/controller/showArticle-controller.php';
|
||||||
|
require_once 'php/model/UserManager.php';
|
||||||
|
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert den vollständigen Namen zu einer gespeicherten E-Mail-Adresse.
|
||||||
|
* Falls kein Benutzer gefunden wird, wird die E-Mail als Ersatz angezeigt.
|
||||||
|
*/
|
||||||
|
function getCommentAuthorName($email, $userManager)
|
||||||
|
{
|
||||||
|
$email = trim($email);
|
||||||
|
$user = $userManager->findUser($email);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vorname = trim($user["vorname"] ?? "");
|
||||||
|
$nachname = trim($user["nachname"] ?? "");
|
||||||
|
|
||||||
|
$fullName = trim($vorname . " " . $nachname);
|
||||||
|
|
||||||
|
return $fullName !== "" ? $fullName : $email;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Ermittelt, ob ohne JavaScript auf einen Kommentar
|
||||||
|
* geantwortet werden soll.
|
||||||
|
*/
|
||||||
|
$replyTo = filter_input(
|
||||||
|
INPUT_GET,
|
||||||
|
"reply_to",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
$replyAuthor = null;
|
||||||
|
|
||||||
|
if ($replyTo !== false && $replyTo !== null) {
|
||||||
|
foreach ($mainComments as $mainComment) {
|
||||||
|
if ($mainComment->getId() === $replyTo) {
|
||||||
|
$replyAuthor = $mainComment->getAuthor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Eine Antwort darf nur auf einen existierenden
|
||||||
|
* Hauptkommentar geschrieben werden.
|
||||||
|
*/
|
||||||
|
if ($replyAuthor === null) {
|
||||||
|
$replyTo = null;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Anzeige für Beiträge
|
||||||
|
Funktion: Stellt einen übergebenen Beitrag dar.
|
||||||
|
-->
|
||||||
|
<!-- Hauptcontainer für die Beitragsansicht (Ausschließlich der Content-Bereich) -->
|
||||||
|
<main class="article-view-container">
|
||||||
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
|
|
||||||
|
<!-- Metadaten & Titel -->
|
||||||
|
<div class="article-view-top-section">
|
||||||
|
|
||||||
|
<div class="category-and-likes-row">
|
||||||
|
<?php if (isset($category) && !empty($category)): ?>
|
||||||
|
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Like-Anzeige und dynamischer Like-Button -->
|
||||||
|
<?php if (isset($articleObj) && $articleObj !== null): ?>
|
||||||
|
<div class="article-view-likes">
|
||||||
|
<span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["user_email"])): ?>
|
||||||
|
<form method="post"
|
||||||
|
action="php/controller/like-controller.php?id=<?php echo $articleObj->getId(); ?>"
|
||||||
|
class="like-toggle-form">
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
<button type="submit" class="like-toggle-btn">
|
||||||
|
<?php echo $articleObj->hasLiked($_SESSION["user_email"]) ? '👎 Gefällt mir nicht mehr' : '👍 Gefällt mir'; ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="login-hint">(Anmelden zum Liken)</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="article-view-title">
|
||||||
|
<?php if (isset($title)) { echo htmlspecialchars($title); } ?>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="article-view-meta">
|
||||||
|
<?php if (isset($name) && !empty($name)): ?>
|
||||||
|
<span class="article-view-author">Von: <strong><?php echo htmlspecialchars($name); ?></strong></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Beitrags-Inhalt -->
|
||||||
|
<div class="article-view-content">
|
||||||
|
<?php if (isset($content)): ?>
|
||||||
|
<?php
|
||||||
|
// Versuchen, den Inhalt von JSON in ein PHP-Array umzuwandeln
|
||||||
|
$blocks = json_decode($content, true);
|
||||||
|
|
||||||
|
// Wenn das JSON valide ist und Blöcke enthält
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($blocks)):
|
||||||
|
foreach ($blocks as $block):
|
||||||
|
if (isset($block['type']) && isset($block['value'])):
|
||||||
|
if ($block['type'] === 'text'): ?>
|
||||||
|
<!-- Textblock mit XSS-Schutz und Erhalt von Zeilenumbrüchen -->
|
||||||
|
<div class="article-view-body block-text">
|
||||||
|
<?php echo nl2br(htmlspecialchars($block['value'])); ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($block['type'] === 'image'): ?>
|
||||||
|
<!-- Bildblock, der auf den relativen Pfad im uploads-Ordner verweist -->
|
||||||
|
<div class="article-view-body block-image">
|
||||||
|
<img src="<?php echo htmlspecialchars($block['value']); ?>" alt="Beitragsbild">
|
||||||
|
</div>
|
||||||
|
<?php endif;
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
else: ?>
|
||||||
|
<!-- Fallback: Wenn der Beitrag alten Reintext aus der DB enthält -->
|
||||||
|
<div class="article-view-body block-text">
|
||||||
|
<?php echo nl2br(htmlspecialchars($content)); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Beitrags-Endbereich (Tags) -->
|
||||||
|
<?php if (isset($tags) && !empty($tags)): ?>
|
||||||
|
<div class="article-view-bottom-section">
|
||||||
|
<div class="article-view-tags-label">Tags:</div>
|
||||||
|
<div class="article-view-tags-list">
|
||||||
|
<?php
|
||||||
|
// Falls $tags ein String ist (z.B. "Web, CSS"), in ein Array umwandeln
|
||||||
|
$tagArray = is_array($tags) ? $tags : explode(',', $tags);
|
||||||
|
foreach ($tagArray as $tag):
|
||||||
|
$trimmedTag = trim($tag);
|
||||||
|
if (!empty($trimmedTag)):
|
||||||
|
?>
|
||||||
|
<span class="article-view-tag-item"><?php echo htmlspecialchars($trimmedTag); ?></span>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="article-comments-section" id="comments">
|
||||||
|
<h2>Kommentare</h2>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["comment_message"])): ?>
|
||||||
|
<div class="alert-message <?php
|
||||||
|
echo ($_SESSION["comment_message_type"] ?? "") === "success"
|
||||||
|
? "is-success"
|
||||||
|
: "is-error";
|
||||||
|
?>">
|
||||||
|
<?php echo htmlspecialchars($_SESSION["comment_message"]); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
unset($_SESSION["comment_message"]);
|
||||||
|
unset($_SESSION["comment_message_type"]);
|
||||||
|
?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div id="comments-list">
|
||||||
|
<?php if (!empty($mainComments)): ?>
|
||||||
|
<?php foreach ($mainComments as $comment): ?>
|
||||||
|
<div class="comment-item"
|
||||||
|
data-comment-id="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$isDeleted = $comment->getContent()
|
||||||
|
=== "Dieser Kommentar wurde gelöscht.";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($isDeleted): ?>
|
||||||
|
|
||||||
|
<p class="deleted-comment">
|
||||||
|
Dieser Kommentar wurde gelöscht.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
<?php
|
||||||
|
echo htmlspecialchars(
|
||||||
|
getCommentAuthorName(
|
||||||
|
$comment->getAuthor(),
|
||||||
|
$userManager
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<?php echo htmlspecialchars($comment->getCreated()); ?>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
echo nl2br(
|
||||||
|
htmlspecialchars($comment->getContent())
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if (
|
||||||
|
isset($_SESSION["user_email"])
|
||||||
|
&& $_SESSION["user_email"] === $comment->getAuthor()
|
||||||
|
): ?>
|
||||||
|
<details class="edit-comment-details">
|
||||||
|
<summary class="edit-comment-button">
|
||||||
|
Kommentar bearbeiten
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<form method="post"
|
||||||
|
action="index.php?pfad=updateComment"
|
||||||
|
class="edit-comment-form">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getArticleId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<label for="edit-comment-<?php
|
||||||
|
echo htmlspecialchars((string) $comment->getId());
|
||||||
|
?>">
|
||||||
|
Kommentar bearbeiten
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
id="edit-comment-<?php
|
||||||
|
echo htmlspecialchars((string) $comment->getId());
|
||||||
|
?>"
|
||||||
|
name="content"
|
||||||
|
required><?php echo htmlspecialchars(
|
||||||
|
$comment->getContent()
|
||||||
|
); ?></textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Änderungen speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<form method="post"
|
||||||
|
action="index.php?pfad=deleteComment"
|
||||||
|
class="delete-comment-form">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getArticleId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="delete-comment-button"
|
||||||
|
onclick="return confirm('Möchtest du diesen Kommentar wirklich löschen?');">
|
||||||
|
Kommentar löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["user_email"])): ?>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=<?php
|
||||||
|
echo urlencode($_GET["pfad"] ?? "showArticle");
|
||||||
|
?>&id=<?php
|
||||||
|
echo urlencode((string) $comment->getArticleId());
|
||||||
|
?>&reply_to=<?php
|
||||||
|
echo urlencode((string) $comment->getId());
|
||||||
|
?>#comment-form"
|
||||||
|
class="reply-button"
|
||||||
|
data-comment-id="<?php echo htmlspecialchars(
|
||||||
|
(string) $comment->getId()
|
||||||
|
); ?>"
|
||||||
|
data-author="<?php echo htmlspecialchars(
|
||||||
|
$comment->getAuthor()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
Antworten
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="comment-replies">
|
||||||
|
<?php if (isset($repliesByParent[$comment->getId()])): ?>
|
||||||
|
<?php foreach ($repliesByParent[$comment->getId()] as $reply): ?>
|
||||||
|
<div class="comment-item comment-reply">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$isReplyDeleted = $reply->getContent()
|
||||||
|
=== "Dieser Kommentar wurde gelöscht.";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($isReplyDeleted): ?>
|
||||||
|
|
||||||
|
<p class="deleted-comment">
|
||||||
|
Dieser Kommentar wurde gelöscht.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
<?php
|
||||||
|
echo htmlspecialchars(
|
||||||
|
getCommentAuthorName(
|
||||||
|
$reply->getAuthor(),
|
||||||
|
$userManager
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<?php echo htmlspecialchars($reply->getCreated()); ?>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
echo nl2br(
|
||||||
|
htmlspecialchars($reply->getContent())
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if (
|
||||||
|
isset($_SESSION["user_email"])
|
||||||
|
&& $_SESSION["user_email"] === $reply->getAuthor()
|
||||||
|
): ?>
|
||||||
|
|
||||||
|
<details class="edit-comment-details">
|
||||||
|
<summary class="edit-comment-button">
|
||||||
|
Antwort bearbeiten
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<form method="post"
|
||||||
|
action="index.php?pfad=updateComment"
|
||||||
|
class="edit-comment-form">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $reply->getId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $reply->getArticleId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<label for="edit-reply-<?php
|
||||||
|
echo htmlspecialchars((string) $reply->getId());
|
||||||
|
?>">
|
||||||
|
Antwort bearbeiten
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
id="edit-reply-<?php
|
||||||
|
echo htmlspecialchars((string) $reply->getId());
|
||||||
|
?>"
|
||||||
|
name="content"
|
||||||
|
required><?php echo htmlspecialchars(
|
||||||
|
$reply->getContent()
|
||||||
|
); ?></textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Änderungen speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<form method="post"
|
||||||
|
action="index.php?pfad=deleteComment"
|
||||||
|
class="delete-comment-form">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $reply->getId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) $reply->getArticleId()
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="delete-comment-button"
|
||||||
|
onclick="return confirm('Möchtest du diesen Kommentar wirklich löschen?');">
|
||||||
|
Kommentar löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="no-comments-message">
|
||||||
|
Noch keine Kommentare vorhanden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["user_email"])): ?>
|
||||||
|
<form id="comment-form"
|
||||||
|
method="post"
|
||||||
|
action="php/ajax/add-comment.php">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars(
|
||||||
|
(string) ($_GET["id"] ?? "")
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="parent_comment_id"
|
||||||
|
id="parent-comment-id"
|
||||||
|
value="<?php echo $replyTo !== null
|
||||||
|
? htmlspecialchars((string) $replyTo)
|
||||||
|
: "";
|
||||||
|
?>">
|
||||||
|
|
||||||
|
<p id="reply-info"
|
||||||
|
class="reply-info"
|
||||||
|
<?php if ($replyAuthor === null): ?>
|
||||||
|
style="display: none;"
|
||||||
|
<?php endif; ?>>
|
||||||
|
|
||||||
|
<?php if ($replyAuthor !== null): ?>
|
||||||
|
Antwort auf <?php echo htmlspecialchars($replyAuthor); ?>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=<?php
|
||||||
|
echo urlencode($_GET["pfad"] ?? "showArticle");
|
||||||
|
?>&id=<?php
|
||||||
|
echo urlencode((string) ($_GET["id"] ?? ""));
|
||||||
|
?>#comment-form">
|
||||||
|
Abbrechen
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<label for="comment-content">
|
||||||
|
Kommentar
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea name="content"
|
||||||
|
id="comment-content"
|
||||||
|
placeholder="Schreibe einen Kommentar..."
|
||||||
|
required></textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Kommentar senden
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="comment-login-hint">
|
||||||
|
<p>Melde dich an, um einen Kommentar zu schreiben.</p>
|
||||||
|
<a href="index.php?pfad=login" class="button">Jetzt anmelden</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
include_once "php/controller/showCategory-controller.php";
|
||||||
|
require_once __DIR__ . '/../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['category_results'] ?? [];
|
||||||
|
$category = $_SESSION['category_name'] ?? ($_GET['category'] ?? '');
|
||||||
|
|
||||||
|
// Sortierung / Suche / Limit: GET-Parameter haben Vorrang, sonst Rückfall auf
|
||||||
|
// die zuletzt in der Session gemerkten Werte (analog zu search-results.php).
|
||||||
|
$currentSort = $_GET['sort'] ?? ($_SESSION['cat_sort'] ?? 'alphabet');
|
||||||
|
if (!in_array($currentSort, ['alphabet', 'likes', 'newest', 'oldest'])) {
|
||||||
|
$currentSort = 'alphabet';
|
||||||
|
}
|
||||||
|
$_SESSION['cat_sort'] = $currentSort;
|
||||||
|
|
||||||
|
$query = trim($_GET['q'] ?? ($_SESSION['cat_query'] ?? ''));
|
||||||
|
$_SESSION['cat_query'] = $query;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : ($_SESSION['cat_limit'] ?? 10);
|
||||||
|
if (!in_array($limit, [10, 20, 50, 100])) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['cat_limit'] = $limit;
|
||||||
|
|
||||||
|
// Serverseitig sortieren + nach Suchbegriff filtern (Kategorie steht bereits fest)
|
||||||
|
$filteredResults = sortSearchResults($rawResults, $currentSort);
|
||||||
|
$filteredResults = filterResultsByQuery($filteredResults, $query);
|
||||||
|
|
||||||
|
$totalResultsCount = count($filteredResults);
|
||||||
|
$totalPages = max(1, (int)ceil($totalResultsCount / $limit));
|
||||||
|
|
||||||
|
$currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
if ($currentPage < 1) {
|
||||||
|
$currentPage = 1;
|
||||||
|
} elseif ($currentPage > $totalPages) {
|
||||||
|
$currentPage = $totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = ($currentPage - 1) * $limit;
|
||||||
|
|
||||||
|
// Dies ist die No-JS-Basisversion: nur die aktuelle Seite wird gerendert.
|
||||||
|
// Bei aktiviertem JS lädt js/showCategory.js zusätzlich den kompletten
|
||||||
|
// Kategorie-Datensatz nach (ein einziger Request) und übernimmt Sortierung,
|
||||||
|
// Suche und Pagination danach komplett im Browser, ohne weitere Serveranfragen.
|
||||||
|
$results = array_slice($filteredResults, $offset, $limit);
|
||||||
|
$resultCount = count($results);
|
||||||
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Kategorie-Ansicht
|
||||||
|
Inhalt: Zeigt alle Beiträge einer Kategorie an, sortierbar und innerhalb
|
||||||
|
der Kategorie durchsuchbar.
|
||||||
|
Funktioniert ohne JavaScript (serverseitige Sortierung/Suche/Pagination
|
||||||
|
über echte Links + Formulare) und wird bei aktiviertem JS von
|
||||||
|
js/showCategory.js vollständig client-seitig übernommen.
|
||||||
|
-->
|
||||||
|
<div class="s-res-layout-grid">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
|
<!-- Links: Seitenleiste für Suche (innerhalb der Kategorie) und Sortierung -->
|
||||||
|
<aside class="s-res-sidebar">
|
||||||
|
|
||||||
|
<!-- Sortiert/durchsucht die BEREITS geladenen Kategorie-Beiträge.
|
||||||
|
Ohne JS: echter Reload dieser Seite, aber ohne erneute Datenbank-Abfrage.
|
||||||
|
Mit JS: js/showCategory.js übernimmt das live im Browser, der Submit-Button
|
||||||
|
wird dafür nicht benötigt und über <noscript> versteckt. -->
|
||||||
|
<form action="index.php" method="GET" id="cat-filter-form" class="s-res-sidebar-form">
|
||||||
|
<input type="hidden" name="pfad" value="showCategory">
|
||||||
|
<input type="hidden" name="category" value="<?php echo htmlspecialchars($category); ?>">
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">In dieser Kategorie suchen</h3>
|
||||||
|
<input type="search" id="cat-search-input" name="q" placeholder="Suchen..." class="nav__search" value="<?php echo htmlspecialchars($query); ?>" maxlength="50">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Sortierung</h3>
|
||||||
|
<div class="s-res-filter-group">
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="alphabet" class="sort-radio" <?php echo $currentSort === 'alphabet' ? 'checked' : ''; ?>>
|
||||||
|
<span>Alphabetisch</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="likes" class="sort-radio" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?>>
|
||||||
|
<span>Beliebtheit (Likes)</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="newest" class="sort-radio" <?php echo $currentSort === 'newest' ? 'checked' : ''; ?>>
|
||||||
|
<span>Neueste Beiträge</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="oldest" class="sort-radio" <?php echo $currentSort === 'oldest' ? 'checked' : ''; ?>>
|
||||||
|
<span>Älteste Beiträge</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="nav__search-button">Anwenden</button>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="s-res-main-content">
|
||||||
|
|
||||||
|
<div class="s-res-header">
|
||||||
|
<h1 class="s-res-main-title">Kategorie: <?php echo htmlspecialchars($category); ?></h1>
|
||||||
|
<p class="s-res-meta">
|
||||||
|
<span id="s-res-result-count"><?php echo $totalResultsCount; ?></span> Treffer in dieser Kategorie<?php echo $query !== '' ? ' für "' . htmlspecialchars($query) . '"' : ''; ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnisliste -->
|
||||||
|
<div class="s-res-list">
|
||||||
|
<?php if (!empty($results)): ?>
|
||||||
|
|
||||||
|
<?php foreach ($results as $item):
|
||||||
|
$likesCount = getLikeCount($item);
|
||||||
|
?>
|
||||||
|
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
|
||||||
|
<div class="s-res-content">
|
||||||
|
<h2 class="s-res-item-title">
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
|
||||||
|
<?php echo htmlspecialchars($item['title']); ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="s-res-meta-row">
|
||||||
|
<p class="s-res-author">Von: <span class="s-res-author-name"><?php echo htmlspecialchars($item['author']); ?></span></p>
|
||||||
|
|
||||||
|
<span class="s-res-likes">
|
||||||
|
❤️ <?php echo $likesCount; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-res-arrow">→</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php elseif ($query !== ''): ?>
|
||||||
|
<p>Keine Beiträge in dieser Kategorie zu diesem Suchbegriff gefunden.</p>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<p>Keine Beiträge in dieser Kategorie gefunden.</p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php unset($_SESSION["message"]); ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-pagination-footer">
|
||||||
|
|
||||||
|
<!-- Auswahl der Ergebnisse pro Seite -->
|
||||||
|
<div class="s-res-limit-selector">
|
||||||
|
<label for="s-res-per-page" class="s-res-limit-label">Ergebnisse pro Seite:</label>
|
||||||
|
<select id="s-res-per-page" name="limit" form="cat-filter-form" class="s-res-limit-select">
|
||||||
|
<option value="10" <?php echo $limit === 10 ? 'selected' : ''; ?>>10</option>
|
||||||
|
<option value="20" <?php echo $limit === 20 ? 'selected' : ''; ?>>20</option>
|
||||||
|
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>50</option>
|
||||||
|
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>100</option>
|
||||||
|
</select>
|
||||||
|
<noscript><button type="submit" form="cat-filter-form" class="nav__search-button">Übernehmen</button></noscript>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="s-res-page-navigation" aria-label="Seitennavigation">
|
||||||
|
<!-- No-JS-Fallback: -->
|
||||||
|
<noscript>
|
||||||
|
<?php echo renderNoJsPaginationCategory($currentPage, $totalPages, $category, $currentSort, $query, $limit); ?>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- JS-Version: wird per js/showCategory.js befüllt/eingeblendet: -->
|
||||||
|
<div id="js-page-navigation" style="display:none;">
|
||||||
|
<button type="button" class="s-res-page-btn" id="prev-page-btn">«</button>
|
||||||
|
<span id="dynamic-page-numbers"></span>
|
||||||
|
<button type="button" class="s-res-page-btn" id="next-page-btn">»</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
<?php
|
||||||
|
$user = $user ?? null;
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
include_once 'php/controller/showArticle-controller.php';
|
||||||
|
|
||||||
|
// Aktuelle Blockliste ermitteln:
|
||||||
|
$rawContent = '[]';
|
||||||
|
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])) {
|
||||||
|
$rawContent = $_SESSION['old_content'];
|
||||||
|
unset($_SESSION['old_content']);
|
||||||
|
} elseif (isset($content) && !empty($content)) {
|
||||||
|
$rawContent = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocks = json_decode($rawContent, true);
|
||||||
|
if (!is_array($blocks)) {
|
||||||
|
$blocks = [];
|
||||||
|
}
|
||||||
|
$blocks = array_values($blocks);
|
||||||
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Beitrag bearbeiten
|
||||||
|
Inhalt: Formular für die Bearbeitung eines Beitrags
|
||||||
|
-->
|
||||||
|
<form method="post" action="php/controller/updateArticle-controller.php?id=<?php if(isset($id) && !empty($id)){echo htmlspecialchars($id);}else{$_SESSION["message"] = "missing_id";} ?>" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
||||||
|
|
||||||
|
<?php csrf_field(); ?>
|
||||||
|
|
||||||
|
<main class="editor-main">
|
||||||
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
|
|
||||||
|
<input type="text" id="title" name="title"
|
||||||
|
value="<?php
|
||||||
|
if (isset($_SESSION['old_title']) && !empty($_SESSION['old_title'])){
|
||||||
|
echo htmlspecialchars($_SESSION['old_title']);
|
||||||
|
unset($_SESSION['old_title']);
|
||||||
|
}elseif (isset($title) && !empty($title)){
|
||||||
|
echo htmlspecialchars($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>"
|
||||||
|
placeholder="Titel hier eingeben" required>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Content-Blöcke: werden serverseitig als echte, benannte Formularfelder gerendert
|
||||||
|
(blocks[i][type]...). Dadurch funktioniert das Hinzufügen/Entfernen von Blöcken und
|
||||||
|
der Bild-Upload auch ganz ohne JavaScript über einen normalen Formular-Submit.
|
||||||
|
Ist JavaScript aktiv, fängt js/editor.js diese Submits ab und erledigt dieselbe
|
||||||
|
Änderung lokal im DOM, ohne den Server zu belasten.
|
||||||
|
-->
|
||||||
|
<div id="block-container">
|
||||||
|
<?php foreach ($blocks as $i => $block): ?>
|
||||||
|
<?php
|
||||||
|
$blockType = $block['type'] ?? '';
|
||||||
|
if ($blockType !== 'text' && $blockType !== 'image') {
|
||||||
|
continue; // unbekannter/kaputter Block wird übersprungen
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="editor-block article-editor-scope" data-index="<?php echo (int)$i; ?>">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][type]" value="<?php echo htmlspecialchars($blockType); ?>">
|
||||||
|
|
||||||
|
<?php if ($blockType === 'text'): ?>
|
||||||
|
<textarea name="blocks[<?php echo (int)$i; ?>][text]"
|
||||||
|
placeholder="Schreibe deinen Textblock..."><?php echo htmlspecialchars($block['value'] ?? ''); ?></textarea>
|
||||||
|
<?php else: /* image */ ?>
|
||||||
|
<?php if (!empty($block['value'])): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($block['value']); ?>"
|
||||||
|
class="block-image-preview"
|
||||||
|
style="max-width:200px;display:block;margin-top:10px;">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][existing_image]" value="<?php echo htmlspecialchars($block['value']); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" name="blocks[<?php echo (int)$i; ?>][image]" accept="image/*">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button type="submit" name="editor_action" value="delete_block:<?php echo (int)$i; ?>" class="delete-block-btn">✕</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="add-block-control" class="article-editor-scope add-block-control">
|
||||||
|
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
||||||
|
<div id="block-popup" class="article-editor-scope block-popup">
|
||||||
|
<button type="submit" name="editor_action" value="add_text" data-type="text">Textblock</button>
|
||||||
|
<button type="submit" name="editor_action" value="add_image" data-type="image">Bild einfügen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Seitenleiste -->
|
||||||
|
<aside class="editor-sidebar">
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<button type="submit" id="editor-button" class="button">Änderungen speichern</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<label for="category">Kategorie <span class="required">*</span></label>
|
||||||
|
<select id="category" name="category" required>
|
||||||
|
<option disabled <?php
|
||||||
|
if ((!isset($_SESSION['old_category']) || empty($_SESSION['old_category'])) && (!isset($category) || empty($category))) {
|
||||||
|
echo 'selected';
|
||||||
|
}
|
||||||
|
?>>Kategorie wählen...</option>
|
||||||
|
|
||||||
|
<optgroup label="Sprachen">
|
||||||
|
<option value="deutsch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'deutsch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'deutsch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Deutsch</option>
|
||||||
|
<option value="englisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'englisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'englisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Englisch</option>
|
||||||
|
<option value="franzoesisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'franzoesisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'franzoesisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Französisch</option>
|
||||||
|
<option value="latein" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'latein') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'latein') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Latein</option>
|
||||||
|
<option value="literatur" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'literatur') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'literatur') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Literatur</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="MINT">
|
||||||
|
<option value="mathe" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'mathe') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'mathe') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Mathematik</option>
|
||||||
|
<option value="biologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'biologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'biologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Biologie</option>
|
||||||
|
<option value="chemie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'chemie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'chemie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Chemie</option>
|
||||||
|
<option value="physik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'physik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'physik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Physik</option>
|
||||||
|
<option value="informatik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'informatik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'informatik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Informatik</option>
|
||||||
|
<option value="astronomie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'astronomie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'astronomie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Astronomie</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="Gesellschaft & Werte">
|
||||||
|
<option value="geschichte" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'geschichte') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'geschichte') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Geschichte</option>
|
||||||
|
<option value="erdkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'erdkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'erdkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Erdkunde</option>
|
||||||
|
<option value="sozialkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sozialkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sozialkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sozialkunde</option>
|
||||||
|
<option value="wirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'wirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'wirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Wirtschaftskunde</option>
|
||||||
|
<option value="religion" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'religion') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'religion') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Religion</option>
|
||||||
|
<option value="ethik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'ethik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'ethik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Ethikunterricht</option>
|
||||||
|
<option value="philosophie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'philosophie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'philosophie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Philosophie</option>
|
||||||
|
<option value="psychologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'psychologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'psychologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Psychologie</option>
|
||||||
|
<option value="kunst" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'kunst') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'kunst') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Kunst</option>
|
||||||
|
<option value="musik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'musik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'musik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Musik</option>
|
||||||
|
<option value="theater" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'theater') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'theater') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Theater</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<optgroup label="Technik & Praxis">
|
||||||
|
<option value="technik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'technik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'technik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Technik</option>
|
||||||
|
<option value="werken" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'werken') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'werken') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Werken</option>
|
||||||
|
<option value="hauswirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Hauswirtschaft</option>
|
||||||
|
<option value="sport" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sport') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sport') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sport</option>
|
||||||
|
</optgroup>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['old_category'])) {
|
||||||
|
unset($_SESSION['old_category']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-block">
|
||||||
|
<label for="tags">Schlagwörter</label>
|
||||||
|
<input type="text" id="tags" name="tags"
|
||||||
|
value="<?php
|
||||||
|
if (isset($_SESSION['old_tags']) && !empty($_SESSION['old_tags'])){
|
||||||
|
echo htmlspecialchars($_SESSION['old_tags']);
|
||||||
|
unset($_SESSION['old_tags']);
|
||||||
|
} elseif (isset($tags) && !empty($tags)){
|
||||||
|
echo htmlspecialchars($tags);
|
||||||
|
}
|
||||||
|
?>"
|
||||||
|
placeholder="z.B. Technik, IT (mit Komma trennen)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/* editor in Flexbox */
|
||||||
|
.article-editor-scope.editor-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
color: #1e1e1e;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto; /* Zentriert den gesamten Container horizontal */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hauptbereich */
|
||||||
|
.article-editor-scope .editor-main {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope #title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope #content {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seitenleiste */
|
||||||
|
.article-editor-scope .editor-sidebar {
|
||||||
|
width: 300px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .sidebar-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .sidebar-block label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .required {
|
||||||
|
color: #d94f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formularelemente innerhalb der Editor-Sidebar */
|
||||||
|
.article-editor-scope .editor-sidebar select,
|
||||||
|
.article-editor-scope .editor-sidebar input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button-Zeugs */
|
||||||
|
.button#editor-button {
|
||||||
|
padding: 18px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container für die dynamisch per JS eingefügten Blöcke */
|
||||||
|
.article-editor-scope #block-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling für jeden einzelnen dynamisch generierten Block */
|
||||||
|
.article-editor-scope .editor-block {
|
||||||
|
position: relative; /* Wichtig für die absolute Positionierung des Lösch-Buttons */
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px dashed #cccccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea innerhalb eines dynamischen Textblocks */
|
||||||
|
.article-editor-scope .editor-block textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Komfort-Löschbutton oben rechts an jedem Block */
|
||||||
|
.article-editor-scope .delete-block-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: -10px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e74c3c;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .delete-block-btn:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Steuerungselement für den Plus-Button und das Pop-up */
|
||||||
|
.article-editor-scope .add-block-control {
|
||||||
|
position: relative; /* Dient als Anker für das absolut positionierte Pop-up */
|
||||||
|
margin-top: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
align-self: flex-start; /* Verhindert, dass der Button die volle Breite spannt */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Der runde Plus-Button */
|
||||||
|
.article-editor-scope .plus-button {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .plus-button:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Das Pop-up Menü */
|
||||||
|
.article-editor-scope .block-popup {
|
||||||
|
position: absolute;
|
||||||
|
left: 60px; /* Platziert das Menü rechts neben dem Plus-Button */
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%); /* Zentriert das Menü vertikal zum Button */
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Die entscheidende Klasse zum Ausblenden */
|
||||||
|
.article-editor-scope .block-popup.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons im Pop-up (Textblock / Bild einfügen) */
|
||||||
|
.article-editor-scope .block-popup button {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dcdde1;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #2f3640;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .block-popup button:hover {
|
||||||
|
background-color: #f1f2f6;
|
||||||
|
border-color: #b2bec3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.article-editor-scope.editor-container {
|
||||||
|
/* Sidebar nach unten */
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .editor-main {
|
||||||
|
/* weniger Innenabstand */
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope #content {
|
||||||
|
/* Verhindert, dass das Textfeld auf kleinen Displays kollabiert */
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .editor-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
/* Entfernt den linken Rand und setzt ihn nach oben als Trenner */
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .editor-block textarea {
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* Footer‑Grundlayout */
|
||||||
|
footer {
|
||||||
|
width: 100%;
|
||||||
|
background: #333d43;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 12px;
|
||||||
|
transition: color 0.2s ease; /* Farbwechsel */
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover,
|
||||||
|
footer a:focus {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
+250
-35
@@ -1,22 +1,37 @@
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
max-width:1200px;
|
max-width:1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #eef2f7;
|
background-color: #eef2f7;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto; /* Header(auto), Inhalt (bekommt Rest), Footer(auto) */
|
||||||
|
}
|
||||||
|
|
||||||
|
a, button, input, select, textarea, label, main{
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flexbox {
|
.flexbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 20px;
|
flex-wrap: wrap;
|
||||||
margin-top: 30px;
|
gap: 20px;
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -26,6 +41,7 @@ body {
|
|||||||
border: 1px solid #dbe3ec;
|
border: 1px solid #dbe3ec;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
flex: 1 1 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container:hover {
|
.container:hover {
|
||||||
@@ -34,15 +50,15 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-link {
|
.category-link {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background-color: #2563eb;
|
background-color: #2563eb;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,9 +82,9 @@ body {
|
|||||||
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.container {
|
.container {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Seite wird horizontal und vertikal zentriert */
|
/* Seite wird horizontal und vertikal zentriert */
|
||||||
@@ -114,13 +130,6 @@ body {
|
|||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-label {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
width: 100%;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-input {
|
.login-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@@ -143,23 +152,34 @@ body {
|
|||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-button {
|
.button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 14px;
|
||||||
background-color: #2563eb;
|
background-color: #2563eb;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: 2px solid transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease,
|
||||||
|
border-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-button:hover {
|
.button:hover {
|
||||||
|
background-color: #1e40af;
|
||||||
|
border-color: #93c5fd;
|
||||||
|
transform: translateY(-3px) scale(1.01);
|
||||||
|
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:active {
|
||||||
background-color: #1d4ed8;
|
background-color: #1d4ed8;
|
||||||
transform: translateY(-2px);
|
transform: translateY(1px) scale(0.99);
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
box-shadow: 0 2px 5px rgba(37, 99, 235, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-link {
|
.register-link {
|
||||||
@@ -182,7 +202,7 @@ body {
|
|||||||
|
|
||||||
/* Versteckt das doppelte Label fuer Screenreader/Accessibility */
|
/* Versteckt das doppelte Label fuer Screenreader/Accessibility */
|
||||||
.screenreader-only {
|
.screenreader-only {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/* Allgemeine Formularseiten */
|
/* Allgemeine Formularseiten */
|
||||||
.form-page {
|
.form-page {
|
||||||
@@ -194,9 +214,204 @@ body {
|
|||||||
.form-container {
|
.form-container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
flex: 1 1 450px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #dbe3ec;
|
border: 1px solid #dbe3ec;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
|
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Darstellung der Registrierungsbestätigung */
|
||||||
|
.confirm-message {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anpassung des Login-Buttons auf der Bestätigungsseite */
|
||||||
|
.confirm-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deutlichere Klickreaktion für Kategorien */
|
||||||
|
.category-link:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sichtbare Tastatur-Markierung */
|
||||||
|
.button:focus-visible,
|
||||||
|
.category-link:focus-visible,
|
||||||
|
.article-link a:focus-visible,
|
||||||
|
.register-link a:focus-visible {
|
||||||
|
outline: 3px solid #fbbf24;
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
/* Kommentarbereich */
|
||||||
|
|
||||||
|
#comments-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list > div,
|
||||||
|
.comment {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #dbe3ec;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease,
|
||||||
|
border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list > div:hover,
|
||||||
|
.comment:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
border-color: #93c5fd;
|
||||||
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list textarea,
|
||||||
|
#comment-content {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
resize: vertical;
|
||||||
|
transition:
|
||||||
|
border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list textarea:focus,
|
||||||
|
#comment-content:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2563eb;
|
||||||
|
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list a {
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition:
|
||||||
|
color 0.2s ease,
|
||||||
|
background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list a:hover {
|
||||||
|
color: #1e40af;
|
||||||
|
background-color: #dbeafe;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.delete-comment-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #dc2626;
|
||||||
|
border: 2px solid #dc2626;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background-color 0.25s ease,
|
||||||
|
color 0.25s ease,
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-comment-button:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
color: #ffffff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 14px rgba(220,38,38,0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-comment-button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.delete-comment-form {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
/* Button zum Öffnen der Kommentarbearbeitung */
|
||||||
|
.edit-comment-button {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
padding: 10px 18px;
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 2px solid #2563eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
border-color 0.2s ease,
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Entfernt das normale Dreieck in einigen Browsern */
|
||||||
|
.edit-comment-button::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Eigenes Symbol vor dem Text */
|
||||||
|
.edit-comment-button::before {
|
||||||
|
content: "✏ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-comment-button:hover {
|
||||||
|
background-color: #1e40af;
|
||||||
|
border-color: #1e40af;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 12px rgba(37, 99, 235, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-comment-button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-comment-button:focus-visible {
|
||||||
|
outline: 3px solid #fbbf24;
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Abstand zwischen Bearbeiten und Löschen */
|
||||||
|
.edit-comment-details {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Geöffneter Bearbeitungsbereich */
|
||||||
|
.edit-comment-details[open] .edit-comment-button {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background-color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Geändertes Symbol, wenn der Bereich geöffnet ist */
|
||||||
|
.edit-comment-details[open] .edit-comment-button::before {
|
||||||
|
content: "▲ ";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
.alert-message {
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 12px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rote Fehlermeldung */
|
||||||
|
.alert-message.is-error {
|
||||||
|
color: #ba1a1a;
|
||||||
|
background-color: #ffeede;
|
||||||
|
border-color: #ffb4ab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grüne Erfolgsmeldung */
|
||||||
|
.alert-message.is-success {
|
||||||
|
color: #006e2c;
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
border-color: #b2dfdb;
|
||||||
|
}
|
||||||
+416
@@ -0,0 +1,416 @@
|
|||||||
|
/*
|
||||||
|
CSS für die navbar
|
||||||
|
*/
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #333d43;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__left,
|
||||||
|
.nav__right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__logo img {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__item {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
background: #f9f9f9;
|
||||||
|
min-width: 12rem;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,.15);
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-menu a {
|
||||||
|
padding: 0.5rem 1rem; /* Abstand um den Text */
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-menu a:hover {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__link {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 600;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px; /* Abgerundete Ecken */
|
||||||
|
background: #fff;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search input {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown:hover .nav__dropdown-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__mobile-submenu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-form {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassung unter 1240px */
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
.nav {
|
||||||
|
flex-wrap: wrap; /* zweite Nav-Zeile*/
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__left {
|
||||||
|
order: 1;
|
||||||
|
flex: 1; /* nimmt den Platz ganz links ein */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__right {
|
||||||
|
order: 2;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: flex-end; /* schiebt Buttons nach ganz rechts */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__center {
|
||||||
|
order: 3;
|
||||||
|
flex-basis: 100%; /* Erzwingt, dass dieses Element eine eigene Zeile einnimmt */
|
||||||
|
justify-content: center; /*zentriert die Links in der zweiten Zeile */
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.1); /* Optionale Trennlinie */
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__item {
|
||||||
|
margin: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobile-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassungen unter 900px (für z.B. Smartphones) */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.nav {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__logo img {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__left {
|
||||||
|
order: 2;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__right {
|
||||||
|
order: 3;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobile-nav {
|
||||||
|
display: block;
|
||||||
|
order: 1;
|
||||||
|
background: #333d43;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0.5rem 0.1rem;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* restliche desktop-navbar aus */
|
||||||
|
.nav__center,
|
||||||
|
.nav__right .nav__button,
|
||||||
|
.nav__right .nav__link {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* off canvas menu */
|
||||||
|
.nav__mobile-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: -100%; /* kommt von links */
|
||||||
|
width: 280px;
|
||||||
|
height: 100vh;
|
||||||
|
background: #333d43;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: left 0.3s ease;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
/* Genug Abstand oben rechts, damit Links nicht hinter dem X liegen */
|
||||||
|
padding: 4rem 1.5rem 2rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__checkbox:checked ~ .nav__mobile-menu {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav_mobile-menu.is-active {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__mobile-menu a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid #333d43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__logout-form {
|
||||||
|
display: contents;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__logout-form .nav__button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__mobile-logout-button {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #333d43;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__mobile-submenu {
|
||||||
|
display: block;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__mobile-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Deutlichere Hover-Effekte für die Navigation */
|
||||||
|
|
||||||
|
.nav__dropdown-toggle,
|
||||||
|
.nav__link {
|
||||||
|
border-radius: 6px;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-toggle:hover,
|
||||||
|
.nav__link:hover {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sichtbare Reaktion beim Anklicken */
|
||||||
|
.nav__dropdown-toggle:active,
|
||||||
|
.nav__link:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deutlichere Effekte für Anmelden, Registrieren usw. */
|
||||||
|
.nav__button {
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease,
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__button:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: #ffffff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deutlichere Hervorhebung der Einträge im Dropdown-Menü */
|
||||||
|
.nav__dropdown-menu a {
|
||||||
|
display: block;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease,
|
||||||
|
padding-left 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-menu a:hover {
|
||||||
|
background-color: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
padding-left: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effekt für den Suchbutton */
|
||||||
|
.nav__search-button {
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sichtbare Markierung bei Tastaturbedienung */
|
||||||
|
.nav a:focus-visible,
|
||||||
|
.nav button:focus-visible,
|
||||||
|
.nav input:focus-visible,
|
||||||
|
.nav label:focus-visible {
|
||||||
|
outline: 3px solid #fbbf24;
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
CSS für die Profilseite
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Roter Button für Account löschen */
|
||||||
|
.delete-account-button {
|
||||||
|
background-color: #dc2626;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-account-button:hover {
|
||||||
|
background-color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Beitragsliste Styling */
|
||||||
|
.section-title {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #1f2937;
|
||||||
|
border-bottom: 2px solid #eef2f7;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.articles-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item {
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-category {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bearbeiten-Button als Link deklariert */
|
||||||
|
.edit-link-button {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2563eb;
|
||||||
|
border: 1px solid #2563eb;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-link-button:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
CSS für die Suchergebnis-Seite
|
||||||
|
*/
|
||||||
|
/* Container und Layout-Schutz vor globalen Styles */
|
||||||
|
.s-res-layout-grid {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 95%; /* Nutzt jetzt 95% der Bildschirmbreite statt starrer Pixel */
|
||||||
|
width: 95%; /* Erhöht das Limit für extrem große Monitore von 1200px auf 1600px */
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color: #212529;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
/* Grid für die Zweispaltigkeit */
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px 1fr; /* Seitenleiste leicht verbreitert, Inhalt nimmt den gesamten Rest ein */
|
||||||
|
gap: 2.5rem; /* Etwas mehr Abstand zwischen Seitenleiste und Inhalt für das breitere Layout */
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-layout-grid * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- SEITENLEISTE (FILTER & SUCHE) --- */
|
||||||
|
.s-res-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-sidebar-box {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-sidebar-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formular & deine Suchleisten-Klassen */
|
||||||
|
.s-res-search-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter Radio-Buttons */
|
||||||
|
.s-res-filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-filter-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-filter-option input[type="radio"] {
|
||||||
|
margin: 0;
|
||||||
|
accent-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- HAUPTINHALT (ERGEBNISSE) --- */
|
||||||
|
.s-res-main-content {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-main-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-meta {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Einzelner Beitrag */
|
||||||
|
.s-res-item {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-item-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-link {
|
||||||
|
color: #3182ce;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-author {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-author-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-arrow {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #a0aec0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
transition: color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-item:hover .s-res-arrow {
|
||||||
|
color: #3182ce;
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ERGEBNISSE PRO SEITE & PAGINIERUNG */
|
||||||
|
.s-res-pagination-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown-Auswahl */
|
||||||
|
.s-res-limit-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-limit-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-limit-select {
|
||||||
|
padding: 0.35rem 1.5rem 0.35rem 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #2d3748;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
/* Diskreter nativer Pfeil-Style */
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://w3.org' viewBox='0 0 24 24' fill='none' stroke='%234a5568' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.4rem center;
|
||||||
|
background-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-limit-select:focus {
|
||||||
|
border-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seitennummern Navigation */
|
||||||
|
.s-res-page-navigation {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-page-btn {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
color: #4a5568;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-page-btn:hover:not(:disabled) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-page-btn-active {
|
||||||
|
background-color: #3182ce;
|
||||||
|
border-color: #3182ce;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-page-btn:disabled {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
color: #94a3b8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-meta-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-res-likes {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.s-res-layout-grid {
|
||||||
|
grid-template-columns: 1fr; /* Stapelt Seitenleiste und Inhalt untereinander */
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
.s-res-pagination-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/* Container für den gesamten articleikel */
|
||||||
|
.article-view-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 900px; /* Angenehme Lesebreite für längere Texte */
|
||||||
|
margin: 3rem auto;
|
||||||
|
padding: 0 2rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color: #212529;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-container * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- KOPFDATEN-BEREICH --- */
|
||||||
|
.article-view-top-section {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kategorie-Badge */
|
||||||
|
.article-view-category {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #ebf8ff;
|
||||||
|
color: #2b6cb0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hauptüberschrift */
|
||||||
|
.article-view-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #1a202c;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 0 0 0.75rem 0;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autoren-Zeile */
|
||||||
|
.article-view-meta {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-author strong {
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- INHALT --- */
|
||||||
|
.article-view-content {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: #2d3748;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TAG-BEREICH --- */
|
||||||
|
.article-view-bottom-section {
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-tags-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #718096;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-tags-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Einzelner Tag */
|
||||||
|
.article-view-tag-item {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-tag-item:hover {
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
color: #1e293b;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-content {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem; /* Erzeugt einen sauberen Abstand zwischen den einzelnen Blöcken */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: #2d3748;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-text {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-image {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Zentriert das Bild horizontal */
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-image img {
|
||||||
|
max-width: 100%; /* Verhindert das Ausbrechen aus der Lesebreite */
|
||||||
|
height: auto; /* Behält das originale Seitenverhältnis bei */
|
||||||
|
border-radius: 6px; /* Optionale leichte Rundung für ein moderneres Layout */
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03); /* Minimaler, eleganter Schatten */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassungen unter 760px (für z.B. Smarticlephones) */
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.article-view-container {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-title {
|
||||||
|
font-size: 1.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- KOMMENTARE --- */
|
||||||
|
|
||||||
|
.article-comments-section {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-comments-section h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-content {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 130px;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-form .button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-button {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-button:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-left: 2rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 3px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-reply {
|
||||||
|
background-color: #eef6ff;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-info {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.comment-login-hint {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-login-hint p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Like-Button etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.category-and-likes-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-likes {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-likes .like-count {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-likes .login-hint {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interaktiver Like/Unlike-Button */
|
||||||
|
.like-toggle-btn {
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-toggle-btn:hover {
|
||||||
|
background-color: #eaeaea;
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/* --- ZWEISPALTIIGES GRID-LAYOUT --- */
|
||||||
|
.cat-view-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 95%; /* 95% Monitorbreite wie das Original */
|
||||||
|
width: 95%;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color: #212529;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
/* Grid für die Zweispaltigkeit */
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px 1fr; /* 320px Sidebar, Rest für Content */
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-container * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- SEITENLEISTE (SORTIERUNG) --- */
|
||||||
|
.cat-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-sidebar-box {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-sidebar-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-option input[type="radio"] {
|
||||||
|
margin: 0;
|
||||||
|
accent-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- HAUPTINHALT --- */
|
||||||
|
.cat-view-main {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-meta-text {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BEITRÄGE LISTE --- */
|
||||||
|
.cat-view-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-link {
|
||||||
|
color: #3182ce;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-meta-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-author {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-author-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-likes {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-arrow {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #a0aec0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
transition: color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item:hover .cat-view-arrow {
|
||||||
|
color: #3182ce;
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- FOOTER PAGINATION --- */
|
||||||
|
.cat-view-pagination-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-navigation {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
color: #4a5568;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn:hover:not(:disabled) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .cat-active-btn {
|
||||||
|
background-color: #3182ce;
|
||||||
|
border-color: #3182ce;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn:disabled:not(.cat-active-btn) {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
color: #94a3b8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESPONSIVE ANPASSUNG (Unter 768px) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cat-view-container {
|
||||||
|
grid-template-columns: 1fr; /* Stapelt Sidebar und Inhalt untereinander */
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
.cat-view-pagination-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<FilesMatch "\.db">
|
||||||
|
deny from all
|
||||||
|
</FilesMatch>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 199 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,100 @@
|
|||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Es ist ein interner Fehler beim Speichern aufgetreten. Bitte versuche es erneut.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_parameters"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Jeder Beitrag muss einen Titel, Kategorie und Inhalt besitzen.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_title"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Der Titel enthält ungültige Zeichen oder erfüllt die Länge von 5-120 Zeichen nicht.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_content"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Ein Beitrag muss Inhalt besitzen. Text- und Bildelemente dürfen nicht leer sein!
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_tags"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Ungültige Schlagworte gefunden. Erlaubt sind nur Buchstaben, Zahlen, Leerzeichen und Bindestriche (2-50 Zeichen).
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "validation_missing"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Bei der Validierung deiner Daten ist ein Fehler aufgetreten. Bitte versuche es erneut.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Diese Kategorie ist nicht gültig.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_id"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Die Artikel-ID fehlt oder ist ungültig.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "not_found_article"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Der angeforderte Beitrag konnte nicht gefunden werden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "unauthorized_access"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Du hast keine Berechtigung, diesen Beitrag zu bearbeiten oder zu löschen.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "article_deleted"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Der Beitrag wurde erfolgreich gelöscht.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "new_article"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Dein Beitrag wurde erfolgreich veröffentlicht!
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "article_updated"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Der Beitrag wurde erfolgreich bearbeitet und gespeichert.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "profile_updated"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Das Profil wurde erfolgreich bearbeitet.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "image_upload_error"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Das Bild konnte nicht hochgeladen werden. Bitte versuche es erneut oder verwende ein anderes Bildformat.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "registration_confirmed"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Ihre Registrierung wurde erfolgreich abgeschlossen. Sie können sich jetzt anmelden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "password_changed"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Ihr Passwort wurde erfolgreich geändert. Sie können sich jetzt anmelden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "database_error"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_csrf_token"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Deine Sitzung ist abgelaufen oder die Anfrage konnte nicht überprüft werden.
|
||||||
|
Bitte lade die Seite neu und versuche es erneut.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
unset($_SESSION["message"]);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Baut die Blockliste aus den POST-Daten (blocks[i][type], blocks[i][text],
|
||||||
|
* blocks[i][existing_image]) und ggf. hochgeladenen Dateien (blocks[i][image])
|
||||||
|
* zusammen. Läuft bei JEDEM Submit (Zwischen-Schritt "Block hinzufügen/löschen"
|
||||||
|
* UND finales Speichern/Veröffentlichen), damit neu ausgewählte Bilder in jedem
|
||||||
|
* Fall persistiert werden, bevor PHP die temporäre Upload-Datei nach
|
||||||
|
* Request-Ende verwirft.
|
||||||
|
*
|
||||||
|
* Wird sowohl vom createArticle- als auch vom updateArticle-Controller genutzt.
|
||||||
|
*
|
||||||
|
* @param array $postBlocks $_POST['blocks'] ?? []
|
||||||
|
* @param array $fileBlocks $_FILES['blocks'] ?? []
|
||||||
|
* @param string $uploadDir absoluter Pfad zum uploads-Verzeichnis (mit trailing slash)
|
||||||
|
* @return array Liste von ['type' => 'text'|'image', 'value' => string]
|
||||||
|
*/
|
||||||
|
function rebuildBlocksFromPost(array $postBlocks, array $fileBlocks, string $uploadDir): array {
|
||||||
|
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
|
||||||
|
$keys = array_keys($postBlocks);
|
||||||
|
if (isset($fileBlocks['name']) && is_array($fileBlocks['name'])) {
|
||||||
|
$keys = array_unique(array_merge($keys, array_keys($fileBlocks['name'])));
|
||||||
|
}
|
||||||
|
sort($keys, SORT_NUMERIC);
|
||||||
|
|
||||||
|
$blocks = [];
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$type = $postBlocks[$key]['type'] ?? null;
|
||||||
|
|
||||||
|
if ($type === 'text') {
|
||||||
|
$blocks[] = [
|
||||||
|
'type' => 'text',
|
||||||
|
'value' => $postBlocks[$key]['text'] ?? '',
|
||||||
|
];
|
||||||
|
} elseif ($type === 'image') {
|
||||||
|
// Vorbelegung: bereits vorhandenes Server-Bild (falls Datei nicht ersetzt wird)
|
||||||
|
$value = $postBlocks[$key]['existing_image'] ?? '';
|
||||||
|
|
||||||
|
$hasUpload = isset($fileBlocks['error'][$key]['image'])
|
||||||
|
&& $fileBlocks['error'][$key]['image'] === UPLOAD_ERR_OK;
|
||||||
|
|
||||||
|
if ($hasUpload) {
|
||||||
|
$tmpName = $fileBlocks['tmp_name'][$key]['image'];
|
||||||
|
$originalName = $fileBlocks['name'][$key]['image'];
|
||||||
|
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($extension, $allowedExtensions, true)) {
|
||||||
|
$extension = 'jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = 'img_' . uniqid() . '.' . $extension;
|
||||||
|
$destination = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($tmpName, $destination)) {
|
||||||
|
$value = 'uploads/' . $fileName;
|
||||||
|
}
|
||||||
|
// Bei Fehler: alter Wert (falls vorhanden) bleibt erhalten, Block wird nicht verworfen
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocks[] = [
|
||||||
|
'type' => 'image',
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// unbekannter/fehlender type -> Block wird ignoriert
|
||||||
|
}
|
||||||
|
|
||||||
|
return $blocks;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CSRF-Schutz nach dem Synchronizer-Token-Pattern.
|
||||||
|
*
|
||||||
|
* Pro Session wird ein einziges, zufälliges Token erzeugt,
|
||||||
|
* das in jedem Formular als verstecktes Feld mitgeschickt und bei jeder
|
||||||
|
* zustandsändernden Anfrage serverseitig mit dem Session-Token verglichen
|
||||||
|
* wird.
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das aktuelle CSRF-Token der Session zurück.
|
||||||
|
*
|
||||||
|
* Existiert noch kein Token, wird eines erzeugt und in der Session
|
||||||
|
* gespeichert.
|
||||||
|
*
|
||||||
|
* @return string Das CSRF-Token
|
||||||
|
*/
|
||||||
|
function csrf_token(): string
|
||||||
|
{
|
||||||
|
if (empty($_SESSION["csrf_token"]) || !is_string($_SESSION["csrf_token"])) {
|
||||||
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $_SESSION["csrf_token"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt ein verstecktes Formularfeld mit dem aktuellen CSRF-Token aus.
|
||||||
|
*
|
||||||
|
* Wird in jedem Formular benötigt, das eine zustandsändernde
|
||||||
|
* Aktion auslöst.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function csrf_field(): void
|
||||||
|
{
|
||||||
|
echo '<input type="hidden" name="csrf_token" value="'
|
||||||
|
. htmlspecialchars(csrf_token())
|
||||||
|
. '">';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob das per POST gesendete CSRF-Token zum Session-Token passt.
|
||||||
|
*
|
||||||
|
* Der Vergleich erfolgt zeitkonstant über hash_equals(), um
|
||||||
|
* Timing-Angriffe auf den Vergleich selbst auszuschließen.
|
||||||
|
*
|
||||||
|
* @return bool true, wenn das Token gültig ist
|
||||||
|
*/
|
||||||
|
function csrf_verify(): bool
|
||||||
|
{
|
||||||
|
$sentToken = $_POST["csrf_token"] ?? "";
|
||||||
|
$sessionToken = $_SESSION["csrf_token"] ?? "";
|
||||||
|
|
||||||
|
if (!is_string($sentToken) || $sentToken === "" || $sessionToken === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash_equals($sessionToken, $sentToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bricht die Anfrage ab und leitet mit einer Fehlermeldung um,
|
||||||
|
* wenn das mitgesendete CSRF-Token ungültig oder nicht vorhanden ist.
|
||||||
|
*
|
||||||
|
* Muss am Anfang jeder zustandsändernden POST-Aktion aufgerufen werden,
|
||||||
|
* bevor irgendeine Änderung an Daten vorgenommen wird.
|
||||||
|
*
|
||||||
|
* @param string $redirectTo Ziel-URL, zu der bei ungültigem Token
|
||||||
|
* weitergeleitet wird
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function csrf_require_valid(string $redirectTo = "index.php"): void
|
||||||
|
{
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
http_response_code(403);
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("Location: " . $redirectTo);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<footer>
|
||||||
|
<a href="index.php?pfad=impressum">Impressum</a>
|
||||||
|
<a href="index.php?pfad=datenschutz">Datenschutz</a>
|
||||||
|
<a href="index.php?pfad=nutzungsbedingungen">Nutzungsbedingungen</a>
|
||||||
|
<a href="index.php?pfad=accessibility">Barrierefreiheit</a>
|
||||||
|
</footer>
|
||||||
+176
-22
@@ -1,28 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Globales Menü, wird via PHP später in alle Seiten eingebunden
|
Globales Menü, wird via PHP später in alle Seiten eingebunden
|
||||||
-->
|
-->
|
||||||
<nav style="background:#808080; height: 60px;">
|
<nav class="nav">
|
||||||
<div style="float:left; text-align: center;">
|
<div class="nav__left">
|
||||||
<a href="index.php">
|
<a href="index.php" class="nav__logo">
|
||||||
<img src="images/logo.png" alt="" style="height: 44px;">
|
<img src="images/logos/logo_full.png" alt="Logo">
|
||||||
</a>
|
</a>
|
||||||
|
|
</div>
|
||||||
<a href = "content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
<!-- Mobile navbar mit Burger-Symbol -->
|
||||||
<a href = "content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
<input type="checkbox" id="nav-toggle" class="nav__checkbox">
|
||||||
|
|
<label for="nav-toggle" class="mobile-nav">
|
||||||
<a href = "content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
<span>☰</span>
|
||||||
|
|
</label>
|
||||||
<a href = "content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
<ul id="mobile-nav" class="nav__mobile-menu">
|
||||||
</div>
|
<li>
|
||||||
<div style="float:right; text-align: center;">
|
<label for="nav-toggle" class="nav__close-btn">×</label>
|
||||||
<?php
|
</li>
|
||||||
include_once 'search.php';
|
|
||||||
?>
|
<?php if (isset($_SESSION['user'])): ?>
|
||||||
|
|
|
||||||
<a href = "auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
<li><a href="index.php?pfad=profile">Profil</a></li>
|
||||||
|
<li><a href="index.php?pfad=logout">Abmelden</a></li>
|
||||||
|
<li><a href="index.php?pfad=createArticle">Beitrag erstellen</a></li>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<li><a href="index.php?pfad=login">Anmelden</a></li>
|
||||||
|
<li><a href="index.php?pfad=register">Registrieren</a></li>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label class="nav__mobile-label">Sprachen</label>
|
||||||
|
<ul class="nav__mobile-submenu">
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=deutsch">Deutsch</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=englisch">Englisch</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=franzoesisch">Französisch</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=latein">Latein</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=literatur">Literatur</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label class="nav__mobile-label">MINT</label>
|
||||||
|
<ul class="nav__mobile-submenu">
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=mathe">Mathematik</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=biologie">Biologie</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=chemie">Chemie</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=physik">Physik</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=informatik">Informatik</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=astronomie">Astronomie</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label class="nav__mobile-label">Gesellschaft & Werte</label>
|
||||||
|
<ul class="nav__mobile-submenu">
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=geschichte">Geschichte</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=erdkunde">Erdkunde</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=sozialkunde">Sozialkunde</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=wirtschaft">Wirtschaftskunde</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=religion">Religion</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=ethik">Ethikunterricht</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=philosophie">Philosophie</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=psychologie">Psychologie</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=kunst">Kunst</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=musik">Musik</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=theater">Theater</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label class="nav__mobile-label">Technik & Praxis</label>
|
||||||
|
<ul class="nav__mobile-submenu">
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=technik">Technik</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=werken">Werken</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=hauswirtschaft">Hauswirtschaft</a></li>
|
||||||
|
<li><a href="index.php?pfad=showCategory&category=sport">Sport</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- normale Navbar (Desktop) -->
|
||||||
|
<div class="nav__center">
|
||||||
|
<div class="nav__item nav__dropdown">
|
||||||
|
<button class="nav__dropdown-toggle">Sprachen</button>
|
||||||
|
<div class="nav__dropdown-menu">
|
||||||
|
<a href="index.php?pfad=showCategory&category=deutsch">Deutsch</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=englisch">Englisch</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=franzoesisch">Französisch</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=latein">Latein</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=literatur">Literatur</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav__item nav__dropdown">
|
||||||
|
<button class="nav__dropdown-toggle">MINT</button>
|
||||||
|
<div class="nav__dropdown-menu">
|
||||||
|
<a href="index.php?pfad=showCategory&category=mathe">Mathematik</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=biologie">Biologie</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=chemie">Chemie</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=physik">Physik</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=informatik">Informatik</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=astronomie">Astronomie</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav__item nav__dropdown">
|
||||||
|
<button class="nav__dropdown-toggle">Gesellschaft & Werte</button>
|
||||||
|
<div class="nav__dropdown-menu">
|
||||||
|
<a href="index.php?pfad=showCategory&category=geschichte">Geschichte</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=erdkunde">Erdkunde</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=sozialkunde">Sozialkunde</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=wirtschaft">Wirtschaftskunde</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=religion">Religion</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=ethik">Ethikunterricht</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=philosophie">Philosophie</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=psychologie">Psychologie</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=kunst">Kunst</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=musik">Musik</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=theater">Theater</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav__item nav__dropdown">
|
||||||
|
<button class="nav__dropdown-toggle">Technik & Praxis</button>
|
||||||
|
<div class="nav__dropdown-menu">
|
||||||
|
<a href="index.php?pfad=showCategory&category=technik">Technik</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=werken">Werken</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=hauswirtschaft">Hauswirtschaft</a>
|
||||||
|
<a href="index.php?pfad=showCategory&category=sport">Sport</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav__right">
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['user'])): ?>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=profile" class="nav__item nav__link">
|
||||||
|
Profil
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="nav__search">
|
||||||
|
<?php include_once __DIR__ . '/search.php'; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=createArticle" class="nav__item nav__button">
|
||||||
|
Beitrag erstellen
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=logout" class="nav__item nav__button">
|
||||||
|
Abmelden
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<div class="nav__search">
|
||||||
|
<?php include_once __DIR__ . '/search.php'; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=login" class="nav__item nav__button">
|
||||||
|
Anmelden
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="index.php?pfad=register" class="nav__item nav__button">
|
||||||
|
Registrieren
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<a href = "auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Hilfsfunktionen rund um die Suchergebnisse.
|
||||||
|
*
|
||||||
|
* Werden sowohl von content/search-results.php (No-JS-Rendering)
|
||||||
|
* als auch von php/controller/search-results-data.php (JSON für
|
||||||
|
* die JS-Hydration) genutzt, damit Sortier-/Filterlogik nur an
|
||||||
|
* einer Stelle gepflegt werden muss
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getLikeCount($item): int
|
||||||
|
{
|
||||||
|
if (isset($item['likes']) && is_array($item['likes'])) {
|
||||||
|
return count($item['likes']);
|
||||||
|
}
|
||||||
|
return (int)($item['likes'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sortiert die Ergebnisse
|
||||||
|
*/
|
||||||
|
function sortSearchResults(array $results, string $sortStyle): array
|
||||||
|
{
|
||||||
|
switch ($sortStyle) {
|
||||||
|
case 'likes':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return getLikeCount($b) <=> getLikeCount($a);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'newest':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcmp($b['creationDate'] ?? '', $a['creationDate'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'oldest':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcmp($a['creationDate'] ?? '', $b['creationDate'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'alphabet':
|
||||||
|
default:
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcasecmp($a['title'] ?? '', $b['title'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtert die Ergebnisse ('all' = kein Filter).
|
||||||
|
*/
|
||||||
|
function filterSearchResultsByCategory(array $results, string $category): array
|
||||||
|
{
|
||||||
|
$category = strtolower(trim($category));
|
||||||
|
if ($category === '' || $category === 'all') {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
return array_values(array_filter($results, function ($item) use ($category) {
|
||||||
|
return strtolower($item['category'] ?? '') === $category;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Such-Ergebnis-URL für einen bestimmten Seitenwechsel (No-JS-Pagination).
|
||||||
|
*/
|
||||||
|
function buildSearchResultsUrl(int $page, string $query, string $sort, string $category, int $limit): string
|
||||||
|
{
|
||||||
|
return "index.php?pfad=search-results"
|
||||||
|
. "&q=" . urlencode($query)
|
||||||
|
. "&sort=" . urlencode($sort)
|
||||||
|
. "&category=" . urlencode($category)
|
||||||
|
. "&limit=" . $limit
|
||||||
|
. "&page=" . $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert eine rein serverseitige Pagination für den No-JS-Fall:
|
||||||
|
*/
|
||||||
|
function renderNoJsPagination(int $currentPage, int $totalPages, string $query, string $sort, string $category, int $limit): string
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if ($currentPage > 1) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($currentPage - 1, $query, $sort, $category, $limit)) . '">«</a> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">«</span> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $totalPages; $i++) {
|
||||||
|
if ($i === $currentPage) {
|
||||||
|
$html .= '<span class="s-res-page-btn s-res-page-btn-active">' . $i . '</span> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($i, $query, $sort, $category, $limit)) . '">' . $i . '</a> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentPage < $totalPages) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($currentPage + 1, $query, $sort, $category, $limit)) . '">»</a>';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">»</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtert Ergebnisse anhand eines Suchbegriffs
|
||||||
|
*
|
||||||
|
* Wird von content/showCategory.php genutzt, um innerhalb einer bereits
|
||||||
|
* geladenen Kategorie zu suchen.
|
||||||
|
*/
|
||||||
|
function filterResultsByQuery(array $results, string $query): array
|
||||||
|
{
|
||||||
|
$query = trim($query);
|
||||||
|
if ($query === '') {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
$needle = mb_strtolower($query);
|
||||||
|
return array_values(array_filter($results, function ($item) use ($needle) {
|
||||||
|
$haystack = mb_strtolower(($item['title'] ?? '') . ' ' . ($item['content'] ?? ''));
|
||||||
|
return mb_strpos($haystack, $needle) !== false;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Kategorie-URL für einen bestimmten Seitenwechsel (No-JS-Pagination).
|
||||||
|
* Analog zu buildSearchResultsUrl, aber für pfad=showCategory (feste Kategorie,
|
||||||
|
* dafür mit Freitext-Suche innerhalb der Kategorie statt Kategorie-Filter).
|
||||||
|
*/
|
||||||
|
function buildCategoryUrl(int $page, string $category, string $sort, string $query, int $limit): string
|
||||||
|
{
|
||||||
|
return "index.php?pfad=showCategory"
|
||||||
|
. "&category=" . urlencode($category)
|
||||||
|
. "&sort=" . urlencode($sort)
|
||||||
|
. "&q=" . urlencode($query)
|
||||||
|
. "&limit=" . $limit
|
||||||
|
. "&page=" . $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert eine rein serverseitige Pagination für showCategory (No-JS-Fall).
|
||||||
|
* Analog zu renderNoJsPagination.
|
||||||
|
*/
|
||||||
|
function renderNoJsPaginationCategory(int $currentPage, int $totalPages, string $category, string $sort, string $query, int $limit): string
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if ($currentPage > 1) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($currentPage - 1, $category, $sort, $query, $limit)) . '">«</a> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">«</span> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $totalPages; $i++) {
|
||||||
|
if ($i === $currentPage) {
|
||||||
|
$html .= '<span class="s-res-page-btn s-res-page-btn-active">' . $i . '</span> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($i, $category, $sort, $query, $limit)) . '">' . $i . '</a> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentPage < $totalPages) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($currentPage + 1, $category, $sort, $query, $limit)) . '">»</a>';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">»</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
+8
-2
@@ -1,5 +1,11 @@
|
|||||||
<!--
|
<!--
|
||||||
Suchleiste. Wird via PHP später in alle Seiten eingebunden
|
Suchleiste. Wird via PHP später in alle Seiten eingebunden
|
||||||
-->
|
-->
|
||||||
<input type="search" id="site-search" name="q" placeholder="Suchen...">
|
<!--<label for="site-search">Suche</label>-->
|
||||||
<button type="submit">Suchen</button>
|
<form action="php/controller/search-results-controller.php" method="GET" class="nav__search-form">
|
||||||
|
|
||||||
|
<input type="hidden" name="pfad" value="search-results">
|
||||||
|
|
||||||
|
<input type="search" id="site-search" name="q" placeholder="Suchen..." class="nav__search" maxlength="50" required>
|
||||||
|
<button type="submit" class="nav__search-button">Suchen</button>
|
||||||
|
</form>
|
||||||
|
|||||||
@@ -1,32 +1,53 @@
|
|||||||
<!--
|
<?php
|
||||||
Seite: Hompage der Lernplattform
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
Funktion: Webseitengerüst, Anzeigen von Beiträgen, Suchfunktion etc.
|
session_start();
|
||||||
-->
|
}
|
||||||
|
|
||||||
|
include_once "includes/csrf.php";
|
||||||
|
include_once "php/controller/index-controller.php";
|
||||||
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="description" content="EduForge">
|
<meta name="description" content="EduForge">
|
||||||
<meta name="author" content="Niklas Ortmann">
|
<meta name="author" content="Niklas Ortmann">
|
||||||
<link rel="stylesheet" href="css/main.css" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/x-icon" href="images/logos/logo_icon.ico">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
<link rel="stylesheet" href="css/navbar.css">
|
||||||
|
<link rel="stylesheet" href="css/footer.css">
|
||||||
|
<link rel="stylesheet" href="css/search-results.css">
|
||||||
|
<link rel="stylesheet" href="css/createArticle.css">
|
||||||
|
<link rel="stylesheet" href="css/profile.css">
|
||||||
|
<link rel="stylesheet" href="css/showArticle.css">
|
||||||
|
<link rel="stylesheet" href="css/message.css">
|
||||||
|
<link rel="stylesheet" href="css/showCategory.css">
|
||||||
|
|
||||||
|
<script src="js/comments.js" defer></script>
|
||||||
|
<script src="js/editor.js" async></script>
|
||||||
|
<script src="js/search-results.js" async></script>
|
||||||
|
<script src="js/showCategory.js" async></script>
|
||||||
|
|
||||||
|
|
||||||
<title>EduForge</title>
|
<title>EduForge</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
include_once 'includes/navbar.php';
|
include_once 'includes/navbar.php';
|
||||||
//Dynamischer Inhalt (später mit php...):
|
|
||||||
include_once 'content/home.php';
|
// Dynamischer Inhalt
|
||||||
?>
|
if (isset($pfad) && $pfad !== "404" && file_exists('content/' . $pfad . '.php')) {
|
||||||
|
include_once 'content/' . $pfad . '.php';
|
||||||
|
} else {
|
||||||
|
include_once 'content/404.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once 'includes/footer.php';
|
||||||
|
?>
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="legal/impressum.php">Impressum</a>
|
|
||||||
<a href="legal/datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="legal/nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
+310
@@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* Initialisiert die Kommentarfunktion.
|
||||||
|
*
|
||||||
|
* Kommentare werden per AJAX gespeichert,
|
||||||
|
* ohne dass die Seite neu geladen werden muss.
|
||||||
|
*/
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const form = document.getElementById("comment-form");
|
||||||
|
const commentsList = document.getElementById("comments-list");
|
||||||
|
const commentContent = document.getElementById("comment-content");
|
||||||
|
const parentCommentInput = document.getElementById("parent-comment-id");
|
||||||
|
const replyInfo = document.getElementById("reply-info");
|
||||||
|
|
||||||
|
if (!form || !commentsList || !commentContent || !parentCommentInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktiviert einen einzelnen Antworten-Link.
|
||||||
|
*
|
||||||
|
* @param {HTMLAnchorElement} replyLink Antworten-Link
|
||||||
|
*/
|
||||||
|
function registerReplyButton(replyLink) {
|
||||||
|
replyLink.addEventListener("click", function (event) {
|
||||||
|
/*
|
||||||
|
* Mit JavaScript wird die Seite nicht neu geladen.
|
||||||
|
* Ohne JavaScript funktioniert der normale Link.
|
||||||
|
*/
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
parentCommentInput.value = replyLink.dataset.commentId;
|
||||||
|
|
||||||
|
if (replyInfo) {
|
||||||
|
replyInfo.textContent =
|
||||||
|
"Antwort auf " + replyLink.dataset.author;
|
||||||
|
replyInfo.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
commentContent.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registriert alle bereits vorhandenen Antwort-Buttons.
|
||||||
|
*/
|
||||||
|
document.querySelectorAll(".reply-button").forEach(function (button) {
|
||||||
|
registerReplyButton(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendet Kommentare per AJAX an den Server.
|
||||||
|
*/
|
||||||
|
form.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const parentCommentId = parentCommentInput.value;
|
||||||
|
|
||||||
|
fetch(form.action, {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Fehlerhafte Serverantwort.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
if (!data.success) {
|
||||||
|
alert(data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyMessage = commentsList.querySelector(
|
||||||
|
".no-comments-message"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emptyMessage) {
|
||||||
|
emptyMessage.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentElement = document.createElement("div");
|
||||||
|
commentElement.classList.add("comment-item");
|
||||||
|
commentElement.dataset.commentId = data.commentId;
|
||||||
|
|
||||||
|
if (parentCommentId !== "") {
|
||||||
|
commentElement.classList.add("comment-reply");
|
||||||
|
|
||||||
|
commentElement.innerHTML = `
|
||||||
|
<p>
|
||||||
|
<strong>${escapeHtml(data.author)}</strong>
|
||||||
|
<span>${escapeHtml(data.created)}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p>
|
||||||
|
|
||||||
|
<details class="edit-comment-details">
|
||||||
|
<summary class="edit-comment-button">
|
||||||
|
Antwort bearbeiten
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="index.php?pfad=updateComment"
|
||||||
|
class="edit-comment-form"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="${escapeHtml(formData.get("csrf_token"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="${escapeHtml(data.commentId)}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="${escapeHtml(formData.get("article_id"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
name="content"
|
||||||
|
required
|
||||||
|
>${escapeHtml(data.content)}</textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Änderungen speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="index.php?pfad=deleteComment"
|
||||||
|
class="delete-comment-form"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="${escapeHtml(formData.get("csrf_token"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="${escapeHtml(data.commentId)}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="${escapeHtml(formData.get("article_id"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="delete-comment-button"
|
||||||
|
onclick="return confirm('Möchtest du diesen Kommentar wirklich löschen?');"
|
||||||
|
>
|
||||||
|
Kommentar löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const parentReplies = commentsList.querySelector(
|
||||||
|
`.comment-item[data-comment-id="${parentCommentId}"] .comment-replies`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parentReplies) {
|
||||||
|
parentReplies.appendChild(commentElement);
|
||||||
|
} else {
|
||||||
|
commentsList.prepend(commentElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commentElement.innerHTML = `
|
||||||
|
<p>
|
||||||
|
<strong>${escapeHtml(data.author)}</strong>
|
||||||
|
<span>${escapeHtml(data.created)}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p>
|
||||||
|
|
||||||
|
<details class="edit-comment-details">
|
||||||
|
<summary class="edit-comment-button">
|
||||||
|
Kommentar bearbeiten
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="index.php?pfad=updateComment"
|
||||||
|
class="edit-comment-form"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="${escapeHtml(formData.get("csrf_token"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="${escapeHtml(data.commentId)}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="${escapeHtml(formData.get("article_id"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
name="content"
|
||||||
|
required
|
||||||
|
>${escapeHtml(data.content)}</textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Änderungen speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="index.php?pfad=deleteComment"
|
||||||
|
class="delete-comment-form"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="${escapeHtml(formData.get("csrf_token"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="comment_id"
|
||||||
|
value="${escapeHtml(data.commentId)}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="${escapeHtml(formData.get("article_id"))}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="delete-comment-button"
|
||||||
|
onclick="return confirm('Möchtest du diesen Kommentar wirklich löschen?');"
|
||||||
|
>
|
||||||
|
Kommentar löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#comment-form"
|
||||||
|
class="reply-button"
|
||||||
|
data-comment-id="${escapeHtml(data.commentId)}"
|
||||||
|
data-author="${escapeHtml(data.author)}"
|
||||||
|
>
|
||||||
|
Antworten
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="comment-replies"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
commentsList.prepend(commentElement);
|
||||||
|
|
||||||
|
const newReplyButton =
|
||||||
|
commentElement.querySelector(".reply-button");
|
||||||
|
|
||||||
|
if (newReplyButton) {
|
||||||
|
registerReplyButton(newReplyButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentContent.value = "";
|
||||||
|
parentCommentInput.value = "";
|
||||||
|
|
||||||
|
if (replyInfo) {
|
||||||
|
replyInfo.textContent = "";
|
||||||
|
replyInfo.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert("Kommentar konnte nicht gesendet werden.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt HTML-Sonderzeichen aus Nutzereingaben.
|
||||||
|
*
|
||||||
|
* @param {*} text Zu bereinigender Text
|
||||||
|
* @returns {string} Sicherer Text
|
||||||
|
*/
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = String(text ?? "");
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
//console.log("editor.js wurde erfolgreich geladen!");
|
||||||
|
|
||||||
|
function initEditor() {
|
||||||
|
const form = document.getElementById("editor-form");
|
||||||
|
|
||||||
|
if (!form) {
|
||||||
|
console.error("editor.js abgebrochen: Formular nicht gefunden!");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log("Formular gefunden und editor.js initialisiert:", form);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById("block-container");
|
||||||
|
const plusButton = document.getElementById("plus-button");
|
||||||
|
const popup = document.getElementById("block-popup");
|
||||||
|
|
||||||
|
// Fortlaufender Zähler für eindeutige Block-Indizes. Wird nie wiederverwendet
|
||||||
|
// (auch nicht nach dem Löschen eines Blocks), damit sich neue und übrig
|
||||||
|
// gebliebene Blöcke beim finalen Submit nie einen Namen teilen.
|
||||||
|
let blockIndex = container.querySelectorAll(".editor-block").length;
|
||||||
|
|
||||||
|
// Ohne JS sind Textblock-/Bild-Button im Pop-up immer sichtbar und ganz normale
|
||||||
|
// Submit-Buttons. Erst mit JS blenden wir das Pop-up standardmäßig aus und
|
||||||
|
// steuern die Sichtbarkeit über den Plus-Button.
|
||||||
|
popup.classList.add("hidden");
|
||||||
|
|
||||||
|
plusButton.addEventListener("click", () => {
|
||||||
|
popup.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Klick auf "Textblock" / "Bild einfügen": lokal im DOM anlegen statt zum
|
||||||
|
// Server zu submitten (entlastet den Server, kein Page-Reload nötig).
|
||||||
|
popup.querySelectorAll('[name="editor_action"]').forEach(btn => {
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const type = this.getAttribute("data-type");
|
||||||
|
addBlockElement(type, "");
|
||||||
|
popup.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bereits vom Server gerenderte Blöcke (z.B. beim Bearbeiten eines bestehenden
|
||||||
|
// Artikels oder nach einem Validierungsfehler) ebenfalls mit JS-Verhalten ausstatten.
|
||||||
|
container.querySelectorAll(".editor-block").forEach(blockDiv => {
|
||||||
|
bindDeleteButton(blockDiv);
|
||||||
|
bindImageInput(blockDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Erstellt einen neuen Block inkl. echter, benannter Formularfelder
|
||||||
|
// (blocks[i][type], blocks[i][text] bzw. blocks[i][image]). Diese Felder werden
|
||||||
|
// beim finalen Submit ganz normal vom Browser als multipart/form-data verschickt –
|
||||||
|
// es ist kein manuelles Zusammenbauen von JSON beim Absenden mehr nötig.
|
||||||
|
function addBlockElement(type, value = "") {
|
||||||
|
const index = blockIndex++;
|
||||||
|
|
||||||
|
const blockDiv = document.createElement("div");
|
||||||
|
blockDiv.classList.add("editor-block", "article-editor-scope");
|
||||||
|
blockDiv.setAttribute("data-index", String(index));
|
||||||
|
|
||||||
|
const typeInput = document.createElement("input");
|
||||||
|
typeInput.type = "hidden";
|
||||||
|
typeInput.name = `blocks[${index}][type]`;
|
||||||
|
typeInput.value = type;
|
||||||
|
blockDiv.appendChild(typeInput);
|
||||||
|
|
||||||
|
if (type === "text") {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.name = `blocks[${index}][text]`;
|
||||||
|
textarea.placeholder = "Schreibe deinen Textblock...";
|
||||||
|
textarea.value = value;
|
||||||
|
blockDiv.appendChild(textarea);
|
||||||
|
} else if (type === "image") {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.name = `blocks[${index}][image]`;
|
||||||
|
fileInput.accept = "image/*";
|
||||||
|
blockDiv.appendChild(fileInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.type = "submit";
|
||||||
|
deleteBtn.name = "editor_action";
|
||||||
|
deleteBtn.value = `delete_block:${index}`;
|
||||||
|
deleteBtn.classList.add("delete-block-btn");
|
||||||
|
deleteBtn.innerHTML = "✕";
|
||||||
|
blockDiv.appendChild(deleteBtn);
|
||||||
|
|
||||||
|
container.appendChild(blockDiv);
|
||||||
|
|
||||||
|
bindDeleteButton(blockDiv);
|
||||||
|
bindImageInput(blockDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Löschen-Button eines Blocks lokal abfangen: entfernt den Block nur aus dem DOM,
|
||||||
|
// statt das Formular zum Server zu senden.
|
||||||
|
function bindDeleteButton(blockDiv) {
|
||||||
|
const btn = blockDiv.querySelector(".delete-block-btn");
|
||||||
|
if (!btn) return;
|
||||||
|
btn.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
blockDiv.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeigt bei Auswahl einer Bilddatei sofort eine Vorschau an. Rein optisch –
|
||||||
|
// der eigentliche Datei-Upload läuft nativ über das <input type="file">.
|
||||||
|
function bindImageInput(blockDiv) {
|
||||||
|
const fileInput = blockDiv.querySelector('input[type="file"]');
|
||||||
|
if (!fileInput) return;
|
||||||
|
|
||||||
|
let imgPreview = blockDiv.querySelector(".block-image-preview");
|
||||||
|
if (!imgPreview) {
|
||||||
|
imgPreview = document.createElement("img");
|
||||||
|
imgPreview.classList.add("block-image-preview");
|
||||||
|
imgPreview.style.maxWidth = "200px";
|
||||||
|
imgPreview.style.display = "none";
|
||||||
|
imgPreview.style.marginTop = "10px";
|
||||||
|
blockDiv.insertBefore(imgPreview, fileInput.nextSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", function () {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
imgPreview.src = e.target.result;
|
||||||
|
imgPreview.style.display = "block";
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SICHERER START: Prüft, ob das HTML bereits bereit ist
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", initEditor);
|
||||||
|
} else {
|
||||||
|
// Falls das DOM schon fertig geladen ist, führen wir es direkt aus
|
||||||
|
initEditor();
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Übernimmt Sortierung, Kategorie-Filter und Pagination der Suchergebnisse
|
||||||
|
* vollständig clientseitig
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Kompletten Ergebnis-Datensatz einmalig per fetch() nachladen
|
||||||
|
* (php/controller/search-results-data.php, liest nur die Session-Daten,
|
||||||
|
* keine erneute Datenbank-Suche).
|
||||||
|
* 2. Sortieren + filtern (JS-Array).
|
||||||
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
||||||
|
*
|
||||||
|
* Ersetzt sorter.js, filter.js und paginator.js, deren Klick-Handler sich
|
||||||
|
* gegenseitig ins Gehege kamen.
|
||||||
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
||||||
|
* voll funktionsfähig.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
const state = {
|
||||||
|
allItems: [],
|
||||||
|
sort: 'alphabet',
|
||||||
|
category: 'all',
|
||||||
|
itemsPerPage: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let listContainer, sortRadios, categorySelect, limitSelect;
|
||||||
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
listContainer = document.querySelector('.s-res-list');
|
||||||
|
if (!listContainer) return;
|
||||||
|
|
||||||
|
sortRadios = document.querySelectorAll('.sort-radio');
|
||||||
|
categorySelect = document.getElementById('category-filter');
|
||||||
|
limitSelect = document.getElementById('s-res-per-page');
|
||||||
|
prevBtn = document.getElementById('prev-page-btn');
|
||||||
|
nextBtn = document.getElementById('next-page-btn');
|
||||||
|
numbersContainer = document.getElementById('dynamic-page-numbers');
|
||||||
|
jsNav = document.getElementById('js-page-navigation');
|
||||||
|
resultCountEl = document.getElementById('s-res-result-count');
|
||||||
|
|
||||||
|
const checkedRadio = document.querySelector('.sort-radio:checked');
|
||||||
|
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
|
||||||
|
state.category = categorySelect ? categorySelect.value : 'all';
|
||||||
|
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
|
||||||
|
|
||||||
|
fetchFullDataset()
|
||||||
|
.then(function (data) {
|
||||||
|
state.allItems = data.results || [];
|
||||||
|
attachEvents();
|
||||||
|
render();
|
||||||
|
if (jsNav) jsNav.style.display = 'flex';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
// JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
|
||||||
|
// (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
|
||||||
|
console.error('Suchergebnisse konnten nicht nachgeladen werden:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchFullDataset() {
|
||||||
|
return fetch('php/controller/search-results-data.php', { credentials: 'same-origin' })
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredSortedItems() {
|
||||||
|
let items = state.allItems;
|
||||||
|
|
||||||
|
if (state.category !== 'all') {
|
||||||
|
const wanted = state.category.toLowerCase();
|
||||||
|
items = items.filter(function (item) {
|
||||||
|
return (item.category || '').toLowerCase() === wanted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items = items.slice().sort(function (a, b) {
|
||||||
|
if (state.sort === 'likes') {
|
||||||
|
return (b.likes || 0) - (a.likes || 0);
|
||||||
|
}
|
||||||
|
if (state.sort === 'newest' || state.sort === 'oldest') {
|
||||||
|
return state.sort === 'newest' ? (b.id - a.id) : (a.id - b.id);
|
||||||
|
}
|
||||||
|
// alphabet (Standard)
|
||||||
|
return (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const items = getFilteredSortedItems();
|
||||||
|
|
||||||
|
if (resultCountEl) resultCountEl.textContent = items.length;
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(items.length / state.itemsPerPage));
|
||||||
|
if (state.currentPage > totalPages) state.currentPage = totalPages;
|
||||||
|
if (state.currentPage < 1) state.currentPage = 1;
|
||||||
|
|
||||||
|
const start = (state.currentPage - 1) * state.itemsPerPage;
|
||||||
|
const pageItems = items.slice(start, start + state.itemsPerPage);
|
||||||
|
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
if (pageItems.length === 0) {
|
||||||
|
const empty = document.createElement('p');
|
||||||
|
empty.textContent = 'Keine Beiträge gefunden.';
|
||||||
|
listContainer.appendChild(empty);
|
||||||
|
} else {
|
||||||
|
pageItems.forEach(function (item) {
|
||||||
|
listContainer.appendChild(buildCard(item));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaginatorUI(state.currentPage, totalPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCard(item) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 's-res-item';
|
||||||
|
card.setAttribute('data-likes', item.likes);
|
||||||
|
card.setAttribute('data-category', (item.category || '').toLowerCase());
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.className = 's-res-link';
|
||||||
|
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
|
||||||
|
link.textContent = item.title;
|
||||||
|
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.className = 's-res-item-title';
|
||||||
|
title.appendChild(link);
|
||||||
|
|
||||||
|
const authorName = document.createElement('span');
|
||||||
|
authorName.className = 's-res-author-name';
|
||||||
|
authorName.textContent = item.author;
|
||||||
|
|
||||||
|
const author = document.createElement('p');
|
||||||
|
author.className = 's-res-author';
|
||||||
|
author.append('Von: ', authorName);
|
||||||
|
|
||||||
|
const likes = document.createElement('span');
|
||||||
|
likes.className = 's-res-likes';
|
||||||
|
likes.textContent = '❤️ ' + item.likes;
|
||||||
|
|
||||||
|
const metaRow = document.createElement('div');
|
||||||
|
metaRow.className = 's-res-meta-row';
|
||||||
|
metaRow.append(author, likes);
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.className = 's-res-content';
|
||||||
|
content.append(title, metaRow);
|
||||||
|
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 's-res-arrow';
|
||||||
|
arrow.textContent = '→';
|
||||||
|
|
||||||
|
card.append(content, arrow);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginatorUI(currentPage, totalPages) {
|
||||||
|
if (!prevBtn || !nextBtn || !numbersContainer) return;
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage <= 1;
|
||||||
|
nextBtn.disabled = currentPage >= totalPages;
|
||||||
|
|
||||||
|
numbersContainer.innerHTML = '';
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 's-res-page-btn' + (i === currentPage ? ' s-res-page-btn-active' : '');
|
||||||
|
btn.textContent = i;
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
state.currentPage = i;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
numbersContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachEvents() {
|
||||||
|
sortRadios.forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function () {
|
||||||
|
state.sort = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (categorySelect) {
|
||||||
|
categorySelect.addEventListener('change', function () {
|
||||||
|
state.category = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitSelect) {
|
||||||
|
limitSelect.addEventListener('change', function () {
|
||||||
|
state.itemsPerPage = parseInt(this.value, 10) || 10;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevBtn) {
|
||||||
|
prevBtn.addEventListener('click', function () {
|
||||||
|
if (state.currentPage > 1) {
|
||||||
|
state.currentPage -= 1;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextBtn) {
|
||||||
|
nextBtn.addEventListener('click', function () {
|
||||||
|
state.currentPage += 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mit aktivem JS übernimmt render() Sortierung/Filter live
|
||||||
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
||||||
|
const filterForm = document.getElementById('s-res-filter-form');
|
||||||
|
if (filterForm) {
|
||||||
|
filterForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Übernimmt Sortierung, Suche (innerhalb der Kategorie) und Pagination der
|
||||||
|
* Kategorie-Ansicht vollständig clientseitig.
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Kompletten Kategorie-Datensatz einmalig per fetch() nachladen
|
||||||
|
* 2. Sortieren + nach Suchbegriff filtern (JS-Array).
|
||||||
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
||||||
|
*
|
||||||
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
||||||
|
* voll funktionsfähig
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
const state = {
|
||||||
|
allItems: [],
|
||||||
|
sort: 'alphabet',
|
||||||
|
query: '',
|
||||||
|
itemsPerPage: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let listContainer, sortRadios, searchInput, limitSelect;
|
||||||
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
||||||
|
let searchDebounceTimer;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
listContainer = document.querySelector('.s-res-list');
|
||||||
|
if (!listContainer) return;
|
||||||
|
|
||||||
|
sortRadios = document.querySelectorAll('.sort-radio');
|
||||||
|
searchInput = document.getElementById('cat-search-input');
|
||||||
|
limitSelect = document.getElementById('s-res-per-page');
|
||||||
|
prevBtn = document.getElementById('prev-page-btn');
|
||||||
|
nextBtn = document.getElementById('next-page-btn');
|
||||||
|
numbersContainer = document.getElementById('dynamic-page-numbers');
|
||||||
|
jsNav = document.getElementById('js-page-navigation');
|
||||||
|
resultCountEl = document.getElementById('s-res-result-count');
|
||||||
|
|
||||||
|
const checkedRadio = document.querySelector('.sort-radio:checked');
|
||||||
|
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
|
||||||
|
state.query = searchInput ? searchInput.value : '';
|
||||||
|
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
|
||||||
|
|
||||||
|
fetchFullDataset()
|
||||||
|
.then(function (data) {
|
||||||
|
state.allItems = data.results || [];
|
||||||
|
attachEvents();
|
||||||
|
render();
|
||||||
|
if (jsNav) jsNav.style.display = 'flex';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
// JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
|
||||||
|
// (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
|
||||||
|
console.error('Kategorie-Beiträge konnten nicht nachgeladen werden:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchFullDataset() {
|
||||||
|
return fetch('php/controller/showCategory-data.php', { credentials: 'same-origin' })
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredSortedItems() {
|
||||||
|
let items = state.allItems;
|
||||||
|
|
||||||
|
const needle = state.query.trim().toLowerCase();
|
||||||
|
if (needle !== '') {
|
||||||
|
items = items.filter(function (item) {
|
||||||
|
const haystack = ((item.title || '') + ' ' + (item.content || '')).toLowerCase();
|
||||||
|
return haystack.indexOf(needle) !== -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items = items.slice().sort(function (a, b) {
|
||||||
|
if (state.sort === 'likes') {
|
||||||
|
return (b.likes || 0) - (a.likes || 0);
|
||||||
|
}
|
||||||
|
if (state.sort === 'newest' || state.sort === 'oldest') {
|
||||||
|
return state.sort === 'newest' ? (b.id - a.id) : (a.id - b.id);
|
||||||
|
}
|
||||||
|
// alphabet (Standard)
|
||||||
|
return (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const items = getFilteredSortedItems();
|
||||||
|
|
||||||
|
if (resultCountEl) resultCountEl.textContent = items.length;
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(items.length / state.itemsPerPage));
|
||||||
|
if (state.currentPage > totalPages) state.currentPage = totalPages;
|
||||||
|
if (state.currentPage < 1) state.currentPage = 1;
|
||||||
|
|
||||||
|
const start = (state.currentPage - 1) * state.itemsPerPage;
|
||||||
|
const pageItems = items.slice(start, start + state.itemsPerPage);
|
||||||
|
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
if (pageItems.length === 0) {
|
||||||
|
const empty = document.createElement('p');
|
||||||
|
empty.textContent = 'Keine Beiträge gefunden.';
|
||||||
|
listContainer.appendChild(empty);
|
||||||
|
} else {
|
||||||
|
pageItems.forEach(function (item) {
|
||||||
|
listContainer.appendChild(buildCard(item));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaginatorUI(state.currentPage, totalPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCard(item) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 's-res-item';
|
||||||
|
card.setAttribute('data-likes', item.likes);
|
||||||
|
card.setAttribute('data-category', (item.category || '').toLowerCase());
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.className = 's-res-link';
|
||||||
|
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
|
||||||
|
link.textContent = item.title;
|
||||||
|
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.className = 's-res-item-title';
|
||||||
|
title.appendChild(link);
|
||||||
|
|
||||||
|
const authorName = document.createElement('span');
|
||||||
|
authorName.className = 's-res-author-name';
|
||||||
|
authorName.textContent = item.author;
|
||||||
|
|
||||||
|
const author = document.createElement('p');
|
||||||
|
author.className = 's-res-author';
|
||||||
|
author.append('Von: ', authorName);
|
||||||
|
|
||||||
|
const likes = document.createElement('span');
|
||||||
|
likes.className = 's-res-likes';
|
||||||
|
likes.textContent = '❤️ ' + item.likes;
|
||||||
|
|
||||||
|
const metaRow = document.createElement('div');
|
||||||
|
metaRow.className = 's-res-meta-row';
|
||||||
|
metaRow.append(author, likes);
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.className = 's-res-content';
|
||||||
|
content.append(title, metaRow);
|
||||||
|
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 's-res-arrow';
|
||||||
|
arrow.textContent = '→';
|
||||||
|
|
||||||
|
card.append(content, arrow);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginatorUI(currentPage, totalPages) {
|
||||||
|
if (!prevBtn || !nextBtn || !numbersContainer) return;
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage <= 1;
|
||||||
|
nextBtn.disabled = currentPage >= totalPages;
|
||||||
|
|
||||||
|
numbersContainer.innerHTML = '';
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 's-res-page-btn' + (i === currentPage ? ' s-res-page-btn-active' : '');
|
||||||
|
btn.textContent = i;
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
state.currentPage = i;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
numbersContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachEvents() {
|
||||||
|
sortRadios.forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function () {
|
||||||
|
state.sort = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
// Live-Filterung während des Tippens (leicht entprellt), da der
|
||||||
|
// Datensatz bereits vollständig im Browser liegt.
|
||||||
|
searchInput.addEventListener('input', function () {
|
||||||
|
clearTimeout(searchDebounceTimer);
|
||||||
|
const value = this.value;
|
||||||
|
searchDebounceTimer = setTimeout(function () {
|
||||||
|
state.query = value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitSelect) {
|
||||||
|
limitSelect.addEventListener('change', function () {
|
||||||
|
state.itemsPerPage = parseInt(this.value, 10) || 10;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevBtn) {
|
||||||
|
prevBtn.addEventListener('click', function () {
|
||||||
|
if (state.currentPage > 1) {
|
||||||
|
state.currentPage -= 1;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextBtn) {
|
||||||
|
nextBtn.addEventListener('click', function () {
|
||||||
|
state.currentPage += 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mit aktivem JS übernimmt render() Sortierung/Suche live
|
||||||
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
||||||
|
const filterForm = document.getElementById('cat-filter-form');
|
||||||
|
if (filterForm) {
|
||||||
|
filterForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Datenschutzerklärung
|
|
||||||
Inhalt: Informationen zur Datenverarbeitung gemäß DSGVO
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Datenschutz</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="impressum.php">Impressum</a>
|
|
||||||
<a href="datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Impressum
|
|
||||||
Inhalt: wer wie was auf der Webseite
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Impressum</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="impressum.php">Impressum</a>
|
|
||||||
<a href="datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Nutzungsbedingungen
|
|
||||||
Inhalt: Regelungen für die Nutzung der Plattform (AGBs und so...)
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="description" content="EduForge">
|
|
||||||
<meta name="author" content="Niklas Ortmann">
|
|
||||||
<link rel="stylesheet" href="../css/main.css" />
|
|
||||||
<title>EduForge</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav style="background:#808080; height: 60px;">
|
|
||||||
<div style="float:left; text-align: center;">
|
|
||||||
<a href="../index.php">
|
|
||||||
<img src="../images/logo.png" alt="" style="height: 44px;">
|
|
||||||
</a>
|
|
||||||
|
|
|
||||||
<a href = "../content/informatik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Informatik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/mathe.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Mathe</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/physik.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Physik</b></a>
|
|
||||||
|
|
|
||||||
<a href = "../content/profile.php" style = "padding: 5px; text-align: center; text-decoration: none; display: inline-block;"><b>Dein Profil</b></a>
|
|
||||||
|
|
|
||||||
</div>
|
|
||||||
<div style="float:right; text-align: center;">
|
|
||||||
<?php
|
|
||||||
include_once '../includes/search.php';
|
|
||||||
?>
|
|
||||||
|
|
|
||||||
<a href = "../auth/login.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Anmelden </a>
|
|
||||||
|
|
||||||
<a href = "../auth/register.php" style = "background-color: #f44336; color: white; padding: 4px 20px; text-align: center; text-decoration: none; display: inline-block;"> Registrieren </a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Nutzungsbedingungen</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer style="position:fixed;bottom:0;width:100%;background:#fff;text-align:center;">
|
|
||||||
<a href="impressum.php">Impressum</a>
|
|
||||||
<a href="datenschutz.php">Datenschutz</a>
|
|
||||||
<a href="nutzungsbedingungen.php">Nutzungsbedingungen</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once "../model/CommentManager.php";
|
||||||
|
require_once "../model/UserManager.php";
|
||||||
|
require_once "../model/ArticleManager.php";
|
||||||
|
require_once "../../includes/csrf.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die Anfrage durch JavaScript per AJAX gesendet wurde.
|
||||||
|
*/
|
||||||
|
$isAjaxRequest = isset($_SERVER["HTTP_X_REQUESTED_WITH"])
|
||||||
|
&& strtolower($_SERVER["HTTP_X_REQUESTED_WITH"]) === "xmlhttprequest";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das Ergebnis entweder als JSON zurück oder leitet
|
||||||
|
* bei einem normalen Formularaufruf wieder zum Beitrag zurück.
|
||||||
|
*
|
||||||
|
* @param bool $success War das Speichern erfolgreich?
|
||||||
|
* @param string $message Rückmeldung für den Benutzer
|
||||||
|
* @param int|null $articleId ID des Beitrags
|
||||||
|
* @param array $additionalData Zusätzliche Daten für AJAX
|
||||||
|
*/
|
||||||
|
function sendCommentResponse(
|
||||||
|
$success,
|
||||||
|
$message,
|
||||||
|
$articleId,
|
||||||
|
$additionalData = []
|
||||||
|
) {
|
||||||
|
global $isAjaxRequest;
|
||||||
|
|
||||||
|
if ($isAjaxRequest) {
|
||||||
|
header("Content-Type: application/json; charset=utf-8");
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
array_merge(
|
||||||
|
[
|
||||||
|
"success" => $success,
|
||||||
|
"message" => $message
|
||||||
|
],
|
||||||
|
$additionalData
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bei deaktiviertem JavaScript wird die Rückmeldung
|
||||||
|
* in der Session gespeichert und die Beitragsseite neu geladen.
|
||||||
|
*/
|
||||||
|
$_SESSION["comment_message"] = $message;
|
||||||
|
$_SESSION["comment_message_type"] = $success ? "success" : "error";
|
||||||
|
|
||||||
|
if ($articleId !== null) {
|
||||||
|
header(
|
||||||
|
"Location: ../../index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nur POST-Anfragen dürfen Kommentare erstellen.
|
||||||
|
*/
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Ungültige Anfrage.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Die Beitrags-ID wird zuerst eingelesen,
|
||||||
|
* damit bei Fehlern wieder zum Beitrag zurückgeleitet werden kann.
|
||||||
|
*/
|
||||||
|
$articleId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"article_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ein Benutzer muss angemeldet sein.
|
||||||
|
*/
|
||||||
|
if (!isset($_SESSION["user_email"])) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Du musst angemeldet sein, um zu kommentieren.",
|
||||||
|
$articleId !== false ? $articleId : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird.
|
||||||
|
*/
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.",
|
||||||
|
$articleId !== false ? $articleId : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Weitere Formulardaten einlesen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$content = trim($_POST["content"] ?? "");
|
||||||
|
|
||||||
|
$parentCommentId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"parent_comment_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ein leerer Wert bedeutet, dass es sich um einen
|
||||||
|
* normalen Hauptkommentar handelt.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
!isset($_POST["parent_comment_id"])
|
||||||
|
|| $_POST["parent_comment_id"] === ""
|
||||||
|
|| $_POST["parent_comment_id"] === "0"
|
||||||
|
) {
|
||||||
|
$parentCommentId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($articleId === false || $articleId === null) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der zugehörige Beitrag ist ungültig.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Der Beitrag muss tatsächlich existieren.
|
||||||
|
*/
|
||||||
|
$existingArticle = ArticleManager::getInstance()->getArticle($articleId);
|
||||||
|
if ($existingArticle === null) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der zugehörige Beitrag wurde nicht gefunden.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($content === "") {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der Kommentar darf nicht leer sein.",
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Eine ungültige Eltern-ID darf nicht gespeichert werden.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
isset($_POST["parent_comment_id"])
|
||||||
|
&& $_POST["parent_comment_id"] !== ""
|
||||||
|
&& $_POST["parent_comment_id"] !== "0"
|
||||||
|
&& $parentCommentId === false
|
||||||
|
) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der ausgewählte Kommentar ist ungültig.",
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Falls eine Eltern-ID angegeben wurde, muss dieser Kommentar
|
||||||
|
* tatsächlich existieren und zum selben Beitrag gehören.
|
||||||
|
*/
|
||||||
|
if ($parentCommentId !== null) {
|
||||||
|
$existingComments = CommentManager::getInstance()->getCommentsByArticle($articleId);
|
||||||
|
$parentExists = false;
|
||||||
|
|
||||||
|
foreach ($existingComments as $existingComment) {
|
||||||
|
if ($existingComment->getId() === $parentCommentId) {
|
||||||
|
$parentExists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$parentExists) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der ausgewählte Kommentar wurde nicht gefunden.",
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
|
||||||
|
$commentId = $commentManager->addComment(
|
||||||
|
$articleId,
|
||||||
|
$_SESSION["user_email"],
|
||||||
|
$content,
|
||||||
|
$parentCommentId
|
||||||
|
);
|
||||||
|
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
$user = $userManager->findUser($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
$authorName = $_SESSION["user_email"];
|
||||||
|
|
||||||
|
if ($user !== null) {
|
||||||
|
$vorname = trim($user["vorname"] ?? "");
|
||||||
|
$nachname = trim($user["nachname"] ?? "");
|
||||||
|
|
||||||
|
$fullName = trim($vorname . " " . $nachname);
|
||||||
|
|
||||||
|
if ($fullName !== "") {
|
||||||
|
$authorName = $fullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCommentResponse(
|
||||||
|
true,
|
||||||
|
"Der Kommentar wurde erfolgreich gespeichert.",
|
||||||
|
$articleId,
|
||||||
|
[
|
||||||
|
"commentId" => $commentId,
|
||||||
|
"author" => $authorName,
|
||||||
|
"content" => $content,
|
||||||
|
"created" => date("Y-m-d H:i:s"),
|
||||||
|
"parentCommentId" => $parentCommentId
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
sendCommentResponse(
|
||||||
|
false,
|
||||||
|
"Der Kommentar konnte nicht gespeichert werden.",
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Verarbeitung der Passwortbestätigung
|
||||||
|
Funktion: Liest die zwischengespeicherten Passwortdaten aus,
|
||||||
|
aktualisiert das Passwort des Benutzers und gibt das Ergebnis
|
||||||
|
für die View zurück.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = "Passwort zurücksetzen";
|
||||||
|
$message = "";
|
||||||
|
$link = "";
|
||||||
|
$isSuccess = false;
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = "data/pending-password/" . $token . ".json";
|
||||||
|
|
||||||
|
if (empty($token) || !file_exists($file)) {
|
||||||
|
|
||||||
|
$message = "Der Bestätigungslink ist ungültig oder bereits abgelaufen.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
|
||||||
|
if ($data === null || empty($data["email"]) || empty($data["password"])) {
|
||||||
|
|
||||||
|
$message = "Die Daten zur Passwortänderung konnten nicht gelesen werden.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
$user = $dao->findUser($data["email"]);
|
||||||
|
|
||||||
|
if ($user !== null) {
|
||||||
|
|
||||||
|
$dao->updateUser(
|
||||||
|
$user["email"],
|
||||||
|
$user["email"],
|
||||||
|
$user["vorname"],
|
||||||
|
$user["nachname"],
|
||||||
|
$data["password"]
|
||||||
|
);
|
||||||
|
|
||||||
|
$_SESSION["message"] = "password_changed";
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$message = "Der Benutzer konnte nicht gefunden werden.";
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($file);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
$message = "Das Passwort konnte nicht geändert werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Verarbeitung der Registrierungsbestätigung
|
||||||
|
Funktion: Liest die zwischengespeicherten Registrierungsdaten aus,
|
||||||
|
legt den Benutzer an und gibt das Ergebnis für die View zurück.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = "Registrierung";
|
||||||
|
$message = "";
|
||||||
|
$link = "";
|
||||||
|
$isSuccess = false;
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = "data/pending/" . $token . ".json";
|
||||||
|
|
||||||
|
if (empty($token) || !file_exists($file)) {
|
||||||
|
|
||||||
|
$message = "Der Registrierungslink ist ungültig oder bereits abgelaufen.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$data === null ||
|
||||||
|
empty($data["email"]) ||
|
||||||
|
empty($data["vorname"]) ||
|
||||||
|
empty($data["nachname"]) ||
|
||||||
|
empty($data["password"])
|
||||||
|
) {
|
||||||
|
|
||||||
|
$message = "Die Registrierungsdaten konnten nicht gelesen werden.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
if ($dao->findUser($data["email"]) === null) {
|
||||||
|
|
||||||
|
$dao->addUser(
|
||||||
|
$data["email"],
|
||||||
|
$data["vorname"],
|
||||||
|
$data["nachname"],
|
||||||
|
$data["password"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($file);
|
||||||
|
|
||||||
|
$_SESSION["message"] = "registration_confirmed";
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
$message = "Die Registrierung konnte nicht abgeschlossen werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once '../model/LocalArticleManager.php';
|
||||||
|
require_once '../model/ArticleManager.php';
|
||||||
|
require_once '../validator/article-validator.php';
|
||||||
|
require_once '../../includes/article-block-helper.php';
|
||||||
|
require_once '../../includes/csrf.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadDir = __DIR__ . '/../../uploads/';
|
||||||
|
if (!file_exists($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formularzustand (Titel/Tags/Kategorie/Blöcke) immer sichern, damit er nach einem
|
||||||
|
// Redirect (PRG-Pattern oder Validierungsfehler) wieder angezeigt werden kann.
|
||||||
|
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
||||||
|
$_SESSION["old_category"] = $_POST["category"] ?? '';
|
||||||
|
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
|
||||||
|
|
||||||
|
$blocks = rebuildBlocksFromPost($_POST['blocks'] ?? [], $_FILES['blocks'] ?? [], $uploadDir);
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Zwischenspeichern
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (isset($_POST['editor_action']) && $_POST['editor_action'] !== '') {
|
||||||
|
$action = $_POST['editor_action'];
|
||||||
|
|
||||||
|
if ($action === 'add_text') {
|
||||||
|
$blocks[] = ['type' => 'text', 'value' => ''];
|
||||||
|
} elseif ($action === 'add_image') {
|
||||||
|
$blocks[] = ['type' => 'image', 'value' => ''];
|
||||||
|
} elseif (str_starts_with($action, 'delete_block:')) {
|
||||||
|
$deleteIndex = (int) substr($action, strlen('delete_block:'));
|
||||||
|
unset($blocks[$deleteIndex]);
|
||||||
|
$blocks = array_values($blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Echtes Veröffentlichen
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (!isset($_POST["title"]) || !isset($_POST["category"])) {
|
||||||
|
$_SESSION["message"] = "missing_parameters";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$title = $_POST["title"];
|
||||||
|
$content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
$author = $_SESSION["user_email"];
|
||||||
|
$category = $_POST["category"];
|
||||||
|
$tags = $_POST['tags'] ?? '';
|
||||||
|
|
||||||
|
// -------------------------------- Validierung der Daten: -------------------------
|
||||||
|
if (!articleTitleValidator($title)) {
|
||||||
|
$_SESSION["message"] = "invalid_title";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleContentValidator($content)) {
|
||||||
|
$_SESSION["message"] = "invalid_content";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleCategoryValidator($category)) {
|
||||||
|
$_SESSION["message"] = "invalid_category";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleTagValidator($tags)) {
|
||||||
|
$_SESSION["message"] = "invalid_tags";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$cleanedTags = [];
|
||||||
|
$rawTags = explode(',', $tags);
|
||||||
|
foreach ($rawTags as $rawTag) {
|
||||||
|
// Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
|
||||||
|
$tag = trim($rawTag);
|
||||||
|
$cleanedTags[] = $tag;
|
||||||
|
}
|
||||||
|
// Duplikate entfernen:
|
||||||
|
$cleanedTags = array_unique($cleanedTags);
|
||||||
|
$cleanedTags = implode(',', $cleanedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
// $content enthält bereits die finalen "uploads/..."-Pfade (kein Base64 mehr),
|
||||||
|
// da rebuildBlocksFromPost() Datei-Uploads sofort verarbeitet.
|
||||||
|
$articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
|
||||||
|
|
||||||
|
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
|
||||||
|
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["message"] = "new_article";
|
||||||
|
// Weiterleitung zur Homepage
|
||||||
|
header("location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../model/UserManager.php";
|
||||||
|
require_once __DIR__ . "/../model/ArticleManager.php";
|
||||||
|
require_once __DIR__ . "/../../includes/csrf.php";
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
|
header("Location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("Location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Deregistrierung
|
||||||
|
Funktion: Entfernt User aus der Datenbank und beendet die Session
|
||||||
|
*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
|
||||||
|
if (isset($_SESSION["user_email"])) {
|
||||||
|
$dao->deleteUser($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
$articles = $articleManager->getArticlesByAuthor($_SESSION["user_email"]);
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
$articleManager->deleteArticle($article->getId(), $_SESSION["user_email"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException("missing_user_email");
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
|
||||||
|
header("Location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../model/ArticleManager.php";
|
||||||
|
require_once __DIR__ . "/../../includes/csrf.php";
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_SESSION["user_email"])) {
|
||||||
|
$user = $_SESSION["user_email"];
|
||||||
|
} else {
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Beitrags-ID muss eine gültige numerische ID sein.
|
||||||
|
$id = filter_input(INPUT_POST, "id", FILTER_VALIDATE_INT);
|
||||||
|
|
||||||
|
if ($id === false || $id === null) {
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
header("location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$articleManager->deleteArticle($id, $user);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$_SESSION["message"] = $e->getMessage();
|
||||||
|
header("location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["message"] = "article_deleted";
|
||||||
|
header("location: ../../index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../model/CommentManager.php";
|
||||||
|
require_once __DIR__ . "/../../includes/csrf.php";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kommentare dürfen nur über ein POST-Formular gelöscht werden.
|
||||||
|
*/
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
$commentId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"comment_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
$articleId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"article_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nur angemeldete Nutzer dürfen Kommentare löschen.
|
||||||
|
*/
|
||||||
|
if (!isset($_SESSION["user_email"])) {
|
||||||
|
$_SESSION["comment_message"] = "Du musst angemeldet sein.";
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
|
||||||
|
if ($articleId !== false && $articleId !== null) {
|
||||||
|
header(
|
||||||
|
"Location: index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
header("Location: index.php");
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["comment_message"] = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu.";
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
|
||||||
|
if ($articleId !== false && $articleId !== null) {
|
||||||
|
header(
|
||||||
|
"Location: index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
header("Location: index.php");
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kommentar-ID und Beitrags-ID müssen gültige Zahlen sein.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$commentId === false
|
||||||
|
|| $commentId === null
|
||||||
|
|| $articleId === false
|
||||||
|
|| $articleId === null
|
||||||
|
) {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar konnte nicht gelöscht werden.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Die E-Mail-Adresse aus der Session wird mitgegeben.
|
||||||
|
* Dadurch kann der Nutzer nur eigene Kommentare löschen.
|
||||||
|
*/
|
||||||
|
$deleted = $commentManager->deleteComment(
|
||||||
|
$commentId,
|
||||||
|
$_SESSION["user_email"]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($deleted) {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar wurde gelöscht.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "success";
|
||||||
|
} else {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar wurde nicht gefunden oder gehört nicht dir.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar konnte nicht gelöscht werden.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Anschließend wird wieder zum Beitrag und zu den Kommentaren geleitet.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
"Location: index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
|
||||||
|
exit();
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once 'php/model/Article.php';
|
||||||
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once 'php/model/LocalArticleManager.php';
|
||||||
|
|
||||||
|
$categoriesWithArticles = [];
|
||||||
|
|
||||||
|
$allowedCategories = [
|
||||||
|
'deutsch', 'englisch', 'franzoesisch', 'latein', 'literatur',
|
||||||
|
'mathe', 'biologie', 'chemie', 'physik', 'informatik', 'astronomie',
|
||||||
|
'geschichte', 'erdkunde', 'sozialkunde', 'wirtschaft', 'religion',
|
||||||
|
'ethik', 'philosophie', 'psychologie', 'kunst', 'musik', 'theater',
|
||||||
|
'technik', 'werken', 'hauswirtschaft', 'sport'
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
|
||||||
|
foreach ($allowedCategories as $categorySlug) {
|
||||||
|
$allCategoryArticles = $articleManager->getArticlesByCategory($categorySlug);
|
||||||
|
|
||||||
|
// nur Kategorien, die Beiträge enthält:
|
||||||
|
if (!empty($allCategoryArticles) && is_array($allCategoryArticles)) {
|
||||||
|
|
||||||
|
// Beiträge nach Erstellungsdatum sortieren:
|
||||||
|
usort($allCategoryArticles, function($a, $b) {
|
||||||
|
$dateA = strtotime($a->getCreationDate());
|
||||||
|
$dateB = strtotime($b->getCreationDate());
|
||||||
|
return $dateB <=> $dateA; // Absteigende Sortierung
|
||||||
|
});
|
||||||
|
|
||||||
|
// auf die 5 zuletzt hinzugefügten Beiträge begrenzen:
|
||||||
|
$limitedArticles = array_slice($allCategoryArticles, 0, 5);
|
||||||
|
|
||||||
|
$categoriesWithArticles[] = [
|
||||||
|
'slug' => $categorySlug,
|
||||||
|
'name' => ucfirst($categorySlug), // Erster Buchstabe groß für die Ansicht
|
||||||
|
'articles' => $limitedArticles
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e){
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
// Standardpfad
|
||||||
|
$pfad = $_GET["pfad"] ?? "home";
|
||||||
|
|
||||||
|
if ($pfad === "logout") {
|
||||||
|
include_once "php/controller/logout-controller.php";
|
||||||
|
exit();
|
||||||
|
} elseif ($pfad === "deleteAccount") {
|
||||||
|
include_once "php/controller/deleteAccount-controller.php";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pfad === "login") {
|
||||||
|
include_once "php/controller/login-controller.php";
|
||||||
|
} elseif ($pfad === "register") {
|
||||||
|
include_once "php/controller/register-controller.php";
|
||||||
|
} elseif ($pfad === "password-forgotten") {
|
||||||
|
include_once "php/controller/password-forgotten-controller.php";
|
||||||
|
} elseif ($pfad === "confirm-register") {
|
||||||
|
include_once "php/controller/confirm-register-controller.php";
|
||||||
|
} elseif ($pfad === "confirm-password") {
|
||||||
|
include_once "php/controller/confirm-password-controller.php";
|
||||||
|
} elseif ($pfad === "updateComment") {
|
||||||
|
include_once "php/controller/updateComment-controller.php";
|
||||||
|
} elseif($pfad === "deleteComment") {
|
||||||
|
include_once "php/controller/deleteComment-controller.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelist
|
||||||
|
$erlaubte_content_seiten = [
|
||||||
|
"accessibility", "confirm-password", "confirm-register", "createArticle",
|
||||||
|
"datenschutz", "home", "impressum", "login", "nutzungsbedingungen",
|
||||||
|
"password-forgotten", "profile", "register", "search-results",
|
||||||
|
"show-mail", "showArticle", "showCategory", "updateArticle"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!in_array($pfad, $erlaubte_content_seiten)) {
|
||||||
|
$pfad = "404";
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../model/Article.php';
|
||||||
|
require_once __DIR__ . '/../model/ArticleManager.php';
|
||||||
|
require_once __DIR__ . '/../../includes/csrf.php';
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Prüfen, ob eine gültige Artikel-ID übergeben wurde
|
||||||
|
if (isset($_GET["id"]) && !empty($_GET["id"])) {
|
||||||
|
$articleId = intval($_GET["id"]);
|
||||||
|
$userEmail = $_SESSION["user_email"];
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user_email"]) || empty($_SESSION["user_email"])) {
|
||||||
|
$_SESSION["message"] = "unauthorized_access";
|
||||||
|
header("Location: ../../index.php?pfad=showArticle&id=" . $articleId);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor der Like-Status verändert wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("Location: ../../index.php?pfad=showArticle&id=" . $articleId);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$articleManager->toggleLike($articleId, $userEmail);
|
||||||
|
|
||||||
|
header("Location: ../../index.php?pfad=showArticle&id=" . $articleId);
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
header("Location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Verarbeitung des Login-Formulars
|
||||||
|
Funktion: Prüft Benutzerdaten und erstellt Session für eingeloggten Nutzer
|
||||||
|
*/
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
$email = $_POST["email"] ?? "";
|
||||||
|
$password = $_POST["password"] ?? "";
|
||||||
|
|
||||||
|
$user = $dao->findUser($email);
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user["password"])) {
|
||||||
|
|
||||||
|
$_SESSION["user"] = $user["vorname"] . " " . $user["nachname"];
|
||||||
|
$_SESSION["user_email"] = $user["email"];
|
||||||
|
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$error = "Login fehlgeschlagen. Bitte überprüfe deine Eingaben.";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Es ist ein interner Fehler aufgetreten. Bitte versuche es erneut.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
require_once "php/validator/user-validator.php";
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
$email = trim($_POST["email"] ?? "");
|
||||||
|
$plainPassword = $_POST["password"] ?? "";
|
||||||
|
|
||||||
|
if (!userEmailValidator($email)) {
|
||||||
|
$error = "Bitte gib eine gültige E-Mail-Adresse ein.";
|
||||||
|
} elseif (!userPasswordValidator($plainPassword)) {
|
||||||
|
$error = "Das Passwort muss 5 bis 12 Zeichen lang sein.";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
$existingUser = $dao->findUser($email);
|
||||||
|
|
||||||
|
if (!is_dir("data/mails") && !mkdir("data/mails", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/mails konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir("data/pending-password") && !mkdir("data/pending-password", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/pending-password konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/mails") || !is_writable("data/pending-password")) {
|
||||||
|
throw new RuntimeException("Ordner sind nicht beschreibbar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser !== null) {
|
||||||
|
$pendingData = [
|
||||||
|
"email" => $email,
|
||||||
|
"password" => $plainPassword
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
"data/pending-password/" . $token . ".json",
|
||||||
|
json_encode($pendingData, JSON_PRETTY_PRINT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser !== null) {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Passwort zurücksetzen</h2>
|
||||||
|
<p>Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=confirm-password&token=$token'>
|
||||||
|
Passwortänderung bestätigen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Passwort zurücksetzen</h2>
|
||||||
|
<p>Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.</p>
|
||||||
|
<p>Für diese E-Mail-Adresse wurde kein Konto gefunden.</p>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents("data/mails/" . $token . ".html", $mailContent);
|
||||||
|
|
||||||
|
$success = 'Weitere Infos finden Sie in der Datei
|
||||||
|
<a href="index.php?pfad=show-mail&token=' . htmlspecialchars($token) . '" target="_blank">xy</a>.';
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Die Passwortänderung konnte nicht verarbeitet werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
require_once "php/model/Article.php";
|
||||||
|
require_once "php/model/ArticleManager.php";
|
||||||
|
require_once "php/model/CommentManager.php";
|
||||||
|
require_once "php/validator/user-validator.php";
|
||||||
|
require_once "includes/csrf.php";
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
$user = $dao->findUser($_SESSION["user_email"] ?? "");
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["saveProfile"])) {
|
||||||
|
|
||||||
|
$oldEmail = $_SESSION["user_email"];
|
||||||
|
|
||||||
|
$newEmail = trim($_POST["email"] ?? "");
|
||||||
|
$vorname = trim($_POST["vorname"] ?? "");
|
||||||
|
$nachname = trim($_POST["nachname"] ?? "");
|
||||||
|
$password = $_POST["password"] ?? "";
|
||||||
|
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
$error = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} elseif (!userEmailValidator($newEmail)) {
|
||||||
|
$error = "Bitte gib eine gültige E-Mail-Adresse ein.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} elseif (!userNameValidator($vorname)) {
|
||||||
|
$error = "Der Vorname muss 2 bis 20 Zeichen lang sein und darf nur Buchstaben, Leerzeichen und Bindestriche enthalten.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} elseif (!userNameValidator($nachname)) {
|
||||||
|
$error = "Der Nachname muss 2 bis 20 Zeichen lang sein und darf nur Buchstaben, Leerzeichen und Bindestriche enthalten.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} elseif (!userOptionalPasswordValidator($password)) {
|
||||||
|
$error = "Das Passwort muss 5 bis 12 Zeichen lang sein.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$updated = $dao->updateUser(
|
||||||
|
$oldEmail,
|
||||||
|
$newEmail,
|
||||||
|
$vorname,
|
||||||
|
$nachname,
|
||||||
|
$password
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($updated) {
|
||||||
|
$_SESSION["user"] = $vorname . " " . $nachname;
|
||||||
|
$_SESSION["user_email"] = $newEmail;
|
||||||
|
|
||||||
|
$_SESSION["message"] = "profile_updated";
|
||||||
|
header("Location: index.php?pfad=profile");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error = "Die Daten konnten nicht gespeichert werden.";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $dao->findUser($_SESSION["user_email"] ?? "");
|
||||||
|
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$userArticles = $articleManager->getArticlesByAuthor($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
$userComments = $commentManager->getCommentsByAuthor($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
if (!isset($userArticles)) {
|
||||||
|
$_SESSION["message"] = "user_has_no_articles";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
$error = $e->getMessage();
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Es ist ein interner Fehler aufgetreten. Bitte versuche es erneut.";
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
$_GET["edit"] = "1";
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Controller für die Liste der eigenen Beiträge eines Nutzers auf der eigenen Profilseite
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'php/model/Article.php';
|
||||||
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
$user = $dao->findUser($_SESSION["user_email"] ?? "");
|
||||||
|
|
||||||
|
$author = $user["email"];
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$userArticles = $articleManager->getArticlesByAuthor($author);
|
||||||
|
if(!isset($userArticles)) {
|
||||||
|
$_SESSION["message"] = "user_has_no_articles";
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
require_once "php/validator/user-validator.php";
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
$email = trim($_POST["email"] ?? "");
|
||||||
|
$vorname = trim($_POST["vorname"] ?? "");
|
||||||
|
$nachname = trim($_POST["nachname"] ?? "");
|
||||||
|
$plainPassword = $_POST["password"] ?? "";
|
||||||
|
$termsAccepted = $_POST["termsAccepted"] ?? null;
|
||||||
|
|
||||||
|
if (!userEmailValidator($email)) {
|
||||||
|
$error = "Bitte gib eine gültige E-Mail-Adresse ein.";
|
||||||
|
} elseif (!userNameValidator($vorname)) {
|
||||||
|
$error = "Der Vorname muss 2 bis 20 Zeichen lang sein und darf nur Buchstaben, Umlaute, Leerzeichen und Bindestriche enthalten.";
|
||||||
|
} elseif (!userNameValidator($nachname)) {
|
||||||
|
$error = "Der Nachname muss 2 bis 20 Zeichen lang sein und darf nur Buchstaben, Umlaute, Leerzeichen und Bindestriche enthalten.";
|
||||||
|
} elseif (!userPasswordValidator($plainPassword)) {
|
||||||
|
$error = "Das Passwort muss 5 bis 12 Zeichen lang sein.";
|
||||||
|
} elseif ($termsAccepted !== "1") {
|
||||||
|
$error = "Bitte akzeptiere die Datenschutzerklärung und die Nutzungsbedingungen.";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
// Token für die simulierte E-Mail und die spätere Bestätigung erzeugen.
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
$existingUser = $dao->findUser($email);
|
||||||
|
|
||||||
|
if (!is_dir("data/mails") && !mkdir("data/mails", 0777, true)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Ordner data/mails konnte nicht erstellt werden."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir("data/pending") && !mkdir("data/pending", 0777, true)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Ordner data/pending konnte nicht erstellt werden."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/mails")) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Ordner data/mails ist nicht beschreibbar."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/pending")) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Ordner data/pending ist nicht beschreibbar."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser === null) {
|
||||||
|
$password = password_hash(
|
||||||
|
$plainPassword,
|
||||||
|
PASSWORD_DEFAULT
|
||||||
|
);
|
||||||
|
|
||||||
|
$pendingData = [
|
||||||
|
"email" => $email,
|
||||||
|
"vorname" => $vorname,
|
||||||
|
"nachname" => $nachname,
|
||||||
|
"password" => $password
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
"data/pending/" . $token . ".json",
|
||||||
|
json_encode(
|
||||||
|
$pendingData,
|
||||||
|
JSON_PRETTY_PRINT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Registrierung bestätigen</h2>
|
||||||
|
<p>
|
||||||
|
Bitte ignorieren Sie diese Nachricht,
|
||||||
|
wenn Sie sich nicht registrieren wollten.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=confirm-register&token=$token'>
|
||||||
|
Registrierung bestätigen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Registrierung</h2>
|
||||||
|
<p>
|
||||||
|
Bitte ignorieren Sie diese Nachricht,
|
||||||
|
wenn Sie sich nicht registrieren wollten.
|
||||||
|
</p>
|
||||||
|
<p>Sie sind bereits registriert.</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=password-forgotten'>
|
||||||
|
Passwort vergessen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
"data/mails/" . $token . ".html",
|
||||||
|
$mailContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// Neutrale Meldung, damit nicht sichtbar wird,
|
||||||
|
// ob die E-Mail bereits registriert ist.
|
||||||
|
$success = 'Weitere Infos finden Sie in der Datei
|
||||||
|
<a href="index.php?pfad=show-mail&token='
|
||||||
|
. htmlspecialchars($token)
|
||||||
|
. '" target="_blank">xy</a>.';
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Die Registrierung konnte nicht verarbeitet werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once '../model/ArticleManager.php';
|
||||||
|
require_once '../model/UserManager.php';
|
||||||
|
require_once '../model/Article.php';
|
||||||
|
require_once '../validator/search-validator.php';
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
|
||||||
|
|
||||||
|
$search = trim($_GET["q"]);
|
||||||
|
if (!searchQueryValidator($search)) {
|
||||||
|
$_SESSION["search_results"] = [];
|
||||||
|
$_SESSION["search_query"] = "";
|
||||||
|
$_SESSION["message"] = "invalid_search_query";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
|
||||||
|
$results = $articleManager->search($search);
|
||||||
|
|
||||||
|
$safeArrayResults = [];
|
||||||
|
foreach ($results as $obj) {
|
||||||
|
$safeArrayResults[] = [
|
||||||
|
"id" => $obj->getId(),
|
||||||
|
"title" => $obj->getTitle(),
|
||||||
|
"content" => $obj->getContent(),
|
||||||
|
"author" => $userManager->findUser($obj->getAuthor())["vorname"] . " " . $userManager->findUser($obj->getAuthor())["nachname"],
|
||||||
|
"category" => $obj->getCategory(),
|
||||||
|
"tags" => $obj->getTags(),
|
||||||
|
"creationDate" => $obj->getCreationDate(),
|
||||||
|
"likes" => $obj->getLikes(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["search_results"] = $safeArrayResults;
|
||||||
|
$_SESSION["search_query"] = $search;
|
||||||
|
$_SESSION["message"] = "new_search_results";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sort = $_GET['sort'] ?? 'alphabet';
|
||||||
|
$_SESSION['search_sort'] = $sort;
|
||||||
|
|
||||||
|
$category = $_GET['category'] ?? 'all';
|
||||||
|
$_SESSION['search_category'] = $category;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
|
||||||
|
if (!searchLimitValidator($limit)) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['search_limit'] = $limit;
|
||||||
|
|
||||||
|
// Neue Suche -> immer wieder bei Seite 1 beginnen
|
||||||
|
header("Location: ../../index.php?pfad=search-results&q=" . urlencode($search) . "&sort=" . urlencode($sort) . "&category=" . urlencode($category) . "&limit=" . $limit . "&page=1");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Liefert den vollständigen, zuletzt gefundenen Ergebnis-Datensatz als JSON.
|
||||||
|
*
|
||||||
|
* Wird von js/search-results.js EINMALIG per fetch() geladen, damit Sortierung,
|
||||||
|
* Filterung und Pagination danach komplett im Browser laufen können, ohne
|
||||||
|
* weitere Serveranfragen oder eine erneute Datenbank-Suche auszulösen.
|
||||||
|
*/
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['search_results'] ?? [];
|
||||||
|
|
||||||
|
$payload = array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
|
'title' => $item['title'],
|
||||||
|
'author' => $item['author'],
|
||||||
|
'category' => $item['category'] ?? '',
|
||||||
|
'likes' => getLikeCount($item),
|
||||||
|
];
|
||||||
|
}, $rawResults);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'query' => $_SESSION['search_query'] ?? '',
|
||||||
|
'results' => array_values($payload),
|
||||||
|
]);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'php/model/Article.php';
|
||||||
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once 'php/model/UserManager.php';
|
||||||
|
require_once 'php/model/CommentManager.php';
|
||||||
|
require_once 'php/validator/article-validator.php';
|
||||||
|
|
||||||
|
// Die übergebene ID muss eine gültige, positive Zahl sein, bevor sie
|
||||||
|
// weiterverwendet wird. Vorher wurde jeder nicht-leere Wert akzeptiert.
|
||||||
|
$id = isset($_GET["id"]) ? articleIdValidator($_GET["id"]) : false;
|
||||||
|
|
||||||
|
if ($id !== false) {
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$article = $articleManager->getArticle($id);
|
||||||
|
if($article != null){
|
||||||
|
$title = $article->getTitle();
|
||||||
|
$content = $article->getContent();
|
||||||
|
$category = $article->getCategory();
|
||||||
|
$author = $article->getAuthor();
|
||||||
|
$tags = $article->getTags();
|
||||||
|
$articleObj = $article; // Objekt für die Like-Abfagen sichern
|
||||||
|
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
$name = $userManager->findUser($author)["vorname"]." ".$userManager->findUser($author)["nachname"];
|
||||||
|
}else{
|
||||||
|
//header("location: index.php?pfad=404");
|
||||||
|
include_once "content/404.php";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($_GET["pfad"] == "updateArticle"){
|
||||||
|
if($article->getAuthor() != $_SESSION["user_email"]){
|
||||||
|
$_SESSION["message"] = "unauthorized_access";
|
||||||
|
header("location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
$comments = $commentManager->getCommentsByArticle($id); // NEU: validierte ID statt rohem $_GET["id"]
|
||||||
|
|
||||||
|
foreach ($comments as $comment) {
|
||||||
|
if ($comment->isReply()) {
|
||||||
|
$parentId = $comment->getParentCommentId();
|
||||||
|
$repliesByParent[$parentId][] = $comment;
|
||||||
|
} else {
|
||||||
|
$mainComments[] = $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once 'php/model/Article.php';
|
||||||
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once 'php/model/UserManager.php';
|
||||||
|
require_once 'php/validator/article-validator.php';
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
if (isset($_GET["category"]) && !empty($_GET["category"]) && articleCategoryValidator($_GET["category"])) {
|
||||||
|
|
||||||
|
$category = $_GET["category"];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
|
||||||
|
$allArticles = $articleManager->getArticlesByCategory($category);
|
||||||
|
|
||||||
|
$safeArrayResults = [];
|
||||||
|
foreach ($allArticles as $article) {
|
||||||
|
$authorName = $userManager->findUser($article->getAuthor())["vorname"] . " " . $userManager->findUser($article->getAuthor())["nachname"];
|
||||||
|
$safeArrayResults[] = [
|
||||||
|
"id" => $article->getID(),
|
||||||
|
"title" => $article->getTitle(),
|
||||||
|
"content" => $article->getContent(),
|
||||||
|
"author" => $authorName,
|
||||||
|
"category" => $article->getCategory(),
|
||||||
|
"tags" => $article->getTags(),
|
||||||
|
"creationDate" => $article->getCreationDate(),
|
||||||
|
"likes" => $article->getLikes(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session dient hier (wie bei search-results) als Zwischenspeicher, damit
|
||||||
|
// js/showCategory.js den kompletten Datensatz per showCategory-data.php
|
||||||
|
// nachladen kann, ohne die Kategorie-Abfrage ein zweites Mal auszuführen.
|
||||||
|
$_SESSION['category_results'] = $safeArrayResults;
|
||||||
|
$_SESSION['category_name'] = $category;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION["message"] = "internal_error";
|
||||||
|
include_once "content/404.php";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$_SESSION["message"] = "invalid_category";
|
||||||
|
include_once "content/404.php";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Liefert den vollständigen, zuletzt geladenen Kategorie-Datensatz als JSON.
|
||||||
|
*
|
||||||
|
* Wird von js/showCategory.js EINMALIG per fetch() geladen, damit Sortierung,
|
||||||
|
* Suche und Pagination danach komplett im Browser laufen können, ohne
|
||||||
|
* weitere Serveranfragen oder eine erneute Datenbank-Abfrage auszulösen
|
||||||
|
*
|
||||||
|
* Analog zu php/controller/search-results-data.php
|
||||||
|
*/
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['category_results'] ?? [];
|
||||||
|
|
||||||
|
$payload = array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
|
'title' => $item['title'],
|
||||||
|
'content' => $item['content'] ?? '',
|
||||||
|
'author' => $item['author'],
|
||||||
|
'category' => $item['category'] ?? '',
|
||||||
|
'likes' => getLikeCount($item),
|
||||||
|
];
|
||||||
|
}, $rawResults);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'category' => $_SESSION['category_name'] ?? '',
|
||||||
|
'results' => array_values($payload),
|
||||||
|
]);
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once '../model/LocalArticleManager.php';
|
||||||
|
require_once '../model/ArticleManager.php';
|
||||||
|
require_once '../model/Article.php';
|
||||||
|
require_once '../validator/article-validator.php';
|
||||||
|
require_once '../../includes/article-block-helper.php';
|
||||||
|
require_once '../../includes/csrf.php'; // NEU: CSRF-Schutz
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["message"] = "invalid_csrf_token";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Beitrags-ID muss eine gültige numerische ID sein
|
||||||
|
$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
|
||||||
|
|
||||||
|
if ($id === false || $id === null) {
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$article = $articleManager->getArticle($id);
|
||||||
|
|
||||||
|
// Existenz des Beitrags prüfen, bevor auf $article zugegriffen wird.
|
||||||
|
if ($article === null) {
|
||||||
|
$_SESSION["message"] = "missing_id";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($article->getAuthor() != $_SESSION["user_email"]) {
|
||||||
|
$_SESSION["message"] = "unauthorized_access";
|
||||||
|
header("location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION["message"] = $e->getMessage();
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadDir = __DIR__ . '/../../uploads/';
|
||||||
|
if (!file_exists($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formularzustand (Titel/Tags/Kategorie/Blöcke) immer sichern, damit er nach einem
|
||||||
|
// Redirect (PRG-Pattern oder Validierungsfehler) wieder angezeigt werden kann.
|
||||||
|
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
||||||
|
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
|
||||||
|
$_SESSION["old_category"] = $_POST["category"] ?? '';
|
||||||
|
|
||||||
|
$blocks = rebuildBlocksFromPost($_POST['blocks'] ?? [], $_FILES['blocks'] ?? [], $uploadDir);
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Zwischen-Schritt: Block hinzufügen oder entfernen (kein echtes Speichern).
|
||||||
|
// Wird bei aktivem JavaScript per preventDefault() abgefangen und lokal im
|
||||||
|
// DOM erledigt (js/editor.js) – ohne JS läuft dieser Server-Roundtrip.
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (isset($_POST['editor_action']) && $_POST['editor_action'] !== '') {
|
||||||
|
$action = $_POST['editor_action'];
|
||||||
|
|
||||||
|
if ($action === 'add_text') {
|
||||||
|
$blocks[] = ['type' => 'text', 'value' => ''];
|
||||||
|
} elseif ($action === 'add_image') {
|
||||||
|
$blocks[] = ['type' => 'image', 'value' => ''];
|
||||||
|
} elseif (str_starts_with($action, 'delete_block:')) {
|
||||||
|
$deleteIndex = (int) substr($action, strlen('delete_block:'));
|
||||||
|
unset($blocks[$deleteIndex]);
|
||||||
|
$blocks = array_values($blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Echtes Speichern
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (!isset($_POST["title"]) || !isset($_POST["category"])) {
|
||||||
|
$_SESSION["message"] = "missing_parameters";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$title = $_POST["title"];
|
||||||
|
$content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
$author = $_SESSION["user_email"];
|
||||||
|
$category = $_POST["category"];
|
||||||
|
$tags = $_POST['tags'] ?? '';
|
||||||
|
|
||||||
|
// -------------------------------- Validierung der Daten: -------------------------
|
||||||
|
if (!articleTitleValidator($title)) {
|
||||||
|
$_SESSION["message"] = "invalid_title";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleContentValidator($content)) {
|
||||||
|
$_SESSION["message"] = "invalid_content";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleCategoryValidator($category)) {
|
||||||
|
$_SESSION["message"] = "invalid_category";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!articleTagValidator($tags)) {
|
||||||
|
$_SESSION["message"] = "invalid_tags";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$cleanedTags = [];
|
||||||
|
$rawTags = explode(',', $tags);
|
||||||
|
foreach ($rawTags as $rawTag) {
|
||||||
|
// Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
|
||||||
|
$tag = trim($rawTag);
|
||||||
|
$cleanedTags[] = $tag;
|
||||||
|
}
|
||||||
|
// Duplikate entfernen:
|
||||||
|
$cleanedTags = array_unique($cleanedTags);
|
||||||
|
$cleanedTags = implode(',', $cleanedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Verwaiste Bilder aufräumen -----------------
|
||||||
|
// Bilder, die im alten (gespeicherten) Content vorkamen, im neuen aber nicht
|
||||||
|
// mehr referenziert werden, wurden vom Nutzer entfernt oder ersetzt -> löschen.
|
||||||
|
// Hinweis/TODO: Bilder, die innerhalb derselben Bearbeitungs-Sitzung neu
|
||||||
|
// hochgeladen und noch vor dem finalen Speichern wieder entfernt wurden,
|
||||||
|
// werden hierüber nicht erfasst (sie tauchten nie im alten Content auf) und
|
||||||
|
// bleiben als Datei liegen. Für eine vollständige Bereinigung würde sich ein
|
||||||
|
// regelmäßiger Cleanup-Job anbieten, der verwaiste Dateien im uploads/-Ordner
|
||||||
|
// mit den in der Datenbank referenzierten Pfaden abgleicht.
|
||||||
|
$oldBlocks = json_decode($article->getContent(), true);
|
||||||
|
$oldImagePaths = [];
|
||||||
|
if (is_array($oldBlocks)) {
|
||||||
|
foreach ($oldBlocks as $oldBlock) {
|
||||||
|
if (($oldBlock['type'] ?? '') === 'image'
|
||||||
|
&& !empty($oldBlock['value'])
|
||||||
|
&& is_string($oldBlock['value'])
|
||||||
|
&& str_starts_with($oldBlock['value'], 'uploads/')) {
|
||||||
|
$oldImagePaths[] = $oldBlock['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newImagePaths = [];
|
||||||
|
foreach ($blocks as $block) {
|
||||||
|
if (($block['type'] ?? '') === 'image' && !empty($block['value'])) {
|
||||||
|
$newImagePaths[] = $block['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$orphanedImages = array_diff($oldImagePaths, $newImagePaths);
|
||||||
|
foreach ($orphanedImages as $orphanedImage) {
|
||||||
|
$absolutePath = __DIR__ . '/../../' . $orphanedImage;
|
||||||
|
if (is_file($absolutePath)) {
|
||||||
|
@unlink($absolutePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||||
|
try {
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$article = $articleManager->getArticle($id);
|
||||||
|
$article->setTitle($title);
|
||||||
|
$article->setContent($content);
|
||||||
|
$article->setCategory($category);
|
||||||
|
$article->setTags($cleanedTags);
|
||||||
|
$articleManager->updateArticle($id, $article, $author);
|
||||||
|
|
||||||
|
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$_SESSION["message"] = $e->getMessage();
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
$_SESSION["message"] = "article_updated";
|
||||||
|
// Weiterleitung zur Homepage
|
||||||
|
header("location: ../../index.php?pfad=showArticle&id=$id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once "php/model/CommentManager.php";
|
||||||
|
require_once "includes/csrf.php";
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user_email"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
|
||||||
|
if (!csrf_verify()) {
|
||||||
|
$_SESSION["comment_message"] = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu.";
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$commentId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"comment_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
$articleId = filter_input(
|
||||||
|
INPUT_POST,
|
||||||
|
"article_id",
|
||||||
|
FILTER_VALIDATE_INT
|
||||||
|
);
|
||||||
|
|
||||||
|
$content = trim($_POST["content"] ?? "");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ohne gültige Beitrags-ID kann nicht sicher
|
||||||
|
* zum ursprünglichen Beitrag zurückgeleitet werden.
|
||||||
|
*/
|
||||||
|
if (!$articleId) {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kommentar-ID und Inhalt werden geprüft.
|
||||||
|
*/
|
||||||
|
if (!$commentId || $content === "") {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar darf nicht leer sein.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
|
||||||
|
header(
|
||||||
|
"Location: index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
|
||||||
|
$updated = $commentManager->updateComment(
|
||||||
|
$commentId,
|
||||||
|
$_SESSION["user_email"],
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($updated) {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar wurde erfolgreich bearbeitet.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "success";
|
||||||
|
} else {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Der Kommentar konnte nicht bearbeitet werden.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$_SESSION["comment_message"] =
|
||||||
|
"Beim Bearbeiten des Kommentars ist ein Fehler aufgetreten.";
|
||||||
|
|
||||||
|
$_SESSION["comment_message_type"] = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
header(
|
||||||
|
"Location: index.php?pfad=showArticle&id="
|
||||||
|
. urlencode((string) $articleId)
|
||||||
|
. "#comments"
|
||||||
|
);
|
||||||
|
exit();
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Klasse: Artikel
|
||||||
|
* Diese Klasse stellt alle Daten eines Beitrags (Beitrag) bereit
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
private $id;
|
||||||
|
private $title;
|
||||||
|
private $content;
|
||||||
|
private $author;
|
||||||
|
private $creationDate;
|
||||||
|
private $category;
|
||||||
|
private $tags;
|
||||||
|
private $likes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor
|
||||||
|
*
|
||||||
|
* @param $id integer ID des Beitrages
|
||||||
|
* @param $title string Titel des Beitrags
|
||||||
|
* @param $content string Inhalt des Beitrags
|
||||||
|
* @param $author string der Autor des des Beitrages NID
|
||||||
|
* @param $category string Kategorie des Beitrages
|
||||||
|
* @param $tags string optionale Schlagworte für eine bessere Suche
|
||||||
|
* @param $creationDate string Datum der Beitragserstellung
|
||||||
|
*/
|
||||||
|
public function __construct(int $id, string $title, string $content, string $author, string $category, string $tags, string $creationDate, array $likes = [])
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->author = $author;
|
||||||
|
$this->creationDate = $creationDate;
|
||||||
|
$this->category = $category;
|
||||||
|
$this->tags = $tags;
|
||||||
|
$this->likes = $likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID eines Beitrags zurück.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Titel eines Beitrags zurück.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Titel eines Beitrags
|
||||||
|
* @param $title
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setTitle($title)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Content eines Beitrags zurück.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Content eines Beitrags.
|
||||||
|
*
|
||||||
|
* @param $content
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setContent($content)
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Autor eines Beitrags zurück.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAuthor(): string
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt einen neuen Autor eines Beitrages.
|
||||||
|
* @param string $author
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setAuthor(string $author)
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das Veröffentlichungsdatum des Beitrags zurück.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCreationDate(): string
|
||||||
|
{
|
||||||
|
return $this->creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Kategorie eines Beitrags zurück.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCategory(): string
|
||||||
|
{
|
||||||
|
return $this->category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Kategorie eines Beitrags.
|
||||||
|
* @param string $category
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setCategory(string $category)
|
||||||
|
{
|
||||||
|
$this->category = $category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Schlagworte eines Beitrags zurück.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTags(): string
|
||||||
|
{
|
||||||
|
return $this->tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Schlagworte eines Beitrags.
|
||||||
|
* @param string $tags
|
||||||
|
*/
|
||||||
|
public function setTags(string $tags)
|
||||||
|
{
|
||||||
|
$this->tags = $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle User-IDs zurück, die diesen Beitrag geliked haben.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getLikes(): array
|
||||||
|
{
|
||||||
|
return $this->likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Gesamtzahl der Likes zurück.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLikeCount(): int
|
||||||
|
{
|
||||||
|
return count($this->likes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob ein bestimmter Nutzer den Beitrag bereits geliked hat.
|
||||||
|
*
|
||||||
|
* @param string $userId
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasLiked(string $userId): bool
|
||||||
|
{
|
||||||
|
return in_array($userId, $this->likes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'LocalArticleManager.php';
|
||||||
|
require_once 'DatabaseArticleManager.php';
|
||||||
|
require_once 'Article.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Klasse beinhaltet alle Methoden für die Operation mit den Beitrags-Daten.
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class ArticleManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Diese Methode erstellt, falls noch keine existiert, eine Instanz einer ArticleManager-Implementierung und
|
||||||
|
* erstellt Dummy-Beiträge.
|
||||||
|
*
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
|
||||||
|
return DatabaseArticleManager::getInstance(); // Hier kann zwischen dem lokalen und datenbankbasiertem ArticleManager gewechselt werden.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
require_once "Article.php";
|
||||||
|
|
||||||
|
class NotFoundException extends Exception {}
|
||||||
|
class UnauthorizedAccessException extends Exception {}
|
||||||
|
class InternalServerErrorException extends Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Klasse beinhaltet alle Methoden für die Operation mit den Artikel-Daten.
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
interface ArticleManagerDAO
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ein angemeldeter Nutzer erstellt einen neuen Beitrag.
|
||||||
|
* @param $title string Titel des Beitrags
|
||||||
|
* @param $content string Inhalt des Beitrags
|
||||||
|
* @param $author string der Autor des des Beitrages NID
|
||||||
|
* @param $category string Kategorie des Beitrages
|
||||||
|
* @param $tags string optionale Schlagworte für eine bessere Suche
|
||||||
|
*
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function addArticle($title, $content, $author, $category, $tags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ändert den gespeicherten Beitrag eines übergebenen Beitrags und eines Autors.
|
||||||
|
* Es wird geprüft, ob der zu änderne Beitrag existiert und ob der übergebene Autor der Autor des originalen
|
||||||
|
* Beitrages ist.
|
||||||
|
* @param $id
|
||||||
|
* @param $article
|
||||||
|
* @param $author
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
* @throws NotFoundException
|
||||||
|
* @throws UnauthorizedAccessException
|
||||||
|
*/
|
||||||
|
public function updateArticle($id, $article, $author);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht einen Beitrag aus übergebener ID und dem Nutzer, der die Löschung ausführt.
|
||||||
|
* @param $id
|
||||||
|
* @param $author
|
||||||
|
* @return void
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
* @throws NotFoundException
|
||||||
|
* @throws UnauthorizedAccessException
|
||||||
|
*/
|
||||||
|
public function deleteArticle($id, $author);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beitrag aufrufen.
|
||||||
|
* $id ID des Beitrags
|
||||||
|
*
|
||||||
|
* @return Article
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function getArticle($id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Beiträge aufrufen.
|
||||||
|
*
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function getAllArticles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Beiträge eines Nutzer mit einer gegebenen ID aus.
|
||||||
|
* @param $author
|
||||||
|
* @return Article[]
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function getArticlesByAuthor($author);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durchsucht die vorhandenen Beiträge nach einem bestimmten Suchbegriff.
|
||||||
|
* Die Suche prüft, ob das übergebene Keyword im Titel oder im Inhalt eines Beitrags vorkommt.
|
||||||
|
* (Unabhängig von Groß-und Kleinschreibung)
|
||||||
|
* @param string $keyword Der eingegebene Suchbegriff.
|
||||||
|
* @return array Ein Array von Artikeln ,die dem Suchkriterium entsprechen. Wenn nichts gefunden wird, ein leeres Array.
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function search(string $keyword): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Beiträge einer gegebenen Kategorie aus.
|
||||||
|
* @param $category
|
||||||
|
* @return mixed
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function getArticlesByCategory($category);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registriert oder entfernt ein Like eines Nutzers für einen Beitrag.
|
||||||
|
* Wenn schon geliked -> Unlike
|
||||||
|
* Wenn noch nicht geliked -> Like
|
||||||
|
*
|
||||||
|
* @param int $articleId Die ID des Beitrags
|
||||||
|
* @param string $userId Die ID des Nutzers
|
||||||
|
* @return bool True wenn geliked, False wenn unliked
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
* @throws NotFoundException
|
||||||
|
*/
|
||||||
|
public function toggleLike(int $articleId, string $userId): bool;
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repräsentiert einen Kommentar unter einem Beitrag.
|
||||||
|
*
|
||||||
|
* Ein Kommentar kann entweder ein Hauptkommentar sein
|
||||||
|
* oder eine Antwort auf einen anderen Kommentar.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
private int $id;
|
||||||
|
private int $articleId;
|
||||||
|
private ?int $parentCommentId;
|
||||||
|
private ?string $author;
|
||||||
|
private string $content;
|
||||||
|
private string $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen Kommentar.
|
||||||
|
*
|
||||||
|
* @param int $id Eindeutige ID des Kommentars
|
||||||
|
* @param int $articleId ID des zugehörigen Beitrags
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
* @param string|null $author Autor des Kommentars oder null bei gelöschten Kommentaren
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param string $created Erstellungsdatum des Kommentars
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
int $id,
|
||||||
|
int $articleId,
|
||||||
|
?int $parentCommentId,
|
||||||
|
?string $author,
|
||||||
|
string $content,
|
||||||
|
string $created
|
||||||
|
) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->articleId = $articleId;
|
||||||
|
$this->parentCommentId = $parentCommentId;
|
||||||
|
$this->author = $author;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->created = $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return int Kommentar-ID
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des zugehörigen Beitrags zurück.
|
||||||
|
*
|
||||||
|
* @return int Beitrags-ID
|
||||||
|
*/
|
||||||
|
public function getArticleId(): int
|
||||||
|
{
|
||||||
|
return $this->articleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des Eltern-Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return int|null ID des Eltern-Kommentars oder null
|
||||||
|
*/
|
||||||
|
public function getParentCommentId(): ?int
|
||||||
|
{
|
||||||
|
return $this->parentCommentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt zurück, ob der Kommentar eine Antwort ist.
|
||||||
|
*
|
||||||
|
* @return bool true wenn der Kommentar eine Antwort ist, sonst false
|
||||||
|
*/
|
||||||
|
public function isReply(): bool
|
||||||
|
{
|
||||||
|
return $this->parentCommentId !== null && $this->parentCommentId !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Autor des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string|null Autor oder null bei gelöschten Kommentaren
|
||||||
|
*/
|
||||||
|
public function getAuthor(): ?string
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Inhalt des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string Kommentarinhalt
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das Erstellungsdatum des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string Erstellungsdatum
|
||||||
|
*/
|
||||||
|
public function getCreated(): string
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "DatabaseCommentManager.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zentrale Zugriffsschicht für Kommentare.
|
||||||
|
*
|
||||||
|
* Die Anwendung arbeitet ausschließlich
|
||||||
|
* mit dem CommentManager und kennt die
|
||||||
|
* konkrete Speicherimplementierung nicht.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class CommentManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gibt die aktive Kommentarverwaltung zurück.
|
||||||
|
*
|
||||||
|
* @return CommentManagerDAO
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
return DatabaseCommentManager::getInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "Comment.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schnittstelle für die Verwaltung von Kommentaren.
|
||||||
|
*
|
||||||
|
* Definiert die grundlegenden Methoden zum
|
||||||
|
* Speichern und Laden von Kommentaren.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
interface CommentManagerDAO
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Speichert einen neuen Kommentar zu einem Beitrag.
|
||||||
|
*
|
||||||
|
* Optional kann eine parentCommentId übergeben werden,
|
||||||
|
* wenn der Kommentar eine Antwort auf einen anderen Kommentar ist.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
* @param string $author Autor des Kommentars
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
*
|
||||||
|
* @return int ID des neu gespeicherten Kommentars
|
||||||
|
*/
|
||||||
|
public function addComment(
|
||||||
|
$articleId,
|
||||||
|
$author,
|
||||||
|
$content,
|
||||||
|
$parentCommentId = null
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Kommentare eines Beitrags zurück.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
*
|
||||||
|
* @return Comment[] Liste der Kommentare
|
||||||
|
*/
|
||||||
|
public function getCommentsByArticle(
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Kommentare eines Autors zurück.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
*
|
||||||
|
* @return Comment[] Liste der Kommentare
|
||||||
|
*/
|
||||||
|
public function getCommentsByAuthor($author);
|
||||||
|
/**
|
||||||
|
* Löscht einen einzelnen Kommentar des angemeldeten Nutzers.
|
||||||
|
*
|
||||||
|
* Kommentare ohne Antworten werden vollständig entfernt.
|
||||||
|
* Kommentare mit Antworten bleiben als anonymer Platzhalter erhalten.
|
||||||
|
*
|
||||||
|
* @param int $commentId ID des Kommentars
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
* @return bool true, wenn der Kommentar gefunden und gelöscht wurde
|
||||||
|
*/
|
||||||
|
public function deleteComment(int $commentId, string $author): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht beziehungsweise anonymisiert alle Kommentare eines Nutzers.
|
||||||
|
*
|
||||||
|
* Diese Methode wird bei der Löschung eines Benutzerkontos verwendet.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Nutzers
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteCommentsByAuthor(string $author): void;
|
||||||
|
/**
|
||||||
|
* Bearbeitet einen Kommentar des angemeldeten Nutzers.
|
||||||
|
*
|
||||||
|
* Nur der Autor des Kommentars darf den Inhalt ändern.
|
||||||
|
*
|
||||||
|
* @param int $commentId ID des Kommentars
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
* @param string $content Neuer Kommentarinhalt
|
||||||
|
* @return bool true, wenn der Kommentar bearbeitet wurde
|
||||||
|
*/
|
||||||
|
public function updateComment(
|
||||||
|
int $commentId,
|
||||||
|
string $author,
|
||||||
|
string $content
|
||||||
|
): bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
|
require_once 'ArticleManagerDAO.php';
|
||||||
|
require_once 'Article.php';
|
||||||
|
/**
|
||||||
|
* Klasse: Eine SQLLite3 Lösung des ArticleManagerDAO.
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class DatabaseArticleManager implements ArticleManagerDAO {
|
||||||
|
|
||||||
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor
|
||||||
|
*
|
||||||
|
* Erstellt die Datenbankverbindung und die Tabelle articles.
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die Verbindung zur Datenbank auf.
|
||||||
|
*
|
||||||
|
* @return PDO Datenbankverbindung
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
private function getConnection()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
return $db;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die DatabaseArticleManager-Instanz zurück.
|
||||||
|
* @return DatabaseArticleManager
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance == null) {
|
||||||
|
self::$instance = new DatabaseArticleManager();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addArticle($title, $content, $author, $category, $tags)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "INSERT INTO articles (title, content, author, category, tags)
|
||||||
|
VALUES (:title, :content, :author, :category, :tags);";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
// Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
|
||||||
|
$command->execute([
|
||||||
|
":title" => $title,
|
||||||
|
":content" => $content,
|
||||||
|
":author" => $author,
|
||||||
|
":category" => $category,
|
||||||
|
":tags" => $tags
|
||||||
|
]);
|
||||||
|
|
||||||
|
return intval($db->lastInsertId());
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateArticle($id, $article, $author)
|
||||||
|
{
|
||||||
|
if (empty($article)) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechtigungsprüfung analog zur lokalen Implementierung:
|
||||||
|
if ($article->getAuthor() !== $author) {
|
||||||
|
throw new UnauthorizedAccessException("unauthorized_access");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "UPDATE articles
|
||||||
|
SET title = :title, content = :content, author = :author, category = :category, tags = :tags
|
||||||
|
WHERE id = :id;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":id" => $id,
|
||||||
|
":title" => $article->getTitle(),
|
||||||
|
":content" => $article->getContent(),
|
||||||
|
":author" => $author,
|
||||||
|
":category" => $article->getCategory(),
|
||||||
|
":tags" => $article->getTags()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// rowCount() prüft, ob eine Zeile mit dieser ID existierte und geändert werden konnte
|
||||||
|
if ($command->rowCount() === 0) {
|
||||||
|
// Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist
|
||||||
|
if (!$this->getArticle($id)) {
|
||||||
|
throw new NotFoundException("missing_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteArticle($id, $author)
|
||||||
|
{
|
||||||
|
$article = $this->getArticle($id);
|
||||||
|
if (empty($article)) {
|
||||||
|
throw new NotFoundException("not_found_article");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechtigungsprüfung:
|
||||||
|
if ($article->getAuthor() !== $author) {
|
||||||
|
throw new UnauthorizedAccessException("unauthorized_access");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "DELETE FROM articles WHERE id = :id;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":id" => $id]);
|
||||||
|
} catch (PDOException $exc) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticle($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "SELECT * FROM articles WHERE id = :id;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":id" => $id]);
|
||||||
|
$row = $command->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
|
return new Article(
|
||||||
|
intval($row['id']),
|
||||||
|
$row['title'],
|
||||||
|
$row['content'],
|
||||||
|
$row['author'],
|
||||||
|
$row['category'],
|
||||||
|
$row['tags'],
|
||||||
|
$row['created'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllArticles()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "SELECT * FROM articles;";
|
||||||
|
|
||||||
|
$command = $db->query($sql);
|
||||||
|
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$articles = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$articles[] = new Article(
|
||||||
|
intval($row['id']),
|
||||||
|
$row['title'],
|
||||||
|
$row['content'],
|
||||||
|
$row['author'],
|
||||||
|
$row['category'],
|
||||||
|
$row['tags'],
|
||||||
|
$row['created']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $articles;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticlesByAuthor($author)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "SELECT * FROM articles WHERE author = :author;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":author" => $author]);
|
||||||
|
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($row['id']),
|
||||||
|
$row['title'],
|
||||||
|
$row['content'],
|
||||||
|
$row['author'],
|
||||||
|
$row['category'],
|
||||||
|
$row['tags'],
|
||||||
|
$row['created'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredArticles;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticlesByCategory($category)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "SELECT * FROM articles WHERE category = :category;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":category" => $category]);
|
||||||
|
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($row['id']),
|
||||||
|
$row['title'],
|
||||||
|
$row['content'],
|
||||||
|
$row['author'],
|
||||||
|
$row['category'],
|
||||||
|
$row['tags'],
|
||||||
|
$row['created'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredArticles;
|
||||||
|
} catch (PDOException $exc) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function search(string $keyword): array
|
||||||
|
{
|
||||||
|
$cleankeyword = trim($keyword);
|
||||||
|
|
||||||
|
// Leeren Suchbegriff sofort abfangen
|
||||||
|
if ($cleankeyword === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "SELECT id, title, content, author, category, tags, created
|
||||||
|
FROM articles
|
||||||
|
WHERE title LIKE :keyword
|
||||||
|
OR content LIKE :keyword
|
||||||
|
OR tags LIKE :keyword;";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
// Wildcards für die Suche hinzufügen
|
||||||
|
$searchParam = '%' . $cleankeyword . '%';
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":keyword" => $searchParam
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$articleId = intval($row['id']);
|
||||||
|
$likes = $this->getLikesForArticle($articleId);
|
||||||
|
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
$articleId,
|
||||||
|
$row['title'] ?? '',
|
||||||
|
$row['content'] ?? '',
|
||||||
|
$row['author'] ?? '',
|
||||||
|
$row['category'] ?? '',
|
||||||
|
$row['tags'] ?? '',
|
||||||
|
$row['created'] ?? '',
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredArticles;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt alle User-IDs, die einen bestimmten Beitrag geliked haben.
|
||||||
|
*
|
||||||
|
* @return String[] UserIDs
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
private function getLikesForArticle(int $articleId): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$sql = "SELECT user_id FROM likes WHERE article_id = :article_id;";
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([':article_id' => $articleId]);
|
||||||
|
|
||||||
|
return $command->fetchAll(PDO::FETCH_COLUMN) ?: [];
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleLike(int $articleId, string $userId): bool
|
||||||
|
{
|
||||||
|
// prüfen, ob der Artikel überhaupt existiert
|
||||||
|
$article = $this->getArticle($articleId);
|
||||||
|
if (!$article) {
|
||||||
|
throw new NotFoundException("missing_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
// prüfen, ob das Like bereits existiert
|
||||||
|
$checkSql = "SELECT COUNT(*) FROM likes WHERE article_id = :article_id AND user_id = :user_id;";
|
||||||
|
$checkCommand = $db->prepare($checkSql);
|
||||||
|
$checkCommand->execute([
|
||||||
|
':article_id' => $articleId,
|
||||||
|
':user_id' => $userId
|
||||||
|
]);
|
||||||
|
|
||||||
|
$hasLiked = (int)$checkCommand->fetchColumn() > 0;
|
||||||
|
|
||||||
|
if ($hasLiked) {
|
||||||
|
// wenn bereits geliked -> Unlike
|
||||||
|
$deleteSql = "DELETE FROM likes WHERE article_id = :article_id AND user_id = :user_id;";
|
||||||
|
$deleteCommand = $db->prepare($deleteSql);
|
||||||
|
$deleteCommand->execute([
|
||||||
|
':article_id' => $articleId,
|
||||||
|
':user_id' => $userId
|
||||||
|
]);
|
||||||
|
return false; // gibt false zurück, da der Beitrag jetzt nicht mehr geliked ist
|
||||||
|
} else {
|
||||||
|
// wenn noch nicht geliked -> Like
|
||||||
|
$insertSql = "INSERT INTO likes (article_id, user_id) VALUES (:article_id, :user_id);";
|
||||||
|
$insertCommand = $db->prepare($insertSql);
|
||||||
|
$insertCommand->execute([
|
||||||
|
':article_id' => $articleId,
|
||||||
|
':user_id' => $userId
|
||||||
|
]);
|
||||||
|
return true; // gibt true zurück, da der Beitrag jetzt geliked ist
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,416 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
|
require_once "CommentManagerDAO.php";
|
||||||
|
require_once "Comment.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verwaltet die Speicherung und das Laden von Kommentaren
|
||||||
|
* über eine SQLite-Datenbank.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class DatabaseCommentManager implements CommentManagerDAO
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die Kommentartabelle, falls diese noch nicht existiert.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die Verbindung zur SQLite-Datenbank auf.
|
||||||
|
*
|
||||||
|
* @return PDO Datenbankverbindung
|
||||||
|
*/
|
||||||
|
private function getConnection()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
return $db;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Singleton-Instanz zurück.
|
||||||
|
*
|
||||||
|
* @return DatabaseCommentManager
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new DatabaseCommentManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert einen neuen Kommentar oder eine Antwort.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
* @param string $author Autor des Kommentars
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
*
|
||||||
|
* @return int ID des neu gespeicherten Kommentars
|
||||||
|
*/
|
||||||
|
public function addComment(
|
||||||
|
$articleId,
|
||||||
|
$author,
|
||||||
|
$content,
|
||||||
|
$parentCommentId = null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
if ($parentCommentId === "" || $parentCommentId === 0 || $parentCommentId === "0") {
|
||||||
|
$parentCommentId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
INSERT INTO comments (
|
||||||
|
article_id,
|
||||||
|
parent_comment_id,
|
||||||
|
author,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
:articleId,
|
||||||
|
:parentCommentId,
|
||||||
|
:author,
|
||||||
|
:content
|
||||||
|
)
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":articleId" => $articleId,
|
||||||
|
":parentCommentId" => $parentCommentId,
|
||||||
|
":author" => $author,
|
||||||
|
":content" => $content
|
||||||
|
]);
|
||||||
|
|
||||||
|
return intval($db->lastInsertId());
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Kommentare eines Beitrags.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
public function getCommentsByArticle($articleId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
article_id,
|
||||||
|
CASE
|
||||||
|
WHEN parent_comment_id IS NULL THEN NULL
|
||||||
|
WHEN parent_comment_id = '' THEN NULL
|
||||||
|
WHEN parent_comment_id = 0 THEN NULL
|
||||||
|
ELSE parent_comment_id
|
||||||
|
END AS parent_comment_id,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
|
created
|
||||||
|
FROM comments
|
||||||
|
WHERE article_id = :articleId
|
||||||
|
ORDER BY created ASC
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":articleId" => $articleId]);
|
||||||
|
|
||||||
|
return $this->mapRowsToComments($command);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Kommentare eines Autors.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
public function getCommentsByAuthor($author)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
article_id,
|
||||||
|
CASE
|
||||||
|
WHEN parent_comment_id IS NULL THEN NULL
|
||||||
|
WHEN parent_comment_id = '' THEN NULL
|
||||||
|
WHEN parent_comment_id = 0 THEN NULL
|
||||||
|
ELSE parent_comment_id
|
||||||
|
END AS parent_comment_id,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
|
created
|
||||||
|
FROM comments
|
||||||
|
WHERE author = :author
|
||||||
|
ORDER BY created DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":author" => $author]);
|
||||||
|
|
||||||
|
return $this->mapRowsToComments($command);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt Datenbankzeilen in Comment-Objekte um.
|
||||||
|
*
|
||||||
|
* @param PDOStatement $command Ausgeführtes Statement
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
private function mapRowsToComments($command)
|
||||||
|
{
|
||||||
|
$comments = [];
|
||||||
|
|
||||||
|
while ($row = $command->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$parentCommentId = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset($row["parent_comment_id"])
|
||||||
|
&& $row["parent_comment_id"] !== null
|
||||||
|
&& $row["parent_comment_id"] !== ""
|
||||||
|
&& intval($row["parent_comment_id"]) !== 0
|
||||||
|
) {
|
||||||
|
$parentCommentId = intval($row["parent_comment_id"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$comments[] = new Comment(
|
||||||
|
intval($row["id"]),
|
||||||
|
intval($row["article_id"]),
|
||||||
|
$parentCommentId,
|
||||||
|
$row["author"],
|
||||||
|
$row["content"],
|
||||||
|
$row["created"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $comments;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Bearbeitet einen eigenen Kommentar.
|
||||||
|
*
|
||||||
|
* Der Kommentar wird nur geändert, wenn er dem
|
||||||
|
* angemeldeten Nutzer gehört.
|
||||||
|
*
|
||||||
|
* @param int $commentId ID des Kommentars
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
* @param string $content Neuer Kommentarinhalt
|
||||||
|
* @return bool true, wenn der Kommentar bearbeitet wurde
|
||||||
|
*/
|
||||||
|
public function updateComment(
|
||||||
|
int $commentId,
|
||||||
|
string $author,
|
||||||
|
string $content
|
||||||
|
): bool {
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$command = $db->prepare("
|
||||||
|
UPDATE comments
|
||||||
|
SET content = :content
|
||||||
|
WHERE id = :commentId
|
||||||
|
AND author = :author
|
||||||
|
");
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":content" => $content,
|
||||||
|
":commentId" => $commentId,
|
||||||
|
":author" => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $command->rowCount() > 0;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Löscht einen eigenen Kommentar.
|
||||||
|
*
|
||||||
|
* Hat der Kommentar Antworten, wird er anonymisiert.
|
||||||
|
* Hat er keine Antworten, wird er vollständig gelöscht.
|
||||||
|
*
|
||||||
|
* @param int $commentId ID des Kommentars
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
* @return bool true, wenn der Kommentar gelöscht wurde
|
||||||
|
*/
|
||||||
|
public function deleteComment(int $commentId, string $author): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zuerst wird geprüft, ob der Kommentar existiert
|
||||||
|
* und wirklich dem angemeldeten Nutzer gehört.
|
||||||
|
*/
|
||||||
|
$checkCommand = $db->prepare("
|
||||||
|
SELECT id
|
||||||
|
FROM comments
|
||||||
|
WHERE id = :commentId
|
||||||
|
AND author = :author
|
||||||
|
");
|
||||||
|
|
||||||
|
$checkCommand->execute([
|
||||||
|
":commentId" => $commentId,
|
||||||
|
":author" => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($checkCommand->fetch() === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Danach wird geprüft, ob Antworten auf den Kommentar existieren.
|
||||||
|
*/
|
||||||
|
$replyCommand = $db->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM comments
|
||||||
|
WHERE parent_comment_id = :commentId
|
||||||
|
");
|
||||||
|
|
||||||
|
$replyCommand->execute([
|
||||||
|
":commentId" => $commentId
|
||||||
|
]);
|
||||||
|
|
||||||
|
$hasReplies = (int) $replyCommand->fetchColumn() > 0;
|
||||||
|
|
||||||
|
if ($hasReplies) {
|
||||||
|
/*
|
||||||
|
* Der Kommentar wird für den Kommentarbaum benötigt.
|
||||||
|
* Deshalb bleibt er als anonymer Platzhalter erhalten.
|
||||||
|
*/
|
||||||
|
$deleteCommand = $db->prepare("
|
||||||
|
UPDATE comments
|
||||||
|
SET author = NULL,
|
||||||
|
content = 'Dieser Kommentar wurde gelöscht.'
|
||||||
|
WHERE id = :commentId
|
||||||
|
AND author = :author
|
||||||
|
");
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Ohne Antworten kann der Kommentar vollständig
|
||||||
|
* aus der Datenbank entfernt werden.
|
||||||
|
*/
|
||||||
|
$deleteCommand = $db->prepare("
|
||||||
|
DELETE FROM comments
|
||||||
|
WHERE id = :commentId
|
||||||
|
AND author = :author
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleteCommand->execute([
|
||||||
|
":commentId" => $commentId,
|
||||||
|
":author" => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $deleteCommand->rowCount() > 0;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Löscht beziehungsweise anonymisiert alle Kommentare eines Nutzers.
|
||||||
|
*
|
||||||
|
* Kommentare ohne Antworten werden vollständig gelöscht.
|
||||||
|
* Kommentare mit Antworten bleiben als anonyme Platzhalter erhalten.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Nutzers
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteCommentsByAuthor(string $author): void
|
||||||
|
{
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zuerst werden alle Kommentare ohne Antworten gelöscht.
|
||||||
|
*
|
||||||
|
* Die Schleife ist wichtig, weil durch das Löschen einer Antwort
|
||||||
|
* eventuell auch der darüberliegende Kommentar keine Antworten
|
||||||
|
* mehr besitzt und anschließend ebenfalls gelöscht werden kann.
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
$deleteCommand = $db->prepare("
|
||||||
|
DELETE FROM comments
|
||||||
|
WHERE author = :author
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM comments AS replies
|
||||||
|
WHERE replies.parent_comment_id = comments.id
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$deleteCommand->execute([
|
||||||
|
":author" => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
$deletedRows = $deleteCommand->rowCount();
|
||||||
|
|
||||||
|
} while ($deletedRows > 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kommentare, auf die noch Antworten anderer Nutzer folgen,
|
||||||
|
* müssen für den Kommentarbaum erhalten bleiben.
|
||||||
|
*/
|
||||||
|
$placeholderCommand = $db->prepare("
|
||||||
|
UPDATE comments
|
||||||
|
SET author = NULL,
|
||||||
|
content = 'Dieser Kommentar wurde gelöscht.'
|
||||||
|
WHERE author = :author
|
||||||
|
");
|
||||||
|
|
||||||
|
$placeholderCommand->execute([
|
||||||
|
":author" => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
if ($db->inTransaction()) {
|
||||||
|
$db->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Diese Klasse legt einmalig beim allerersten Start in der exakt korrekten Reihenfolge die Tabellen an
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class DatabaseInitializer {
|
||||||
|
public static function initialize($dbPath) {
|
||||||
|
if (!file_exists($dbPath)) {
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $dbPath);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
email TEXT PRIMARY KEY,
|
||||||
|
vorname TEXT NOT NULL,
|
||||||
|
nachname TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS articles (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT,
|
||||||
|
content TEXT,
|
||||||
|
author TEXT,
|
||||||
|
category TEXT,
|
||||||
|
tags TEXT,
|
||||||
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (author) REFERENCES users(email) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS likes (
|
||||||
|
article_id INTEGER,
|
||||||
|
user_id TEXT,
|
||||||
|
PRIMARY KEY (article_id, user_id),
|
||||||
|
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(email) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS comments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
article_id INTEGER NOT NULL,
|
||||||
|
parent_comment_id INTEGER NULL,
|
||||||
|
author TEXT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (author) REFERENCES users(email) ON DELETE SET NULL,
|
||||||
|
FOREIGN KEY (parent_comment_id) REFERENCES comments(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$initializer = new self();
|
||||||
|
$availableEmails = $initializer->seedDummyUsers($db);
|
||||||
|
$initializer->seedDummyArticles($db, $availableEmails);
|
||||||
|
|
||||||
|
unset($db);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal-error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die Dummy-Benutzer in der Datenbank.
|
||||||
|
*
|
||||||
|
* @param PDO $db Die aktive Datenbankverbindung
|
||||||
|
* @return array Liste der erfolgreich angelegten E-Mail-Adressen
|
||||||
|
*/
|
||||||
|
private function seedDummyUsers(PDO $db): array {
|
||||||
|
$dummyUsers = [
|
||||||
|
['email' => 'max.mustermann@web.de', 'vorname' => 'Max', 'nachname' => 'Mustermann'],
|
||||||
|
['email' => 'erika.mustermann@web.de', 'vorname' => 'Erika', 'nachname' => 'Mustermann'],
|
||||||
|
['email' => 'john.doe@gmail.com', 'vorname' => 'John', 'nachname' => 'Doe'],
|
||||||
|
['email' => 'jane.doe@gmail.com', 'vorname' => 'Jane', 'nachname' => 'Doe'],
|
||||||
|
['email' => 'anna.schmidt@gmx.de', 'vorname' => 'Anna', 'nachname' => 'Schmidt'],
|
||||||
|
['email' => 'thomas.mueller@gmx.de', 'vorname' => 'Thomas', 'nachname' => 'Müller'],
|
||||||
|
['email' => 'sabine.fischer@outlook.com', 'vorname' => 'Sabine', 'nachname' => 'Fischer'],
|
||||||
|
['email' => 'michael.weber@outlook.com', 'vorname' => 'Michael', 'nachname' => 'Weber'],
|
||||||
|
['email' => 'julia.wagner@t-online.de', 'vorname' => 'Julia', 'nachname' => 'Wagner'],
|
||||||
|
['email' => 'stefan.becker@t-online.de', 'vorname' => 'Stefan', 'nachname' => 'Becker']
|
||||||
|
];
|
||||||
|
|
||||||
|
$passwordHash = password_hash("test12345", PASSWORD_DEFAULT);
|
||||||
|
$userInsertStmt = $db->prepare("INSERT INTO users (email, vorname, nachname, password) VALUES (:email, :vorname, :nachname, :password);");
|
||||||
|
|
||||||
|
$availableEmails = [];
|
||||||
|
foreach ($dummyUsers as $user) {
|
||||||
|
$userInsertStmt->execute([
|
||||||
|
':email' => $user['email'],
|
||||||
|
':vorname' => $user['vorname'],
|
||||||
|
':nachname' => $user['nachname'],
|
||||||
|
':password' => $passwordHash
|
||||||
|
]);
|
||||||
|
$availableEmails[] = $user['email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $availableEmails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die 100 Fachbeiträge in der Datenbank.
|
||||||
|
*
|
||||||
|
* @param PDO $db Die aktive Datenbankverbindung
|
||||||
|
* @param array $availableEmails Pool für die Foreign-Key-Zuweisung des Autors
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function seedDummyArticles(PDO $db, array $availableEmails): void
|
||||||
|
{
|
||||||
|
$dummyArticles = [
|
||||||
|
// --- INFORMATIK & MATHE (1-20) ---
|
||||||
|
1 => ["Satz des Pythagoras", "Der Satz des Pythagoras beschreibt das Verhältnis der Seitenlängen in einem rechtwinkligen Dreieck.", "mathe", "Dreiecke, Geometrie"],
|
||||||
|
2 => ["Tunneleffekt", "Der Tunneleffekt beschreibt das quantenmechanische Phänomen, dass Teilchen Barrieren überwinden können.", "physik", "Quantenphysik, Energie"],
|
||||||
|
3 => ["Datenschutz vs Datensicherheit", "Datenschutz schützt Personen, während Datensicherheit Systeme vor unbefugten Zugriffen schützt.", "informatik", "Daten, DSGVO"],
|
||||||
|
4 => ["Einführung in Algorithmen", "Ein Algorithmus ist eine präzise Handlungsanweisung zur Lösung eines vordefinierten Problems.", "informatik", "Code, Logik"],
|
||||||
|
5 => ["Primzahlen im Detail", "Primzahlen sind natürliche Zahlen, die nur durch eins und sich selbst ohne Rest teilbar sind.", "mathe", "Zahlentheorie"],
|
||||||
|
6 => ["Lineare Algebra", "Vektoren und Matrizen bilden das Fundament für moderne Computergrafik und 3D-Engines.", "mathe", "Matrizen, Vektoren"],
|
||||||
|
7 => ["Objektorientierte Programmierung", "Die OOP nutzt Klassen und Objekte, um reale Strukturen im Quellcode abzubilden.", "informatik", "OOP, Klassen"],
|
||||||
|
8 => ["Grundlagen von HTML und CSS", "HTML strukturiert den Inhalt einer Webseite, während CSS für das optische Design zuständig ist.", "informatik", "Web, Frontend"],
|
||||||
|
9 => ["Die Relativitätstheorie", "Einsteins Theorie revolutionierte unser Verständnis von Raum, Zeit und Gravitation massiv.", "physik", "Einstein, Gravitation"],
|
||||||
|
10 => ["Datenbanken und SQL", "Strukturierte Abfragesprachen erlauben das effiziente Speichern und Abrufen großer Datenmengen.", "informatik", "SQL, Datenbank"],
|
||||||
|
11 => ["Wahrscheinlichkeitsrechnung", "Die Stochastik befasst sich mit der mathematischen Analyse von Zufallsexperimenten.", "mathe", "Zufall, Stochastik"],
|
||||||
|
12 => ["Quantencomputing", "Künftige Quantencomputer nutzen Qubits, um komplexe Berechnungen in Rekordzeit zu lösen.", "informatik", "Hardware, Zukunft"],
|
||||||
|
13 => ["Die Fibonacci-Folge", "Diese Zahlenreihe beschreibt mathematische Wachstumsmuster, die oft in der Natur vorkommen.", "mathe", "Zahlen, Natur"],
|
||||||
|
14 => ["Thermodynamik", "Die Hauptsätze der Thermodynamik regeln den Energieaustausch und die Entropie in Systemen.", "physik", "Energie, Wärme"],
|
||||||
|
15 => ["Cybersecurity Grundlagen", "Die Absicherung von Netzwerken erfordert Firewalls, Verschlüsselung und regelmäßige Audits.", "informatik", "Sicherheit, Hacker"],
|
||||||
|
16 => ["Kryptographie", "Asymmetrische Verschlüsselungsverfahren sichern heutzutage den gesamten Datenverkehr im Web.", "informatik", "Krypto, Security"],
|
||||||
|
17 => ["Analysis und Ableitungen", "Die Differentialrechnung untersucht die lokalen Änderungsraten von mathematischen Funktionen.", "mathe", "Analysis, Funktionen"],
|
||||||
|
18 => ["Schwarze Löcher", "Diese Regionen im Raum besitzen eine so starke Gravitation, dass selbst Licht nicht entkommt.", "physik", "Astronomie, Kosmos"],
|
||||||
|
19 => ["Git Versionsverwaltung", "Git erlaubt Entwicklern das parallele Arbeiten an Code-Projekten ohne Datenverlust.", "informatik", "Git, DevOps"],
|
||||||
|
20 => ["Künstliche Intelligenz", "Neuronale Netze versuchen das menschliche Gehirn für maschinelles Lernen nachzubilden.", "informatik", "KI, Software"],
|
||||||
|
|
||||||
|
// --- CHEMIE & BIOLOGIE (21-40) ---
|
||||||
|
21 => ["Das Periodensystem", "Die Elemente sind nach ihrer Ordnungszahl und chemischen Eigenschaften geordnet.", "chemie", "Elemente, Moleküle"],
|
||||||
|
22 => ["Photosynthese", "Pflanzen wandeln mithilfe von Sonnenlicht Kohlendioxid und Wasser in Glukose und Sauerstoff um.", "biologie", "Pflanzen, Energie"],
|
||||||
|
23 => ["Aufbau einer Zelle", "Die Zelle ist die kleinste lebende Einheit aller Organismen mit spezialisierten Organellen.", "biologie", "Zellen, Biologie"],
|
||||||
|
24 => ["Säuren und Basen", "Der pH-Wert misst die Konzentration von Wasserstoff-Ionen in einer wässrigen Lösung.", "chemie", "Labor, pH-Wert"],
|
||||||
|
25 => ["Die DNA-Struktur", "Die Doppelhelix enthält den genetischen Bauplan für die Entwicklung aller Lebewesen.", "biologie", "Genetik, Erbgut"],
|
||||||
|
26 => ["Chemische Bindungen", "Kovalente Bindungen entstehen durch das Teilen von Elektronenpaaren zwischen Atomen.", "chemie", "Atome, Bindung"],
|
||||||
|
27 => ["Das Immunsystem", "Weiße Blutkörperchen und Antikörper schützen den menschlichen Körper vor Krankheitserregern.", "biologie", "Gesundheit, Abwehr"],
|
||||||
|
28 => ["Katalysatoren", "Katalysatoren beschleunigen chemische Reaktionen, ohne dabei selbst verbraucht zu werden.", "chemie", "Reaktion, Chemie"],
|
||||||
|
29 => ["Ökosystem Wald", "Das Zusammenspiel von Flora, Fauna und Klima bildet ein hochsensibles ökologisches System.", "biologie", "Natur, Wald"],
|
||||||
|
30 => ["Die Mendelschen Regeln", "Diese Grundgesetze der Vererbung beschreiben, wie Merkmale an Nachkommen weitergegeben werden.", "biologie", "Genetik, Erbung"],
|
||||||
|
31 => ["Zustandsformen der Materie", "Fest, flüssig und gasförmig sind die klassischen Aggregatzustände von Stoffen.", "chemie", "Physik, Materie"],
|
||||||
|
32 => ["Evolutionstheorie", "Charles Darwin begründete die Theorie der natürlichen Auslese und Anpassung von Arten.", "biologie", "Darwin, Evolution"],
|
||||||
|
33 => ["Organische Chemie", "Die Chemie der Kohlenstoffverbindungen bildet die Basis für alles bekannte Leben.", "chemie", "Kohlenstoff, Chemie"],
|
||||||
|
34 => ["Das menschliche Gehirn", "Milliarden von Neuronen kommunizieren über Synapsen, um Reize und Gedanken zu verarbeiten.", "biologie", "Neurologie, Nerven"],
|
||||||
|
35 => ["Der Wasserkreislauf", "Verdunstung, Kondensation und Niederschlag halten das Wasser auf der Erde in Bewegung.", "geographie", "Wasser, Klima"],
|
||||||
|
36 => ["Aggregatzustände von Wasser", "Wasser zeigt ungewöhnliche Eigenschaften wie die Dichteanomalie beim Gefrieren.", "chemie", "Wasser, Eis"],
|
||||||
|
37 => ["Blutkreislauf des Menschen", "Das Herz pumpt sauerstoffreiches Blut durch Arterien in alle Organe des Körpers.", "biologie", "Herz, Medizin"],
|
||||||
|
38 => ["Das Ohmsche Gesetz", "Es beschreibt den direkten Zusammenhang zwischen Spannung, Stromstärke und Widerstand.", "physik", "Strom, Elektronik"],
|
||||||
|
39 => ["Plattentektonik", "Die Bewegung der Kontinentalplatten führt zu Erdbeben, Vulkanismus und Gebirgsbildung.", "geographie", "Erde, Geologie"],
|
||||||
|
40 => ["Proteine und Enzyme", "Enzyme wirken als Biokatalysatoren und steuern fast alle Stoffwechselprozesse.", "biologie", "Biochemie, Enzyme"],
|
||||||
|
|
||||||
|
// --- GESCHICHTE & WIRTSCHAFT (41-60) ---
|
||||||
|
41 => ["Das Römische Reich", "Vom Stadtstaat zum Weltreich prägte Rom die Rechts- und Kulturgeschichte Europas.", "geschichte", "Antike, Rom"],
|
||||||
|
42 => ["Die Französische Revolution", "Freiheit, Gleichheit, Brüderlichkeit beendeten 1789 die absolute Monarchie in Frankreich.", "geschichte", "Europa, Politik"],
|
||||||
|
43 => ["Inflation erklärt", "Inflation bezeichnet die kontinuierliche Geldentwertung und den Kaufkraftverlust.", "wirtschaft", "Geld, Finanzen"],
|
||||||
|
44 => ["Die Industrielle Revolution", "Die Erfindung der Dampfmaschine veränderte die Produktion und die Gesellschaft tiefgreifend.", "geschichte", "Industrie, Arbeit"],
|
||||||
|
45 => ["Angebot und Nachfrage", "Dieses fundamentale Marktgesetz bestimmt den Preis von Gütern in einer freien Wirtschaft.", "wirtschaft", "Markt, Preise"],
|
||||||
|
46 => ["Der Buchdruck", "Johannes Gutenbergs Erfindung revolutionierte die Verbreitung von Wissen im Mittelalter.", "geschichte", "Medien, Wissen"],
|
||||||
|
47 => ["Die Entdeckung Amerikas", "Kolumbus' Seereise im Jahr 1492 leitete das Zeitalter der Kolonialisierung ein.", "geschichte", "Entdeckung, Seefahrt"],
|
||||||
|
48 => ["Kryptowährungen", "Bitcoin nutzt Blockchain-Technologie, um dezentralen digitalen Werttransfer zu erlauben.", "wirtschaft", "Blockchain, Finanzen"],
|
||||||
|
49 => ["Das antike Griechenland", "Die Wiege der Demokratie und Philosophie brachte Denker wie Sokrates und Platon hervor.", "geschichte", "Antike, Philosophie"],
|
||||||
|
50 => ["Globalisierung", "Die weltweite Verflechtung in Wirtschaft, Kultur und Politik bringt Chancen und Risiken.", "wirtschaft", "Weltwirtschaft, Handel"],
|
||||||
|
51 => ["Der Dreißigjährige Krieg", "Ein religiöser und politischer Konflikt verwüstete zwischen 1618 und 1648 Mitteleuropa.", "geschichte", "Krieg, Europa"],
|
||||||
|
52 => ["Die Weimarer Republik", "Die erste deutsche Demokratie scheiterte an wirtschaftlichen und politischen Krisen.", "geschichte", "Deutschland, Weimar"],
|
||||||
|
53 => ["Aktien und Börse", "Unternehmen beschaffen sich Kapital durch die Ausgabe von Anteilen an Investoren.", "wirtschaft", "Aktien, Investieren"],
|
||||||
|
54 => ["Das alte Ägypten", "Pharaonen, Pyramiden und Hieroglyphen zeugen von einer hochentwickelten Hochkultur am Nil.", "geschichte", "Ägypten, Antike"],
|
||||||
|
55 => ["Der Kalte Krieg", "Das Wettrüsten zwischen USA und UdSSR prägte die globale Politik der Nachkriegszeit.", "geschichte", "Ost-West, Politik"],
|
||||||
|
56 => ["Zentralbanken und Leitzins", "Durch Zinsänderungen steuern Notenbanken die Geldmenge und bekämpfen Inflation.", "wirtschaft", "Zinsen, Geldpolitik"],
|
||||||
|
57 => ["Die Seidenstraße", "Das historische Netzwerk von Handelsrouten verband über Jahrhunderte Asien und Europa.", "geschichte", "Handel, Asien"],
|
||||||
|
58 => ["Das Mittelalter", "Ritter, Burgen und das Feudalsystem prägten diese tausendjährige Epoche Europas.", "geschichte", "Mittelalter, Feudalismus"],
|
||||||
|
59 => ["Planwirtschaft vs Marktwirtschaft", "Zentrale staatliche Steuerung steht dem freien Spiel der Marktkräfte gegenüber.", "wirtschaft", "Systeme, Wirtschaft"],
|
||||||
|
60 => ["Die Berliner Mauer", "Ihr Bau 1961 zementierte die Teilung Deutschlands, ihr Fall 1989 beendete sie.", "geschichte", "DDR, Wiedervereinigung"],
|
||||||
|
|
||||||
|
// --- ANWENDUNGEN & WEITERE THEMEN (61-100) ---
|
||||||
|
61 => ["Cloud Computing", "Das Auslagern von Rechenleistung in das Internet spart lokale IT-Infrastruktur ein.", "informatik", "Cloud, Web"],
|
||||||
|
62 => ["Responsive Webdesign", "Moderne Webseiten passen ihr Layout dynamisch an Smartphones und Desktops an.", "informatik", "Design, CSS"],
|
||||||
|
63 => ["Der Treibhauseffekt", "Gase in der Atmosphäre verhindern das Entweichen von Wärme ins Weltall.", "physik", "Klima, Umwelt"],
|
||||||
|
64 => ["Mechanik und Kräfte", "Die Newtonschen Axiome beschreiben, wie Kräfte auf Körper wirken und sie bewegen.", "physik", "Newton, Kraft"],
|
||||||
|
65 => ["Die Mendelschen Gesetze", "Die Vererbung von Genen folgt klaren statistischen Wahrscheinlichkeiten.", "biologie", "Genetik, Erbsen"],
|
||||||
|
66 => ["Lichtgeschwindigkeit", "Im Vakuum bewegt sich Licht mit knapp 300.000 Kilometern pro Sekunde.", "physik", "Licht, Relativität"],
|
||||||
|
67 => ["Die Funktion von APIs", "Programmierschnittstellen erlauben den Datenaustausch zwischen verschiedenen Systemen.", "informatik", "API, Schnittstelle"],
|
||||||
|
68 => ["Der Goldstandard", "Ein historischen Währungssystem, bei dem Geld durch echtes Gold gedeckt war.", "wirtschaft", "Gold, Währung"],
|
||||||
|
69 => ["Der Wiener Kongress", "1815 ordneten die europäischen Mächte die Landkarte nach den Napoleonischen Kriegen neu.", "geschichte", "Europa, Diplomatie"],
|
||||||
|
70 => ["Integrierte Schaltkreise", "Mikrochips enthalten Millionen Transistoren auf kleinstem Raum für Logikschaltungen.", "informatik", "Hardware, Chips"],
|
||||||
|
71 => ["Die Magellan-Expedition", "Die erste erfolgreiche Weltumsegelung bewies praktisch die Kugelgestalt der Erde.", "geschichte", "Seefahrt, Erde"],
|
||||||
|
72 => ["Das Internet der Dinge", "Alltagsgegenstände werden vernetzt, um smarte Automatisierungen zu ermöglichen.", "informatik", "IoT, SmartHome"],
|
||||||
|
73 => ["Halbwertszeit", "Die Zeitspanne, in der sich die Hälfte der instabilen Atome radioaktiv abbaut.", "physik", "Atomphysik, Strahlung"],
|
||||||
|
74 => ["Elektromagnetismus", "Die Verknüpfung von elektrischen Strömen und magnetischen Feldern treibt Motoren an.", "physik", "Strom, Magnet"],
|
||||||
|
75 => ["Das Ökosystem Meer", "Ozeane regulieren das Weltklima und bieten Lebensraum für unzählige Arten.", "biologie", "Meer, Ökologie"],
|
||||||
|
76 => ["Einführung in Docker", "Containerisierung isoliert Anwendungen samt Abhängigkeiten für stabilen Betrieb.", "informatik", "Docker, DevOps"],
|
||||||
|
77 => ["Das Römische Recht", "Viele moderne europäische Gesetzbücher basieren auf antiken römischen Rechtsprinzipien.", "geschichte", "Recht, Gesetz"],
|
||||||
|
78 => ["Verhaltensbiologie", "Untersuchung von angeborenen und erlernten Verhaltensweisen bei Mensch und Tier.", "biologie", "Verhalten, Tiere"],
|
||||||
|
79 => ["Verschlüsselung im Alltag", "HTTPS schützt Passwörter und Zahlungsdaten beim Surfen vor dem Mitlesen.", "informatik", "Web, HTTPS"],
|
||||||
|
80 => ["Die Magna Carta", "1215 schränkte dieses Dokument die absolute Macht des englischen Königs ein.", "geschichte", "England, Verfassung"],
|
||||||
|
81 => ["Marktversagen", "Wenn der freie Markt Ressourcen unvollständig verteilt, muss der Staat eingreifen.", "wirtschaft", "Markt, Staat"],
|
||||||
|
82 => ["Optische Linsen", "Konvexe und konkave Linsen brechen Licht für Brillen, Mikroskope und Kameras.", "physik", "Optik, Licht"],
|
||||||
|
83 => ["Die Entstehung der Erde", "Vor rund 4,5 Milliarden Jahren ballte sich kosmischer Staub zu unserem Planeten.", "geographie", "Erde, Kosmos"],
|
||||||
|
84 => ["Grundlagen von JavaScript", "Diese Skriptsprache macht statische Webseiten interaktiv und dynamisch nutzbar.", "informatik", "JS, Webentwicklung"],
|
||||||
|
85 => ["Die industrielle Landwirtschaft", "Moderne Techniken sichern Welternährung, belasten jedoch oft die Umwelt.", "biologie", "Landwirtschaft, Umwelt"],
|
||||||
|
86 => ["Das Schwarze Jahr 1929", "Der New Yorker Börsencrash löste die verheerende Weltwirtschaftskrise aus.", "geschichte", "Krise, Finanzen"],
|
||||||
|
87 => ["Die Evolution des Menschen", "Der Stammbaum des Homo Sapiens entwickelte sich über Millionen Jahre in Afrika.", "biologie", "Mensch, Evolution"],
|
||||||
|
88 => ["Einführung in Linux", "Das Open-Source-Betriebssystem bildet das Rückgrat moderner Server-Infrastrukturen.", "informatik", "Linux, OS"],
|
||||||
|
89 => ["Der Urknall", "Die Urknalltheorie beschreibt den Beginn des Universums aus einer Singularität.", "physik", "Astronomie, Urknall"],
|
||||||
|
90 => ["Das Periodensystem der Elemente", "Dmitri Mendelejew ordnete Elemente logisch nach ihren Atommassen.", "chemie", "Periodensystem, Chemie"],
|
||||||
|
91 => ["Die Hanse", "Ein mächtiger mittelalterlicher Bund von Kaufleuten dominierte den Nordseehandel.", "geschichte", "Handel, Mittelalter"],
|
||||||
|
92 => ["Wirtschaftswachstum", "Die Steigerung des Bruttoinlandsprodukts gilt oft als Indikator für Wohlstand.", "wirtschaft", "BIP, Finanzen"],
|
||||||
|
93 => ["Neuronale Netze", "Diese Strukturen lernen durch mathematische Gewichtung aus riesigen Datenmengen.", "informatik", "KI, Mathematik"],
|
||||||
|
94 => ["Der Erste Weltkrieg", "Der globale Konflikt von 1914 bis 1918 zerstörte das alte europäische Machtgefüge.", "geschichte", "Europa, Krieg"],
|
||||||
|
95 => ["Das Gehirn und Hormone", "Botenstoffe steuern Gefühle, Schlafzyklen und Reaktionen des Körpers.", "biologie", "Medizin, Hormone"],
|
||||||
|
96 => ["SQL Joins erklärt", "Joins verknüpfen Daten aus mehreren Tabellen über gemeinsame Schlüssel.", "informatik", "SQL, Datenbanken"],
|
||||||
|
97 => ["Wellen-Teilchen-Dualismus", "Quantenobjekte zeigen je nach Messaufbau Eigenschaften von Wellen oder Teilchen.", "physik", "Quanten, Licht"],
|
||||||
|
98 => ["Der Absolutismus", "Der Sonnenkönig Ludwig XIV. verkörperte die unbeschränkte Herrschaft des Monarchen.", "geschichte", "Frankreich, Monarchie"],
|
||||||
|
99 => ["Die Funktion von Routern", "Netzwerkgeräte leiten Datenpakete über IP-Adressen an den richtigen Empfänger.", "informatik", "Netzwerk, Internet"],
|
||||||
|
100 => ["Die Entstehung des Geldes", "Vom Tauschhandel über Naturalgeld bis hin zu modernen digitalen Fiat-Währungen.", "wirtschaft", "Geld, Geschichte"]
|
||||||
|
|
||||||
|
];
|
||||||
|
$articleInsertStmt = $db->prepare("
|
||||||
|
INSERT INTO articles (title, content, author, category, tags)
|
||||||
|
VALUES (:title, :content, :author, :category, :tags);");
|
||||||
|
|
||||||
|
foreach ($dummyArticles as $article) {
|
||||||
|
// Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails
|
||||||
|
$randomAuthor = $availableEmails[array_rand($availableEmails)];
|
||||||
|
|
||||||
|
// Text in (blocks[i][type]/[text]/[image])-Format umwandeln:
|
||||||
|
$content = json_encode([
|
||||||
|
['type' => 'text', 'value' => $article[1]]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
$articleInsertStmt->execute([
|
||||||
|
':title' => $article[0],
|
||||||
|
':content' => $content,
|
||||||
|
':author' => $randomAuthor,
|
||||||
|
':category' => $article[2],
|
||||||
|
':tags' => $article[3]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
|
require_once "UserManagerDAO.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Klasse für den Zugriff auf Benutzerdaten über eine SQLite-Datenbank.
|
||||||
|
*
|
||||||
|
* Diese Klasse verwendet PDO, Prepared Statements und speichert
|
||||||
|
* Benutzerdaten in der Datei db/users.db.
|
||||||
|
*/
|
||||||
|
class DatabaseUserManager implements UserManagerDAO {
|
||||||
|
|
||||||
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor.
|
||||||
|
*
|
||||||
|
* Erstellt die Benutzerdatenbank und die Tabelle users,
|
||||||
|
* falls diese noch nicht existieren.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn die Datenbank nicht erstellt werden kann
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Verbindung zur SQLite-Datenbank auf.
|
||||||
|
*
|
||||||
|
* @return PDO Datenbankverbindung
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn keine Verbindung hergestellt werden kann
|
||||||
|
*/
|
||||||
|
private function getConnection()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
return $db;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Singleton-Instanz des DatabaseUserManagers zurück.
|
||||||
|
*
|
||||||
|
* @return DatabaseUserManager Instanz des DatabaseUserManagers
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance == null) {
|
||||||
|
self::$instance = new DatabaseUserManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse des Benutzers
|
||||||
|
*
|
||||||
|
* @return array|null Benutzerdaten als Array oder null,
|
||||||
|
* wenn kein Benutzer gefunden wurde
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn der Benutzer nicht geladen werden kann
|
||||||
|
*/
|
||||||
|
public function findUser($email)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM users WHERE email = :email";
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":email" => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $command->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $user ?: null;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("Benutzer konnte nicht geladen werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt einen neuen Benutzer in die Datenbank ein.
|
||||||
|
*
|
||||||
|
* Die Speicherung erfolgt innerhalb einer Transaktion.
|
||||||
|
* Doppelte E-Mail-Adressen werden durch den Primary Key verhindert.
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse des Benutzers
|
||||||
|
* @param string $vorname Vorname des Benutzers
|
||||||
|
* @param string $nachname Nachname des Benutzers
|
||||||
|
* @param string $password Passwort-Hash des Benutzers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException wenn die E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn der Benutzer nicht gespeichert werden kann
|
||||||
|
*/
|
||||||
|
public function addUser($email, $vorname, $nachname, $password)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
INSERT INTO users (email, vorname, nachname, password)
|
||||||
|
VALUES (:email, :vorname, :nachname, :password)
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":email" => $email,
|
||||||
|
":vorname" => $vorname,
|
||||||
|
":nachname" => $nachname,
|
||||||
|
":password" => $password
|
||||||
|
]);
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
if (isset($db) && $db->inTransaction()) {
|
||||||
|
$db->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Benutzer konnte nicht gespeichert werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert die Daten eines bestehenden Benutzers.
|
||||||
|
*
|
||||||
|
* Optional kann zusätzlich das Passwort geändert werden.
|
||||||
|
* Wenn kein neues Passwort übergeben wird, bleibt das alte Passwort erhalten.
|
||||||
|
*
|
||||||
|
* @param string $oldEmail Aktuelle E-Mail-Adresse des Benutzers
|
||||||
|
* @param string $newEmail Neue E-Mail-Adresse des Benutzers
|
||||||
|
* @param string $vorname Neuer Vorname des Benutzers
|
||||||
|
* @param string $nachname Neuer Nachname des Benutzers
|
||||||
|
* @param string|null $password Neues Passwort oder null
|
||||||
|
*
|
||||||
|
* @return bool true, wenn der Benutzer aktualisiert wurde,
|
||||||
|
* sonst false
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException wenn die neue E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn der Benutzer nicht aktualisiert werden kann
|
||||||
|
*/
|
||||||
|
public function updateUser($oldEmail, $newEmail, $vorname, $nachname, $password = null)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
if (!empty($password)) {
|
||||||
|
$sql = "
|
||||||
|
UPDATE users
|
||||||
|
SET email = :newEmail,
|
||||||
|
vorname = :vorname,
|
||||||
|
nachname = :nachname,
|
||||||
|
password = :password
|
||||||
|
WHERE email = :oldEmail
|
||||||
|
";
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
":newEmail" => $newEmail,
|
||||||
|
":vorname" => $vorname,
|
||||||
|
":nachname" => $nachname,
|
||||||
|
":password" => password_hash($password, PASSWORD_DEFAULT),
|
||||||
|
":oldEmail" => $oldEmail
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$sql = "
|
||||||
|
UPDATE users
|
||||||
|
SET email = :newEmail,
|
||||||
|
vorname = :vorname,
|
||||||
|
nachname = :nachname
|
||||||
|
WHERE email = :oldEmail
|
||||||
|
";
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
":newEmail" => $newEmail,
|
||||||
|
":vorname" => $vorname,
|
||||||
|
":nachname" => $nachname,
|
||||||
|
":oldEmail" => $oldEmail
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute($params);
|
||||||
|
|
||||||
|
return $command->rowCount() > 0;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Benutzer konnte nicht aktualisiert werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse des zu löschenden Benutzers
|
||||||
|
*
|
||||||
|
* @return bool true, wenn der Benutzer gelöscht wurde,
|
||||||
|
* sonst false
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn der Benutzer nicht gelöscht werden kann
|
||||||
|
*/
|
||||||
|
public function deleteUser($email)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "DELETE FROM users WHERE email = :email";
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":email" => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $command->rowCount() > 0;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("Benutzer konnte nicht gelöscht werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'ArticleManagerDAO.php';
|
||||||
|
require_once 'Article.php';
|
||||||
|
/**
|
||||||
|
* Klasse: Eine lokale Lösung des ArticleManagerDAO.
|
||||||
|
* Schreibt zunächst Daten lokal in das Verzeichnis und kann diese wieder auslesen.
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class LocalArticleManager implements ArticleManagerDAO {
|
||||||
|
|
||||||
|
private $file;
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->file = __DIR__ . '/../../data/articles.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die LocalArticleManager-Instanz zurück.
|
||||||
|
* @return LocalArticleManager
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance == null) {
|
||||||
|
self::$instance = new LocalArticleManager();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert alle Artikel/Beiträge in der Datei.
|
||||||
|
* @param $articles
|
||||||
|
* @return void
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
|
private function saveArticle($articles)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
file_put_contents(
|
||||||
|
$this->file,
|
||||||
|
json_encode($articles, JSON_PRETTY_PRINT)
|
||||||
|
);
|
||||||
|
}catch (Exception $e){
|
||||||
|
throw new InternalServerErrorException($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addArticle($title, $content, $author, $category, $tags)
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
|
||||||
|
$articles[] = [
|
||||||
|
"id" => count($articles)+1,
|
||||||
|
"title" => $title,
|
||||||
|
"content" => $content,
|
||||||
|
"author" => $author,
|
||||||
|
"category" => $category,
|
||||||
|
"tags" => $tags,
|
||||||
|
"creationDate" => date("Y-m-d H:i:s"),
|
||||||
|
"likes" => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->saveArticle($articles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateArticle($id, $article, $author)
|
||||||
|
{
|
||||||
|
if (empty($article)) {
|
||||||
|
throw new NotFoundException("not_found_article");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechtigungsprüfung:
|
||||||
|
if ($article->getAuthor() !== $author) {
|
||||||
|
throw new UnauthorizedAccessException("unauthorized_access");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beitrag aktualisieren:
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$updated = false;
|
||||||
|
|
||||||
|
foreach ($articles as $index => $storedArticle) {
|
||||||
|
if (isset($storedArticle['id']) && $storedArticle['id'] == $id) {
|
||||||
|
$articles[$index] = [
|
||||||
|
"id" => $id,
|
||||||
|
"title" => $article->getTitle(),
|
||||||
|
"content" => $article->getContent(),
|
||||||
|
"author" => $author,
|
||||||
|
"category" => $article->getCategory(),
|
||||||
|
"tags" => $article->getTags(),
|
||||||
|
"creationDate" => $article->getCreationDate(),
|
||||||
|
"likes" => $storedArticle['likes'] ?? []
|
||||||
|
];
|
||||||
|
$updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur speichern, wenn Beitrag geändert wurde:
|
||||||
|
if ($updated) {
|
||||||
|
$this->saveArticle($articles);
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException("missing_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteArticle($id, $author)
|
||||||
|
{
|
||||||
|
$article = getArticle($id);
|
||||||
|
if (empty($article)) {
|
||||||
|
throw new NotFoundException("not_found_article");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechtigungsprüfung:
|
||||||
|
if ($article->getAuthor() !== $author) {
|
||||||
|
throw new UnauthorizedAccessException("unauthorized_access");
|
||||||
|
}
|
||||||
|
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$articleFound = false;
|
||||||
|
|
||||||
|
foreach ($articles as $index => $article) {
|
||||||
|
if (isset($article['id']) && $article['id'] == $id) {
|
||||||
|
unset($articles[$index]);
|
||||||
|
$articleFound = true;
|
||||||
|
break; // Schleife abbrechen, da die ID eindeutig ist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($articleFound) {
|
||||||
|
// array_values stellt sicher, dass die Array-Keys wieder fortlaufend bei 0 beginnen
|
||||||
|
$this->saveArticle(array_values($articles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticle($id)
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
if (isset($article['id']) && $article['id'] == $id) {
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
|
return new Article(
|
||||||
|
intval($article['id']),
|
||||||
|
$article['title'],
|
||||||
|
$article['content'],
|
||||||
|
$article['author'],
|
||||||
|
$article['category'],
|
||||||
|
$article['tags'],
|
||||||
|
$article['creationDate'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllArticles(): array
|
||||||
|
{
|
||||||
|
if (!file_exists($this->file)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = file_get_contents($this->file);
|
||||||
|
$articles = json_decode($json, true);
|
||||||
|
|
||||||
|
return is_array($articles) ? $articles : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticlesByAuthor($author)
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
if (isset($article['author']) && $article['author'] == $author) {
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($article['id']),
|
||||||
|
$article['title'],
|
||||||
|
$article['content'],
|
||||||
|
$article['author'],
|
||||||
|
$article['category'],
|
||||||
|
$article['tags'],
|
||||||
|
$article['creationDate'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $filteredArticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function search(string $keyword): array
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
if (!is_array($articles)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleanKeyword = strtolower(trim($keyword));
|
||||||
|
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
$title = isset($article['title']) ? strtolower((string)$article['title']) : '';
|
||||||
|
$content = isset($article['content']) ? strtolower((string)$article['content']) : '';
|
||||||
|
$tags = isset($article['tags']) ? strtolower((string)$article['tags']) : '';
|
||||||
|
|
||||||
|
if (($cleanKeyword !== '' && strpos($title, $cleanKeyword) !== false) ||
|
||||||
|
($cleanKeyword !== '' && strpos($content, $cleanKeyword) !== false) ||
|
||||||
|
($cleanKeyword !== '' && strpos($tags, $cleanKeyword) !== false)) {
|
||||||
|
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($article['id'] ?? 0),
|
||||||
|
$article['title'] ?? '',
|
||||||
|
$article['content'] ?? '',
|
||||||
|
$article['author'] ?? '',
|
||||||
|
$article['category'] ?? '',
|
||||||
|
$article['tags'] ?? '',
|
||||||
|
$article['creationDate'] ?? '',
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredArticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticlesByCategory($category)
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
if (isset($article['category']) && $article['category'] == $category) {
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($article['id']),
|
||||||
|
$article['title'],
|
||||||
|
$article['content'],
|
||||||
|
$article['author'],
|
||||||
|
$article['category'],
|
||||||
|
$article['tags'],
|
||||||
|
$article['creationDate'],
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $filteredArticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleLike(int $articleId, string $userId): bool
|
||||||
|
{
|
||||||
|
$articles = $this->getAllArticles();
|
||||||
|
$articleFound = false;
|
||||||
|
$isLikedNow = false;
|
||||||
|
|
||||||
|
foreach ($articles as $index => $article) {
|
||||||
|
if (isset($article['id']) && $article['id'] == $articleId) {
|
||||||
|
$articleFound = true;
|
||||||
|
|
||||||
|
// Likes-Array initialisieren, falls nicht vorhanden
|
||||||
|
if (!isset($articles[$index]['likes']) || !is_array($articles[$index]['likes'])) {
|
||||||
|
$articles[$index]['likes'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$likeIndex = array_search($userId, $articles[$index]['likes']);
|
||||||
|
|
||||||
|
if ($likeIndex !== false) {
|
||||||
|
// Bereits geliked -> Unlike
|
||||||
|
unset($articles[$index]['likes'][$likeIndex]);
|
||||||
|
// Array-Keys neu indizieren, damit JSON sauber bleibt
|
||||||
|
$articles[$index]['likes'] = array_values($articles[$index]['likes']);
|
||||||
|
$isLikedNow = false;
|
||||||
|
} else {
|
||||||
|
// Noch nicht geliked -> Like (User-ID hinzufügen)
|
||||||
|
$articles[$index]['likes'][] = $userId;
|
||||||
|
$isLikedNow = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$articleFound) {
|
||||||
|
throw new NotFoundException("missing_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveArticle($articles);
|
||||||
|
return $isLikedNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "UserManagerDAO.php";
|
||||||
|
|
||||||
|
class LocalUserManager implements UserManagerDAO {
|
||||||
|
|
||||||
|
private string $file = "data/users.json";
|
||||||
|
|
||||||
|
public static function getInstance(){
|
||||||
|
// TODO: implement the getIsntance method.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Benutzer aus der JSON-Datei.
|
||||||
|
*
|
||||||
|
* @return array Liste aller Benutzer
|
||||||
|
* @throws RuntimeException wenn die Datei nicht gelesen werden kann
|
||||||
|
* oder fehlerhafte JSON-Daten enthält
|
||||||
|
*/
|
||||||
|
private function loadUsers() {
|
||||||
|
if (!file_exists($this->file)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = file_get_contents($this->file);
|
||||||
|
|
||||||
|
if ($json === false) {
|
||||||
|
throw new RuntimeException("Benutzerdaten konnten nicht gelesen werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = json_decode($json, true);
|
||||||
|
|
||||||
|
if ($users === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new RuntimeException("Benutzerdaten sind fehlerhaft.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($users) ? $users : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert alle Benutzer in die JSON-Datei.
|
||||||
|
*
|
||||||
|
* @param array $users Liste aller Benutzer
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException wenn die Daten nicht gespeichert werden können
|
||||||
|
*/
|
||||||
|
private function saveUsers($users) {
|
||||||
|
$json = json_encode($users, JSON_PRETTY_PRINT);
|
||||||
|
|
||||||
|
if ($json === false) {
|
||||||
|
throw new RuntimeException("Benutzerdaten konnten nicht umgewandelt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = file_put_contents($this->file, $json);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
throw new RuntimeException("Benutzerdaten konnten nicht gespeichert werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse des gesuchten Benutzers
|
||||||
|
* @return array|null Benutzerdaten oder null, wenn kein Benutzer gefunden wurde
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen werden können
|
||||||
|
*/
|
||||||
|
public function findUser($email) {
|
||||||
|
$users = $this->loadUsers();
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if (isset($user["email"]) && $user["email"] === $email) {
|
||||||
|
|
||||||
|
if (!isset($user["vorname"]) && isset($user["username"])) {
|
||||||
|
$nameParts = explode(" ", $user["username"], 2);
|
||||||
|
$user["vorname"] = $nameParts[0] ?? "";
|
||||||
|
$user["nachname"] = $nameParts[1] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt einen neuen Benutzer hinzu.
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse
|
||||||
|
* @param string $vorname Vorname
|
||||||
|
* @param string $nachname Nachname
|
||||||
|
* @param string $password Passwort-Hash
|
||||||
|
* @return void
|
||||||
|
* @throws InvalidArgumentException wenn die E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function addUser($email, $vorname, $nachname, $password) {
|
||||||
|
$users = $this->loadUsers();
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if (isset($user["email"]) && $user["email"] === $email) {
|
||||||
|
throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$users[] = [
|
||||||
|
"email" => $email,
|
||||||
|
"vorname" => $vorname,
|
||||||
|
"nachname" => $nachname,
|
||||||
|
"password" => $password
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->saveUsers($users);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert einen bestehenden Benutzer.
|
||||||
|
*
|
||||||
|
* @param string $oldEmail Alte E-Mail-Adresse
|
||||||
|
* @param string $newEmail Neue E-Mail-Adresse
|
||||||
|
* @param string $vorname Neuer Vorname
|
||||||
|
* @param string $nachname Neuer Nachname
|
||||||
|
* @param string|null $password Neues Passwort oder null
|
||||||
|
* @return bool true, wenn der Benutzer aktualisiert wurde, sonst false
|
||||||
|
* @throws InvalidArgumentException wenn die neue E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function updateUser($oldEmail, $newEmail, $vorname, $nachname, $password = null) {
|
||||||
|
$users = $this->loadUsers();
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if (
|
||||||
|
isset($user["email"])
|
||||||
|
&& $user["email"] === $newEmail
|
||||||
|
&& $newEmail !== $oldEmail
|
||||||
|
) {
|
||||||
|
throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($users as $i => $user) {
|
||||||
|
if (isset($user["email"]) && $user["email"] === $oldEmail) {
|
||||||
|
$users[$i]["email"] = $newEmail;
|
||||||
|
$users[$i]["vorname"] = $vorname;
|
||||||
|
$users[$i]["nachname"] = $nachname;
|
||||||
|
|
||||||
|
unset($users[$i]["username"]);
|
||||||
|
|
||||||
|
if (!empty($password)) {
|
||||||
|
$users[$i]["password"] = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveUsers($users);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* TODO: wenn ein Benutzer gelöscht wird, sollten dann auch seine Beiträge gelöscht werden?
|
||||||
|
*
|
||||||
|
* @param string $email E-Mail-Adresse des zu löschenden Benutzers
|
||||||
|
* @return bool true, wenn der Benutzer gelöscht wurde, sonst false
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function deleteUser($email) {
|
||||||
|
$users = $this->loadUsers();
|
||||||
|
|
||||||
|
foreach ($users as $i => $user) {
|
||||||
|
if (isset($user["email"]) && $user["email"] === $email) {
|
||||||
|
unset($users[$i]);
|
||||||
|
$users = array_values($users);
|
||||||
|
$this->saveUsers($users);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "LocalUserManager.php";
|
||||||
|
require_once "DatabaseUserManager.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zentrale Klasse für den Zugriff auf Benutzerdaten.
|
||||||
|
*
|
||||||
|
* Hier kann zwischen lokaler Speicherung und Datenbankspeicherung
|
||||||
|
* gewechselt werden.
|
||||||
|
*/
|
||||||
|
class UserManager {
|
||||||
|
|
||||||
|
public static function getInstance() {
|
||||||
|
|
||||||
|
return DatabaseUserManager::getInstance();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface für den Zugriff auf Benutzerdaten.
|
||||||
|
*
|
||||||
|
* Definiert die Methoden, die jede UserDAO-Implementierung
|
||||||
|
* bereitstellen muss.
|
||||||
|
*/
|
||||||
|
interface UserManagerDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* Funktion:
|
||||||
|
* Liefert die gespeicherten Benutzerdaten zu einer E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* Eingabe:
|
||||||
|
* @param string $email E-Mail-Adresse des gesuchten Benutzers
|
||||||
|
*
|
||||||
|
* Ausgabe:
|
||||||
|
* @return array|null Benutzerdaten als Array oder null
|
||||||
|
*
|
||||||
|
* Mögliche Fehler:
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen werden können
|
||||||
|
*/
|
||||||
|
public function findUser($email);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt einen neuen Benutzer hinzu.
|
||||||
|
*
|
||||||
|
* Funktion:
|
||||||
|
* Erstellt einen neuen Benutzereintrag und speichert ihn
|
||||||
|
* in der jeweiligen Datenquelle.
|
||||||
|
*
|
||||||
|
* Eingabe:
|
||||||
|
* @param string $email E-Mail-Adresse des Benutzers
|
||||||
|
* @param string $vorname Vorname des Benutzers
|
||||||
|
* @param string $nachname Nachname des Benutzers
|
||||||
|
* @param string $password Passwort des Benutzers
|
||||||
|
*
|
||||||
|
* Ausgabe:
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* Mögliche Fehler:
|
||||||
|
* @throws InvalidArgumentException wenn die E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function addUser($email, $vorname, $nachname, $password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert einen bestehenden Benutzer.
|
||||||
|
*
|
||||||
|
* Funktion:
|
||||||
|
* Ändert die E-Mail-Adresse, den Vornamen, den Nachnamen
|
||||||
|
* und optional das Passwort eines bestehenden Benutzers.
|
||||||
|
*
|
||||||
|
* Eingabe:
|
||||||
|
* @param string $oldEmail Alte E-Mail-Adresse
|
||||||
|
* @param string $newEmail Neue E-Mail-Adresse
|
||||||
|
* @param string $vorname Neuer Vorname
|
||||||
|
* @param string $nachname Neuer Nachname
|
||||||
|
* @param string|null $password Neues Passwort oder null
|
||||||
|
*
|
||||||
|
* Ausgabe:
|
||||||
|
* @return bool true, wenn der Benutzer aktualisiert wurde, sonst false
|
||||||
|
*
|
||||||
|
* Mögliche Fehler:
|
||||||
|
* @throws InvalidArgumentException wenn die neue E-Mail-Adresse bereits verwendet wird
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function updateUser($oldEmail, $newEmail, $vorname, $nachname, $password = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht einen Benutzer anhand seiner E-Mail-Adresse.
|
||||||
|
*
|
||||||
|
* Funktion:
|
||||||
|
* Entfernt einen vorhandenen Benutzer aus der Datenquelle.
|
||||||
|
*
|
||||||
|
* Eingabe:
|
||||||
|
* @param string $email E-Mail-Adresse des zu löschenden Benutzers
|
||||||
|
*
|
||||||
|
* Ausgabe:
|
||||||
|
* @return bool true, wenn der Benutzer gelöscht wurde, sonst false
|
||||||
|
*
|
||||||
|
* Mögliche Fehler:
|
||||||
|
* @throws RuntimeException wenn die Benutzerdaten nicht gelesen oder gespeichert werden können
|
||||||
|
*/
|
||||||
|
public function deleteUser($email);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user