Compare commits

...

173 Commits

Author SHA1 Message Date
c44f035dcd preparations
Some checks failed
Build and push / Pulling repo on server (push) Successful in 6s
/ update_database (push) Failing after 14m28s
2023-12-26 21:31:53 +01:00
piapassecker
8543ebac7f flow changes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-25 16:42:59 +01:00
piapassecker
4de6df7175 Merge branch 'master' of https://gitea.haschek.at/Crispi/dogstats 2023-12-25 16:39:12 +01:00
Gitea
1dabcfbe0b update database 2023-12-24 03:07:50 +00:00
d299edd10d less errors please
Some checks failed
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Failing after 6m58s
2023-12-23 20:08:39 +01:00
9757e58998 real deal
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Successful in 21s
2023-12-23 20:07:20 +01:00
a0f70c6619 so?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Successful in 21s
2023-12-23 20:06:01 +01:00
63cbfb3681 just php
Some checks failed
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Failing after 21s
2023-12-23 20:05:20 +01:00
1e1617eb53 shorter
Some checks failed
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Failing after 11s
2023-12-23 20:04:18 +01:00
b1dbebf17f test2
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Successful in 1m0s
2023-12-23 20:02:41 +01:00
4eb299339e test 2023-12-23 20:02:16 +01:00
b6b82cae41 streamlined
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-23 19:53:52 +01:00
807aa70719 skip existing runs 2023-12-23 19:51:46 +01:00
piapassecker
53a614c5fd tried to implement import 2023-12-23 18:20:50 +01:00
bd49027ad6 fixed path
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-23 16:09:46 +01:00
1b5d515724 update server as well
Some checks failed
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Failing after 13m51s
2023-12-22 22:09:04 +01:00
a22dbd7eaf more days
All checks were successful
Build and push / Pulling repo on server (push) Successful in 11s
2023-12-22 21:41:21 +01:00
8de033b58b lets see if this works
All checks were successful
Build and push / Pulling repo on server (push) Successful in 18s
2023-12-22 21:33:07 +01:00
aebe6508d1 geht aber hmmmm
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 22:27:49 +01:00
9b3d8a18fc yes
All checks were successful
/ update_database (push) Successful in 19m59s
Build and push / Pulling repo on server (push) Successful in 3s
2023-12-12 21:36:06 +01:00
5da1fbf71c yes
Some checks failed
/ update_database (push) Failing after 5s
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 21:27:18 +01:00
dd962a7cd5 now
Some checks failed
/ update_database (push) Failing after 6s
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 21:23:52 +01:00
d2ebd3d81e yetzt?
Some checks failed
/ update_database (push) Failing after 6s
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 21:22:50 +01:00
d9a915bfce with cache?
Some checks failed
/ update_database (push) Failing after -17s
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 21:07:53 +01:00
0a0b7d4f4e ups
Some checks failed
Build and push / Pulling repo on server (push) Successful in 2s
/ update_database (push) Failing after -12s
2023-12-12 21:04:45 +01:00
e653a6667e testing
Some checks are pending
/ update_database (push) Waiting to run
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-12 20:48:00 +01:00
e4604649cc Merge branch 'master' of gitea.haschek.at:Crispi/dogstats 2023-12-12 20:47:42 +01:00
4742e2a9f8 testing schedule 2023-12-12 20:47:37 +01:00
fb3c107e1a fixed pias deletions
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-11 19:44:27 +00:00
b00f652283 crawler update
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-12-11 19:29:14 +00:00
piapassecker
4fc04c2793 text correction
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-12-11 19:59:58 +01:00
piapassecker
03a3c03433 Merge branch 'master' of https://gitea.haschek.at/Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-12-11 19:57:06 +01:00
piapassecker
064180f0a5 training page added 2023-12-11 19:54:37 +01:00
piapassecker
a7f8efe628 smart changes 2023-12-11 19:54:21 +01:00
05b56f50ac new data
All checks were successful
Build and push / Pulling repo on server (push) Successful in 4s
2023-12-05 18:04:45 +01:00
6371f25a47 newdata
All checks were successful
Build and push / Pulling repo on server (push) Successful in 4s
2023-12-02 21:49:45 +01:00
86f2adf668 new
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-12-02 20:46:20 +01:00
10233f019e scan last
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 14:57:34 +01:00
b9a628bee6 yay
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 14:27:49 +01:00
f7a47dd91f statttss
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 14:26:03 +01:00
916429ea72 more stats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 14:22:41 +01:00
3b7df5e29d fix for ylvie
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 13:55:28 +01:00
153e5d6305 better graphs
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-29 13:53:05 +01:00
piapassecker
44c0cfcfae separated home logged in and not
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-28 16:23:22 +01:00
ffb8e552d2 new database
All checks were successful
Build and push / Pulling repo on server (push) Successful in 5s
2023-11-27 09:16:12 +01:00
9b4d159615 more data
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-26 15:48:28 +01:00
949a9dfe62 ups
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 14:35:28 +01:00
afa288e702 not quite
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-26 14:33:47 +01:00
a20749d900 safer
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 14:10:26 +01:00
a67d1eb05b graph it
All checks were successful
Build and push / Pulling repo on server (push) Successful in 5s
2023-11-26 14:00:23 +01:00
26ec6de1e3 nice
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 11:01:19 +01:00
95837394a9 sorted
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 10:52:43 +01:00
cd65cba8c4 post
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 10:40:45 +01:00
1dc90e8e36 query log
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 10:39:08 +01:00
41e14cdfa0 better order
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-26 10:33:33 +01:00
8bc91a14f5 smart test
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-26 10:30:00 +01:00
e944c552ba attributes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-25 20:14:40 +01:00
3d9a948beb progress with the crawler
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-25 20:12:33 +01:00
9571c10db3 Merge branch 'master' of gitea.haschek.at:Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 32s
2023-11-24 20:49:37 +00:00
751a764c34 tool added 2023-11-24 20:49:22 +00:00
piapassecker
5879a462de limit to 5 submenuitems
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-20 20:24:04 +01:00
piapassecker
000c964317 table layout changed
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-11-20 20:18:17 +01:00
piapassecker
9f3ca1153f changed headings 2023-11-20 20:18:03 +01:00
piapassecker
503afa3cee Layout change
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-20 19:49:06 +01:00
piapassecker
6ad5deab10 Fonts added 2023-11-20 19:48:54 +01:00
piapassecker
9c9dad6efb platz wird jetzt korrekt dargestellt
Some checks failed
Build and push / Pulling repo on server (push) Failing after 2s
2023-11-08 14:38:18 +01:00
piapassecker
10bf9a8d72 layout changes
Some checks failed
Build and push / Pulling repo on server (push) Failing after 2s
2023-11-03 21:07:34 +01:00
piapassecker
4280b6900a colors changed
Some checks failed
Build and push / Pulling repo on server (push) Failing after 3s
2023-11-03 20:51:05 +01:00
piapassecker
115fc247c4 list edited
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-03 19:44:47 +01:00
piapassecker
bdaa417009 event list page events added
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-03 19:04:50 +01:00
piapassecker
151e06299d tournaments page added 2023-11-03 12:37:44 +01:00
piapassecker
99f44c9bb2 responsive tabel added
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-02 18:52:16 +01:00
piapassecker
2f771958d1 form controll added 2023-11-02 18:52:05 +01:00
0ed5ea12fe fixed
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-02 18:16:22 +01:00
piapassecker
457dd2efd2 float edited
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-02 18:06:23 +01:00
piapassecker
7e16ddfa1f form control for inputs added
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-02 17:59:19 +01:00
bb26619858 floaaat
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-02 15:29:43 +01:00
328633a4b3 disable button during load
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-01 22:03:37 +01:00
19622359f0 photo upload bei runs geht jetzt (beim ergebnis eintragen)
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-01 21:48:45 +01:00
e03d9ab449 nettere anzeige wenn keine daten vorhanden sind
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-01 20:01:21 +01:00
4794f46add hundeprofile sind jetzt öffentlich
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-11-01 19:53:36 +01:00
46cf4dcd40 stats prepared
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 22:42:26 +00:00
faacf4ec2d graph for dog
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 22:16:41 +00:00
a68d94f482 dog overview
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 21:50:36 +00:00
7b79995796 dog overview
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 21:43:08 +00:00
af05f1d520 streamlined breadcrumbs
All checks were successful
Build and push / Pulling repo on server (push) Successful in 12s
2023-10-31 20:38:03 +00:00
9bded6133e implemented theme changer
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 20:29:17 +00:00
3614ba829c eintragen möglich
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 15:35:42 +01:00
e550c42488 eintragung geht 🎉
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 15:32:29 +01:00
4ef26e2b64 icons make it better
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 11:55:27 +00:00
a19cc42d69 ergebnisformular fast fertig
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 11:49:20 +00:00
d96dc2fae3 fast kann man schon ergebnisse eintragne
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-31 11:26:54 +01:00
9b6e3080c2 it's the little things that count
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-31 10:02:32 +01:00
cca9797f4e umgehen?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:51:51 +01:00
f54717c798 reverted.. not today
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:48:49 +01:00
eb50f9f41c after
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:45:46 +01:00
fa3b3e20fc streamlined
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:44:32 +01:00
0126cea6b1 back to int
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:43:49 +01:00
95a564b859 stranger?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:43:04 +01:00
3e26658a38 yes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:40:36 +01:00
c5615940f1 more data
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:40:14 +01:00
ececc6235c yes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:39:07 +01:00
7f7142bcf4 yes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:37:40 +01:00
462165e81e yes
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:36:56 +01:00
b4ce0c89ed now
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:35:03 +01:00
0d0b69615a more
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:34:10 +01:00
6aed7dd976 test
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:33:22 +01:00
8508f42216 now
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:30:23 +01:00
9c1fd320f7 pub
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:29:06 +01:00
a8e7437eef now?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:28:25 +01:00
cd0a3005d2 now?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:27:01 +01:00
6f0f8041c0 end it
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:26:01 +01:00
88d1936264 afterlogin
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:25:50 +01:00
ff31a10882 debug
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:21:32 +01:00
e413c7f8fb undebug
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:13:44 +01:00
312c4a1d27 more debug
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:08:15 +01:00
682252503b more heavy
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 23:00:17 +01:00
8f4675ba93 heavy debug mode
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:57:31 +01:00
a604d2b97d hmm
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:47:44 +01:00
462935bafb show me
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:46:34 +01:00
33a21003c5 error message
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:45:42 +01:00
dc2fba8dab with bool?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-30 22:41:11 +01:00
38f65d6ede show me
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:38:08 +01:00
604b0fbb74 potential fix for active is required
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:36:18 +01:00
242cf9583e breadcrumbs
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:24:26 +01:00
1909be5314 run hinzufügen geht
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 22:20:13 +01:00
8656fd5d60 Merge branch 'master' of gitea.haschek.at:Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 19:27:46 +01:00
298e8e6cd1 implemented profile 2023-10-30 19:27:44 +01:00
eca5c5eab7 updated things
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-30 07:57:18 +00:00
0ed8ab9137 perfected automated logins
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 19:52:08 +01:00
e9a718ee96 dog image fixed
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 17:43:26 +01:00
1931547d3c fixed path
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 17:39:33 +01:00
4516486b8e loaing of model via arbitrary field and nice home page
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 17:37:28 +01:00
fea847afd2 auto login when chosen so
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 15:58:16 +01:00
35c496b187 fixed
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 09:45:58 +01:00
cfa964b4c5 implemented cookie login
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 09:41:02 +01:00
460fb6b1ee fixed array saving bug
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-29 09:16:10 +01:00
727a7d3dfb added extension and fixed table header
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-29 08:54:45 +01:00
87401267e1 Merge branch 'master' of gitea.haschek.at:Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:39:12 +02:00
69a4a47b54 members and admins now showing 2023-10-27 12:39:11 +02:00
piapassecker
a63f81eabe Profile page added
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:32:04 +02:00
piapassecker
9d5e02bb01 responsive table 2023-10-27 12:31:50 +02:00
bb51933dd1 for now..
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:15:19 +02:00
ef760f0c8e added error message
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:13:19 +02:00
757dca8bf5 ups encode
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:08:56 +02:00
40ae06d4b6 error catch
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 12:06:09 +02:00
c720b34c6f oh popper is schon im bundle
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 11:55:49 +02:00
dc6adf606a Merge branch 'master' of gitea.haschek.at:Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 11:52:33 +02:00
9e0d1cfa35 updated events and added popovers 2023-10-27 11:52:31 +02:00
piapassecker
34fb71ce07 Input Fields styled
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 11:30:29 +02:00
c22b000f46 brauchen vielleicht doch eine uuid für user
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 11:02:35 +02:00
75b96f4f27 fixed path thing?
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-27 10:58:20 +02:00
339321a322 autofworward after login
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 22:04:05 +02:00
5dda6b958d join leave works
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 21:48:04 +02:00
ebe969c7de fixed
All checks were successful
Build and push / Pulling repo on server (push) Successful in 10s
2023-10-26 21:33:29 +02:00
b429513ab4 nonadminsmaysee
All checks were successful
Build and push / Pulling repo on server (push) Successful in 21s
2023-10-26 21:32:37 +02:00
00c2e1ded6 tournament controls
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 21:18:32 +02:00
135958c9f0 404 seiten auf unterseiten gehen jetzt, icons werden jetzt korrekt bei menüs angezeigt, die submenüs haben
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 20:26:28 +02:00
ea4069f8b8 Merge branch 'master' of gitea.haschek.at:Crispi/dogstats
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 17:51:13 +02:00
ddf13b2413 added nicer overview and delete function 2023-10-26 17:50:54 +02:00
piapassecker
cda6945324 tournaments added
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 17:42:33 +02:00
0f13bdea27 shortref is now 10
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 16:44:36 +02:00
c3fbb0fc67 demo update
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-26 16:41:33 +02:00
12c11b65ba doggo upload
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 13:46:55 +02:00
61d7ae9adf other way around
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 11:19:13 +02:00
916d5c4ac4 version tag
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 11:18:29 +02:00
7738472559 fixed version
All checks were successful
Build and push / Pulling repo on server (push) Successful in 3s
2023-10-26 11:17:53 +02:00
c0b4b08cbb footer and version
Some checks failed
Build and push / Pulling repo on server (push) Failing after 3s
2023-10-26 11:15:39 +02:00
501b893688 footer not working grrrrr
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-23 17:42:12 +02:00
9f3aab4033 smart menu things
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-23 14:36:28 +02:00
88ec74af75 hunde menü nach home
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-23 14:15:33 +02:00
e343bb8f48 mehr bootstrap updates
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-23 13:59:26 +02:00
5424eec55d brauchen kein tailwind mehr
All checks were successful
Build and push / Pulling repo on server (push) Successful in 2s
2023-10-23 13:06:03 +02:00
90 changed files with 6054 additions and 346 deletions

View File

@@ -0,0 +1,48 @@
on:
schedule:
- cron: '0 3 * * 0,1,6' # at 1:01am on Friday, Saturday and Sunday
# on: [push]
jobs:
update_database:
runs-on: ubuntu-latest
steps:
- name: All tools we need
run: apt update && apt install -y default-jre git php php-dom php-curl php-sqlite3
- name: Checkout
uses: actions/checkout@v4
- name: run crawler
run: |
sed -i "/^error_reporting/c\error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_WARNING & ~E_NOTICE" /etc/php/7.4/cli/php.ini
cd crawler
php crawler.php
git config --global user.email "gitea@haschek.at"
git config --global user.name "Gitea"
git pull
git add data.db
git commit -m "update database"
git push
- name: Configure SSH
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$SSH_KEY" > ~/.ssh/staging.key
chmod 600 ~/.ssh/staging.key
- name: Run git pull on Server
run: |
ssh -i ~/.ssh/staging.key -o StrictHostKeyChecking=no -p 22 -o UserKnownHostsFile=/dev/null ${{ secrets.SSH_WEBSERVER_IP }} "cd /var/www/dogstats/ && git pull"
- name: Prepare
id: prep
run: |
SHORTREF=${GITHUB_SHA::10}
# Set output parameters.
echo ::set-output name=shortref::${SHORTREF}
- name: Updating version
run: |
ssh -i ~/.ssh/staging.key -o StrictHostKeyChecking=no -p 22 -o UserKnownHostsFile=/dev/null ${{ secrets.SSH_WEBSERVER_IP }} "echo v${{ steps.prep.outputs.shortref }} > /var/www/dogstats/web/version.txt"

View File

@@ -19,16 +19,18 @@ jobs:
mkdir -p ~/.ssh/
echo "$SSH_KEY" > ~/.ssh/staging.key
chmod 600 ~/.ssh/staging.key
- name: Run docker restart on Server
- name: Run git pull on Server
run: |
ssh -i ~/.ssh/staging.key -o StrictHostKeyChecking=no -p 22 -o UserKnownHostsFile=/dev/null ${{ secrets.SSH_WEBSERVER_IP }} "cd /var/www/dogstats/ && git pull"
- name: Getting repo
uses: actions/checkout@v2
with:
path: 'dogstats'
- name: Building minified CSS
run: cd dogstats/tools && ./tailwindcss-linux-x64 -i ../web/css/input.css -o ../web/css/output.css --minify
- name: Copying to server
run: scp -i ~/.ssh/staging.key -o StrictHostKeyChecking=no -P 22 -o UserKnownHostsFile=/dev/null dogstats/web/css/output.css root@${{ secrets.SSH_WEBSERVER_IP }}:/var/www/dogstats/web/css/.
- name: Prepare
id: prep
run: |
SHORTREF=${GITHUB_SHA::10}
# Set output parameters.
echo ::set-output name=shortref::${SHORTREF}
- name: Updating version
run: |
ssh -i ~/.ssh/staging.key -o StrictHostKeyChecking=no -p 22 -o UserKnownHostsFile=/dev/null ${{ secrets.SSH_WEBSERVER_IP }} "echo v${{ steps.prep.outputs.shortref }} > /var/www/dogstats/web/version.txt"

3
.gitignore vendored
View File

@@ -1 +1,2 @@
web/css/output.css
web/version.txt
database.sqlite3

View File

@@ -1,12 +1,11 @@
{
"recommendations": [
"zarifprogrammer.tailwind-snippets",
"dawhite.mustache",
"otovo-oss.htmx-tags",
"devsense.phptools-vscode",
"bmewburn.vscode-intelephense-client",
"github.copilot",
"github.copilot-chat",
"bradlc.vscode-tailwindcss"
"anbuselvanrocky.bootstrap5-vscode",
"hansuxdev.bootstrap5-snippets",
]
}

22
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Xdebug server",
"type": "php",
"request": "launch",
"runtimeArgs": [
"-S",
"localhost:8080",
"-t",
"web"
],
"port": 5902,
"serverReadyAction": {
"action": "openExternally",
"pattern": "listening on http://localhost:([0-9]+)",
"uriFormat": "http://localhost:%s",
},
}
]
}

View File

@@ -1,4 +1,8 @@
{
"html.format.indentInnerHtml": true,
"html.format.wrapLineLength": 0
"html.format.wrapLineLength": 0,
"intelephense.environment.documentRoot": "web/",
"terminal.integrated.splitCwd": "workspaceRoot",
"phpserver.port": 8080,
"phpserver.relativePath": "web/."
}

View File

@@ -27,6 +27,22 @@ Im Terminal dann Webserver starten
2. `php -S localhost:8080`
3. Browser auf http://localhost:8080 öffnen
## oder mit Xdebug
0. php server vom vorherigen schritt darf nicht laufen
1. xdebug installieren (`sudo apt install php-xdebug` oder `sudo apk add php82-pecl-xdebug`)
2. So oder so ähnlich muss die config aussehen `/etc/php82/conf.d/50_xdebug.ini`
```ini
[Xdebug]
zend_extension=xdebug
xdebug.mode=debug,develop
xdebug.client_host=host.docker.internal
xdebug.client_port=5902 #same port as in launch.json
```
3. Dann im VScode auf debugging -> Grünen pfeil `Xdebug server` drücken
## Ordnerstruktur
```
@@ -46,3 +62,16 @@ Im Terminal dann Webserver starten
└── templates Allgemeine Templates, mal schauen ob benötigt
└── partials
```
#Colors
#041337 - dunkelblau
#041337 - hellblau
#4c5597 - blau
#fefefc - white
#c8d2e9 - grau
#c9dd55 - hellgrün
#c9dd55 - dunkelgrün
#f0dcb3 - hellbraun
#a98d5d - dunkelbraun
#150d05 - schwarz

1
crawler/README.md Normal file
View File

@@ -0,0 +1 @@
java -jar tabula-1.0.5-jar-with-dependencies.jar --format CSV datei.pdf

39
crawler/base.sql Normal file
View File

@@ -0,0 +1,39 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "events" (
"ID" INTEGER UNIQUE,
"name" TEXT,
"date" TEXT,
"organizer" TEXT,
PRIMARY KEY("ID")
);
CREATE TABLE IF NOT EXISTS "runs" (
"id" INTEGER UNIQUE,
"event" INTEGER,
"name" TEXT,
"lk" TEXT,
"gk" TEXT,
FOREIGN KEY("event") REFERENCES "events"("ID"),
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "results" (
"id" INTEGER,
"event" INTEGER,
"run" INTEGER,
"rang" INTEGER,
"stnr" INTEGER,
"teilnehmer" TEXT,
"hund" TEXT,
"verein" TEXT,
"f" INTEGER,
"vw" INTEGER,
"zf" INTEGER,
"zeit" REAL,
"gf" REAL,
"msek" REAL,
"bew" TEXT,
"punkte" TEXT,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("run") REFERENCES "runs"("id"),
FOREIGN KEY("event") REFERENCES "events"("ID")
);
COMMIT;

246
crawler/crawler.php Normal file
View File

@@ -0,0 +1,246 @@
<?php
$GLOBALS['db'] = new SQLite3('data.db');
if(!$GLOBALS['db']) exit("Error loading database");
// if(!file_exists('tmp/ergebnisse.html'))
// {
// $ergebnisse = file_get_contents('https://www.dognow.at/ergebnisse/');
// file_put_contents('tmp/ergebnisse.html', $ergebnisse);
// }
// $html = file_get_contents('tmp/ergebnisse.html');
// $dom = new DOMDocument;
// $dom->loadHTML($html);
// $xpath = new DOMXPath($dom);
// $query = '//ul[@class="pagination"]/child::*';
// $nodes = $xpath->query($query);
// $GLOBALS['pdfs'] = 0;
// // Loop through the selected nodes
// foreach ($nodes as $node) {
// // Do something with each node, for example, echo its content
// $url = $node->getElementsByTagName('a')[0]->getAttribute('href');
// $number = intval($node->nodeValue);
// if($number > $last_page){
// $last_page = $number;
// }
// }
// echo "[i] Found $last_page pages\n";
// //create an array with all pages
// $pages = range(1,65);
// foreach($pages as $page)
// {
// echo "[i] Crawling page $page\n";
// scanPage($page);
// }
scanPage(1,false);
function scanPage($key,$usecache=true)
{
$page = 'https://www.dognow.at/ergebnisse/?page=' . $key;
if(file_exists('tmp/pages/' . ($key) . '.html' && $usecache===true)){
$html = file_get_contents($page);
}
else
{
$html = file_get_contents($page);
file_put_contents('tmp/pages/' . ($key) . '.html', $html);
}
$dom = new DOMDocument;
$dom->loadHTML($html);
// search for all divs with class "resultboard"
$xpath = new DOMXPath($dom);
$query = '//div[@class="resultboard info-board info-board-default2"]';
$nodes = $xpath->query($query);
// Loop through the selected nodes
foreach ($nodes as $node) {
// CUPs
$div = $node->getElementsByTagName('div')[0];
$id = $div->getAttribute('data-event');
$name = trim($div->getElementsByTagName('div')[1]->nodeValue);
$organizer = trim($div->getElementsByTagName('div')[2]->nodeValue);
$date = trim($div->getElementsByTagName('div')[3]->nodeValue);
$db_date = date(DATE_RFC3339, strtotime($date));
//if not exists, add to db
$res = $GLOBALS['db']->query("SELECT * FROM events WHERE id = '$id'");
if($res->fetchArray() == false)
$GLOBALS['db']->exec("INSERT INTO events (id, name, organizer, date) VALUES ('$id', '$name', '$organizer', '$db_date')");
crawlRuns($id,$usecache);
echo " [E] $id - $name - $organizer - $date\n";
}
}
var_dump($GLOBALS['pdfs']);
function crawlRuns($eventid,$usecache=true)
{
if(file_exists('tmp/events/' . $eventid . '.html') && $usecache===true)
$data = file_get_contents('tmp/events/' . $eventid . '.html');
else
{
//sleep(1);
$data = file_get_contents('https://www.dognow.at/ergebnisse/src/data.php?event='. $eventid .'&lauf=0');
file_put_contents('tmp/events/' . $eventid . '.html', $data);
}
//get first table using DOMDocument
$dom = new DOMDocument;
$dom->loadHTML($data);
if(strpos($data,"<b>Einzelwertung</b><br>
Derzeit sind keine Ergebnisse")!==false)
{
echo " [i] Keine Einzelwertungen\n";
return;
}
// when the string "Cup-Wertung" is found
if(strpos($data, 'Cup-Wertung') !== false){
echo " [i] Found Cup-Wertung, skipping first table\n";
$table = $dom->getElementsByTagName('table')[1];
}
else
$table = $dom->getElementsByTagName('table')[0];
if(!$table) return;
foreach($table->getElementsByTagName('tr') as $row){
if(!$row) continue;
$rid = $row->getAttribute('id');
if($rid)
$rid = explode('_', $rid)[1];
$tds = $row->getElementsByTagName('td');
if(count($tds) == 3) //rally obedience
{
$runname = trim($tds[0]->nodeValue);
$lk = trim($tds[1]->nodeValue);
$pdf = $tds[2]->getElementsByTagName('a')[0]->getAttribute('href');
}
else if(count($tds) == 4) // agility
{
$runname = trim($tds[0]->nodeValue);
$lk = trim($tds[1]->nodeValue);
$gk = trim($tds[2]->nodeValue);
$pdf = $tds[3]->getElementsByTagName('a')[0]->getAttribute('href');
//add run to db if not exists
$res = $GLOBALS['db']->query("SELECT * FROM runs WHERE id = '$rid'");
if($res->fetchArray() == false)
$GLOBALS['db']->exec("INSERT INTO runs (id, name, event, lk, gk) VALUES ('$rid', '$runname', '$eventid', '$lk', '$gk')");
getResults($rid,$eventid);
}
if(!$runname || !$lk || !$pdf) continue;
echo " [R-$rid] $runname - $lk - $gk - $pdf\n";
}
//exit("Crawling $eventid");
}
function getResults($run,$event)
{
$GLOBALS['pdfs']++;
//return;
if(!$run || !$event) return;
$url = "https://www.dognow.at/ergebnisse/pdf.php?lauf=$run&event=$event";
if(!file_exists('tmp/results/' . $event . '-' . $run . '.pdf'))
file_put_contents('tmp/results/' . $event . '-' . $run . '.pdf',file_get_contents($url));
/*if($GLOBALS['db']->query("SELECT * FROM runs WHERE id = '$run' AND event = '$event'")->fetchArray() != false)
{
echo " [i] Skipping run $run in event $event\n";
return;
}*/
convertPDFtoCSV('tmp/results/' . $event . '-' . $run . '.pdf','tmp/csv/' . $event . '-' . $run . '.pdf.csv');
analyzeResultCSV('tmp/csv/' . $event . '-' . $run . '.pdf.csv',$run,$event);
}
function analyzeResultCSV($csvfile,$run,$event)
{
if(!file_exists($csvfile)) die(" ERR: File $csvfile not found");
$csv = array_map('str_getcsv', file($csvfile));
//prepare header for database
foreach($csv[0] as $key=>$value){
$csv[0][$key] = preg_replace('/[^A-Za-z0-9]/', '', strtolower($value));
}
array_walk($csv, function(&$a) use ($csv) {
$a = array_combine($csv[0], $a);
});
array_shift($csv); # remove column header
foreach($csv as $row)
{
$stnr = $row['stnr'];
$teilnehmer = $row['teilnehmer'];
$hund = $row['hund'];
$rang = $row['rang'];
$verein = $row['verein'];
$f = $row['f'];
$vw = $row['vw'];
$zf = $row['zf'];
$zeit = $row['zeit'];
$gf = $row['gf'];
$msek = $row['msek'];
$punkte = $row['punkte'];
$bew = $row['bew'];
//add result to db if not exists
try
{
$res = $GLOBALS['db']->query("SELECT * FROM results WHERE stnr = '$stnr' AND run = '$run' AND event = '$event'");
if($res->fetchArray() == false)
$GLOBALS['db']->exec("INSERT INTO results (stnr, rang, run, event, teilnehmer, hund, verein, f, vw, zf, zeit, gf, msek, bew, punkte) VALUES ('$stnr', '$rang', '$run', '$event', '$teilnehmer', '$hund', '$verein', '$f', '$vw', '$zf', '$zeit', '$gf', '$msek', '$bew', '$punkte')");
//else echo " [i] Skipping $teilnehmer in run $run in event $event\n";
}
catch(Exception $ex) {
//die( $ex->getMessage() );
exit($GLOBALS['db']->lastErrorMsg());
}
}
}
function convertPDFtoCSV($pdf,$targetname)
{
if(file_exists($targetname)) return;
$csv = analyze($pdf);
file_put_contents($targetname, $csv);
}
function analyze($pdf) {
echo " [i] Analyzing $pdf\n";
$cmd = "java -jar tabula-1.0.5-jar-with-dependencies.jar -f CSV $pdf";
$output = shell_exec($cmd);
//var_dump($output);
return $output;
}
?>

BIN
crawler/data.db Normal file

Binary file not shown.

19
crawler/parse.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
require_once('../web/inc/helpers.php');
//loop all files in results folder
$files = scandir('tmp/results');
foreach ($files as $file) {
if ($file == '.' || $file == '..' || !endsWith($file, '.pdf') || file_exists('tmp/csv/' . $file . '.csv'))
continue;
$csv = analyze('tmp/results/' . $file);
file_put_contents('tmp/csv/' . $file . '.csv', $csv);
}
function analyze($pdf) {
$cmd = "java -jar tabula-1.0.5-jar-with-dependencies.jar -f CSV $pdf";
$output = shell_exec($cmd);
return $output;
}

Binary file not shown.

4
crawler/tmp/.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
results/*.pdf filter=lfs diff=lfs merge=lfs -text
csv/*.csv filter=lfs diff=lfs merge=lfs -text
pages/*.html filter=lfs diff=lfs merge=lfs -text
events/*.html filter=lfs diff=lfs merge=lfs -text

2
crawler/tmp/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.html
!.gitignore

2
crawler/tmp/csv/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

2
crawler/tmp/results/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,9 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["../web/**/*.{html,js,php}"],
theme: {
extend: {},
},
plugins: [],
}

Binary file not shown.

Binary file not shown.

157
web/css/dogstats.css Normal file
View File

@@ -0,0 +1,157 @@
:root {
--white: #fefefd;
--lightblue: #47cafe;
--blue: #2ca6fc;
--darkblue: #021135;
--lightgreen: #cadc57;
--darkgreen: #788f08;
--lightbrown: #eddbb6;
--darkbrown: #ab8f5f;
--grey: #c5cbe9;
--black: #150d05;
}
@font-face {
font-family: 'Kalam-Light';
src: url(../webfonts/kalam/Kalam-Light.woff2) format('woff2');
}
@font-face {
font-family: 'Kalam-Regular';
src: url(../webfonts/kalam/Kalam-Regular.woff2) format('woff2');
}
@font-face {
font-family: 'Kalam-Bold';
src: url(../webfonts/kalam/Kalam-Bold.woff2) format('woff2');
}
h1, h2, h3 {
font-family: 'Kalam-Bold', sans-serif;
}
h4, h5, h6 {
font-family: 'Kalam-Regular', sans-serif;
}
.btn-primary {
--bs-btn-bg: var(--blue);
--bs-btn-border-color: var(--blue);
--bs-btn-hover-bg: var(--lightblue);
--bs-btn-hover-border-color: var(--lightblue);
--bs-btn-active-bg: var(--lightblue);
--bs-btn-disabled-color: var(--grey);
--bs-btn-disabled-bg: var(--blue);
--bs-btn-disabled-border-color: var(--blue);
}
.btn-secondary {
--bs-btn-bg: var(--darkgreen);
--bs-btn-border-color: var(--darkgreen);
--bs-btn-hover-bg: var(--lightgreen);
--bs-btn-hover-border-color: var(--lightgreen);
--bs-btn-active-bg: var(--lightgreen);
--bs-btn-active-border-color: var(--lightgreen);
--bs-btn-disabled-bg: var(--darkgreen);
--bs-btn-disabled-border-color: var(--darkgreen);
}
main>.container {
padding: 60px 15px 0;
}
table {
font-size: smaller;
}
.tooltipp {
position: relative;
/* making the .tooltipp span a container for the tooltipp text */
border-bottom: 1px dashed #000;
/* little indicater to indicate it's hoverable */
}
.tooltipp:before {
content: attr(data-text);
/* here's the magic */
position: absolute;
/* vertically center */
top: 50%;
transform: translateY(-50%);
/* move to right */
left: 100%;
margin-left: 15px;
/* and add a small left margin */
/* basic styles */
width: 200px;
padding: 10px;
border-radius: 10px;
background: #000;
color: #fff;
text-align: center;
display: none;
/* hide by default */
opacity: 0;
transition: .3s opacity;
}
.tooltipp:hover:before {
display: block;
opacity: 1;
}
.tooltipp:after {
opacity: 0;
transition: .3s;
}
.tooltipp:hover:after {
opacity: 1;
}
/* Dropzone overrides */
.dropzone {
min-height: 150px;
border: 2px solid rgba(0,0,0,.3);
padding: 20px 20px;
}
/* XDEBUG */
.xdebug-error th
{
font-family:monospace;
font-weight:normal;
font-size:15px;
padding: 6px 6px 6px 6px;
border:1px solid black;
background: #FFCC99;
color:#000000;
}
.xdebug-error > tr:first-child > th:first-child,
.xdebug-error > tbody > tr:first-child > th:first-child
{
line-height:1.6em;
padding: 10px 10px 10px 10px;
border:1px solid #000000;
background: #000000;
color:#FFFFFF;
}
.xdebug-error td
{
font-size:14px;
padding: 6px 6px 6px 6px;
border:1px solid green;
background: #D1FFE8;
color:#000000;
}
#dog-data {
padding: 1rem 1rem 1rem 6rem;
border: 2px solid var(--blue);
}
#dog-data .dog-image {
position: absolute;
left: -50px;
top: -35px;
border: 1px solid var(--grey);
}

1
web/css/dropzone.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1778
web/css/output.css Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ class Model {
{
//redis
if ($GLOBALS['redis'])
$this->redis = $GLOBALS['redis'];
$GLOBALS['redis'] = $GLOBALS['redis'];
}
/**
@@ -55,18 +55,31 @@ class Model {
return $data;
}
public function getField($field,$id=false)
{
if(!$id)
$id = $this->id;
return $GLOBALS['redis']->hget($this->dbTable.':'.$id,$field);
}
public function save()
{
$newgen = false;
if (!$this->id)
{
$this->id = gen_ulid();
if (!$this->validate())
$newgen = true;
}
if (!$this->validate($newgen))
return false;
foreach($this->dbFields as $field=>$options)
{
if(isset($this->data[$field]))
{
if($options['type']=='array')
if($options['type']=='array' && is_array($this->data[$field]))
$GLOBALS['redis']->hset($this->dbTable.':'.$this->id,$field,json_encode($this->data[$field]));
else if($options['type']=='array' && !is_array($this->data[$field]))
$GLOBALS['redis']->hset($this->dbTable.':'.$this->id,$field,json_encode([]));
else
{
$GLOBALS['redis']->hset($this->dbTable.':'.$this->id,$field,$this->data[$field]);
@@ -78,9 +91,33 @@ class Model {
return $this->id;
}
public function load($id)
/**
* @param $value The value to search for
* @param string $field The field to search in. HAS to be marked as unique otherwise will throw error
*/
public function load($value=false,$field='id')
{
if(!$value)
$value = $this->id;
if($field!='id')
{
//sanity check. Check if $field is marked as unique
if(!in_array('unique',$this->dbFields[$field]))
throw new Exception($field.' is not unique');
//we need to find the id first
$keys = $GLOBALS['redis']->keys($this->dbTable.':*');
foreach($keys as $key){
$id = end(explode(':',$key));
$thisval = $GLOBALS['redis']->hget($this->dbTable.':'.$id,$field);
if($thisval==$value)
{
$this->id = $id;
break;
}
}
}
else
$this->id = $value;
if(!$GLOBALS['redis']->exists($this->dbTable.':'.$this->id))
throw new Exception($this->dbTable.':'.$this->id.' not found');
$keys = array_keys($this->dbFields);
@@ -89,7 +126,9 @@ class Model {
{
$value = $GLOBALS['redis']->hget($this->dbTable.':'.$this->id,$key);
if($value!==NULL) //we'll leave null values
//var_dump("BEFORE[".$this->dbTable.':'.$this->id."] Loading from DB $key: '$value'");
if($value!==NULL) //we'll leave null values alone
switch($this->dbFields[$key]['type'])
{
case 'int': $value = intval($value);break;
@@ -97,11 +136,13 @@ class Model {
case 'float': $value = floatval($value);break;
case 'double': $value = doubleval($value);break;
case 'array':
$value = json_decode($value,true);
if($value===null)
if(!$value)
$value = [];
else if(is_string($value))
$value = json_decode($value,true);
break;
}
//var_dump("AFTER[".$this->dbTable.':'.$this->id."] Loading from DB $key: '$value'");
$this->data[$key] = $value;
}
@@ -111,7 +152,7 @@ class Model {
/**
* @param array $data
*/
private function validate()
private function validate($newgen=false)
{
if (!$this->dbFields)
return true;
@@ -133,8 +174,8 @@ class Model {
if (is_array($value))
continue;
if (isset($desc[0]))
$type = $desc[0];
// if (isset($desc[0]))
// $type = $desc[0];
if (in_array('required',$options))
$required = true;
@@ -149,8 +190,11 @@ class Model {
$this->data[$key] = $value;
}
if($options['default']!==null && $value===null)
if($options['default']!==null && $newgen === true && $value===null)
{
$value = $options['default'];
$this->data[$key] = $value;
}
if ($required && strlen($value) == 0) {
throw new Exception($this->dbTable . "." . $key . " is required");
@@ -199,6 +243,16 @@ class Model {
return true;
}
function getTable()
{
return $this->dbTable;
}
function exists($id)
{
return $GLOBALS['redis']->exists($this->dbTable.':'.$id);
}
function delete()
{
return $GLOBALS['redis']->del($this->dbTable.':'.$this->id);

View File

@@ -18,6 +18,7 @@ class Page
public $menu_text;
public $menu_image;
public $menu_priority;
public $menu_classes;
public $submenu;
private $r;
@@ -46,7 +47,7 @@ class Page
$this->menu_priority = 1;
}
function addSubmenuItem($text, $action, $icon = '', $classes = '')
function addSubmenuItem($text, $action='', $icon = '', $classes = '')
{
$active = $GLOBALS['url'][0];
if (!$active) $active = 'index';

View File

@@ -82,14 +82,19 @@ function callHook($url)
if (!$dispatch->maySeeThisPage()) {
$componentName = 'err';
$action = 'notallowed';
$dispatch = new $componentName('error', $action, true);
$dispatch = new $componentName('err', $action, true);
} else
$dispatch = new $componentName($component, $action, true, $queryString);
if (method_exists($componentName, $action)) {
if (method_exists($componentName, $action))
$response = call_user_func_array(array($dispatch, $action), $queryString);
} else if (method_exists($componentName, 'catchAll'))
else if (method_exists($componentName, 'catchAll'))
$response = call_user_func_array(array($dispatch, 'catchAll'), array($params));
else
{
$dispatch = new Err('err', 'notfound', false);
$response = call_user_func_array(array($dispatch, 'notfound'), array($params));
}
if(is_string($response))
return $response;
@@ -118,7 +123,7 @@ function getMenu()
{
while($arr[$menu_priority])
$menu_priority++;
$arr[$menu_priority] = array('text'=>$menu_text,'image'=>$instance->menu_image, 'url'=>$file,'menu_class'=>$instance->menu_class,'submenu'=>$instance->submenu);
$arr[$menu_priority] = array('text'=>$menu_text,'image'=>$instance->menu_image, 'url'=>$file,'menu_classes'=>$instance->menu_classes,'submenu'=>$instance->submenu);
}
}
}
@@ -133,3 +138,35 @@ function getMenu()
return $arr;
}
function autoLoginCheck()
{
//check if user has a cookie and if so, logg them in and refresh the page
if(isset($_COOKIE['token']) && $_COOKIE['token'] != '' && !$_SESSION['user'])
{
$u = new User();
$allusers = $u->getAll(false);
foreach($allusers as $user)
{
if($user['token'] && $user['token'] == $_COOKIE['token'])
{
$u->id = $user['id'];
break;
}
}
if($u->id) //valid cookie, users gets logged in
{
$u->load($u->id);
$u->login();
$url = '/'.implode('/',$GLOBALS['url']);
header("HX-Redirect: ". $url);
exit('<meta http-equiv="Refresh" content="seconds; url='. $url.'"> <script>window.location.href="'. $url.'"</script> ');
}
else //invalid cookie gets deleted
{
setcookie('token', '', time() - 3600, "/");
}
}
}

View File

@@ -210,3 +210,146 @@ function uuid4($data = null) {
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/*
* @param $path string Path to the file that should be uploaded
* @param $hash string Optional. File name we want on pictshare for the file
*/
function pictshareUploadImage($path,$hash=false)
{
if(!file_exists($path)) return false;
$request = curl_init('https://i.haschek.at/api/upload.php');
curl_setopt($request,CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($request, CURLOPT_POST, true);
curl_setopt(
$request,
CURLOPT_POSTFIELDS,
array(
'file' => curl_file_create($path),
'hash'=>$hash
));
// output the response
curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
$answer = curl_exec($request);
if($answer === false) return ['status'=>'error','error'=>curl_error($request)];
else
$json = json_decode($answer.PHP_EOL,true);
// close the session
curl_close($request);
return $json;
}
// takes $_FILES['file'] as input and validates, throws error if fails, else returns URL of the image
function pictShareFormValidateAndUpload($file,$key=false)
{
if($key===false)
{
$photo_name = $file['name'];
$photo_tmp_name = $file['tmp_name'];
$photo_size = $file['size'];
$photo_error = $file['error'];
$photo_type = $file['type'];
}
else
{
$photo_name = $file['name'][$key];
$photo_tmp_name = $file['tmp_name'][$key];
$photo_size = $file['size'][$key];
$photo_error = $file['error'][$key];
$photo_type = $file['type'][$key];
}
$allowed = ['jpg','jpeg','png','gif'];
$photo_ext = strtolower(end(explode('.', $photo_name)));
if(in_array($photo_ext, $allowed))
{
if($photo_error === 0)
{
if($photo_size < 10000000)
{
$answer = pictshareUploadImage($photo_tmp_name);
if($answer['status']=='ok' && in_array($answer['filetype'],['jpeg','png','gif']))
return $answer['url'];
else
throw new Exception('Fehler beim CDN Upload: '.json_encode($answer,true));
}
else
throw new Exception('Die Datei ist zu groß. Bitte eine kleinere Datei hochladen');
}
else
throw new Exception('Fehler beim Upload: '.getFileUploadError($photo_error));
}
else
throw new Exception('Dateityp nicht erlaubt. Bitte nur '.implode(', ',$allowed).' hochladen');
}
function getFileUploadError($error)
{
$phpFileUploadErrors = array(
0 => 'There is no error, the file uploaded with success',
1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
3 => 'The uploaded file was only partially uploaded',
4 => 'No file was uploaded',
6 => 'Missing a temporary folder',
7 => 'Failed to write file to disk.',
8 => 'A PHP extension stopped the file upload.',
);
return $phpFileUploadErrors[$error];
}
function partial($name,$variables=[])
{
$templatefile = ROOT.DS.'templates'.DS.'partials'.DS.$name;
return template($templatefile,$variables);
}
function template($templatefile,$variables=[])
{
ob_start();
if(is_array($variables))
extract($variables);
if(file_exists($templatefile))
include($templatefile);
$pagecontent = ob_get_contents();
ob_end_clean();
return $pagecontent;
}
function printRelativeTime($timestamp1, $timestamp2)
{
$diff = abs($timestamp2 - $timestamp1);
$intervals = array(
'year' => 31536000,
'month' => 2592000,
'week' => 604800,
'day' => 86400,
'hour' => 3600,
'minute' => 60,
'second' => 1
);
$output = '';
foreach ($intervals as $interval => $seconds) {
$count = floor($diff / $seconds);
if ($count > 0) {
$output .= $count . ' ' . ($count === 1 ? $interval : $interval . 's') . ', ';
$diff -= $count * $seconds;
}
}
$output = rtrim($output, ', ');
return $output;
}

View File

@@ -29,6 +29,8 @@ if($url==[] && $_SERVER['HTTP_HX_CURRENT_URL'])
$GLOBALS['url'] = $url;
//echo print_r(['url'=>$url,'server'=>$_SERVER,'request'=>$_REQUEST,'cookie'=>$_COOKIE,'session'=>$_SESSION],true);
autoLoginCheck();
$response = callHook($url);
if(is_string($response))

80
web/js/color-modes.js Normal file
View File

@@ -0,0 +1,80 @@
/*!
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
*/
(() => {
'use strict'
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
const setTheme = theme => {
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
setTheme(getPreferredTheme())
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active use')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.setAttribute('href', svgOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
document.querySelectorAll('[data-bs-theme-value]')
.forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
})()

1
web/js/dropzone.min.js vendored Normal file

File diff suppressed because one or more lines are too long

45
web/js/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
<?php
class Dog extends Model {
class Dog extends Model
{
protected $dbTable = "dogs";
protected $dbFields = Array (
'uuid' => ['type'=>'text','required','unique','autoValMethod'=>'gen_ulid'],
protected $dbFields = array(
'registered' => ['type' => 'datetime', 'required', 'unique', 'autoValMethod' => 'getDateTime'],
'name' => ['type' => 'text'],
'kennel_name' => ['type' => 'text'],
@@ -11,7 +11,56 @@ class Dog extends Model {
'size' => ['type' => 'text'], //in cm
'birthday' => ['type' => 'text'],
'agility_size' => ['type' => 'text'], //S,M,I,L
'active' => ['type'=>'int','default'=>1]
'photo' => ['type' => 'text', 'default' => 'https://pictshare.net/1ch3e5.png'],
'active' => ['type' => 'bool', 'default' => 1]
);
function getResults($dogid=false)
{
if(!$dogid)
$dogid = $this->id;
$res = new Result();
$keys = $GLOBALS['redis']->keys($res->getTable() . ':*');
$results = [];
foreach ($keys as $key) {
$id = end(explode(':', $key));
$res = new Result();
$res->load($id);
if ($res->data['dog'] == $dogid) {
$results[] = $res->data;
}
}
return $results;
}
function getRuns()
{
$run = new Run();
$keys = $GLOBALS['redis']->keys($run->dbTable . ':*');
$runs = [];
foreach ($keys as $key) {
$id = end(explode(':', $key));
$run = new Run();
$run->load($id);
if ($run->data['dog'] == $this->id)
$runs[] = $run->data;
}
return $runs;
}
function isMyDog($dog = false)
{
if ($dog == false)
$dog = $this->id;
if (!$this->exists($dog))
return false;
else if(!is_array($_SESSION['user']->data['dogs']))
return false;
else {
if (in_array($dog, $_SESSION['user']->data['dogs']))
return true;
else return false;
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
class Result extends Model
{
protected $dbTable = "result";
protected $dbFields = array(
'tournament' => ['type' => 'text', 'required'], //tournament ID
'run' => ['type' => 'text', 'required'], //run ID
'user' => ['type' => 'text', 'required'], //user ID
'dog' => ['type' => 'text', 'required'], //dog ID
'disqualified' => ['type' => 'bool','default'=>0],
'refusals' => ['type' => 'int','default'=>0],
'errors' => ['type' => 'int','default'=>0],
'timefaults' => ['type' => 'float','default'=>0],
'runtime' => ['type' => 'float','default'=>0],
'penalties' => ['type' => 'float','default'=>0],
'rating' => ['type' => 'text'],
'points' => ['type' => 'int','default'=>0],
'speed' => ['type' => 'float','default'=>0], //in m/sec
'ranking' => ['type' => 'int','default'=>0],
'photos' => ['type' => 'array','default'=>[]],
'videos' => ['type' => 'array','default'=>[]],
'memo' => ['type' => 'text'],
'public' => ['type' => 'bool','default'=>1],
);
function getDataOfRun($rid)
{
$run = new Run();
if($run->exists($rid))
$run->load($rid);
else return false;
$results = $run->data['results'];
if(is_array($results) && count($results)>0)
{
$res = [];
foreach($results as $r)
{
$result = new Result();
if($result->exists($r))
{
$result->load($r);
$res[] = $result->data;
}
}
return $res;
}
else return [];
}
}

18
web/models/Run.model.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
class Run extends Model
{
protected $dbTable = "run";
protected $dbFields = array(
'tournament' => ['type' => 'text', 'required'],
'name' => ['type' => 'text', 'required'],
'category' => ['type' => 'text'],
'length' => ['type' => 'int'], //in meters
'time_standard' => ['type' => 'int'], // in sek
'time_max' => ['type' => 'int'], // in sek
'referee' => ['type' => 'text'],
'photo' => ['type' => 'text'],
'results' => ['type' => 'array','default'=>[]],
);
}

View File

@@ -0,0 +1,122 @@
<?php
class Tournament extends Model
{
protected $dbTable = "tournaments";
protected $dbFields = array(
'registered' => ['type' => 'datetime', 'required', 'unique', 'autoValMethod' => 'getDateTime'],
'name' => ['type' => 'text', 'required'],
'date' => ['type' => 'datetime','required'],
'duration' => ['type' => 'int'], //in days
'text' => ['type' => 'text'],
'url' => ['type' => 'text'], //if there is one
'logo' => ['type' => 'text', 'default' => 'https://pictshare.net/prrnrk.jpg'],
'admins' => ['type'=> 'array', 'default'=>[]],
'members' => ['type'=> 'array', 'default'=>[]],
'runs' => ['type'=> 'array', 'default'=>[]],
);
function joinUser($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
if (!in_array($_SESSION['user']->id, $this->data['members']))
$this->data['members'][] = $_SESSION['user']->id;
if(!in_array($tournament,$_SESSION['user']->data['tournaments']))
{
$_SESSION['user']->data['tournaments'][] = $tournament;
$_SESSION['user']->save();
}
$this->save();
}
}
function removeUser($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
if (in_array($_SESSION['user']->id, $this->data['members']))
$this->data['members'] = array_diff($this->data['members'],[$_SESSION['user']->id]);
if(in_array($tournament,$_SESSION['user']->data['tournaments']))
{
$_SESSION['user']->data['tournaments'] = array_diff($_SESSION['user']->data['tournaments'],[$tournament]);
$_SESSION['user']->save();
}
$this->save();
}
}
function isMyEvent($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
if (in_array($tournament, $_SESSION['user']->data['tournaments']))
return true;
else return false;
}
}
function amIAdmin($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
if (in_array($_SESSION['user']->id, $this->data['admins']))
return true;
else return false;
}
}
function getAdmins($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
$admins = [];
foreach($this->data['admins'] as $admin)
{
$u = new User();
$u->load($admin);
$admins[] = $u->getDataFiltered();
}
return $admins;
}
}
function getMembers($tournament = false)
{
if ($tournament == false)
$tournament = $this->id;
if (!$this->exists($tournament))
return false;
else {
$members = [];
foreach($this->data['members'] as $member)
{
$u = new User();
$u->load($member);
$members[] = $u->getDataFiltered();
}
return $members;
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
class Training extends Model
{
protected $dbTable = "trainings";
protected $dbFields = array(
'registered' => ['type' => 'datetime', 'required', 'unique', 'autoValMethod' => 'getDateTime'],
'name' => ['type' => 'text', 'required'],
'date' => ['type' => 'datetime','required'],
'duration' => ['type' => 'int'], //in days
'text' => ['type' => 'text'],
'url' => ['type' => 'text'], //if there is one
'logo' => ['type' => 'text', 'default' => 'https://pictshare.net/prrnrk.jpg'],
'admins' => ['type'=> 'array', 'default'=>[]],
'members' => ['type'=> 'array', 'default'=>[]],
'runs' => ['type'=> 'array', 'default'=>[]],
);
function joinUser($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
if (!in_array($_SESSION['user']->id, $this->data['members']))
$this->data['members'][] = $_SESSION['user']->id;
if(!in_array($training,$_SESSION['user']->data['trainings']))
{
$_SESSION['user']->data['trainings'][] = $training;
$_SESSION['user']->save();
}
$this->save();
}
}
function removeUser($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
if (in_array($_SESSION['user']->id, $this->data['members']))
$this->data['members'] = array_diff($this->data['members'],[$_SESSION['user']->id]);
if(in_array($training,$_SESSION['user']->data['trainings']))
{
$_SESSION['user']->data['trainings'] = array_diff($_SESSION['user']->data['trainings'],[$training]);
$_SESSION['user']->save();
}
$this->save();
}
}
function isMyEvent($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
if (in_array($training, $_SESSION['user']->data['trainings']))
return true;
else return false;
}
}
function amIAdmin($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
if (in_array($_SESSION['user']->id, $this->data['admins']))
return true;
else return false;
}
}
function getAdmins($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
$admins = [];
foreach($this->data['admins'] as $admin)
{
$u = new User();
$u->load($admin);
$admins[] = $u->getDataFiltered();
}
return $admins;
}
}
function getMembers($training = false)
{
if ($training == false)
$training = $this->id;
if (!$this->exists($training))
return false;
else {
$members = [];
foreach($this->data['members'] as $member)
{
$u = new User();
$u->load($member);
$members[] = $u->getDataFiltered();
}
return $members;
}
}
}

View File

@@ -5,14 +5,19 @@ class User extends Model {
protected $dbFields = Array (
'uuid' => ['type'=>'text','required','unique','autoValMethod'=>'gen_ulid'],
'password' => ['type'=>'text'],
'registered' => ['type'=>'datetime','required','unique','autoValMethod'=>'getDateTime'],
'registered' => ['type'=>'datetime','required','autoValMethod'=>'getDateTime'],
'email' => ['type'=>'email','unique'],
'firstname' => ['type'=>'text'],
'lastname' => ['type'=>'text'],
'birthday' => ['type' => 'text'],
'club' => ['type'=>'text'],
'last_login' => ['type'=>'datetime','required','unique','autoValMethod'=>'getDateTime'],
'token' => ['type'=>'text','required','unique','autoValMethod'=>'uuid4'],
'timezone' => ['type'=>'int'],
'dogs' => ['type'=> 'array','default'=>[]],
'tournaments' =>['type'=> 'array','default'=>[]],
'photo' => ['type'=>'text','default'=>'https://pictshare.net/pj7vzx.jpg'],
'theme' => ['type'=>'text','default'=>'light'], //light/dark
'active' => ['type'=>'int','default'=>0]
);
protected $hidden = ['password','token'];
@@ -20,11 +25,13 @@ class User extends Model {
function login()
{
if(!$this->id) return false;
$this->load();
$this->last_login = $this->getDateTime();
$this->save();
$_SESSION['user'] = $this;
$_SESSION['userid'] = $this->id;
$_SESSION['user']->save();
}
function logout()
@@ -33,14 +40,19 @@ class User extends Model {
unset($_SESSION['user']);
}
function exists($id)
function removeDog($dogid)
{
return $this->redis->exists($this->dbTable.':'.$id);
if(!$this->id) return false;
if (in_array($dogid, $this->data['dogs']))
{
$this->data['dogs'] = array_diff($this->data['dogs'],[$dogid]);
$this->save();
}
}
function getAll($filtered = true)
{
$keys = $this->redis->keys($this->dbTable.':*');
$keys = $GLOBALS['redis']->keys($this->dbTable.':*');
$users = [];
foreach($keys as $key)
{
@@ -48,9 +60,13 @@ class User extends Model {
$u = new User();
$u->load($id);
if($filtered===true)
$users[] = $u->getDataFiltered();
$thisdata = $u->getDataFiltered();
else
$users[] = $u->data;
$thisdata = $u->data;
$thisdata['id'] = $id;
$users[] = $thisdata;
}
return $users;
}

View File

@@ -28,7 +28,7 @@
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
<button type="submit" class="btn btn-primary">Save</button>
<div id="response"></div>
</div>
</div>

View File

@@ -0,0 +1,78 @@
<?php
class Demo extends Page{
function setMenu()
{
$this->menu_text = 'Demo';
$this->menu_image = 'fas fa-do';
$this->menu_priority = 5;
}
function setSubmenu()
{
//$this->addSubmenuItem('Hunde anzeigen','/dogs','far fa-list-alt');
//$this->addSubmenuItem('Hund hinzufügen','/dogs/add','fas fa-plus-circle');
}
function test()
{
$graph1 = partial('graph.html.php', [
'id' => 'graph1',
'title' => 'Graph 1',
'legend' => ['Geschwindigkeit', 'Fehler', 'Verweigerungen', 'Zeit', 'Geschwindigkeit', 'Punkte', 'Platz'],
'xaxis' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
'seriesdata' => [
['name' => 'Geschwindigkeit', 'type'=>'line','stack'=>'Total', 'data' => [120, 132, 101, 134, 90, 230, 210]],
['name' => 'Fehler', 'type'=>'line','stack'=>'Total', 'data' => [220, 182, 191, 234, 290, 330, 310]],
['name' => 'Verweigerungen', 'type'=>'line','stack'=>'Total', 'data' => [150, 232, 201, 154, 190, 330, 410]],
['name' => 'Zeit', 'type'=>'line','stack'=>'Total', 'data' => [320, 332, 301, 334, 390, 330, 320]],
['name' => 'Punkte', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]],
['name' => 'Platz', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]]
],
]);
$graph2 = partial('graph.html.php', [
'id' => 'graph2',
'title' => 'Punkte',
'xaxis' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
'seriesdata' => [
['name' => 'Punkte', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]],
],
]);
$graph3 = partial('graph.html.php', [
'id' => 'graph3',
'title' => 'Graph 3',
'legend' => ['Punkte'],
'xaxis' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
'seriesdata' => [
['name' => 'Punkte', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]],
],
]);
return '<div class="container text-center">
<div class="row">
<div class="col">
'.$graph1.'
</div>
<div class="col">
'.$graph2.'
</div>
<div class="col">
'.$graph3.'
</div>
</div>
</div>';
}
function index()
{
$this->set('successTitle','Erfolgreich');
$this->set('successMessage','Text davon');
$this->set('errorMessage','Fehler ohne Titel');
$this->set('template','demo.html');
}
}

9
web/pages/demo/demo.html Normal file
View File

@@ -0,0 +1,9 @@
<?= partial('success.html',['successTitle'=>'Dies ist ein Titel','successMessage'=>'Erfolgreich']); ?>
<?= partial('error.html',['errorMessage'=>'Fehlernacricht ohne titel']); ?>
<?= partial('info.html',['infoTitle'=>'Dies ist ein Info titel','infoMessage'=>'InfoNachricht ist so']); ?>
<?= partial('notice.html',['noticeTitle'=>'Dies ist ein notice titel','noticeMessage'=>'noticeNachricht ist so']); ?>
<?= partial('../../pages/tournaments/edit.html',[]); ?>

View File

@@ -6,13 +6,23 @@ class Dogs extends Page {
{
$this->menu_text = 'Meine Hunde';
$this->menu_image = 'far fa-dog';
$this->menu_priority = 0;
$this->menu_priority = 1;
}
function setSubmenu()
{
$this->addSubmenuItem('Hunde anzeigen','/dogs','far fa-list-alt');
$this->addSubmenuItem('Hund hinzufügen','/dogs/add','fas fa-plus-circle');
if($_SESSION['user']->data['dogs'] && count($_SESSION['user']->data['dogs']) > 0)
{
$this->addSubmenuItem('divider');
foreach($_SESSION['user']->data['dogs'] as $dogid)
{
$dog = new Dog();
$dog->load($dogid);
$this->addSubmenuItem($dog->data['name'],'/dogs/overview/'.$dogid,'fas fa-dog');
}
}
}
function index() {
@@ -56,6 +66,20 @@ class Dogs extends Page {
$this->set('template', 'edit.html');
}
function delete(){
$dogid = $this->params[0];
$d = new Dog();
if(!$d->isMyDog($dogid))
return 'Not your dog :(';
$d->load($dogid);
$d->delete();
$_SESSION['user']->removeDog($dogid);
$this->redirect('/dogs');
}
function edit() {
if($_REQUEST['submit'])
{
@@ -63,9 +87,45 @@ class Dogs extends Page {
$name = $_REQUEST['name'];
$kennel_name = $_REQUEST['kennel_name'];
$dog_breed = $_REQUEST['dog_breed'];
$dog_size = $_REQUEST['dog_size'];
$dog_size = intval($_REQUEST['dog_size']);
$dog_birthday = $_REQUEST['dog_birthday'];
$agi_height_category = $_REQUEST['agi_height_category'];
$active = intval($_REQUEST['agi_active']);
$newphoto = false;
if($_FILES['photo'])
{
$photo = $_FILES['photo'];
$photo_name = $photo['name'];
$photo_tmp_name = $photo['tmp_name'];
$photo_size = $photo['size'];
$photo_error = $photo['error'];
$photo_type = $photo['type'];
$allowed = ['jpg','jpeg','png','gif'];
$photo_ext = strtolower(end(explode('.', $photo_name)));
if(in_array($photo_ext, $allowed))
{
if($photo_error === 0)
{
if($photo_size < 10000000)
{
$answer = pictshareUploadImage($photo_tmp_name);
if($answer['status']=='ok' && in_array($answer['filetype'],['jpeg','png','gif']))
$newphoto = $answer['url'];
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Fehler beim CDN Upload: '.json_encode($answer,true)]);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Die Datei ist zu groß. Bitte eine kleinere Datei hochladen']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Beim Upload der Datei ist ein Fehler aufgetreten']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieser Dateityp ist nicht erlaubt. Bitte nur jpg, jpeg oder png Dateien hochladen']);
}
$error = false;
if(!$name || !$dog_birthday )
@@ -90,6 +150,9 @@ class Dogs extends Page {
$dog->size = $dog_size;
$dog->birthday = $dog_birthday;
$dog->agility_size = $agi_height_category;
$dog->active = $active;
if($newphoto)
$dog->photo = $newphoto;
try
{
@@ -142,6 +205,22 @@ class Dogs extends Page {
}
}
function overview()
{
$dogid = $this->params[0];
$d = new Dog();
//if(!$d->isMyDog($dogid))
// return 'Not your dog :(';
$d->load($dogid);
$this->set('ismydog',$d->isMyDog($dogid));
$this->set('results', $d->getResults());
$this->set('dogdata', $d->data);
$this->set('dogid', $dogid);
$this->set('template', 'dog.html.php');
}
function maySeeThisPage() {
if($_SESSION['user']) //wenn eingeloggt, kein problem
return true;

245
web/pages/dogs/dog.html.php Normal file
View File

@@ -0,0 +1,245 @@
<!-- FILEPATH: /home/chris/git/dogstats/web/pages/dogs/dog.html -->
<div class="container">
<div class="">
<div class="row">
<div id="dog-data" class="col-sm-6 offset-sm-3 position-relative rounded-2 shadow mb-3 mb-md-5">
<img src="<?= $dogdata['photo']?:'https://pictshare.net/1ch3e5.png' ?>/120x160/forcesize" width="120" height="160" class="rounded-5 rounded-top-0 dog-image shadow" alt="<?= escape($dogdata['name']); ?>'s profile Picture">
<h1 class="mb-3">
<?= escape($dogdata['name']); ?>
</h1>
<div class="table-responsive">
<table class="table">
<tbody>
<tr>
<td>Alter:</td>
<td><?= date_diff(date_create($dogdata['birthday']), date_create('now'))->y ?></td>
</tr>
<?php if($dogdata['breed']): ?>
<tr>
<td>Rasse:</td>
<td><?= escape($dogdata['breed']); ?></td>
</tr>
<?php endif; ?>
<?php if($dogdata['kennel_name']): ?>
<tr>
<td>Zuchtname:</td>
<td><?= escape($dogdata['kennel_name']); ?></td>
</tr>
<?php endif; ?>
<?php if($dogdata['size']): ?>
<tr>
<td>Größe:</td>
<td><?= escape($dogdata['size']); ?> cm</td>
</tr>
<?php endif; ?>
<?php if($dogdata['agility_size']): ?>
<tr>
<td>Agility Größe:</td>
<td><?= escape($dogdata['agility_size']); ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if($ismydog===true): ?>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-secondary" hx-get="/dogs/edit/<?= $dogid; ?>" hx-target="#main">
<i class="fas fa-edit"></i>
</button>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="container pb-4" id="sitemain">
<!-- table of all runs of this dog-->
<h2>Alle Ergebnisse</h2>
<div class="accordion" id="accordion-dog-runs">
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
2023
</button>
</h3>
<div id="collapseOne" class="accordion-collapse collapse" data-bs-parent="#accordion-dog-runs">
<div class="accordion-body">
<table class="table">
<thead>
<tr>
<th>Turnier</th>
<th>Lauf</th>
<th class="text-end">VW</th>
<th class="text-end">F</th>
<th class="text-end">ZF</th>
<th class="text-end">Zeit</th>
<th class="text-end">GF</th>
<th class="text-end">m/Sek</th>
<th class="text-end">Bew.</th>
<th class="text-end">Platz</th>
</tr>
</thead>
<tbody>
<?php if(count($results) > 0): ?>
<?php foreach($results as $result) : ?>
<?php
$t = new Tournament();
$run = new Run();
$tname = $t->getField('name',$result['tournament']);
$tdate = $t->getField('date',$result['tournament']);
$rname = $run->getField('name',$result['run']);
$sdata['dates'][] = $tdate;
$sdata['speed'][] = $result['speed'];
$sdata['errors'][] = $result['errors'];
$sdata['refusals'][] = $result['refusals'];
$sdata['time'][] = $result['runtime'];
$sdata['points'][] = $result['points'];
$sdata['ranking'][] = $result['ranking'];
//'Geschwindigkeit', 'Fehler', 'Verweigerungen', 'Zeit', 'Punkte','Platz'
?>
<tr>
<td><a href="/tournaments/event/<?= $result['tournament']; ?>">
<?= $tname; ?>
</a></td>
<td><a href="/runs/overview/<?= $result['run']; ?>">
<?= $rname; ?>
</a></td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['refusals']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['errors']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['timefaults']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['runtime']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['penalties']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['speed']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'':escape($result['rating']); ?>
</td>
<td class="text-end">
<?= $result['disqualified']?'Dis':escape($result['ranking'] . '. Platz'); ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="12">
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle"></i> <?= escape($dogdata['name']); ?> hat noch keine Ergebnisse
</div>
</td>
<?php endif; ?>
</tbody>
</table>
<?php if(count($results) > 0): ?>
<div class="col">
<div id="graph" data-bs-theme="light" class="card bg-light text-black" style="min-height: 400px;"></div>
<script type="text/javascript">
// Initialize the echarts instance based on the prepared dom
var myChart = echarts.init(document.getElementById('graph'));
// Specify the configuration items and data for the chart
var option = {
title: {
text: 'Stacked Line'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['Geschwindigkeit', 'Fehler', 'Verweigerungen', 'Zeit', 'Geschwindigkeit','Punkte','Platz']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: <?= json_encode($sdata['dates']); ?>
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Geschwindigkeit',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['speed']); ?>
},
{
name: 'Fehler',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['errors']); ?>
},
{
name: 'Verweigerungen',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['refusals']); ?>
},
{
name: 'Zeit',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['time']); ?>
},
{
name: 'Punkte',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['points']); ?>
},
{
name: 'Platz',
type: 'line',
stack: 'Total',
data: <?= json_encode($sdata['ranking']); ?>
}
]
};
// Display the chart using the configuration items and data just specified.
myChart.setOption(option);
</script>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,38 +1,56 @@
<div>
<h1>Hund hinzufügen/bearbeiten</h1>
<h1>Hund <?= $dogid?'Bearbeiten':'Hinzufügen'; ?></h1>
<form hx-post="/dogs/edit" hx-target="#response">
<form id="dogeditform" hx-post="/dogs/edit" hx-encoding='multipart/form-data' hx-target="#response" class="row row-cols-2">
<input type="hidden" name="dog_id" value="<?= $dogid; ?>">
<div>
<div class="mb-3 mb-md-4">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
<input type="text" value="<?= $dogdata['name']; ?>" id="name" name="name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Max" required>
<input type="text" value="<?= $dogdata['name']; ?>" id="name" name="name" class="form-control" placeholder="Max" required>
</div>
<div>
<div class="mb-3 mb-md-4">
<label for="kennel_name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Zuchtname</label>
<input type="text" value="<?= $dogdata['kennel_name']; ?>" id="kennel_name" name="kennel_name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Zuchtname">
<input type="text" value="<?= $dogdata['kennel_name']; ?>" id="kennel_name" name="kennel_name" class="form-control" placeholder="Zuchtname">
</div>
<div>
<div class="mb-3 mb-md-4">
<label for="dog_breed" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Rasse</label>
<input type="text" value="<?= $dogdata['breed']; ?>" id="dog_breed" name="dog_breed" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Flowbite">
<input type="text" value="<?= $dogdata['breed']; ?>" id="dog_breed" name="dog_breed" class="form-control" placeholder="Flowbite">
</div>
<div>
<div class="mb-3 mb-md-4">
<label for="dog_size" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Größe des Hundes (in cm)</label>
<input type="number" value="<?= $dogdata['size']; ?>" id="dog_size" name="dog_size" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="">
<input type="number" value="<?= $dogdata['size']; ?>" id="dog_size" name="dog_size" class="form-control" placeholder="">
</div>
<div>
<div class="mb-3 mb-md-4">
<label for="dog_birthday" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Geburtstag des Hundes</label>
<input type="date" value="<?= $dogdata['birthday']; ?>" id="dog_birthday" name="dog_birthday" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="" required>
<input type="date" value="<?= $dogdata['birthday']; ?>" id="dog_birthday" name="dog_birthday" class="form-control" placeholder="" required>
</div>
<div>
<div class="mb-3 mb-md-4">
<label for="agi_active" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Hund ist noch aktiv im Sport?</label>
<select id="agi_active" name="agi_active" class="form-control">
<option value="1" <?= $dogdata['active']=='1'?'selected':''; ?>>Ja</option>
<option value="0" <?= $dogdata['active']=='0'?'selected':''; ?>>Nein</option>
</select>
</div>
<div class="mb-3 mb-md-4">
<label for="agi_height_category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Agility Größenklasse</label>
<select id="agi_height_category" name="agi_height_category" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<select id="agi_height_category" name="agi_height_category" class="form-control">
<option value="S" <?= $dogdata['agility_size']=='S'?'selected':''; ?>>S</option>
<option value="M" <?= $dogdata['agility_size']=='M'?'selected':''; ?>>M</option>
<option value="I" <?= $dogdata['agility_size']=='I'?'selected':''; ?>>I</option>
<option value="L" <?= $dogdata['agility_size']=='L'?'selected':''; ?>>L</option>
</select>
</div>
<div class="mb-3 mb-md-4">
<label for="photo" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Photo</label>
<input type="file" accept="image/png, image/jpeg, image/gif" id="photo" name="photo">
</div>
<button type="submit" name="submit" value="true" class="btn btn-primary">Submit</button>
</form>
<progress id='progress' value='0' max='100'></progress>
<div id="response"></div>
</div>
<script>
htmx.on('#dogeditform', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
});
</script>

View File

@@ -1,42 +1,26 @@
<h1">Meine Hunde</h1>
<h1>Meine Hunde</h1>
<figure>
<table>
<table class="table">
<thead>
<tr>
<?php foreach (array_keys($doggos[0]) as $key) : ?>
<th>
<?= $key ?>
</th>
<?php endforeach; ?>
<th>
Bearbeiten
</th>
<th>
Löschen
</th>
<th>Foto</th>
<th>Name</th>
<th>Geburstag</th>
<th>Aktiv</th>
<th>Bearbeiten</th>
<th>Löschen</th>
</tr>
</thead>
<tbody>
<?php foreach ($doggos as $dog) : ?>
<tr>
<?php foreach (array_keys($dog) as $key) : ?>
<td>
<?= $dog[$key] ?>
</td>
<?php endforeach; ?>
<td>
<button hx-get="/dogs/edit/<?= $dog['id'] ?>" hx-push-url="/dogs/edit/<?= $dog['id'] ?>" hx-target="#main" >Bearbeiten</button>
</td>
<td>
<button hx-get="/dogs/delete/<?= $dog['id'] ?>" hx-target="#main" >Löschen</button>
</td>
<td><img src="<?= $dog['photo'] ?>/50x50/forcesize"></td>
<td><?= escape($dog['name']); ?></td>
<td><?= escape($dog['birthday']); ?></td>
<td><?= escape($dog['active'])?'Ja':'Nein'; ?></td>
<td><button hx-get="/dogs/edit/<?= $dog['id'] ?>" hx-push-url="/dogs/edit/<?= $dog['id'] ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-edit"></i></button></td>
<td><button hx-get="/dogs/delete/<?= $dog['id'] ?>" hx-target="#main" hx-confirm="Bist du sicher, dass du <?= escape($dog['name']); ?> löschen willst" class="btn btn-danger"><i class="fas fa-trash"></i></button></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -2,15 +2,17 @@
class Err extends Page {
function notfound($params)
function notfound($params=false)
{
$this->set("page",$params[0]);
$this->set('template', "notfound.html");
}
function notallowed()
{
return json_encode(['error'=>'not allowed']);
$this->set("loggedin",(isset($_SESSION['user']) && $_SESSION['user'] !== false));
$this->set('template', "notallowed.html");
}
}

View File

@@ -0,0 +1,13 @@
<div class="alert alert-danger animate__animated animate__headShake" role="alert">
<h4 class="alert-heading">Not allowed</h4>
Zugriff nicht erlaubt
</div>
<div class="row justify-content-center align-items-center g-2">
<div class="col"></div>
<div class="col">
<?php if(!$loggedin) : ?>
<?= partial('../../pages/login/login.html'); ?>
<?php endif; ?></div>
<div class="col"></div>
</div>

View File

@@ -1,5 +1,5 @@
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
<p class="font-bold">404 - Not found</p>
<p>I have no memory of <?= escape($page) ?></p>
<p>I have no memory of <span class="badge"><?= escape($page) ?></span></p>
<img src="https://pictshare.net/4s9gi3.gif" alt="Gandalf: I have no memory of this place"/>
</div>

View File

@@ -1,9 +0,0 @@
<?php
class Features extends Page {
function index() {
$this->set('template', 'features.html');
}
}

View File

@@ -1,62 +0,0 @@
<section class="text-gray-600 body-font">
<div class="container px-5 py-24 mx-auto">
<h1 class="sm:text-3xl text-2xl font-medium title-font text-center text-gray-900 mb-20">Raw Denim Heirloom Man Braid
<br class="hidden sm:block">Selfies Wayfarers
</h1>
<div class="flex flex-wrap sm:-m-4 -mx-4 -mb-10 -mt-4 md:space-y-0 space-y-6">
<div class="p-4 md:w-1/3 flex">
<div class="w-12 h-12 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 mb-4 flex-shrink-0">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-6 h-6" viewBox="0 0 24 24">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
</svg>
</div>
<div class="flex-grow pl-6">
<h2 class="text-gray-900 text-lg title-font font-medium mb-2">Shooting Stars</h2>
<p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine, ramps microdosing banh mi pug VHS try-hard ugh iceland kickstarter tumblr live-edge tilde.</p>
<a class="mt-3 text-indigo-500 inline-flex items-center">Learn More
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
<div class="p-4 md:w-1/3 flex">
<div class="w-12 h-12 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 mb-4 flex-shrink-0">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-6 h-6" viewBox="0 0 24 24">
<circle cx="6" cy="6" r="3"></circle>
<circle cx="6" cy="18" r="3"></circle>
<path d="M20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12"></path>
</svg>
</div>
<div class="flex-grow pl-6">
<h2 class="text-gray-900 text-lg title-font font-medium mb-2">The Catalyzer</h2>
<p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine, ramps microdosing banh mi pug VHS try-hard ugh iceland kickstarter tumblr live-edge tilde.</p>
<a class="mt-3 text-indigo-500 inline-flex items-center">Learn More
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
<div class="p-4 md:w-1/3 flex">
<div class="w-12 h-12 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 mb-4 flex-shrink-0">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-6 h-6" viewBox="0 0 24 24">
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
<div class="flex-grow pl-6">
<h2 class="text-gray-900 text-lg title-font font-medium mb-2">Neptune</h2>
<p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine, ramps microdosing banh mi pug VHS try-hard ugh iceland kickstarter tumblr live-edge tilde.</p>
<a class="mt-3 text-indigo-500 inline-flex items-center">Learn More
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
</div>
</div>
</section>

View File

@@ -13,8 +13,37 @@ class Home extends Page
{
$u = new User();
$this->set('userdata', $u->getAll());
$this->set('template', "home.html");
//return $this->renderPagecontent();
if(!$_SESSION['user'])
{
$this->set('template', 'home-nouser.html');
return;
}
else {
$dogs = $_SESSION['user']->data['dogs'];
$doggos = [];
foreach($dogs as $key => $dogid)
{
$dog = new Dog();
try{
$dog->load($dogid);
}
catch(Exception $e)
{
error_log("Dog $dogid not found. Deleting from user");
unset($_SESSION['user']->data['dogs'][$key]);
$_SESSION['user']->save();
continue;
}
if($dog->data)
$doggos[] = array_merge($dog->data,['id'=>$dogid]);
$this->set('doggos',$doggos);
}
//var_dump($doggos);
$this->set('template', "home.html");
}
}
function maySeeThisPage(){return true;}

View File

@@ -0,0 +1,56 @@
<div class="text-center mt-3">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">Dogstats - der Agilityhelfer</h1>
<p class="mt-6 text-lg leading-8 text-gray-600">Steigere deine Leistung mit Dogstats.</p>
</div>
<h1>Admin stuff</h1>
<div class="container text-center">
<div class="row">
<?php foreach ($userdata as $user) : ?>
<div class="m-3 col">
<div class="card">
<img src="<?= $user['photo']?:'https://pictshare.net/pj7vzx.jpg' ?>" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><?= escape($user['firstname'].' '.$user['lastname']) ?></h5>
<p class="card-text"><?= escape($user['email']) ?></p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<h6>uuid</h6>
<span class="badge rounded-pill text-bg-secondary"><?= escape($user['uuid']); ?></span>
</li>
<li class="list-group-item">
<h6>Registered</h6>
<?= escape($user['registered']); ?>
</li>
<li class="list-group-item">
<h6>last_login</h6>
<?= escape($user['last_login']); ?>
</li>
<li class="list-group-item">
<h6>Active</h6>
<?= $user['active']?'yes':'no'; ?>
</li>
<li class="list-group-item">
<h6>Dogs</h6>
<?php foreach($user['dogs'] as $dog): ?>
<a href="/dogs/overview/<?= $dog ?>" class="btn btn-secondary"><?= escape((new Dog())->getField('name',$dog)); ?></a>
<?php endforeach; ?>
</li>
</ul>
<div class="card-body">
<button class="btn btn-primary" name="email" value="<?= $user['email'] ?>" hx-post="/admin/loginas/">Login</button>
<button class="btn btn-primary"name="email" value="<?= $user['email'] ?>" hx-post="/admin/edituser/" hx-target="#main">Edit</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
<div class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Currently logged in as:
<?= $_SESSION['userid']?:'nobody' ?>
</div>
</div>

View File

@@ -1,58 +1,30 @@
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
<div class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Currently logged in as:
<?= $_SESSION['userid']?:'nobody' ?>
</div>
</div>
<div class="text-center">
<div class="text-center mt-3">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">Dogstats - der Agilityhelfer</h1>
<p class="mt-6 text-lg leading-8 text-gray-600">Anim aute id magna aliqua ad ad non deserunt sunt.
Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.</p>
<div class="mt-10 flex items-center justify-center gap-x-6">
<a href="#" class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get
started</a>
<a href="#" class="text-sm font-semibold leading-6 text-gray-900">Learn more <span aria-hidden="true"></span></a>
</div>
<p class="mt-6 text-lg leading-8 text-gray-600">Steigere deine Leistung mit Dogstats.</p>
</div>
<h1>Admin stuff</h1>
<figure>
<table role="grid">
<thead>
<tr>
<?php foreach (array_keys($userdata[0]) as $key) : ?>
<th>
<?= $key ?>
</th>
<div>
<h3 class="text-center">Schnellauswahl</h3>
<div class="d-sm-flex justify-content-sm-center">
<div class="m-sm-2 border p-3 rounded-2 shadow">
<a href="/tournaments/add" class="btn btn-secondary mb-2">Turnier hinzufügen</a>
</div>
<?php if ($doggos) : ?>
<?php foreach ($doggos as $dog) : ?>
<?php if (!$dog['active']) continue; ?>
<div class="m-sm-2 border p-3 rounded-2 shadow">
<div class="d-flex mb-2 align-items-center">
<img src="<?= $dog['photo'] ?>/100x100/forcesize">
<a href="/dogs/overview/<?= $dog['id'] ?>" class="ms-3 link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"><?= escape($dog['name']); ?></a>
</div>
<a href="/smart#<?= escape($dog['name']); ?>" class="btn btn-secondary mb-2 d-block">Turnier importieren</a>
<a href="#" class="btn btn-secondary mb-2 d-block">Training hinzufügen</a>
</div>
<?php endforeach; ?>
<th>
Login
</th>
<th>
Edit
</th>
</tr>
</thead>
<tbody>
<?php foreach ($userdata as $user) : ?>
<tr>
<?php foreach (array_keys($user) as $key) : ?>
<td class="px-6 py-4 text-sm text-gray-500">
<?= is_array($user[$key])?json_encode($user[$key]):$user[$key] ?>
</td>
<?php endforeach; ?>
<td>
<button name="email" value="<?= $user['email'] ?>" hx-post="/admin/loginas/">Login</button>
</td>
<td>
<button name="email" value="<?= $user['email'] ?>" hx-post="/admin/edituser/" hx-target="#main">Edit</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</figure>
<?php else : ?>
<div class="">
<a href="/dogs/add" class="btn btn-secondary mb-2 d-block">Hund hinzufügen</a>
</div>
<?php endif; ?>
</div>
</div>

View File

@@ -5,7 +5,9 @@ class Login extends Page {
function setMenu()
{
if($_SESSION['user'])
{
$this->menu_text = $_SESSION['userid'];
}
else
$this->menu_text = 'Login';
$this->menu_image = 'far fa-user';
@@ -16,6 +18,7 @@ class Login extends Page {
{
if($_SESSION['user'])
{
$this->addSubmenuItem('Profil', '/profile', 'fas fa-user');
$this->addSubmenuItem('Settings', '/settings', 'fas fa-cog');
$this->addSubmenuItem('Logout', '/login/logout', 'fas fa-sign-out-alt', 'bg-red-500');
}
@@ -25,7 +28,7 @@ class Login extends Page {
function index()
{
$this->set('hello','world');
$this->set('template', 'login.html');
//return print_r($_REQUEST, true);
}
@@ -41,17 +44,78 @@ class Login extends Page {
function logout()
{
//delete cookie
setcookie('token', '', time() - 3600, "/");
session_destroy();
$this->redirect('/');
}
function validate()
{
$email = $_REQUEST['email'];
$password = $_REQUEST['password'];
$email = trim($_REQUEST['email']);
$password = trim($_REQUEST['password']);
$remember = $_REQUEST['remember'];
return print_r(['email'=>$email,'password'=>$password,'remember'=>$remember], true);
$error = false;
$u = new User();
if(!$email || !$password)
$error = 'Bitte gib deine E-Mail-Adresse und dein Passwort ein';
else if(!filter_var($email, FILTER_VALIDATE_EMAIL))
$error = 'Bitte gib eine gültige E-Mail-Adresse ein';
else if(!$u->exists($email))
$error = 'Benutzer nicht gefunden. Schon registriert?';
else {
try{
$u->load($email);
}
catch(Exception $e){
$error = $e->getMessage();
}
if(!password_verify($password, $u->data['password']))
$error = 'E-Mail-Adresse oder Passwort falsch';
else if($u->data['active'] == 0)
$error = 'Dein Account ist noch nicht aktiviert';
else
{
//if $remmeber is true, create and set cookie so the user will be automatically logged in next time
if($remember)
{
//check if user has a valid cookie
if(isset($_COOKIE['token']) && $u->token == $_COOKIE['token'])
{
$token =$u->token;
setcookie('token', $token, time() + (86400 * 30), "/");
}
else
{
//if no, create a new token
$token = uuid4();
$u->token = $token;
$u->save();
setcookie('token', $token, time() + (86400 * 30), "/");
}
}
$u->login();
if($_SERVER['HTTP_HX_CURRENT_URL'] && !endsWith($_SERVER['HTTP_HX_CURRENT_URL'],'/login'))
$this->redirect($_SERVER['HTTP_HX_CURRENT_URL']);
else
$this->redirect('/');
}
}
if($error)
{
$this->set('template', '/templates/partials/error.html');
$this->set('errorTitle', 'Error');
$this->set('errorMessage', $error);
}
//return print_r(['email'=>$email,'password'=>$password,'remember'=>$remember], true);
}
}

View File

@@ -1,39 +1,20 @@
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto lg:py-0">
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Sign in to your account
</h1>
<div id="response" class="text-gray-900 dark:text-white">
<?= $hello ?>
</div>
<form class="space-y-4 md:space-y-6" hx-post="/login/validate" hx-target="#response">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" required="">
<div id="response" class="text-gray-900 dark:text-white"></div>
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input name="email" type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input name="password" type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="flex items-center justify-between">
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="remember" name="remember" aria-describedby="remember" type="checkbox" value="true" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800">
<div class="form-group form-check">
<input name="remember" type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Remember me</label>
</div>
<div class="ml-3 text-sm">
<label for="remember" class="text-gray-500 dark:text-gray-300">Remember me</label>
</div>
</div>
<a href="#" class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500 dark:text-gray-400">Forgot password?</a>
</div>
<button type="submit" class="bg-sky-500 hover:bg-sky-700 p-2 rounded">
Sign in
</button>
<button type="submit" class="btn btn-primary">Submit</button>
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
Dont have an account yet? <a href="#" hx-get="/register" hx-push-url="/register" hx-target="#main" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
Don't have an account yet? <a href="#" hx-get="/register" hx-push-url="/register" hx-target="#main" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
</p>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,87 @@
<?php
class Profile extends Page {
function index()
{
$this->set('user', $_SESSION['user']->data);
$this->set('template', 'profile.html');
}
function edit()
{
if($_REQUEST['submit']=='true')
{
$error = false;
$_SESSION['user']->load();
$_SESSION['user']->data['firstname'] = trim($_REQUEST['firstname']);
$_SESSION['user']->data['lastname'] = trim($_REQUEST['lastname']);
//$_SESSION['user']->data['email'] = $_REQUEST['email'];
$_SESSION['user']->data['birthday'] = $_REQUEST['birthday'];
$_SESSION['user']->data['club'] = trim($_REQUEST['club']);
//$_SESSION['user']->data['timezone'] = $_REQUEST['timezone'];
if(!strtotime($_SESSION['user']->data['birthday']))
$error = 'Das Geburstdatum ist ungültig. Bitte die Eingabe prüfen';
$newphoto = false;
if($_FILES['photo'])
{
$photo = $_FILES['photo'];
$photo_name = $photo['name'];
$photo_tmp_name = $photo['tmp_name'];
$photo_size = $photo['size'];
$photo_error = $photo['error'];
$photo_type = $photo['type'];
$allowed = ['jpg','jpeg','png','gif'];
$photo_ext = strtolower(end(explode('.', $photo_name)));
if(in_array($photo_ext, $allowed))
{
if($photo_error === 0)
{
if($photo_size < 10000000)
{
$answer = pictshareUploadImage($photo_tmp_name);
if($answer['status']=='ok' && in_array($answer['filetype'],['jpeg','png','gif']))
$newphoto = $answer['url'];
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Fehler beim CDN Upload: '.json_encode($answer,true)]);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Die Datei ist zu groß. Bitte eine kleinere Datei hochladen']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Beim Upload der Datei ist ein Fehler aufgetreten']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieser Dateityp ist nicht erlaubt. Bitte nur jpg, jpeg oder png Dateien hochladen']);
}
if($newphoto)
$_SESSION['user']->data['photo'] = $newphoto;
try{
$_SESSION['user']->save();
}
catch(Exception $e)
{
return partial('error.html', ['errorMessage' => 'Fehler beim Speichern des Profils: '.$e->getMessage()]);
}
$this->redirect('/profile');
}
$this->set('user', $_SESSION['user']->data);
$this->set('template', 'edit_profile.html');
}
function maySeeThisPage() {
if($_SESSION['user']) //wenn eingeloggt, kein problem
return true;
else return false;
}
}

View File

@@ -0,0 +1,39 @@
<div>
<h1>Mein Profil bearbeiten</h1>
<form id="dogeditform" hx-post="/profile/edit" hx-encoding='multipart/form-data' hx-target="#response" class="row row-cols-2">
<div>
<label for="firstname">Vorname</label>
<input type="text" value="<?= $user['firstname']; ?>" id="firstname" name="firstname" class="form-control" placeholder="Max">
</div>
<div>
<label for="lastname">Nachname</label>
<input type="text" value="<?= $user['lastname']; ?>" id="lastname" name="lastname" class="form-control" placeholder="Mustermann">
</div>
<div>
<label for="birthday">Geburtstag</label>
<input type="date" value="<?= $user['birthday']; ?>" id="birthday" name="birthday" class="form-control" placeholder="">
</div>
<div>
<label for="email">E-Mail</label>
<input type="text" value="<?= $user['email']; ?>" id="email" name="email" class="form-control" placeholder="max@max.com" disabled>
</div>
<div>
<label for="club">Verein</label>
<input type="text" value="<?= $user['club']; ?>" id="club" name="club" class="form-control" placeholder="Vereinsname">
</div>
<div>
<label for="photo">Photo</label>
<input type="file" accept="image/png, image/jpeg, image/gif" id="photo" name="photo">
</div>
<button type="submit" name="submit" value="true" class="btn btn-primary">Speichern</button>
</form>
<progress id='progress' value='0' max='100'></progress>
<div id="response"></div>
</div>
<script>
htmx.on('#dogeditform', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
});
</script>

View File

@@ -0,0 +1,29 @@
<!-- FILEPATH: /home/chris/git/dogstats/web/pages/dogs/dog.html -->
<div class="container">
<h1>Profil</h1>
<div class="row">
<div class="col-3">
<img src="<?= $user['photo']?:'https://pictshare.net/pj7vzx.jpg' ?>/300x300/forcesize" class="card-img-top" alt="<?= escape($user['name']); ?>'s profile Picture">
</div>
<div class="col-9">
<h3 class="card-title"><?= escape($user['firstname'].' '.$user['lastname']); ?></h3>
<div>
<?php if($user['birthday']): ?><p>Geburstag: <?= $user['birthday'].' ('.date_diff(date_create($user['birthday']), date_create('now'))->y.')' ?></p> <?php endif; ?>
<?php if($user['email']): ?><p>Email: <?= escape($user['email']); ?></p> <?php endif; ?>
<?php if($user['club']): ?><p>Verein: <?= escape($user['club']); ?></p> <?php endif; ?>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-secondary" hx-get="/profile/edit" hx-target="#main">
<i class="fas fa-edit"></i>
</button>
</div>
</div>
</div>
<div class="col-8" id="sitemain">
</div>
</div>

View File

@@ -47,6 +47,7 @@ class Register extends Page {
try
{
$u->save();
$this->redirect('/register/success');
}
catch(Exception $e)
{
@@ -55,7 +56,7 @@ class Register extends Page {
$this->set('errorMessage', $e->getMessage());
return;
}
//$this->redirect('/register/success');
return;
}

View File

@@ -1,28 +1,19 @@
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto lg:py-0">
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Register
</h1>
<div id="response" class="text-gray-900 dark:text-white"></div>
<form class="space-y-4 md:space-y-6" hx-post="/register/validate" hx-target="#response">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" required="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<div>
<label for="password2" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Repeat Password</label>
<input type="password" name="password2" id="password2" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<h1>Registrieren</h1>
<button type="submit" class="bg-sky-500 hover:bg-sky-700 p-2 rounded">
Account erstellen
</button>
<form class="space-y-4 md:space-y-6" hx-post="/register/validate" hx-target="#response">
<div id="response"></div>
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input name="email" type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Passwort</label>
<input name="password" type="password" class="form-control" id="exampleInputPassword1" placeholder="Passwort">
</div>
<div class="form-group">
<label for="exampleInputPassword2">Passwort wiederholen</label>
<input name="password2" type="password" class="form-control" id="exampleInputPassword2" placeholder="Passwort Wiederholen">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,247 @@
<?php
class Runs extends Page {
function setMenu()
{
// $this->menu_text = 'Läufe';
// $this->menu_image = 'fas fa-running';
// $this->menu_priority = 3;
}
function overview()
{
$run = new Run();
$rid = $this->params[0];
if(!$run->exists($rid))
return partial('error.html', ['errorMessage' => 'Dieser Lauf existiert nicht']);
$run->load($rid);
$t = new Tournament();
$t->load($run->data['tournament']);
$res = new Result();
$results = $res->getDataOfRun($rid);
$this->set('admin',$t->amIAdmin());
$this->set('tournament', $t->data);
$this->set('tournament_id', $run->data['tournament']);
$this->set('results', $results);
$this->set('run', $run->data);
$this->set('run_id', $rid);
$this->set('template', 'run.html');
}
function addresults()
{
$rid = $this->params[0];
$run = new Run();
if(!$run->exists($rid))
return partial('error.html', ['errorMessage' => 'Dieser Lauf existiert nicht']);
$run->load($rid);
$t = new Tournament();
$tid = $run->data['tournament'];
$t->load($tid);
if(!$t->amIAdmin() && !$t->isMyEvent($tid))
return partial('error.html', ['errorMessage' => 'Du bist in diesem Turnier nicht angemeldet']);
else
{
$this->set('tournament', $t->data);
$this->set('tournament_id', $tid);
$this->set('run', $run->data);
$this->set('run_id', $rid);
$this->set('template', 'edit_result.html');
}
}
function validateresults()
{
$rid = $_REQUEST['run_id'];
$run = new Run();
if(!$run->exists($rid))
return partial('error.html', ['errorMessage' => 'Dieser Lauf existiert nicht']);
$run->load($rid);
$t = new Tournament();
$tid = $run->data['tournament'];
$t->load($tid);
$res = new Result();
if($_REQUEST['result_id'])
{
if(!$res->exists($_REQUEST['result_id']))
return partial('error.html', ['errorMessage' => 'Dieses Ergebnis existiert nicht']);
$res->load($_REQUEST['result_id']);
if(!$res->data['run']==$rid)
return partial('error.html', ['errorMessage' => 'Dieses Ergebnis gehört nicht zu diesem Lauf']);
}
else
$res->id = gen_ulid();
if(!$t->amIAdmin() && !$t->isMyEvent($tid))
return partial('error.html', ['errorMessage' => 'Du bist in diesem Turnier nicht angemeldet']);
else
{
$dogid = $_REQUEST['dog'];
if(!$dogid || $dogid=='false')
return partial('error.html', ['errorMessage' => 'Du musst einen Hund auswählen']);
$dog = new Dog();
if(!$dog->exists($dogid))
return partial('error.html', ['errorMessage' => 'Dieser Hund existiert nicht']);
else if(!$dog->isMyDog($dogid))
return partial('error.html', ['errorMessage' => 'Dieser Hund gehört nicht dir']);
$res->dog = $dogid;
$res->disqualified = $_REQUEST['disqualified'];
$res->refusals = $_REQUEST['refusals'];
$res->timefaults = $_REQUEST['time_faults'];
$res->runtime = $_REQUEST['time'];
$res->penalties = $_REQUEST['penalties'];
$res->speed = $_REQUEST['time_speed'];
$res->rating = $_REQUEST['bewertung'];
$res->points = $_REQUEST['points'];
$res->ranking = $_REQUEST['rank'];
$res->public = $_REQUEST['public'];
$res->tournament = $tid;
$res->run = $rid;
$res->user = $_SESSION['user']->id;
if($_FILES['photos'])
{
foreach($_FILES['photos']['tmp_name'] as $key => $tmp_name)
{
try{
$url = pictShareFormValidateAndUpload($_FILES['photos'],$key);
$res->data['photos'][] = $url;
}
catch(Exception $e)
{
return partial('error.html', ['errorMessage' => 'Fehler beim Upload des Fotos: '.$e->getMessage()]);
}
}
//filter out unique values so we don't have double entries
$res->data['photos'] = array_unique($res->data['photos']);
}
try{
$resid = $res->save();
if(!in_array($resid, $run->data['results']))
{
$run->data['results'][] = $resid;
$run->save();
}
}
catch(Exception $e)
{
return partial('error.html', ['errorMessage' => 'Fehler beim Speichern des Ergebnisses: '.$e->getMessage()]);
}
$this->redirect('/runs/overview/'.$rid);
}
}
function add()
{
$tournament = $this->params[0];
$t = new Tournament();
if(!$t->exists($tournament))
return partial('error.html', ['errorMessage' => 'Dieses Turnier existiert nicht']);
$t->load($tournament);
if(!$t->amIAdmin($tournament))
return partial('error.html', ['errorMessage' => 'Du bist kein Admin dieses Turniers']);
else
{
$this->set('tournament', $t->data);
$this->set('tournament_id', $tournament);
$this->set('template', 'edit_run.html');
}
}
function edit()
{
$rid = $this->params[0];
$r = new Run();
$t = new Tournament();
if(!$r->exists($rid))
return partial('error.html', ['errorMessage' => 'Dieser Run existiert nicht']);
$r->load($rid);
$tournament = $r->data['tournament'];
$t->load($tournament);
if(!$t->amIAdmin($tournament))
return partial('error.html', ['errorMessage' => 'Du bist kein Admin dieses Turniers']);
else
{
$this->set('tournament', $t->data);
$this->set('tournament_id', $tournament);
$this->set('run_id',$rid);
$this->set('run',$r->data);
$this->set('template', 'edit_run.html');
}
}
function validate()
{
if($_REQUEST['submit'])
{
$t = new Tournament();
if(!$t->exists($_REQUEST['tournament_id']))
return partial('error.html', ['errorMessage' => 'Dieses Turnier existiert nicht']);
$t->load($_REQUEST['tournament_id']);
if(!$t->amIAdmin($_REQUEST['tournament_id']))
return partial('error.html', ['errorMessage' => 'Du bist kein Admin dieses Turniers']);
$run = new Run();
if($_REQUEST['run_id'])
{
if(!$run->exists($_REQUEST['run_id']))
return partial('error.html', ['errorMessage' => 'Dieser Lauf existiert nicht']);
$run->load($_REQUEST['run_id']);
if(!$run->data['tournament']==$t->id)
return partial('error.html', ['errorMessage' => 'Dieser Lauf gehört nicht zu diesem Turnier']);
}
else
$run->id = gen_ulid();
$run->data['tournament'] = $_REQUEST['tournament_id'];
$run->data['name'] = trim($_REQUEST['name']);
$run->data['category'] = $_REQUEST['category'];
$run->data['length'] = $_REQUEST['length'];
$run->data['time_standard'] = $_REQUEST['time_standard'];
$run->data['time_max'] = $_REQUEST['time_max'];
$run->data['referee'] = $_REQUEST['referee'];
try{
$runid = $run->save();
}
catch(Exception $e)
{
return partial('error.html', ['errorMessage' => 'Fehler beim Speichern des Laufs: '.$e->getMessage()]);
}
if(!in_array($runid, $t->data['runs']))
{
$t->data['runs'][] = $runid;
$t->save();
}
if($_REQUEST['submit']=='forward')
{
$this->redirect('/runs/addresults/'.$runid);
}
else
$this->redirect('/tournaments/event/'.$t->id);
}
else return partial('error.html', ['errorMessage' => 'Fehler beim Speichern des Laufs']);
}
function maySeeThisPage() {
if($_SESSION['user']) //wenn eingeloggt, kein problem
return true;
else return false;
}
}

View File

@@ -0,0 +1,116 @@
<div class="container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="/tournaments/event/<?= $tournament_id; ?>" hx-push-url="/tournaments/event/<?= $tournament_id; ?>" hx-get="/tournaments/event/<?= $tournament_id; ?>" hx-target="#main">
<i class="fas fa-calendar-star"></i>
<?= escape($tournament['name']); ?>
</a>
</li>
<li class="breadcrumb-item">
<a href="/runs/overview/<?= $run_id; ?>" hx-push-url="/runs/overview/<?= $run_id; ?>" hx-get="/runs/overview/<?= $run_id; ?>" hx-target="#main">
<i class="far fa-running"></i>
<?= escape($run['name']); ?>
</a>
</li>
<li class="breadcrumb-item active" aria-current="page">
<i class="fas fa-edit"></i>
<?= $result_id?'Bearbeiten':'Eintragen'; ?>
</li>
</ol>
</nav>
<h2>Ergebnis
<?= $result_id?'Bearbeiten':'Eintragen'; ?>
</h2>
<form id="resulteditform" hx-post="/runs/validateresults" hx-encoding='multipart/form-data' hx-target="#response" hx-disabled-elt="#submit">
<div class="dropzone-previews"></div>
<input type="hidden" name="result_id" value="<?= $result_id; ?>">
<input type="hidden" name="run_id" value="<?= $run_id; ?>">
<div>
<label for="dog">Hund</label>
<select id="dog" name="dog" class="form-control">
<option value="false" <?=!$result['dog']?'selected':''; ?>>-- Bitte auswählen --</option>
<?php foreach($_SESSION['user']->data['dogs'] as $dogid) : ?>
<?php
$dog = new Dog();
$dog->load($dogid);
$dd = $dog->data;
?>
<option value="<?= $dogid ?>" <?=$result['dog']==$dogid ?'selected':''; ?>>
<?= escape($dd['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-check form-switch">
<label class="form-check-label" for="disqualified">Disqualifiziert</label>
<input class="form-control form-check-input" type="checkbox" id="disqualified" name="disqualified" value="1" <?=$result['disqualified']=='1' ?'checked':''; ?>>
</div>
<div>
<label for="refusals">Verweigerungen</label>
<input type="number" value="<?= $result['refusals']; ?>" id="refusals" name="refusals" class="form-control">
</div>
<div>
<label for="faults">Fehler</label>
<input type="number" value="<?= $result['faults']; ?>" id="faults" name="faults" class="form-control">
</div>
<div>
<label for="time_faults">Zeitfehler</label>
<input type="number" step="0.01" value="<?= $result['time_faults']; ?>" id="time_faults" name="time_faults" class="form-control">
</div>
<div>
<label for="time">Zeit</label>
<input type="number" step="0.01" value="<?= $result['time']; ?>" id="time" name="time" class="form-control">
</div>
<div>
<label for="penalties">Gesamtfehler</label>
<input type="number" step="0.01" value="<?= $result['penalties']; ?>" id="penalties" name="penalties" class="form-control">
</div>
<div>
<label for="time_speed">m/Sek</label>
<input type="number" step="0.01" value="<?= $result['time_speed']; ?>" id="time_speed" name="time_speed" class="form-control">
</div>
<div>
<label for="bewertung">Bewertung</label>
<select id="bewertung" name="bewertung" class="form-control">
<option value="V" <?=$result['bewertung']=='V' ?'selected':''; ?>>V</option>
<option value="SG" <?=$result['bewertung']=='SG' ?'selected':''; ?>>SG</option>
<option value="G" <?=$result['bewertung']=='G' ?'selected':''; ?>>G</option>
<option Galue="B" <?=$result['bewertung']=='B' ?'selected':''; ?>>B</option>
</select>
</div>
<div>
<label for="points">Punkte</label>
<input type="number" value="<?= $result['points']; ?>" id="points" name="points" class="form-control">
</div>
<div>
<label for="rank">Platz</label>
<input type="number" value="<?= $result['rank']; ?>" id="rank" name="rank" class="form-control">
</div>
<div>
<label for="uploads">Uploads</label>
<input type="file" accept="image/png, image/jpeg, image/gif" id="uploads" class="form-control" name="photos[]" multiple>
</div>
<div>
<label for="video-url">Video Url</label>
<input type="text" value="<?= $result['video-url']; ?>" class="form-control" id="video-url" aria-describedby="basic-addon3" placeholder="https://www.youtube.com/...">
</div>
<div>
<label for="memo">Memo</label>
<textarea value="<?= $result['memo']; ?>" id="memo" name="memo" class="form-control" placeholder="Deine Gedankengänge"></textarea>
</div>
<div class="form-check form-switch">
<label class="form-check-label" for="public">Daten im Lauf veröffentlichen</label>
<input class="form-control form-check-input" type="checkbox" id="public" name="public" value="1" <?=$result['public']!='0' ?'checked':''; ?>>
</div>
<button type="submit" id="submit" name="submit" value="true" class="btn btn-primary">Speichern</button>
<progress id='progress' value='0' max='100'></progress>
<div id="response"></div>
</form>
</div>
<script>
htmx.on('#resulteditform', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
});
</script>

View File

@@ -0,0 +1,48 @@
<!-- das hier sollte bei einem Turnier mehrfach hinzugefügt werden können. Auswahl aus Agility & Jumping -->
<div>
<h1>Lauf <?= $run_id?'Bearbeiten':'Hinzufügen'; ?></h1>
<form id="tournamenteditform" hx-post="/runs/validate" hx-encoding='multipart/form-data' hx-target="#response">
<input type="hidden" name="tournament_id" value="<?= $tournament_id; ?>">
<input type="hidden" name="run_id" value="<?= $run_id; ?>">
<h2>Parcourdetails</h2>
<div class="mb-3 mb-md-4">
<label for="name">Bezeichnung</label>
<input type="text" value="<?= $run['name']; ?>" id="name" name="name" class="form-control" placeholder="zb: Oldies Jumping" required>
</div>
<div class="mb-3 mb-md-4">
<label for="category">Parcourtyp</label>
<select id="category" name="category" class="form-control">
<option value="Agility" <?= $run['category']=='Agility'?'selected':''; ?>>Agility</option>
<option value="Jumping" <?= $run['category']=='Jumping'?'selected':''; ?>>Jumping</option>
</select>
</div>
<div class="mb-3 mb-md-4">
<label for="length">Parcourlänge (in m)</label>
<input type="number" min="1" max="1000" value="<?= $run['length']; ?>" id="length" name="length" class="form-control" placeholder="180">
</div>
<div class="mb-3 mb-md-4">
<label for="time_standard">Normzeit</label>
<input type="number" value="<?= $run['time_standard']?:0; ?>" id="time_standard" name="time_standard" class="form-control" placeholder="50.000">
</div>
<div class="mb-3 mb-md-4">
<label for="time_max">Maxzeit</label>
<input type="number" value="<?= $run['time_max']?:0; ?>" id="time_max" name="time_max" class="form-control" placeholder="100.000">
</div>
<div class="mb-3 mb-md-4">
<label for="referee">Richter</label>
<input type="text" value="<?= $run['referee']; ?>" id="referee" name="referee" class="form-control" placeholder="Franz Ferdinand" class="form-control">
</div>
<button type="submit" name="submit" value="true" class="btn btn-primary">Speichern</button>
<button type="submit" name="submit" value="forward" class="btn btn-primary">Speichern und Ergebnisse eintragen</button>
</form>
<progress id='progress' value='0' max='100'></progress>
<div id="response"></div>
</div>
<script>
htmx.on('#dogeditform', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
});
</script>

154
web/pages/runs/run.html Normal file
View File

@@ -0,0 +1,154 @@
<div class="container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<i class="fas fa-calendar-star"></i>
<a href="/tournaments/event/<?= $tournament_id; ?>" hx-push-url="/tournaments/event/<?= $tournament_id; ?>" hx-get="/tournaments/event/<?= $tournament_id; ?>" hx-target="#main">
<?= escape($tournament['name']); ?>
</a>
</li>
<li class="breadcrumb-item active" aria-current="page">
<i class="far fa-running"></i>
<?= escape($run['name']); ?>
</li>
</ol>
</nav>
<div class="row">
<div class="col-3">
<div class="card">
<?php if($run['photo']): ?><img src="<?= $run['photo'] ?>/300x170/fixedsize" class="card-img-top" alt="<?= escape($run['name']); ?>'s profile Picture">
<?php endif; ?>
<div class="card-body">
<h5 class="card-title">
<?= escape($run['name']); ?>
</h5>
<p class="card-text">
<ul>
<li>Gehört zu <a href="/tournaments/event/<?= $tournament_id; ?>" hx-push-url="/tournaments/event/<?= $tournament_id; ?>" hx-get="/tournaments/event/<?= $tournament_id; ?>" hx-target="#main">
<?= escape($tournament['name']); ?>
</a></li>
<?php if($run['category']): ?>
<li>Typ:
<?= escape($run['category']); ?>
</li>
<?php endif; ?>
<?php if($run['length']): ?>
<li>Länge:
<?= escape($run['length']); ?>m
</li>
<?php endif; ?>
<?php if($run['time_standard']): ?>
<li>Normzeit:
<?= escape($run['time_standard']); ?>s
</li>
<?php endif; ?>
<?php if($run['time_max']): ?>
<li>Maxzeit:
<?= escape($run['time_max']); ?>s
</li>
<?php endif; ?>
<?php if($run['referee']): ?>
<li>Richter:
<?= escape($run['referee']); ?>
</li>
<?php endif; ?>
</ul>
<div class="d-flex justify-content-end">
<?php if($admin===true): ?>
<button type="button" class="btn btn-secondary" hx-get="/runs/edit/<?= $run_id; ?>" hx-target="#main">
<i class="fas fa-edit"></i>
</button>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="col" id="sitemain">
<div class="card p-2 mb-2">
<div class="row">
<h3>Fotos</h3>
<?php foreach($results as $result) : ?>
<?php if($result['photos']): ?>
<?php foreach($result['photos'] as $photo) : ?>
<div class="col-md-4 mt-3 col-lg-3">
<a href="<?= $photo; ?>">
<img src="<?= $photo; ?>/100x100/forcesize" alt="<?= escape( (new Dog())->getField('name',$result['dog']) ) ?>'s Lauf" class="figure-img rounded">
</a>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="card p-2">
<h4>Ergebnisse</h4>
<p class="card-text">
<button hx-get="/runs/addresults/<?= $run_id; ?>" hx-push-url="/runs/addresults/<?= $run_id; ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-plus-circle"></i> Ergebnis Eintragen</button>
</p>
<table class="table">
<thead>
<tr>
<th>Platz</th>
<th>Disqualifiziert</th>
<th>Verweigerungen</th>
<th>Fehler</th>
<th>Zeitfehler</th>
<th>Zeit</th>
<th>Gesamtfehler</th>
<th>m/Sek</th>
<th>Bewertung</th>
<th>Teilnehmer</th>
</tr>
</thead>
<tbody>
<?php foreach($results as $result) : ?>
<tr>
<td>
<?= escape($result['ranking']); ?>
</td>
<td>
<?= $result['disqualified']?'Ja':'Nein'; ?>
</td>
<td>
<?= escape($result['refusals']); ?>
</td>
<td>
<?= escape($result['errors']); ?>
</td>
<td>
<?= escape($result['timefaults']); ?>
</td>
<td>
<?= escape($result['runtime']); ?>
</td>
<td>
<?= escape($result['penalties']); ?>
</td>
<td>
<?= escape($result['speed']); ?>
</td>
<td>
<?= escape($result['rating']); ?>
</td>
<td><a href="/dogs/overview/<?= $result['dog'] ?>">
<?= escape( (new Dog())->getField('name',$result['dog']) ); ?>
</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
<?php
class Settings extends Page {
function index()
{
$this->set('userdata', $_SESSION['user']->data);
$this->set('template', "settings.html");
}
function edit()
{
$theme = $_REQUEST['theme'];
switch($theme)
{
case 'light':
case 'dark':
$_SESSION['user']->data['theme'] = $theme;
$_SESSION['user']->save();
$this->redirect('/settings');
break;
default:
return partial('error.html', ['errorMessage' => 'Dieses Theme existiert nicht']);
}
}
function maySeeThisPage(){
if($_SESSION['user'])
return true;
else return false;
}
}

View File

@@ -0,0 +1,13 @@
<form hx-post="/settings/edit" hx-target="#response">
<div>
<label for="theme">Farbschema</label>
<select name="theme">
<option value="light" <?= $_SESSION['user']->data['theme']=='light'?'selected':''; ?>>Hell</option>
<option value="dark" <?= $_SESSION['user']->data['theme']=='dark'?'selected':''; ?>>Dunkel</option>
</select>
</div>
<div id="response"></div>
<button type="submit" name="submit" value="true" class="btn btn-primary">Speichern</button>
</form>

View File

@@ -0,0 +1,65 @@
<?php
class Smart extends Page {
function setMenu()
{
$this->menu_text = 'Smart';
$this->menu_image = 'fas fa-robot';
$this->menu_priority = 1;
}
function index()
{
$this->set('user', $_SESSION['user']->data);
$this->set('template', "smart.html.php");
}
function search()
{
$db = new SQLite3(ROOT.DS.'../crawler/data.db', SQLITE3_OPEN_READONLY);
$q = $_REQUEST['q'];
if(!$q || strlen($q) < 3 || strpos($q, ' ') === false)
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Error: Bitte Nachname und Vorname eingeben.']);
$stmt = $db->prepare("SELECT DISTINCT(hund) FROM results WHERE teilnehmer LIKE :q");
$stmt->bindValue(':q', $q, SQLITE3_TEXT);
$res = $stmt->execute();
$results = [];
$dogs = [];
while($row = $res->fetchArray())
{
$dogs[] = $row['hund'];
}
if(count($dogs) == 0)
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Error: Keine Ergebnisse gefunden. Bitte Nachname und Vorname prüfen.']);
foreach($dogs as $dog)
{
$res = $db->query("SELECT * FROM results WHERE teilnehmer LIKE '$q' AND hund LIKE '$dog'");
while($row = $res->fetchArray())
{
$row['date'] = $db->querySingle("SELECT date FROM events WHERE id = ".$row['event']);
$row['event'] = $db->querySingle("SELECT name FROM events WHERE id = ".$row['event']);
$row['unixtimestamp'] = strtotime($row['date']);
$row['run'] = $db->querySingle("SELECT name FROM runs WHERE id = ".$row['run']);
$row['ago'] = printRelativeTime(time(),$row['unixtimestamp']);
$results[$dog][] = $row;
}
//sort results by date
usort($results[$dog], function($a, $b) {
return $a['unixtimestamp'] <=> $b['unixtimestamp'];
});
}
$this->set('results_dogs', $results);
$this->set('dogs', $dogs);
$this->set('template', 'search.html.php');
}
}

View File

@@ -0,0 +1,165 @@
<h2>Hunde:</h2>
<ul>
<?php foreach ($dogs as $dog) : ?>
<li><a href="#<?= $dog ?>"><?= $dog ?></a></li>
<?php endforeach; ?>
</ul>
<?php foreach ($dogs as $dog) :
$dogid=preg_replace("/[^A-Za-z0-9]/", '', $dog);
$results = $results_dogs[$dog];
$sdata = [];
?>
<h1 id="<?= $dogid; ?>"><?= $dog; ?></h1>
<button onClick="getElementById('table_<?= $dogid ?>').style.display='table'">Tabelle anzeigen</button>
<table id="table_<?= $dogid ?>" class="table" style="display:none">
<tr>
<th>event</th>
<th>Wann wars</th>
<th>run</th>
<th>rang</th>
<th>stnr</th>
<th>teilnehmer</th>
<th>hund</th>
<th>verein</th>
<th>f</th>
<th>vw</th>
<th>zf</th>
<th>zeit</th>
<th>gf</th>
<th>msek</th>
<th>bew</th>
<th>punkte</th>
<th>Action</th>
</tr>
<?php foreach ($results as $res) :
// graph data preparation
if ($res['bew'] != 'DIS' && $res['punkte'] != 'DIS') {
$sdata['dates'][] = date("d.m.Y", strtotime($res['date']));
$sdata['speed'][] = $res['msek'] ?: 0;
$sdata['errors'][] = $res['f'] ?: 0;
$sdata['refusals'][] = $res['vw'] ?: 0;
$sdata['time'][] = $res['zeit'] ?: 0;
$sdata['points'][] = $res['punkte'];
$sdata['ranking'][] = $res['rang'];
$a = array_filter($sdata['speed']);
if(count($a)>0)
$averagespeed = round(array_sum($a)/count($a),2);
$a = $sdata['errors'];
if(count($a)>0)
$averageerrors = round(array_sum($a)/count($a),2);
$a = $sdata['refusals'];
if(count($a)>0)
$averagerefusals = round(array_sum($a)/count($a),2);
$a = array_filter($sdata['ranking']);
if(count($a)>0)
$averageranking = round(array_sum($a)/count($a),2);
}
?>
<tr>
<td><?= $res['event'] ?></td>
<td><?= date("d.m.Y", strtotime($res['date'])) ?></td>
<td><?= $res['run'] ?></td>
<td><?= $res['rang'] ?></td>
<td><?= $res['stnr'] ?></td>
<td><?= $res['teilnehmer'] ?></td>
<td><?= $res['hund'] ?></td>
<td><?= $res['verein'] ?></td>
<td><?= $res['f'] ?></td>
<td><?= $res['vw'] ?></td>
<td><?= $res['zf'] ?></td>
<td><?= $res['zeit'] ?></td>
<td><?= $res['gf'] ?></td>
<td><?= $res['msek'] ?></td>
<td><?= $res['bew'] ?></td>
<td><?= $res['punkte'] ?></td>
<td>
<form id="tournament_import" hx-post="/tournaments/add" hx-encoding="multipart/form-data" hx-target="#main">
<input type="hidden" name="tournament_name" value="<?= $res['event'] ?>">
<input type="hidden" name="tournament_date" value="<?= date("d.m.Y", strtotime($res['date'])) ?>">
<input type="hidden" name="tournament_run" value="<?= $res['run'] ?>">
<input type="hidden" name="tournament_ranking" value="<?= $res['rang'] ?>">
<input type="hidden" name="tournament_number" value="<?= $res['stnr'] ?>">
<input type="hidden" name="tournament_participant" value="<?= $res['teilnehmer'] ?>">
<input type="hidden" name="tournament_dog" value="<?= $res['hund'] ?>">
<input type="hidden" name="tournament_club" value="<?= $res['verein'] ?>">
<input type="hidden" name="tournament_faults" value="<?= $res['f'] ?>">
<input type="hidden" name="tournament_refusals" value="<?= $res['vw'] ?>">
<input type="hidden" name="tournament_time" value="<?= $res['zeit'] ?>">
<input type="hidden" name="tournament_speed" value="<?= $res['msek'] ?>">
<input type="hidden" name="tournament_points" value="<?= $res['punkte'] ?>">
<button type="submit">Importieren</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
<h4>Statistik</h4>
<div class="card" style="width: 25rem;">
<ul class="list-group list-group-flush">
<li class="list-group-item"><i class="fas fa-rabbit-fast"></i> Durchschnittsgeschwinigkeit: <span class="badge bg-primary" style="float:right;"><?= $averagespeed ?> m/s</span></li>
<li class="list-group-item"><i class="fas fa-exclamation-triangle"></i> Fehler pro Lauf: <span class="badge bg-primary" style="float:right;"><?= $averageerrors ?></span></li>
<li class="list-group-item"><i class="fas fa-do-not-enter"></i> Verweigerungen pro Lauf: <span class="badge bg-primary" style="float:right;"><?= $averagerefusals ?></span></li>
<li class="list-group-item"><i class="fas fa-trophy-alt"></i> Durchschnittliche Platzierung: <span class="badge bg-primary" style="float:right;"><?= $averageranking ?></span></li>
</ul>
</div>
<br/>
<div class="row">
<?php if (count($results) > 0) : ?>
<div class="col">
<?= partial('graph.html.php', [
'id' => 'graph1'.$dogid,
'title' => 'Fehler und Verweigerungen',
'xaxis' => $sdata['dates'],
'seriesdata' => [
['name' => 'Fehler', 'type' => 'line', 'stack' => 'Total', 'data' => $sdata['errors']],
['name' => 'Verweigerungen', 'type' => 'line', 'stack' => 'Total', 'data' => $sdata['refusals']],
],
]);
?>
</div>
<div class="col">
<?= partial('graph.html.php', [
'id' => 'graph2'.$dogid,
'title' => 'Geschwindigkeit',
'xaxis' => $sdata['dates'],
'seriesdata' => [
['name' => 'Geschwindigkeit', 'type' => 'line', 'stack' => 'Total', 'data' => $sdata['speed']],
],
]);
?>
</div>
<div class="col">
<?= partial('graph.html.php', [
'id' => 'graph3'.$dogid,
'title' => 'Platzierung',
'xaxis' => $sdata['dates'],
'seriesdata' => [
['name' => 'Platzierung', 'type' => 'line', 'stack' => 'Total', 'data' => $sdata['ranking']],
],
]);
?>
</div>
</div>
<?php endif; ?>
<hr>
<?php endforeach; ?>

View File

@@ -0,0 +1,23 @@
<h2>Dein Name wie der auf Teilnahmelisten zu finden ist</h2>
<div class="mb-3">
<label for="basic-url" class="form-label">Dein Name wie der auf Teilnahmelisten zu finden ist (Nachname Vorname)</label>
<div class="input-group">
<input type="text" name="q"
hx-post="/smart/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
placeholder="Suchen..."
hx-indicator="#indicator"
class="form-control" id="basic-url"
value="<?= escape($user['lastname'].' '.$user['firstname']); ?>"
>
</div>
</div>
<i id="indicator" class="fad fa-spinner-third fa-spin htmx-indicator"></i>
<div id="search-results"></div>

View File

@@ -0,0 +1,296 @@
<?php
class Tournaments extends Page {
function setMenu()
{
$this->menu_text = 'Turniere';
$this->menu_image = 'far fa-medal';
$this->menu_priority = 1;
}
function setSubmenu()
{
$this->addSubmenuItem('Übersicht','/tournaments','far fa-list-alt');
$this->addSubmenuItem('Turnier anlegen','/tournaments/add','fas fa-calendar-plus');
if($_SESSION['user']->data['tournaments'] && count($_SESSION['user']->data['tournaments']) > 0)
{
$this->addSubmenuItem('divider');
$counter = 0;
foreach($_SESSION['user']->data['tournaments'] as $tid)
{
$t = new Tournament();
$t->load($tid);
$this->addSubmenuItem($t->data['name'],'/tournaments/event/'.$tid,'fas fa-calendar-star');
$counter++;
if ($counter === 5) {
break;
}
}
}
}
function add()
{
$t = new Tournament();
$t->name = $_REQUEST['tournament_name'];
$t->date = date("d.m.Y", strtotime($_REQUEST['tournament_date']));
$t->duration = intval($_REQUEST['tournament_duration']);
$t->referee = $_REQUEST['tournament_referee'];
$t->text = $_REQUEST['tournament_text'];
$t->url = $_REQUEST['tournament_url'];
$t->logo = $_REQUEST['tournament_logo'];
$t->run = $_REQUEST['tournament_run'];
$t->run = $_REQUEST['tournament_run'];
$t->ranking = $_REQUEST['tournament_ranking'];
$t->number = $_REQUEST['tournament_number'];
$t->participant = $_REQUEST['tournament_participant'];
$t->dog = $_REQUEST['tournament_dog'];
$t->club = $_REQUEST['tournament_club'];
$t->faults = $_REQUEST['tournament_faults'];
$t->refusals = $_REQUEST['tournament_refusals'];
$t->time = $_REQUEST['tournament_time'];
$t->points = $_REQUEST['tournament_points'];
$t->speed = $_REQUEST['tournament_speed'];
var_dump($t->data);
$this->set('tournamentdata',$t->data);
$this->set('template','edit_tournament.html');
}
function index() {
$events = $_SESSION['user']->data['tournaments'];
$tournaments = [];
foreach($events as $key => $eventid)
{
//var_dump($dogid);
$event = new Tournament();
try{
$event->load($eventid);
}
catch(Exception $e)
{
error_log("Event $eventid not found. Deleting from user");
unset($_SESSION['user']->data['tournament'][$key]);
$_SESSION['user']->save();
continue;
}
if($event->data)
$tournaments[] = array_merge($event->data,['id'=>$eventid]);
}
if(count($_SESSION['user']->data['tournaments']) > 0)
{
$this->set('tournaments',$tournaments);
$this->set('template', 'tournaments.html');
}
}
function manage()
{
$action = $this->params[0];
$tid = $this->params[1];
$t = new Tournament();
if($t->exists($tid))
$t->load($tid);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
switch($action)
{
case 'join':
if(in_array($tid, $_SESSION['user']->data['tournaments']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist bereits für dieses Turnier angemeldet']);
else
{
$t->joinUser($tid);
return partial('success.html', ['successTitle' => 'Erfolgreich', 'successMessage' => 'Du wurdest erfolgreich für dieses Turnier angemeldet']);
}
case 'leave':
if(!in_array($tid, $_SESSION['user']->data['tournaments']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist für dieses Turnier nicht angemeldet']);
else
{
$t->removeUser($tid);
return partial('success.html', ['successTitle' => 'Erfolgreich', 'successMessage' => 'Du wurdest erfolgreich für dieses Turnier abgemeldet']);
}
}
}
function leave()
{
}
function event()
{
$tid = $this->params[0];
$t = new Tournament();
if($t->exists($tid))
$t->load($tid);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
$this->set('admin',$t->amIAdmin($tid));
$this->set('joined',$t->isMyEvent($tid));
$this->set('tournament_id',$tid);
$this->set('admins',$t->getAdmins($tid));
$this->set('members',$t->getMembers($tid));
$this->set('tdata',$t->data);
$this->set('template','event.html');
}
function edit()
{
if($_REQUEST['submit'])
{
var_dump($_REQUEST);
$id = $_REQUEST['tournament_id'];
$name = $_REQUEST['tournament_name'];
$date = $_REQUEST['tournament_date'];
$tournament_referee = $_REQUEST['tournament_referee'];
$tournament_duration = intval($_REQUEST['tournament_duration']);
$tournament_url = $_REQUEST['tournament_url'];
$tournament_text = $_REQUEST['tournament_text'];
$newlogo = false;
if($_FILES['logo'])
{
$logo = $_FILES['logo'];
$logo_name = $logo['name'];
$logo_tmp_name = $logo['tmp_name'];
$logo_size = $logo['size'];
$logo_error = $logo['error'];
$logo_type = $logo['type'];
$allowed = ['jpg','jpeg','png','gif'];
$logo_ext = strtolower(end(explode('.', $logo_name)));
$logo_name = $name.'.'.$logo_ext;
$logo_path = 'uploads/'.$logo_name;
if(in_array($logo_ext, $allowed))
{
if($logo_error === 0)
{
if($logo_size < 10000000)
{
$answer = pictshareUploadImage($logo_tmp_name);
if($answer['status']=='ok' && in_array($answer['filetype'],['jpeg','png','gif']))
$newlogo = $answer['url'];
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Die Datei ist zu groß. Bitte eine kleinere Datei hochladen']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Beim Upload der Datei ist ein Fehler aufgetreten']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieser Dateityp ist nicht erlaubt. Bitte nur jpg, jpeg oder png Dateien hochladen']);
}
$error = false;
if(!$name || !$date )
$error = 'Bitte zumindest Name und Datum angeben';
else if(!strtotime($date))
$error = 'Das Datumm ist ungültig. Bitte die Eingabe prüfen';
if($error){
$this->set('errorMessage', $error);
$this->set('template', '/templates/partials/error.html');
return;
}
else
{
$t = new Tournament();
if($id)
{
if($t->exists($id))
$t->load($id);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
if(!in_array($_SESSION['user']->id, $t->data['admins']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist nicht berechtigt, dieses Turnier zu bearbeiten']);
else
$t->load($id);
}
$t->name = $name;
$t->date = $date;
$t->duration = $tournament_duration;
$t->referee = $tournament_referee;
$t->text = $tournament_text;
$t->url = $tournament_url;
if(!$t->data['admins'] || !is_array($t->data['admins'] || count($t->data['admins']) == 0))
$t->data['admins'] = [];
if(!in_array($_SESSION['user']->id, $t->data['admins']))
$t->data['admins'][] = $_SESSION['user']->id;
if($newlogo)
$t->logo = $newlogo;
try
{
$tid = $t->save();
}
catch(Exception $e)
{
$this->set('template', '/templates/partials/error.html');
$this->set('errorTitle', 'Error');
$this->set('errorMessage', $e->getMessage());
return;
}
//var_dump($_SESSION['user']->data['tournaments']);
if(!is_array($_SESSION['user']->data['tournaments']) || !in_array($tid, $_SESSION['user']->data['tournaments'])) // new t!
{
$_SESSION['user']->data['tournaments'][] = $tid;
try
{
$_SESSION['user']->save();
}
catch(Exception $e)
{
$this->set('template', '/templates/partials/error.html');
$this->set('errorTitle', 'Error');
$this->set('errorMessage', $e->getMessage());
return;
}
$this->redirect('/tournaments/event/'.$tid);
}
else
{
$this->set('template', '/templates/partials/success.html');
$this->set('successMessage', "Daten erfolgreich gespeichert");
return;
}
}
}
else
{
$id = $this->params[0];
$t = new Tournament();
$t->load($id);
$this->set('tournamentdata',$t->data);
$this->set('tournamentid',$t->id);
$this->set('template', 'edit_tournament.html');
}
}
function maySeeThisPage() {
if($_SESSION['user']) //wenn eingeloggt, kein problem
return true;
else return false;
}
}

View File

@@ -0,0 +1,38 @@
<div>
<h1>Turnier <?= $tournamentid?'Bearbeiten':'Hinzufügen'; ?></h1>
<form id="tournamenteditform" hx-post="/tournaments/edit" hx-encoding='multipart/form-data' hx-target="#response">
<input type="hidden" name="tournament_id" value="<?= $tournamentid; ?>">
<div class="mb-3 mb-md-4">
<label for="tournament_name">Veranstaltungsname</label>
<input type="text" value="<?= $tournamentdata['name']; ?>" id="tournament_name" name="tournament_name" placeholder="Event" class="form-control" required>
</div>
<div class="mb-3 mb-md-4">
<label for="tournament_date">Datum</label>
<input type="date" value="<?= $tournamentdata['date']; ?>" id="date" name="tournament_date" class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="tournament_duration">Dauer der Veranstaltung in Tagen</label>
<input type="number" min="1" max="7" value="<?= $tournamentdata['duration']; ?>" id="tournament_duration" name="tournament_duration" value="1" class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="tournament_url">Weblink</label>
<input type="text" value="<?= $tournamentdata['url']; ?>" id="tournament_url" name="tournament_url" placeholder="https://www.dognow.at/..." class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="logo">Logo des Events</label>
<input type="file" accept="image/png, image/jpeg, image/gif" id="logo" name="logo" class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="tournament_text">Beschreibungstext</label>
<textarea id="tournament_text" name="tournament_text" class="form-control"><?= $tournamentdata['text']; ?></textarea>
</div>
<button type="submit" name="submit" value="true" class="btn btn-primary">Weiter</button> <!-- Submit Button führt auf eine neue Seite wo man die Laufinfo eingibt -->
</form>
<div id="response"></div>
</div>

View File

@@ -0,0 +1,111 @@
<!-- FILEPATH: /home/chris/git/tournamentstats/web/pages/tournaments/dog.html -->
<div class="container">
<div class="row">
<div class="col-3">
<div class="card">
<img src="<?= $tdata['logo'] ?>/300x170/fixedsize" class="card-img-top" alt="<?= escape($tdata['name']); ?>'s profile Picture">
<div class="card-body">
<h5 class="card-title"><?= escape($tdata['name']); ?></h5>
<p class="card-text">
<ul>
<li>Datum: <?= escape($tdata['date']) ?></li>
<?php if($tdata['duration']): ?><li>Dauer: <?= escape($tdata['duration']); ?> Tag/e</li> <?php endif; ?>
<?php if($tdata['referee']): ?><li>Richter: <?= escape($tdata['referee']); ?></li> <?php endif; ?>
<?php if($tdata['size']): ?><li>Größe: <?= escape($tdata['size']); ?> cm</li> <?php endif; ?>
<?php if($tdata['url']): ?><li>Webseite: <a href="<?= $tdata['url']; ?>"><?= escape($tdata['url']); ?></a></li> <?php endif; ?>
</ul>
<div class="d-flex justify-content-end">
<?php if($admin===true): ?>
<button type="button" class="btn btn-secondary" hx-get="/tournaments/edit/<?= $tournament_id; ?>" hx-target="#main">
<i class="fas fa-edit"></i>
</button>
<?php endif; ?>
<?php if($joined===false): ?>
<button type="button" class="btn btn-secondary" hx-get="/tournaments/manage/join/<?= $tournament_id; ?>" hx-swap="outerHTML">
<i class="fas fa-plus-circle"></i> Diesem Event beitreten
</button>
<?php elseif($admin===false): ?>
<button type="button" class="btn btn-danger" hx-get="/tournaments/manage/leave/<?= $tournament_id; ?>" hx-swap="outerHTML" hx-confirm="Möchtest du dieses Event wirklich verlassen?">
<i class="fas fa-minus-circle"></i> Event verlassen
</button>
<?php endif; ?>
</div>
</div>
</div>
<?php if(($admins && count($admins)>1) || ($members && count($members)>0)): ?>
<div class="card p-2">
<?php if($admins && count($admins)>0): ?> <h6>Admins</h6> <?php endif; ?>
<?php foreach($admins as $adm) : ?>
<img src="https://pictshare.net/identicon/<?= $adm['email']?>" height="50" width="50" class="rounded-circle" alt="<?= escape($adm['name']); ?>" title="<?= escape($adm['firstname'].' '.$adm['lastname']); ?>">
<?php endforeach; ?>
<?php if($members && count($members)>0): ?> <hr/> <h6>Mitglieder</h6> <?php endif; ?>
<?php foreach($members as $member) : ?>
<img src="https://pictshare.net/identicon/<?= $member['email']?>" height="50" width="50" class="rounded-circle" alt="<?= escape($member['name']); ?>" title="<?= escape($member['firstname'].' '.$member['lastname']); ?>">
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="col-8" id="sitemain">
<?php if($tdata['text']): ?>
<div class="card p-2 mb-4">
<h4>Beschreibungstext</h4>
<p class="card-text">
<pre><?= escape($tdata['text']); ?></pre>
</p>
</div>
<?php endif; ?>
<div class="card p-2">
<h4>Läufe</h4>
<p class="card-text">
<?php if($admin===true): ?>
<button hx-get="/runs/add/<?= $tournament_id; ?>" hx-push-url="/runs/add/<?= $tournament_id; ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-plus-circle"></i> Lauf hinzufügen</button>
<?php endif; ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Lauf</th>
<th>Parcourlänge</th>
<th>Normzeit</th>
<th>Maxzeit</th>
<th>Richter</th>
<th>Hund</th>
</tr>
</thead>
<tbody>
<?php foreach($tdata['runs'] as $rid) : ?>
<?php
$run = new Run();
$run->load($rid);
?>
<tr>
<td><a href="/runs/overview/<?= $rid; ?>" hx-get="/runs/overview/<?= $rid; ?>" hx-push-url="/runs/overview/<?= $rid; ?>" hx-target="#main"><?= escape($run->data['name']); ?></a></td>
<td><?= escape($run->data['category']); ?></td>
<td><?= escape($run->data['length']); ?>m</td>
<td><?= escape($run->data['time_standard']); ?>s</td>
<td><?= escape($run->data['time_max']); ?>s</td>
<td><?= escape($run->data['referee']); ?></td>
<td><?= escape($run->data['dog']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</p>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<!-- FILEPATH: /home/chris/git/dogstats/web/pages/dogs/dog.html -->
<h1>Turnier Veranstaltungen</h1>
<p>Hier kannst du alle Veranstaltungen einsehen.</p>
<div class="tournaments-list row row-cols-sm-2">
<?php foreach ($tournaments as $tournament) : ?>
<div class="p-2">
<div class="d-flex border">
<img src="<?= $tournament['photo']?:'https://pictshare.net/prrnrk.jpg' ?>/130x100/forcesize" class="" alt="<?= escape($tournament['name']); ?>'s event Picture">
<div class="m-3">
<h3 class="h5"><a href="/tournaments/event/<?= $tournament['id'] ?>" class="text-body link-underline link-underline-opacity-0"><?= escape($tournament['name']); ?></a></h3>
<p><?= date("d.m.y",strtotime($tournament['date'])); ?></p>
</div>
<div class="m-3 ms-auto">
<button hx-get="/tournament/edit/<?= $tournament['id'] ?>" hx-push-url="/tournament/edit/<?= $tournament['id'] ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-edit"></i></button>
<button hx-get="/tournament/delete/<?= $tournament['id'] ?>" hx-target="#main" hx-confirm="Bist du sicher, dass du <?= escape($tournament['name']); ?> löschen willst" class="btn btn-danger"><i class="fas fa-trash"></i></button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,269 @@
<?php
class Trainings extends Page {
function setMenu()
{
$this->menu_text = 'Trainings';
$this->menu_image = 'fas fa-running';
$this->menu_priority = 1;
}
function setSubmenu()
{
$this->addSubmenuItem('Übersicht','/trainings','far fa-list-alt');
$this->addSubmenuItem('Training hinzufügen','/trainings/add','fas fa-calendar-plus');
if($_SESSION['user']->data['trainings'] && count($_SESSION['user']->data['trainings']) > 0)
{
$this->addSubmenuItem('divider');
$counter = 0;
foreach($_SESSION['user']->data['trainings'] as $tid)
{
$t = new Training();
$t->load($tid);
$this->addSubmenuItem($t->data['name'],'/trainings/event/'.$tid,'fas fa-calendar-star');
$counter++;
if ($counter === 5) {
break;
}
}
}
}
function add()
{
$this->set('template','edit_training.html');
}
function index() {
$events = $_SESSION['user']->data['trainings'];
$trainings = [];
foreach($events as $key => $eventid)
{
//var_dump($dogid);
$event = new Training();
try{
$event->load($eventid);
}
catch(Exception $e)
{
error_log("Event $eventid not found. Deleting from user");
unset($_SESSION['user']->data['training'][$key]);
$_SESSION['user']->save();
continue;
}
if($event->data)
$trainings[] = array_merge($event->data,['id'=>$eventid]);
}
if(count($_SESSION['user']->data['trainings']) > 0)
{
$this->set('trainings',$trainings);
$this->set('template', 'trainings.html');
}
}
function manage()
{
$action = $this->params[0];
$tid = $this->params[1];
$t = new Training();
if($t->exists($tid))
$t->load($tid);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
switch($action)
{
case 'join':
if(in_array($tid, $_SESSION['user']->data['trainings']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist bereits für dieses Turnier angemeldet']);
else
{
$t->joinUser($tid);
return partial('success.html', ['successTitle' => 'Erfolgreich', 'successMessage' => 'Du wurdest erfolgreich für dieses Turnier angemeldet']);
}
case 'leave':
if(!in_array($tid, $_SESSION['user']->data['trainings']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist für dieses Turnier nicht angemeldet']);
else
{
$t->removeUser($tid);
return partial('success.html', ['successTitle' => 'Erfolgreich', 'successMessage' => 'Du wurdest erfolgreich für dieses Turnier abgemeldet']);
}
}
}
function leave()
{
}
function event()
{
$tid = $this->params[0];
$t = new Training();
if($t->exists($tid))
$t->load($tid);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
$this->set('admin',$t->amIAdmin($tid));
$this->set('joined',$t->isMyEvent($tid));
$this->set('training_id',$tid);
$this->set('admins',$t->getAdmins($tid));
$this->set('members',$t->getMembers($tid));
$this->set('tdata',$t->data);
$this->set('template','event.html');
}
function edit()
{
if($_REQUEST['submit'])
{
$id = $_REQUEST['training_id'];
$name = $_REQUEST['training_name'];
$date = $_REQUEST['training_date'];
$training_referee = $_REQUEST['training_referee'];
$training_duration = intval($_REQUEST['training_duration']);
$training_url = $_REQUEST['training_url'];
$training_text = $_REQUEST['training_text'];
$newlogo = false;
if($_FILES['logo'])
{
$logo = $_FILES['logo'];
$logo_name = $logo['name'];
$logo_tmp_name = $logo['tmp_name'];
$logo_size = $logo['size'];
$logo_error = $logo['error'];
$logo_type = $logo['type'];
$allowed = ['jpg','jpeg','png','gif'];
$logo_ext = strtolower(end(explode('.', $logo_name)));
$logo_name = $name.'.'.$logo_ext;
$logo_path = 'uploads/'.$logo_name;
if(in_array($logo_ext, $allowed))
{
if($logo_error === 0)
{
if($logo_size < 10000000)
{
$answer = pictshareUploadImage($logo_tmp_name);
if($answer['status']=='ok' && in_array($answer['filetype'],['jpeg','png','gif']))
$newlogo = $answer['url'];
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Die Datei ist zu groß. Bitte eine kleinere Datei hochladen']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Beim Upload der Datei ist ein Fehler aufgetreten']);
}
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieser Dateityp ist nicht erlaubt. Bitte nur jpg, jpeg oder png Dateien hochladen']);
}
$error = false;
if(!$name || !$date )
$error = 'Bitte zumindest Name und Datum angeben';
else if(!strtotime($date))
$error = 'Das Datumm ist ungültig. Bitte die Eingabe prüfen';
if($error){
$this->set('errorMessage', $error);
$this->set('template', '/templates/partials/error.html');
return;
}
else
{
$t = new Training();
if($id)
{
if($t->exists($id))
$t->load($id);
else
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Dieses Turnier existiert nicht']);
if(!in_array($_SESSION['user']->id, $t->data['admins']))
return partial('error.html', ['errorTitle' => 'Error', 'errorMessage' => 'Du bist nicht berechtigt, dieses Turnier zu bearbeiten']);
else
$t->load($id);
}
$t->name = $name;
$t->date = $date;
$t->duration = $training_duration;
$t->referee = $training_referee;
$t->text = $training_text;
$t->url = $training_url;
if(!$t->data['admins'] || !is_array($t->data['admins'] || count($t->data['admins']) == 0))
$t->data['admins'] = [];
if(!in_array($_SESSION['user']->id, $t->data['admins']))
$t->data['admins'][] = $_SESSION['user']->id;
if($newlogo)
$t->logo = $newlogo;
try
{
$tid = $t->save();
}
catch(Exception $e)
{
$this->set('template', '/templates/partials/error.html');
$this->set('errorTitle', 'Error');
$this->set('errorMessage', $e->getMessage());
return;
}
//var_dump($_SESSION['user']->data['trainings']);
if(!is_array($_SESSION['user']->data['trainings']) || !in_array($tid, $_SESSION['user']->data['trainings'])) // new t!
{
$_SESSION['user']->data['trainings'][] = $tid;
try
{
$_SESSION['user']->save();
}
catch(Exception $e)
{
$this->set('template', '/templates/partials/error.html');
$this->set('errorTitle', 'Error');
$this->set('errorMessage', $e->getMessage());
return;
}
$this->redirect('/trainings/event/'.$tid);
}
else
{
$this->set('template', '/templates/partials/success.html');
$this->set('successMessage', "Daten erfolgreich gespeichert");
return;
}
}
}
else
{
$id = $this->params[0];
$t = new Training();
$t->load($id);
$this->set('trainingdata',$t->data);
$this->set('trainingid',$t->id);
$this->set('template', 'edit_training.html');
}
}
function maySeeThisPage() {
if($_SESSION['user']) //wenn eingeloggt, kein problem
return true;
else return false;
}
}

View File

@@ -0,0 +1,82 @@
<div>
<h1>Training <?= $trainingid?'Bearbeiten':'Hinzufügen'; ?></h1>
<form id="trainingeditform" hx-post="/trainings/edit" hx-encoding='multipart/form-data' hx-target="#response" class="container">
<input type="hidden" name="training_id" value="<?= $trainingid; ?>">
<div class="row">
<div class="col-md">
<div class="mb-3 mb-md-4">
<label for="training_date">Datum</label>
<input type="date" value="<?= $trainingdata['date']; ?>" id="date" name="training_date" class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="training_focus">Trainingsfokus</label>
<select class="form-select" aria-label="Trainingsfokus" id="training_focus" name="training_focus">
<option value="_none">- None -</option>
<option value="1">Parcourlauf</option>
<option value="2">Distanzarbeit</option>
<option value="3">Führtechnik</option>
<option value="4">Einzelgeräte</option>
<option value="5">Geräteunterscheidung</option>
</select>
</div>
<div class="mb-3 mb-md-4">
<label for="training_url">Video</label>
<input type="text" value="<?= $trainingdata['url']; ?>" id="training_url" name="training_url" placeholder="https://www.youtube.com/..." class="form-control">
</div>
<div class="mb-3 mb-md-4">
<label for="training_mood_barometer_human">Stimmungsbarometer Mensch</label>
<select class="form-select" aria-label="Stimmungsbarometer Mensch" id="training_mood_barometer_human" name="training_mood_barometer_human">
<option value="_none">- None -</option>
<option value="1">😕</option>
<option value="2">🙁</option>
<option value="3">😐</option>
<option value="4">😊</option>
<option value="5">🤩</option>
</select>
</div>
<div class="mb-3 mb-md-4">
<label for="training_mood_barometer_dog">Stimmungsbarometer Hund</label>
<select class="form-select" aria-label="Stimmungsbarometer Hund" id="training_mood_barometer_dog" name="training_mood_barometer_dog">
<option value="_none">- None -</option>
<option value="1">😕</option>
<option value="2">🙁</option>
<option value="3">😐</option>
<option value="4">😊</option>
<option value="5">🤩</option>
</select>
</div>
<div>
<label for="uploads">Uploads</label>
<input type="file" accept="image/png, image/jpeg, image/gif" id="uploads" class="form-control" name="photos[]" multiple>
</div>
</div>
<div class="col-md">
<div class="mb-3 mb-md-4">
<label for="training_that_worked">Das hat gut geklappt</label>
<textarea id="training_that_worked" name="training_that_worked" class="form-control" rows="4"><?= $trainingdata['text']; ?></textarea>
</div>
<div class="mb-3 mb-md-4">
<label for="training_more_excercise">Das müssen wir intensiver üben</label>
<textarea id="training_more_excercise" name="training_more_excercise" class="form-control" rows="4"><?= $trainingdata['text']; ?></textarea>
</div>
<div class="mb-3 mb-md-4">
<label for="training_text">Notizen</label>
<textarea id="training_text" name="training_text" class="form-control" rows="4"><?= $trainingdata['text']; ?></textarea>
</div>
<div class="text-end">
<button type="submit" name="submit" value="true" class="btn btn-primary text-end d-inline-block">Hinzufügen</button>
</div>
</div>
</div>
</form>
<div id="response"></div>
</div>

View File

@@ -0,0 +1,111 @@
<!-- FILEPATH: /home/chris/git/trainingstats/web/pages/trainings/dog.html -->
<div class="container">
<div class="row">
<div class="col-3">
<div class="card">
<img src="<?= $tdata['logo'] ?>/300x170/fixedsize" class="card-img-top" alt="<?= escape($tdata['name']); ?>'s profile Picture">
<div class="card-body">
<h5 class="card-title"><?= escape($tdata['name']); ?></h5>
<p class="card-text">
<ul>
<li>Datum: <?= escape($tdata['date']) ?></li>
<?php if($tdata['duration']): ?><li>Dauer: <?= escape($tdata['duration']); ?> Tag/e</li> <?php endif; ?>
<?php if($tdata['referee']): ?><li>Richter: <?= escape($tdata['referee']); ?></li> <?php endif; ?>
<?php if($tdata['size']): ?><li>Größe: <?= escape($tdata['size']); ?> cm</li> <?php endif; ?>
<?php if($tdata['url']): ?><li>Webseite: <a href="<?= $tdata['url']; ?>"><?= escape($tdata['url']); ?></a></li> <?php endif; ?>
</ul>
<div class="d-flex justify-content-end">
<?php if($admin===true): ?>
<button type="button" class="btn btn-secondary" hx-get="/trainings/edit/<?= $training_id; ?>" hx-target="#main">
<i class="fas fa-edit"></i>
</button>
<?php endif; ?>
<?php if($joined===false): ?>
<button type="button" class="btn btn-secondary" hx-get="/trainings/manage/join/<?= $training_id; ?>" hx-swap="outerHTML">
<i class="fas fa-plus-circle"></i> Diesem Event beitreten
</button>
<?php elseif($admin===false): ?>
<button type="button" class="btn btn-danger" hx-get="/trainings/manage/leave/<?= $training_id; ?>" hx-swap="outerHTML" hx-confirm="Möchtest du dieses Event wirklich verlassen?">
<i class="fas fa-minus-circle"></i> Event verlassen
</button>
<?php endif; ?>
</div>
</div>
</div>
<?php if(($admins && count($admins)>1) || ($members && count($members)>0)): ?>
<div class="card p-2">
<?php if($admins && count($admins)>0): ?> <h6>Admins</h6> <?php endif; ?>
<?php foreach($admins as $adm) : ?>
<img src="https://pictshare.net/identicon/<?= $adm['email']?>" height="50" width="50" class="rounded-circle" alt="<?= escape($adm['name']); ?>" title="<?= escape($adm['firstname'].' '.$adm['lastname']); ?>">
<?php endforeach; ?>
<?php if($members && count($members)>0): ?> <hr/> <h6>Mitglieder</h6> <?php endif; ?>
<?php foreach($members as $member) : ?>
<img src="https://pictshare.net/identicon/<?= $member['email']?>" height="50" width="50" class="rounded-circle" alt="<?= escape($member['name']); ?>" title="<?= escape($member['firstname'].' '.$member['lastname']); ?>">
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="col-8" id="sitemain">
<?php if($tdata['text']): ?>
<div class="card p-2 mb-4">
<h4>Beschreibungstext</h4>
<p class="card-text">
<pre><?= escape($tdata['text']); ?></pre>
</p>
</div>
<?php endif; ?>
<div class="card p-2">
<h4>Läufe</h4>
<p class="card-text">
<?php if($admin===true): ?>
<button hx-get="/runs/add/<?= $training_id; ?>" hx-push-url="/runs/add/<?= $training_id; ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-plus-circle"></i> Lauf hinzufügen</button>
<?php endif; ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Lauf</th>
<th>Parcourlänge</th>
<th>Normzeit</th>
<th>Maxzeit</th>
<th>Richter</th>
<th>Hund</th>
</tr>
</thead>
<tbody>
<?php foreach($tdata['runs'] as $rid) : ?>
<?php
$run = new Run();
$run->load($rid);
?>
<tr>
<td><a href="/runs/overview/<?= $rid; ?>" hx-get="/runs/overview/<?= $rid; ?>" hx-push-url="/runs/overview/<?= $rid; ?>" hx-target="#main"><?= escape($run->data['name']); ?></a></td>
<td><?= escape($run->data['category']); ?></td>
<td><?= escape($run->data['length']); ?>m</td>
<td><?= escape($run->data['time_standard']); ?>s</td>
<td><?= escape($run->data['time_max']); ?>s</td>
<td><?= escape($run->data['referee']); ?></td>
<td><?= escape($run->data['dog']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</p>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<!-- FILEPATH: /home/chris/git/dogstats/web/pages/dogs/dog.html -->
<h1>Turnier Veranstaltungen</h1>
<p>Hier kannst du alle Veranstaltungen einsehen.</p>
<div class="trainings-list row row-cols-sm-2">
<?php foreach ($trainings as $training) : ?>
<div class="p-2">
<div class="d-flex border">
<img src="<?= $training['photo']?:'https://pictshare.net/prrnrk.jpg' ?>/130x100/forcesize" class="" alt="<?= escape($training['name']); ?>'s event Picture">
<div class="m-3">
<h3 class="h5"><a href="/trainings/event/<?= $training['id'] ?>" class="text-body link-underline link-underline-opacity-0"><?= escape($training['name']); ?></a></h3>
<p><?= date("d.m.y",strtotime($training['date'])); ?></p>
</div>
<div class="m-3 ms-auto">
<button hx-get="/training/edit/<?= $training['id'] ?>" hx-push-url="/training/edit/<?= $training['id'] ?>" hx-target="#main" class="btn btn-primary"><i class="fas fa-edit"></i></button>
<button hx-get="/training/delete/<?= $training['id'] ?>" hx-target="#main" hx-confirm="Bist du sicher, dass du <?= escape($training['name']); ?> löschen willst" class="btn btn-danger"><i class="fas fa-trash"></i></button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="h-100" data-bs-theme="<?= ($_SESSION['user'] && $_SESSION['user']->theme)?$_SESSION['user']->theme:'autoy' ?>">
<head>
<meta charset="UTF-8">
@@ -8,20 +8,39 @@
<link href="/css/animate.min.css" rel="stylesheet">
<link href="/css/fontawesome.min.css" rel="stylesheet">
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">
<link href="/css/dogstats.css" rel="stylesheet">
<script src="/js/echarts.min.js"></script>
</head>
<body>
<body class="d-flex flex-column h-100">
<?php include(ROOT."/templates/menu.html") ?>
<main id="main" class="container" hx-get="/" hx-trigger="load" hx-indicator="#spinner">
<i id="spinner" class="fa-solid fa-spinner"></i>
<main id="main" class="container" hx-get="/" hx-trigger="load">
</main>
<footer class="footer mt-auto py-3 bg-body-tertiary ">
<!-- <ul class="nav justify-content-center border-bottom pb-3 mb-3">
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Home</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Features</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Pricing</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">FAQs</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">About</a></li>
</ul> -->
<p class="text-center text-muted">
© 2023 Crispi
<br/>
<small hx-get="/version.txt" hx-trigger="load"></small>
</p>
</footer>
<script src="/js/htmx.min.js"></script>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/dropzone.min.js"></script>
<?php if((!$_SESSION['user'] || !$_SESSION['user']->theme)): ?><script src="/js/color-modes.js"></script><?php endif; ?>
</body>
</html>

View File

@@ -15,11 +15,15 @@
<li <?php if (count($item['submenu'])) : ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle <?= $item['menu_classes'] ?>" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="<?= $item['image'] ?>"></i>
<?= $item['text']?>
</a>
<ul class="dropdown-menu">
<?php foreach($item['submenu'] as $sub) : ?>
<?php if($sub['text']=='divider') : ?>
<li><hr class="dropdown-divider"></li>
<?php else : ?>
<li>
<a href="<?= $sub['action'] ?>" hx-push-url="<?= $sub['action'] ?>" hx-get="<?= $sub['action'] ?>" hx-target="#main" class="dropdown-item <?= $sub['classes']?:'' ?>">
<?php if($sub['icon']) : ?>
@@ -29,12 +33,13 @@
<?= $sub['text'] ?>
</a>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
</li>
<?php else : ?>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/<?= $item['url'] ?>" hx-push-url="/<?= $item['url'] ?>" hx-get="/<?= $item['url'] ?>" hx-target="#main">
<a class="nav-link <?= $item['menu_classes'] ?>" aria-current="page" href="/<?= $item['url'] ?>" hx-push-url="/<?= $item['url'] ?>" hx-get="/<?= $item['url'] ?>" hx-target="#main">
<i class="<?= $item['image'] ?>"></i>
<?= $item['text'] ?>
</a>

View File

@@ -1,4 +1,6 @@
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 animate__animated animate__fadeInDown" role="alert">
<p class="font-bold"><?= $errorTitle ?></p>
<p><?= $errorMessage ?></p>
<div class="alert alert-danger animate__animated animate__headShake" role="alert">
<?php if($errorTitle) : ?>
<h4 class="alert-heading"><?= $errorTitle ?></h4>
<?php endif; ?>
<?= $errorMessage ?>
</div>

View File

@@ -0,0 +1,73 @@
<?php
/*
usage:
partial('graph.html.php', [
'id' => 'graph1',
'title' => 'Graph 1',
'gclass' => '', // classes for the graph div
'legend' => ['Geschwindigkeit', 'Fehler', 'Verweigerungen', 'Zeit', 'Geschwindigkeit', 'Punkte', 'Platz'],
'xaxis' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
'seriesdata' => [
['name' => 'Geschwindigkeit', 'type'=>'line','stack'=>'Total', 'data' => [120, 132, 101, 134, 90, 230, 210]],
['name' => 'Fehler', 'type'=>'line','stack'=>'Total', 'data' => [220, 182, 191, 234, 290, 330, 310]],
['name' => 'Verweigerungen', 'type'=>'line','stack'=>'Total', 'data' => [150, 232, 201, 154, 190, 330, 410]],
['name' => 'Zeit', 'type'=>'line','stack'=>'Total', 'data' => [320, 332, 301, 334, 390, 330, 320]],
['name' => 'Punkte', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]],
['name' => 'Platz', 'type'=>'line','stack'=>'Total', 'data' => [820, 932, 901, 934, 1290, 1330, 1320]]
],
])
*/
?>
<div id="graph<?= $id ?>" data-bs-theme="light" class="card bg-light text-black <?= $gclass ?>" style="min-height: 200px;"></div>
<script type="text/javascript">
// Initialize the echarts instance based on the prepared dom
var myChart<?= $id ?> = echarts.init(document.getElementById('graph<?= $id ?>'));
// Specify the configuration items and data for the chart
var option = {
title: {
text: '<?= $title ?>'
},
tooltip: {
trigger: 'axis'
},
<?php if(isset($legend)): ?>
legend: {
data: <?= json_encode($legend); ?>
},
<?php endif; ?>
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: <?= json_encode($xaxis); ?>
},
yAxis: {
type: 'value'
},
series: <?= json_encode($seriesdata, JSON_PRETTY_PRINT); ?>
};
// Display the chart using the configuration items and data just specified.
myChart<?= $id ?>.setOption(option);
window.addEventListener('resize', function(event) {
if(myChart<?= $id ?> != null && myChart<?= $id ?> != undefined){
myChart<?= $id ?>.resize();
}
}, true);
</script>

View File

@@ -1,4 +1,6 @@
<div class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 animate__animated animate__fadeInDown" role="alert">
<p class="font-bold"><?= $infoTitle ?></p>
<p><?= $infoMessage ?></p>
<div class="alert alert-info animate__animated animate__flash" role="alert">
<?php if($infoTitle) : ?>
<h4 class="alert-heading"><?= $infoTitle ?></h4>
<?php endif; ?>
<?= $infoMessage ?>
</div>

View File

@@ -1,4 +1,6 @@
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4 animate__animated animate__fadeInDown" role="alert">
<p class="font-bold"><?= $title ?></p>
<p><?= $message ?></p>
<div class="alert alert-warning animate__animated animate__jello" role="alert">
<?php if($noticeTitle) : ?>
<h4 class="alert-heading"><?= $noticeTitle ?></h4>
<?php endif; ?>
<?= $noticeMessage ?>
</div>

View File

@@ -1,4 +1,6 @@
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 animate__animated animate__fadeInDown" role="alert">
<p class="font-bold"><?= $successTitle ?></p>
<p><?= $successMessage ?></p>
<div class="alert alert-success animate__animated animate__fadeInDown" role="alert">
<?php if($successTitle) : ?>
<h4 class="alert-heading"><?= $successTitle ?></h4>
<?php endif; ?>
<?= $successMessage ?>
</div>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.