Compare commits

..

138 Commits

Author SHA1 Message Date
ff6abe4001 Mise à jour lien vers Studs 2023-08-17 17:54:55 +02:00
6d8c60c88b Fix ability to update an existing poll (6122) 2023-03-26 11:15:46 +02:00
obitanz
cd9b00d0e9 Correction erreur de syntaxe 2023-02-25 15:56:31 +01:00
obitanz
36681dcdea 23-12-2021
- Remove the X-Mailer header in e-mails, as this causes some email servers to see emails sent by Framadate as spam
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEExMITpfxOHHCvHn8FoGG53eDKB3MFAmHElvwACgkQoGG53eDK
 B3PO3w/+LyyC4Y7fDtl4hm9ClIswp62ozhxlIzRDJbza2S2S37A0ssXJMYCf2VOR
 ak+vWFUs9xj3o6G3oGlhLF1KMpWJ/nFVhyhCGgV1mQmUHSX2gnn6S9PBFenOGc9w
 zD2onJhVhJ/tbCJbF0Yl6zPitkfiucJI3FFIauByhkZ8wI3MPIYo/2m+H7KeVwLp
 YgJU0VqUQNMYl1clC+9Vu6gpSk/f6RklEVzJfEgV4MWMfoM6hLFqo0F6IDyWmgKp
 eNi1KHmFBGKFIexQeI+AvDyJyYsZH7uJrB0F7bV4wJGS7MTPOJlOJG5MXlLDcJgK
 EKNFUqrb9pQT5TWkO5FbJjfHwbP5bhO7TAEEvdIzhFqziR6gqdr2uXi+HiwG7yHO
 21YzDMnRZPqiYTBJuP0XIhsJrgpIBQDuQ3u9XJTs4pfyxQDSR8m9sHdiPSEZySRN
 p7XJSsDyLhcjjAobdodLuZLSWqOvZfaHzXEEKZO3HdQmccbwRHvniXDEy0FHEt2A
 L7LVvd4Qpa4wi6T2b3UIK4ubuC4xYdtYBfpzOklcti5TwDT2jbSTJRbv5oGAPX1b
 HfMzMoi/os3ZDSnPPRUaBxxrbjFQ9bX4bdxfQZeC2XIBWMBOcF3SeSbC1vVlcu+3
 bgwo3mehLvBCY0uMCGrjdymcAQq2IwX5Df8dGS7jFWUS5rAUi7s=
 =OVGk
 -----END PGP SIGNATURE-----

Merge tag '1.1.19' into chapril-1.1.19
2023-02-25 12:11:22 +01:00
Thomas Citharel
125045bb74
Release 1.1.19
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-23 16:34:08 +01:00
Thomas Citharel
c2f20b1ab2
Add CHANGELOG entry for 1.1.19
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-23 16:33:55 +01:00
Thomas Citharel
cffbeaf51d Merge branch 'remove-php-mailer-x-mailer-header' into 'v1.1.x'
Remove PHPMailer X-Mailer header

See merge request framasoft/framadate/framadate!500
2021-12-23 15:31:16 +00:00
Thomas Citharel
7343463c74
Remove PHPMailer X-Mailer header
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-23 16:29:31 +01:00
Thomas Citharel
7465852eb4
Merge pull request #71 from damufo/master
Galician translation
2021-12-21 11:23:09 +01:00
Thomas Citharel
f30fe54c4c
Update README.md
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 11:18:43 +01:00
Thomas Citharel
ac932e55f6
Add release file
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 10:53:40 +01:00
Thomas Citharel
fb04860c78 Merge branch 'fixes' into 'v1.1.x'
Release 1.1.18

See merge request framasoft/framadate/framadate!499
2021-12-21 09:51:25 +00:00
Thomas Citharel
9322a41d0c
Release 1.1.18
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 10:50:02 +01:00
Thomas Citharel
65cbc8b4b4
Cleanup CI
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 10:50:02 +01:00
Thomas Citharel
fc353ce708
Fix another missing translation in the french file
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 10:31:07 +01:00
Thomas Citharel
7603bed6d9
Modernize project
- Use PHP typings
- Update some front-end libraries

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-21 10:31:07 +01:00
Thomas Citharel
6144f33e9f
Update CHANGELOG.md with missing entries
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-20 11:08:39 +01:00
Thomas Citharel
2995851e2a Merge branch 'fixes' into 'v1.1.x'
Various fixes

Closes #566

See merge request framasoft/framadate/framadate!498
2021-12-17 14:23:42 +00:00
Thomas Citharel
0d4bbe8f2d
Allow /abc/ as well, for Framasoft
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 15:21:05 +01:00
Thomas Citharel
dffd7edf42
Improve metadata on poll pages
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 15:14:29 +01:00
Thomas Citharel
e7f7e26141
Fix a missing translation in the french file
Closes #566

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 15:00:43 +01:00
Thomas Citharel
3de07eb565
Activate Catalan language
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:58:46 +01:00
Thomas Citharel
9239954123
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:54:07 +01:00
Thomas Citharel
76e59902e4
Fix depreciated return boolean value from uasort
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:54:06 +01:00
Thomas Citharel
2c3148fe9e
Fix some HTML structure and JS formatting
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:54:06 +01:00
Thomas Citharel
5a5c233a5e
Refactor calculating max/min expiration date and enforce on poll edition
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:54:06 +01:00
Thomas Citharel
9c969f8896
Cleanup some invalid HTML
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:09:52 +01:00
Thomas Citharel
3b9dcd8085
Replace SimpleMDE with EasyMDE fork
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-12-17 14:09:52 +01:00
Thomas Citharel
bd12f33e12 Update README.md
Closes #565
2021-11-30 10:14:30 +00:00
Thomas Citharel
f877e4a27f Merge branch 'add-version-to-latest-zip' into 'v1.1.x'
Add version to latest zip

See merge request framasoft/framadate/framadate!497
2021-10-21 07:52:59 +00:00
Luc Didry
86934e9753
👷 — Automatically add VERSION file to release zip file 2021-10-21 09:42:38 +02:00
Luc Didry
12ce504421
🌐 — Remove Zanata stuff (we use weblate now) 2021-10-21 09:42:09 +02:00
Thomas Citharel
0aa11211f3
Add french translation for ICS feature
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 16:51:09 +02:00
Thomas Citharel
61a63c55bd
CSS and a11y improvements for export to ics feature
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 16:50:51 +02:00
Thomas Citharel
03dbceea5a
Use Content-Type: text/calendar for ics files
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 16:50:34 +02:00
Thomas Citharel
d971e015a6 Merge branch 'update-deps' into 'v1.1.x'
Update deps and bump to 1.1.17

See merge request framasoft/framadate/framadate!496
2021-10-18 14:30:59 +00:00
Thomas Citharel
bfd2fe5351
Bump to 1.1.17
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 16:29:55 +02:00
Thomas Citharel
d87243873f
Update deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 16:29:55 +02:00
Thomas Citharel
6e40f1cf02 Merge branch 'fix-graph-xss' into 'v1.1.x'
Fix an XSS in the result graph

See merge request framasoft/framadate/framadate!493
2021-10-18 14:12:34 +00:00
ff62406853 Added version number (#4871). 2021-08-28 18:44:20 +02:00
8d9770c584 Fixed typography. 2021-08-28 16:25:02 +02:00
eed7da32bd Merge tag '1.1.16' into chapril-1.1.16 2021-08-28 15:59:04 +02:00
Thomas Citharel
02229c671b
Fix an XSS in the result graph
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-07-19 11:17:00 +02:00
Thomas Citharel
e0028dc813 Merge branch 'ical-1.1.x-backport' into 'v1.1.x'
[1.1.x backport] Allow downloading ics/ical files for best choices

See merge request framasoft/framadate/framadate!482
2021-04-22 07:23:40 +00:00
Thomas Citharel
ecee1b5025 Merge branch 'smtp-authtype-1.1.x-backport' into 'v1.1.x'
[1.1.x backport] MailService: Allow configuring AuthType.

See merge request framasoft/framadate/framadate!484
2021-04-21 18:09:22 +00:00
Kevin Kofler
ab211a93e8 MailService: Allow configuring AuthType.
This works around a broken mail server that claims to support CRAM-MD5,
but then actually does not.
2021-04-21 19:49:50 +02:00
Kevin Kofler
6ec188da23 ICalService: Support start_time-end_time time specs
E.g., "13:00-14:00".
2021-04-21 19:44:06 +02:00
Michael Schär
709b48f293 Allow downloading ics/ical files for best choices
Backported to 1.1.x by Kevin Kofler.
2021-04-21 19:43:57 +02:00
Thomas Citharel
b46430ebe2
Bump version to 1.1.16
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-22 19:06:57 +01:00
Thomas Citharel
36cef8cc39
Actually enforce the length limitation on the poll creator name
Show a nice error message if it's too long (not on the edit page because
there's no support for this right now)

mbstring is now a required extension

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-22 19:04:44 +01:00
Thomas Citharel
a5c7df64b2
Bump version to 1.1.15
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-22 18:10:56 +01:00
Thomas Citharel
c266373344
Add DOMPurify to sanitize markdown
Closes #546

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-22 15:56:09 +01:00
Thomas Citharel
76f936b0cf
Bump version to 1.1.14
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-08 19:04:54 +01:00
Thomas Citharel
fa6bd17365 Merge branch 'add-maxlength-for-author-name' into 'v1.1.x'
Add a maxlength attribute for the author name

See merge request framasoft/framadate/framadate!475
2021-03-08 18:04:11 +00:00
Thomas Citharel
2ce6e56f8a
Add a maxlength attribute for the author name
Closes #530

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-08 18:52:14 +01:00
Thomas Citharel
9dd52be963
Bump version to 1.1.13
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-08 16:31:18 +01:00
Thomas Citharel
299b87a2b6 Merge branch 'fix-poll-closed' into 'v1.1.x'
Fix poll not possible to be closed

See merge request framasoft/framadate/framadate!474
2021-03-08 15:30:44 +00:00
Thomas Citharel
00ad08d037
Fix poll not possible to be closed
Closes #532

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-03-08 16:27:42 +01:00
Thomas Citharel
12d4bdb857 Merge branch 'master' into 'v1.1.x'
Add datapicker for Occitan

See merge request framasoft/framadate/framadate!469
2021-01-13 19:02:35 +01:00
Quentin
15e72d6957 Upload New File 2021-01-13 18:37:30 +01:00
Thomas Citharel
dda9c851ab
Bump version to 1.1.12
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-21 11:28:28 +01:00
Thomas Citharel
cb7823574e
Bump php version to 7.3
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-21 11:28:13 +01:00
Thomas Citharel
eab3150ba6
Bump version to 1.1.11
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-21 11:28:13 +01:00
Thomas Citharel
69e69efe32
Fix leftovers
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-18 17:37:21 +01:00
Thomas Citharel
dcd30e0974
Fix translation keys missing into emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-18 17:37:21 +01:00
Thomas Citharel
d0e01434b7
Fix nested ca translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2020-12-18 17:37:20 +01:00
f1587f7b36 Ajout d'un test pour éliminer les colonnes choices vides dans la table fd_vote (#4617) 2020-08-26 12:00:37 +02:00
9068919c47 Added 3 keys in ../locale/*.json (#4620,#4619,#4618) 2020-08-11 21:53:13 +02:00
c269048008 Modified message in fr_FR.json (#4542) 2020-06-10 15:59:20 +02:00
4ec047218a Added keys for date poll (#3921) 2020-03-11 17:21:37 +01:00
d0ffcd0dba Suppressed comment in index.tpl 2020-03-10 16:52:08 +01:00
fe0558955b Modified again key for messages (#4299) 2020-03-10 14:54:25 +01:00
a48b48abc8 Modified key for messages (#4299) 2020-03-10 14:43:10 +01:00
75ee2ef419 Changed meta description to DateChaprilOrg (#4294) 2020-03-01 16:49:02 +01:00
Antoni Serrano i Cortès
8357c0e757 Translated using Weblate (Catalan)
Currently translated at 100.0% (401 of 401 strings)

Translation: Framadate/Framadate v1.1.x
Translate-URL: https://weblate.framasoft.org/projects/framadate/framadate-v11x/ca/
2020-02-07 12:09:10 +01:00
Thomas Citharel
6d2101a3cb Merge branch 'weblate-framadate-framadate-v11x' into 'v1.1.x'
Update from Weblate

See merge request framasoft/framadate/framadate!424
2020-02-06 15:59:01 +01:00
Thomas Citharel
1b87fac9fa Translated using Weblate (Catalan)
Currently translated at 93.3% (374 of 401 strings)

Translation: Framadate/Framadate v1.1.x
Translate-URL: https://weblate.framasoft.org/projects/framadate/framadate-v11x/ca/
2020-02-06 15:58:13 +01:00
Thomas Citharel
de56c42d64 Added translation using Weblate (Catalan) 2020-02-06 15:44:39 +01:00
f6855dce8f Directory tools moved to srv (#4127) 2020-01-31 16:57:12 +01:00
6030c3da3a Corrected bug in rapport_activite.sh (#3528) 2020-01-01 23:07:28 +01:00
163630bc66 renamed date.chapril.org.cron (#4089) 2019-12-27 22:17:52 +01:00
a540df01ef Added check_datechaprilorg_update (#3591) 2019-12-07 19:13:11 +01:00
2db36d63bd Added date.chapril.org.cron (#4087) 2019-12-07 13:03:10 +01:00
415637f4f8 Improved statistics on ip (#3332) 2019-10-01 16:56:10 +02:00
88c62dc13d Added user count with ipv4 or ipv6 2019-09-18 23:03:33 +02:00
51b27b6b73 Normalized Apache access log file. 2019-08-14 17:02:23 +02:00
bbe505ed41 Correction d'un bug dans le script ligne 86 base decimale forcée (#3528) 2019-08-01 22:23:13 +02:00
0bab6f939f Added statistics on users (#3332) 2019-07-12 19:18:23 +02:00
c6fda43581 Merge tag '1.1.10' into chapril-1.1.9 to create chapril-1.1.10. 2019-05-10 18:50:09 +02:00
fd91b558a3 Merge tag '1.1.9' into chapril-1.1.8 to create chapril-1.1.9. 2019-05-10 18:31:03 +02:00
Thomas Citharel
e8747ffef6 Merge branch 'release' into 'master'
Version 1.1.10

See merge request framasoft/framadate/framadate!402
2019-05-06 12:06:06 +02:00
Thomas Citharel
0900c92fd6 Merge branch 'add-link-to-release' into 'master'
[CI] Add link of artifact to release’s assets

See merge request framasoft/framadate/framadate!401
2019-05-06 11:59:21 +02:00
Thomas Citharel
b30d7f2076
Version 1.1.10
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2019-05-06 11:58:01 +02:00
Luc Didry
ee8a423e00
[CI] 🏗️ Add link of artifact to release’s assets 2019-05-02 10:36:25 +02:00
23f933595b Removed list of web browers (#3626) 2019-03-25 17:09:06 +01:00
a75ce8aab5 Improved english translation of FOOTER (#3250) 2019-03-10 17:42:11 +01:00
b108cbbd15 Added directory tools and script rapport_activite.sh (#3592) 2019-02-20 15:29:17 +01:00
4d58bc9371 Updated locale/fr.json and fr_FR.json (#3538,#3493,#3491) 2019-01-13 22:49:04 +01:00
Thomas Citharel
bf3313118a Merge branch 'push-artifacts-to-tags-notes' into 'master'
On tag, upload artifacts to project and update tag note

See merge request framasoft/framadate/framadate!356
2019-01-07 11:16:06 +01:00
Luc Didry
ec53d9c504
On tag, upload artifacts to project and update tag note 2019-01-07 10:11:03 +01:00
JosephK
5b29497859 Merge branch 'patch-1' into 'master'
.git folder deleted before creating latest.zip

See merge request framasoft/framadate/framadate!355
2019-01-07 08:45:00 +01:00
JosephK
30045fd56d .git folder deleted before creating latest.zip 2019-01-07 08:44:36 +01:00
add73d3189 Customization of another text for DateChaprilOrg in locale/en.json (#3538) 2019-01-04 23:22:53 +01:00
80f99c9c68 Customization of text for DateChaprilOrg in locale/en.json (#3538) 2019-01-04 23:17:45 +01:00
f3aab00199 Customization of text for DateChaprilOrg in locale/fr.json (#3538) 2019-01-04 23:06:54 +01:00
b9715d7142 Improved message 'où sont mes sondages?' (#3493) 2019-01-01 22:08:44 +01:00
2c7d79dca2 Modified third section in locale/fr.json (#3491) 2018-12-31 12:42:19 +01:00
4cf50cb7bf modified the mail text of 'where are my polls' (#3493) 2018-12-28 21:51:05 +01:00
1bf1656d87 personalized third section of comment (#3491) 2018-12-28 21:34:53 +01:00
8d76fae8a1 renamed logo-DateChaprilOrg.png (#3478) 2018-12-28 17:52:10 +01:00
Thomas Citharel
0e72dafbf6 Merge branch 'release-1.1.9' into 'master'
Release 1.1.9

See merge request framasoft/framadate/framadate!353
2018-12-04 18:23:24 +01:00
Thomas Citharel
e35589e1a8
Bump version
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2018-12-04 18:22:32 +01:00
Yoann
4fa7ae12d4
Fix wrong display of email subject with a date poll
fix parenthesis

Fix

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2018-12-04 18:18:56 +01:00
Thomas Citharel
15e1d1cc18 Merge branch 'backport-session-fix' into 'master'
backport session fix

See merge request framasoft/framadate/framadate!352
2018-12-04 18:08:23 +01:00
Lazare Olivry
ca347b024d backport session fix 2018-12-04 18:02:54 +01:00
4106e2ca96 Changed new logo DateChaprilOrg 2018-12-02 15:34:16 +01:00
Thomas Citharel
faa2ee1289 Merge branch 'patch-1' into 'master'
Set $rowDatetime to int for comparison

See merge request framasoft/framadate/framadate!350
2018-11-05 16:25:04 +01:00
Thomas Citharel
46338cc078 Set $rowDatetime to int for comparison
Closes #379
2018-11-05 16:22:04 +01:00
cb10c87d07 Added script for automatic poll purge (#3150). 2018-08-15 11:28:41 +02:00
ebe9add057 Fixed mistaken message about expiry date (#3205). 2018-08-13 16:02:06 +02:00
13c2543a7a Updated AdminPollService to correct bug on end_date 2018-08-13 12:19:06 +02:00
c2eb45666a Updated fr.json to suppress <br/> 2018-08-12 16:03:58 +02:00
30c94c7aa1 Merge remote-tracking branch 'origin/master' into chapril-1.1.8
mise à jour vers 1.1.8
2018-08-12 15:00:08 +02:00
4fe0b2097f Updated AdminPollService to limit end_date (#3206). 2018-08-08 22:56:34 +02:00
f4debb7da8 Updated one little mistake. Suppressed one blank character 2018-08-07 23:18:58 +02:00
506c56aa28 Updated one word forgetten 2018-08-07 22:36:08 +02:00
782a42d571 Updated according with agir issue 3205 2018-08-07 22:32:25 +02:00
205c9230ad Updated one word for test 2018-08-07 21:55:21 +02:00
46356b9744 Changed pushline APRIL instead of Framasoft. 2018-08-03 20:33:52 +02:00
25def10867 Improved English translation 2018-08-03 17:26:20 +02:00
fcfc8786fa Updated English punch line 2018-08-03 12:53:57 +02:00
7ba226af4a Removed <br>, custom the email. 2018-08-01 17:39:18 +02:00
07368eb2d5 updated fr_FR.json 2018-08-01 17:29:57 +02:00
9a6784c6a6 Merge remote-tracking branch 'origin/develop' tag1.1.7 into chapril
Conflicts:
	locale/fr.json
2018-07-28 17:57:58 +02:00
damufo
2badf15074 Galician translation 2018-07-10 19:04:34 +02:00
a31dcd60d6 Removed initial htaccess.txt file (previously renamed .htaccess). 2018-05-27 12:37:07 +02:00
7088684b2e Add phpinfo.php file for easy check. 2018-05-27 12:19:28 +02:00
f62e0408de Personnalisation du pied de page de courriel. 2018-05-06 11:50:15 +02:00
214 changed files with 14487 additions and 39787 deletions

6
.gitignore vendored
View File

@ -10,7 +10,7 @@ vendor
cache/
tpl_c/*
!tpl_c/.gitkeep
.php_cs.cache
.php-cs-fixer.cache
.zanata-cache/
# Temp files
@ -25,4 +25,6 @@ Thumbs.db
.project
.idea/
*.iml
test_database.sqlite
#ics temp file
out.ics

View File

@ -1,91 +1,26 @@
variables:
TESTING_IMAGE: framasoft/framadate-ci:php-7.2
image: ${TESTING_IMAGE}
cache:
key: "${TESTING_IMAGE}:${CI_PROJECT_ID}"
paths:
- vendor/
before_script:
- composer install -o --no-interaction --no-progress --prefer-dist
image: framasoft/framadate-ci:7.3-pdo_mysql
stages:
- test
- deploy
- beta
- funky
# Run phpunit on all branches
# Run php-cs-fixer and phpunit on all branches
test:
stage: test
script:
- composer install -o --no-interaction --no-progress --prefer-dist
- php vendor/bin/php-cs-fixer fix --verbose --dry-run
- vendor/bin/phpunit --bootstrap app/tests/bootstrap.php --debug app/tests
# Run php-cs-fixer on all branches
lint:
stage: test
script:
- php vendor/bin/php-cs-fixer fix --verbose --dry-run --diff
# Run doctrine migrations with MySQL
.db:migration:mysql:
stage: test
variables:
APP_ENV: "test"
MYSQL_DATABASE: "fd_test"
MYSQL_ROOT_PASSWORD: "coucou"
FRAMADATE_DB_DRIVER: 'pdo_mysql'
FRAMADATE_DB_HOST: "mysql"
FRAMADATE_DB_NAME: "${MYSQL_DATABASE}"
FRAMADATE_DB_USER: "root"
FRAMADATE_DB_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
script:
- docker-php-ext-install "${FRAMADATE_DB_DRIVER}"
- bin/doctrine migrations:status -vvv
- bin/doctrine migrations:migrate --no-interaction -vvv
- bin/doctrine migrations:status -vvv
db:migration:mysql:5.6:
extends: .db:migration:mysql
services:
- 'mysql:5.6'
db:migration:mysql:5.7:
extends: .db:migration:mysql
services:
- 'mysql:5.7'
db:migration:mysql:8:
extends: .db:migration:mysql
services:
- name: 'mysql:8'
command: ['--default-authentication-plugin=mysql_native_password']
# Run doctrine migrations with PostgreSQL
.db:migration:postgresql:
stage: test
variables:
APP_ENV: "test"
POSTGRES_DB: "fd_test"
POSTGRES_USER: "marmotte"
POSTGRES_PASSWORD: "sleeping"
FRAMADATE_DB_DRIVER: 'pdo_pgsql'
FRAMADATE_DB_HOST: "postgres"
FRAMADATE_DB_NAME: "${POSTGRES_DB}"
FRAMADATE_DB_USER: "${POSTGRES_USER}"
FRAMADATE_DB_PASSWORD: "${POSTGRES_PASSWORD}"
script:
- docker-php-ext-install "${FRAMADATE_DB_DRIVER}"
- bin/doctrine migrations:status -vvv
- bin/doctrine migrations:migrate --no-interaction -vvv
- bin/doctrine migrations:status -vvv
db:migration:postgresql:9.6:
extends: .db:migration:postgresql
services:
- 'postgres:9.6'
image: framasoft/framadate-ci:${PHP_VERSION}-pdo_mysql
parallel:
matrix:
- PHP_VERSION:
- "7.3"
- "7.4"
- "8.0"
- "8.1"
cache:
paths:
- vendor/
# Create artifacts on master
pages:
@ -97,13 +32,27 @@ pages:
- composer dump-autoload --optimize --no-dev --classmap-authoritative
- mkdir framadate
- mv `ls -A | grep -v framadate` ./framadate
- echo $latesttag > framadate/VERSION
- find framadate/ -type d -exec chmod 750 {} \;
- find framadate/ -type f -exec chmod 640 {} \;
- rm -rf framadate/.git
- zip -r latest.zip framadate
- export RELEASE_ZIP="framadate-${CI_COMMIT_TAG}.zip"
- zip -r $RELEASE_ZIP framadate
- mkdir .public
- cp latest.zip .public
- cp $RELEASE_ZIP .public/latest.zip
- mv .public public
- if [[ -z $GITLAB_API_TOKEN ]]; then exit; fi
- export PROJECT_API_URL="https://framagit.org/api/v4/projects/${CI_PROJECT_ID}"
- export DESCRIPTION_URL="${PROJECT_API_URL}/repository/tags/${CI_COMMIT_TAG}"
- export RELEASE_URL="${DESCRIPTION_URL}/release"
- 'export HEADER="Private-Token: ${GITLAB_API_TOKEN}"'
- export artifactUrl=$(curl -s --request POST --header "${HEADER}" --form "file=@${RELEASE_ZIP}" "${PROJECT_API_URL}/uploads" | jq .url)
- export artifactAbsoluteUrl="${CI_PROJECT_URL}${artifactUrl}"
- export description=$(curl -s --header "${HEADER}" "${DESCRIPTION_URL}" | jq .release.description | sed -e 's@"@@g')
- if [[ $description == 'null' ]]; then export METHOD="POST"; echo -e 'You can download the release zip here:'" [${RELEASE_ZIP}](${artifactAbsoluteUrl})" > /tmp/text; fi
- if [[ $description != 'null' ]]; then export METHOD="PUT"; echo -e "${description}\n\n"'You can download the release zip here:'" [${RELEASE_ZIP}](${artifactAbsoluteUrl})" > /tmp/text; fi
- curl -s --request $METHOD --data-urlencode "description@/tmp/text" --header "${HEADER}" "${RELEASE_URL}"
- curl -s --request POST --header "${HEADER}" --data name="${RELEASE_ZIP}" --data url="${artifactAbsoluteUrl}" "${PROJECT_API_URL}/releases/${CI_COMMIT_TAG}/assets/links"
artifacts:
paths:
- public
@ -111,40 +60,3 @@ pages:
- tags
except:
- (beta|alpha)
# Deploy on develop
beta:
stage: beta
script:
- composer install -o --no-interaction --no-progress --prefer-dist --no-dev
- composer dump-autoload --optimize --no-dev --classmap-authoritative
- mkdir .public
- cp -r * .public
- cp -r .git .public
- mv .public public
- mkdir "${HOME}/.ssh"
- chmod 700 "${HOME}/.ssh"
- if [ ! -z ${DEPLOYEMENT_KNOWN_HOSTS+x} ]; then echo -e "${DEPLOYEMENT_KNOWN_HOSTS}" > ${HOME}/.ssh/known_hosts; fi
- eval `ssh-agent -s`
- if [ ! -z ${BETA_KEY+x} ]; then ssh-add <(echo "${BETA_KEY}" | base64 --decode -i); fi
- if [ ! -z ${BETA_KEY+x} ]; then rsync -a --delete --exclude admin/.stdout.log --exclude admin/.htpasswd --exclude app/inc/config.php --exclude stats/ --exclude error/ public/ ${BETA_USER}@${DEPLOYEMENT_HOST}:../../web/; fi
only:
- develop
# Deploy on funky
funky:
stage: funky
script:
- composer install
- mkdir tpl_c
- mkdir .public
- cp -r * .public
- mv .public public
- mkdir "${HOME}/.ssh"
- chmod 700 "${HOME}/.ssh"
- if [ ! -z ${DEPLOYEMENT_KNOWN_HOSTS+x} ]; then echo -e "${DEPLOYEMENT_KNOWN_HOSTS}" > ${HOME}/.ssh/known_hosts; fi
- eval `ssh-agent -s`
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}" | base64 --decode -i); fi
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then rsync -a --delete --exclude admin/.stdout.log --exclude admin/.htpasswd --exclude app/inc/config.php --exclude stats/ --exclude error/ public/ ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/; fi
only:
- funky

View File

@ -1,6 +1,6 @@
<?php
return PhpCsFixer\Config::create()
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'array_syntax' => [
@ -8,16 +8,18 @@ return PhpCsFixer\Config::create()
],
'combine_consecutive_unsets' => true,
'heredoc_to_nowdoc' => true,
'no_extra_consecutive_blank_lines' => [
'break',
'continue',
'extra',
'return',
'throw',
'use',
'parenthesis_brace_block',
'square_brace_block',
'curly_brace_block'
'no_extra_blank_lines' => [
'tokens' => [
'break',
'continue',
'extra',
'return',
'throw',
'use',
'parenthesis_brace_block',
'square_brace_block',
'curly_brace_block'
]
],
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
@ -39,7 +41,7 @@ return PhpCsFixer\Config::create()
'vendor',
'var',
'web',
'tpl_c',
'tpl_c'
])
->in(__DIR__)
)

9
.po2json.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
po2json -i po/en.po -t locale/en.json --progress none -o po/default.json
for i in po/*.po
do
j=$(echo $i | cut -d '.' -f 1 | cut -d '/' -f 2)
po2json -i $i -t locale/en.json --progress none | ./.renest_json.pl > po/$j.json
mv po/$j.json locale/
done

40
.renest_json.pl Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/perl
use strict;
use warnings;
use JSON;
my $json = JSON->new->utf8->space_before(0)->space_after(1)->indent(4)->canonical(1);
my $en_file = 'po/default.json';
my $en;
{
open my $fh, '<', $en_file or die;
local $/ = undef;
$en = <$fh>;
close $fh;
}
$en = $json->decode($en);
my $new_json = {};
my $old_json = '';
while (defined(my $line = <STDIN>)) {
$old_json .= $line;
}
$old_json = $json->decode($old_json);
for my $key (keys %{$old_json}) {
my $index = index($key, '.');
my $real_key = substr($key, 0, $index++);
my $trad_key = substr($key, $index);
if ($old_json->{$key}) {
$new_json->{$real_key}->{$trad_key} = $old_json->{$key};
} else {
$new_json->{$real_key}->{$trad_key} = $en->{$key};
}
}
print $json->encode($new_json);

View File

@ -1,9 +1,215 @@
This changelog file is **deprecated**. For an up-to-date changelog, please check [the tags](https://framagit.org/framasoft/framadate/tags).
---------------------
# Changelog de framadate
## 1.1.19
23-12-2021
### Fixed
- Remove the X-Mailer header in e-mails, as this causes some email servers to see emails sent by Framadate as spam
## 1.1.18
20-12-2021
### Changed
- Dependency updates
- Replace abandonned SimpleMDE with EasyMDE fork
### Fixed
- Enforce the instance expiration limits when editing the poll expiration date once created, from poll admin
- Fixed some HTML markup validity
### Translations
- Fixed a missing french language key
- Enable Catalan language
## 1.1.17
18-10-2021
### Added
- Allow to export to ICS the best choices
### Changed
- Allow configuring AuthType for MailService
### Security
- Fix an XSS possibility in the result graph
## 1.1.16
22-03-2021
### Changed
- **Framadate now requires the `mbstring` PHP extension.** Make sure it's installed and activated before updating.
### Fixed
- Handle poll creator names being too long properly
## 1.1.15
22-03-2021
### Security
- Fixed cross-site scripting (XSS) attacks in poll description markdown preview. All administrators are encouraged to upgrade, especially if you have sensitive services and data on the same domain name.
This was reported by @martgil
https://framagit.org/framasoft/framadate/framadate/-/issues/546
## 1.1.14
08-03-2021
### Fixed
- Avoid error with a name too long https://framagit.org/framasoft/framadate/framadate/-/issues/530
## 1.1.13
08-03-2021
### Fixed
- Fixed error when closing a poll https://framagit.org/framasoft/framadate/framadate/-/issues/532
## 1.1.12
18-12-2020
### Changed
* Framadate now requires PHP 7.3
## 1.1.11
18-12-2020
### Fixed
- Fixed translations keys missing into emails https://framagit.org/framasoft/framadate/framadate/-/issues/463
### Translations
- Added Catalan translation
## 1.1.10
### Fixed
* Remove .git folder inside releases.
* Create releases through CI
## 1.1.9
### Fixed
- Fixes session issue https://framagit.org/framasoft/framadate/framadate/issues/255
- Fixes bug when editing column https://framagit.org/framasoft/framadate/framadate/issues/379
- Fix mail subject escaping https://framagit.org/framasoft/framadate/framadate/issues/375
## 1.1.8
### Fixed
- Stop creating `tpl_c` directory in releases and add a `.gitkeep`
- Show database connection issue details on installation panel
- Set the proper file rights on release packages
- Added `session.cookie_httponly = 1` to local php.ini file
## 1.1.7
### Fixed
- Fix issue with maximum number of participants https://framagit.org/framasoft/framadate/issues/353 (thanks to @lohmeyer for reporting it)
## 1.1.6
### Fixed
- Bump dependencies, including PHPMailer to version 6.x
- Fix an small issue with Smarty template
## 1.1.5
### Fixed
- Restrict custom poll URLs against app urls (thanks @mosterdt)
- Add a parameter to disable build-in font-awesome (thanks @mm)
- Fix an XSS security issue with time slots (thanks https://bitsoffreedom.nl for responsibly disclosing it).
## 1.1.4
### Fixed
* Add Fork-awesome, remove dependency to Font-Awesome Bootstrap CDN, add an option to disable it (https://framagit.org/framasoft/framadate/merge_requests/300 - @tcit)
## 1.1.3
### Fixed
* Fixing issue when no choice is selected introducted in https://framagit.org/framasoft/framadate/merge_requests/284 (https://framagit.org/framasoft/framadate/merge_requests/298 - @mm)
## 1.1.2
### Fixed
- Use Parsedown's Safe Mode
## 1.1.1
### Bug fixes
- Send email with correct vote address (thanks to @lohmeyer for finding it)
## 1.1.0
### Warning
**Framadate now requires PHP 5.6** to be used (it should still work under 5.4 but will not be supported anymore).
### Features
- Markdown editor for descriptions ! (@Antonin)
- Adding a maximum participants number (@SuperNach0)
- Allow setting SMTP config (Simon LEBLANC)
- Allow admins to give the vote link back to the voters (@mm, @tcit)
- Sending voters emails to remind themselves their voting url now works (@mm)
### Enhancements
- UI improvements for responsive design (@marjolaine-v)
- Better coherence for visible results and passwords (@TDavid)
- Added an edit button on the right when too many options (@SuperNach0)
- Emails with international characters are now allowed (added an unit test) (@mm)
### Translations
**New strings are available, don't hesitate to head to <https://trad.framasoft.org/zanata/project/view/framadate> to translate them into your language !**
### Fixed
- Reschedule function (https://framagit.org/framasoft/framadate/issues/203) (@TDavid)
- lang attribute must be a valid IETF language tag (@Rudloff)
- Fix datepicker js locale file path
- Fix everyone can always vote #267
- Fix MySQL error with `NO_ZERO_DATE` #224
- SimpleMDE Markdown Editor has been updated the latest version to remove console.log calls
- Fix width of `if need be` vote option and missing parenthesis
- Remove autocomplete on date fields
- Various fixes for value max error handling
- New error strings for bad formatted inputs (admin name, wrong value max option)
- Email is now a email field (better for virtual keyboards) and is html required as well as title
- Advanced settings for poll are now opened if there's error within them
- css fixes for pictures inside columns, and little space between editor and description text area (@marjolaine-v)
- released zip files now have proper chmod rights (@tcit)
- Best choices now work properly when there's no votes (@mm)
- Don't allow an existing name when updating a vote (@mm)
- Keep vote selections when there's an error on the name (@mm)
- Add a message « Your poll has been created » at the end of the poll form process (@mm)
### Documentation
- Move everything to wiki, translate everything to English
### Technical
- Continuous Integration handles the release process
- Translations with Zanata : https://trad.framasoft.org/zanata/project/view/framadate (@luc)
- Style fixes with PHP-CS
- Libraries updated
- Improved a few docs
- Use own Framadate Docker Image for CI
- https://beta.framadate.org now gets the latest translations for each deployment (@luc)
- A CI job tells if translations strings are up-to-date (@luc)
## 1.0.3
- Corrections de wording (fr / en)
## Version 1.0 (Erik - Markus - Ecmu - Julien - Imre - Luc - Pierre - Antonin - Olivier)
- Amélioration : Conserver les votes en cours lors que l'utilisateur envoie un commentaire
- Amélioration : Les mails sont envoyés en multipart pour les lecteurs ne supportant pas HTML
@ -109,7 +315,7 @@ This changelog file is **deprecated**. For an up-to-date changelog, please check
- Fix : Bug à la création d'un sondage sans Javascript ou sans Cookies
- Fix : Erreur d'url avec les noms de domaine contenant "admin"
- Fix : Mise à jour de la doc d'installation
## Version 0.8 (juillet 2014 Pascal Chevrel - Armony Altinier - JosephK)
- Améliorations sur l'accessibilité
- Améliorations sur l'ergonomie
@ -143,7 +349,7 @@ This changelog file is **deprecated**. For an up-to-date changelog, please check
## Changelog des 22 et 23 juin (pyg@framasoft.net)
- très nombreuses modifications CSS
- ajout de buttons.css pour des boutons plus propres
- ajout de buttons.css pour des boutons plus propres
- ajout de print.css pour une impression sans la classe "corps"
- refonte de la page d'accueil
- ajout de la framanav
@ -209,7 +415,7 @@ This changelog file is **deprecated**. For an up-to-date changelog, please check
- Traduction de STUdS en anglais, allemand et espagnol,
- Changement de la CSS avec ajout du logo de l'Université de Strasbourg,
- Possibilité d'ajouter un commentaire pour les sondés.
Changelog version 0.4 (janvier 2009) :
- Possibilité de faire un export PDF pour envoyer la lettre de convocation à la date de réunion,
- Possibilité de rajouter des colonnes dans la partie administration de sondage,
@ -220,7 +426,7 @@ This changelog file is **deprecated**. For an up-to-date changelog, please check
- Mise en place d'un repository Subversion pour partager les nouvelles versions de STUdS,
- Amélioration de la CSS pour un meilleur affichage,
- Modification du code source pour le rendre portable vers une autre machine.
Changelog version 0.2 (novembre 2008) :
- Lors de la création d'un sondage DATE, classement des dates par ordre croissant,
- Lors de la création d'un sondage DATE, accepter les horaires au format "8h" ou "8H",

View File

@ -1,7 +0,0 @@
# Contributing
Please report issues on <https://framagit.org/framasoft/framadate/issues>
If you made a change and want it to be available in official repository, merge requests are welcome!
Read the [guidelines](https://framagit.org/framasoft/framadate/wikis/coding) to submit your changes.

View File

@ -1 +1 @@
# [Now available here](https://framagit.org/framasoft/framadate/framadate/wikis/home)
# [Now available here](https://framagit.org/framasoft/framadate/wikis/home)

View File

@ -1,8 +1,12 @@
push-locales:
zanata-cli -q -B push --errors --project-version `git branch | grep \* | cut -d ' ' -f2-`
locales:
json2po -P -i locale/en.json -t locale/en.json -o po/framadate.pot
push-locales: locales
zanata-cli -q -B push
pull-locales:
zanata-cli -q -B pull --min-doc-percent 50 --project-version `git branch | grep \* | cut -d ' ' -f2-`
zanata-cli -q -B pull --min-doc-percent 50
./.po2json.sh
stats-locales:
zanata-cli -q stats --project-version `git branch | grep \* | cut -d ' ' -f2-`
zanata-cli -q stats

View File

@ -4,26 +4,28 @@
![Français](https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Flag_of_France.svg/20px-Flag_of_France.svg.png) Framadate est un service en ligne permettant de planifier un rendez-vous ou prendre des décisions rapidement et simplement. Aucune inscription préalable nest nécessaire.
**Framadate is now in maintenance mode.** [Read more](https://framagit.org/framasoft/framadate/framadate/-/issues/545#note_920869)
---
# Installation
Follow the instructions on our Wiki : <https://framagit.org/framasoft/framadate/framadate/wikis/home>
Follow the instructions on our Wiki : <https://framagit.org/framasoft/framadate/framadate/-/wikis/home>
# Contribute
## Code
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/wikis/coding>
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/-/wikis/coding>
# Traductions
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/wikis/translating>
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/-/wikis/translating>
# Used libraries
* PHP [PHP 5.6](http://php.net)
* PHP [PHP 7.3](http://php.net)
* Templating [Smarty](http://www.smarty.net/),
* i18n [Symfony/Translation](https://github.com/symfony/Translation)
* Database: PostgreSQL 9.6 or [MySQL 5.6+](https://dev.mysql.com/downloads/mysql/5.6.html)/[MariaDB 10.1+](https://www.debian.org/releases/stable/mips/release-notes/ch-whats-new.en.html#mariadb-replaces-mysql)
* I18N [o80-i18n](https://framagit.org/framasoft/framadate/o80-i18nn)
* Database: MySQL or MariaDB.
---

View File

@ -17,7 +17,12 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Message;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\NotificationService;
use Framadate\Services\PollService;
use Framadate\Services\SecurityService;
include_once __DIR__ . '/../app/inc/init.php';
@ -34,10 +39,12 @@ $is_admin = false;
/* Services */
/*----------*/
$inputService = Services::input();
$notificationService = Services::notification();
$pollService = Services::poll();
$securityService = Services::security();
$logService = new LogService();
$pollService = new PollService($logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$notificationService = new NotificationService($mailService);
$securityService = new SecurityService();
/* PAGE */
/* ---- */
@ -55,8 +62,8 @@ if (!empty($_POST['poll_admin'])) {
}
if (!$poll) {
$message = new Message('error', __('Error', "This poll doesn't exist!"));
} else if ($poll && !$securityService->canAccessPoll($poll) && !$is_admin) {
$message = new Message('error', __('Error', 'This poll doesn\'t exist !'));
} else if (!$is_admin && !$securityService->canAccessPoll($poll)) {
$message = new Message('error', __('Password', 'Wrong password'));
} else {
$name = $inputService->filterName($_POST['name']);
@ -70,7 +77,7 @@ if (!$poll) {
// Add comment
$result = $pollService->addComment($poll_id, $name, $comment);
if ($result) {
$message = new Message('success', __('Comments', 'Comment saved'));
$message = new Message('success', __('Comments', 'Comment added'));
$notificationService->sendUpdateNotification($poll, NotificationService::ADD_COMMENT, $name);
} else {
$message = new Message('danger', __('Error', 'Comment failed'));
@ -81,8 +88,10 @@ if (!$poll) {
$smarty->error_reporting = E_ALL & ~E_NOTICE;
$smarty->assign('comments', $comments);
$smarty->assign('poll_id', $poll_id);
$smarty->assign('admin_poll_id', $admin_poll_id);
$comments_html = $smarty->fetch('part/comments_list.tpl');
$response = ['result' => $result, 'message' => $message, 'comments' => $comments_html];
echo json_encode($response);
echo json_encode($response, JSON_THROW_ON_ERROR);

View File

@ -18,14 +18,18 @@
*/
use Framadate\Message;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\PollService;
use Framadate\Services\SessionService;
use Framadate\Utils;
include_once __DIR__ . '/../app/inc/init.php';
$mailService = Services::mail();
$notificationService = Services::notification();
$pollService = Services::poll();
$sessionService = Services::session();
$logService = new LogService();
$sessionService = new SessionService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$pollService = new PollService($logService);
$result = false;
$message = null;
@ -41,9 +45,9 @@ if (!empty($_POST['poll'])) {
$token = $sessionService->get("Common", SESSION_EDIT_LINK_TOKEN);
$token_form_value = empty($_POST['token']) ? null : $_POST['token'];
$editedVoteUniqueId = filter_input(INPUT_POST, 'editedVoteUniqueId', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
if (is_null($poll) || $config['use_smtp'] === false || is_null($token) || is_null($token_form_value)
if ($config['use_smtp'] === false || is_null($poll) || is_null($token) || is_null($token_form_value)
|| !$token->check($token_form_value) || is_null($editedVoteUniqueId)) {
$message = new Message('error', __('Error', 'Something has gone wrong...'));
$message = new Message('error', __('Error', 'Something is going wrong...'));
}
if (is_null($message)) {
@ -66,7 +70,16 @@ if (is_null($message)) {
}
if (is_null($message)) {
$notificationService->sendEditedVoteNotification($email, $poll, $poll_id, $editedVoteUniqueId);
$url = Utils::getUrlSondage($poll_id, false, $editedVoteUniqueId);
$smarty->assign('poll', $poll);
$smarty->assign('poll_id', $poll_id);
$smarty->assign('editedVoteUniqueId', $editedVoteUniqueId);
$body = $smarty->fetch('mail/remember_edit_link.tpl');
$subject = '[' . NOMAPPLICATION . '][' . __('EditLink', 'REMINDER') . '] ' . __f('EditLink', 'Edit link for poll "%s"', $poll->title);
$mailService->send($email, $subject, $body);
$sessionService->remove("Common", SESSION_EDIT_LINK_TOKEN);
$sessionService->set("Common", SESSION_EDIT_LINK_TIME, time());
@ -78,4 +91,4 @@ $smarty->error_reporting = E_ALL & ~E_NOTICE;
$response = ['result' => $result, 'message' => $message];
echo json_encode($response);
echo json_encode($response, JSON_THROW_ON_ERROR);

View File

@ -20,12 +20,12 @@
use Framadate\Message;
use Framadate\Utils;
define('ROOT_DIR', __DIR__ . '/../');
const ROOT_DIR = __DIR__ . '/../';
/**
* Checking for missing vendors.
*/
if (!file_exists(ROOT_DIR . 'vendor/autoload.php')) {
if (!file_exists(ROOT_DIR . 'vendor/autoload.php') || !file_exists(ROOT_DIR . 'vendor/o80/i18n/src/shortcuts.php')) {
die ("ERROR: You should use <code>composer install</code> to fetch dependant libraries.");
}
@ -33,6 +33,7 @@ if (!file_exists(ROOT_DIR . 'vendor/autoload.php')) {
* Stripped ini sequence
*/
require_once ROOT_DIR . 'vendor/autoload.php';
require_once ROOT_DIR . 'vendor/o80/i18n/src/shortcuts.php';
require_once ROOT_DIR . 'app/inc/constants.php';
if (session_id() === '') {
session_start();
@ -45,6 +46,7 @@ $ALLOWED_LANGUAGES = [
'de' => 'Deutsch',
'it' => 'Italiano',
'br' => 'Brezhoneg',
'ca' => 'Català',
];
const DEFAULT_LANGUAGE = 'en';
require_once ROOT_DIR . 'app/inc/i18n.php';
@ -56,7 +58,7 @@ require_once ROOT_DIR . 'app/inc/i18n.php';
* @param Message $b
* @return int
*/
function compareCheckMessage(Message $a, Message $b)
function compareCheckMessage(Message $a, Message $b): int
{
$values = [
'danger' => 0,
@ -88,7 +90,7 @@ $conf_filename = $inc_directory . 'config.php';
if (version_compare(PHP_VERSION, PHP_NEEDED_VERSION) >= 0) {
$messages[] = new Message('info', __f('Check','PHP version %s is enough (needed at least PHP %s).', PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION, PHP_NEEDED_VERSION));
} else {
$messages[] = new Message('danger', __f('Check','Your PHP version (%s) is too old. This application needs at least PHP %s.', phpversion(), PHP_NEEDED_VERSION));
$messages[] = new Message('danger', __f('Check','Your PHP version (%s) is too old. This application needs at least PHP %s.', PHP_VERSION, PHP_NEEDED_VERSION));
}
// INTL extension
@ -98,6 +100,13 @@ if (extension_loaded('intl')) {
$messages[] = new Message('danger', __('Check','You need to enable the PHP Intl extension.'));
}
// mbstring extension
if (extension_loaded('mbstring')) {
$messages[] = new Message('info', __('Check','PHP mbstring extension is enabled.'));
} else {
$messages[] = new Message('danger', __('Check','You need to enable the PHP mbstring extension.'));
}
// Is template compile dir exists and writable ?
if (!file_exists(ROOT_DIR . COMPILE_DIR)) {
$messages[] = new Message('danger', __f('Check','The template compile directory (%s) doesn\'t exist in "%s". Retry the installation process.', COMPILE_DIR, realpath(ROOT_DIR)));
@ -174,16 +183,16 @@ usort($messages, 'compareCheckMessage');
<body>
<div class="container ombre">
<div class="row">
<form method="get" action="" class="hidden-print">
<form method="get" class="hidden-print">
<div class="input-group input-group-sm pull-right col-xs-12 col-sm-2">
<select name="lang" class="form-control" title="<?=__('Language selector', 'Select language')?>" >
<select name="lang" class="form-control" title="<?=__('Language selector', 'Select the language')?>" >
<?php foreach ($ALLOWED_LANGUAGES as $lang_key => $language) { ?>
<option lang="fr" <?php if (substr($lang_key, 0, 2)===$locale) { echo 'selected';} ?> value="<?=substr($lang_key, 0, 2)?>"><?=$language?></option>
<option lang="fr" <?php if (strpos($lang_key, $locale) === 0) { echo 'selected';} ?> value="<?=substr($lang_key, 0, 2)?>"><?=$language?></option>
<?php } ?>
</select>
<span class="input-group-btn">
<button type="submit" class="btn btn-default btn-sm" title="<?=__('Language selector', 'Select language')?>">OK</button>
</span>
<span class="input-group-btn">
<button type="submit" class="btn btn-default btn-sm" title="<?=__('Language selector', 'Select the language')?>">OK</button>
</span>
</div>
</form>
</div>

View File

@ -1,22 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
include_once __DIR__ . '/../app/inc/init.php';
Services::purge()->cleanDemoPoll();

91
admin/cron_purge.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* Copyright 2018 Christian P. MOMON <cmomon@april.org>
*
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
*/
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\PurgeService;
use Framadate\Services\SecurityService;
// /////////////////////////////////////////////////
// ////////// include_once __DIR__ . '/../app/inc/init.php';
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
define('ROOT_DIR', __DIR__ . '/../');
// Autoloading of dependencies with Composer
require_once ROOT_DIR . '/vendor/autoload.php';
require_once ROOT_DIR . '/vendor/o80/i18n/src/shortcuts.php';
if (ini_get('date.timezone') === '') {
date_default_timezone_set('Europe/Paris');
}
require_once ROOT_DIR . '/app/inc/constants.php';
define('CONF_FILENAME', ROOT_DIR . '/app/inc/config.php');
if (is_file(CONF_FILENAME)) {
@include_once CONF_FILENAME;
// Connection to database
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
$err = 0;
} else {
define('NOMAPPLICATION', 'Framadate');
define('DEFAULT_LANGUAGE', 'fr');
define('IMAGE_TITRE', 'images/logo-framadate.png');
define('LOG_FILE', 'admin/stdout.log');
}
require_once ROOT_DIR . '/app/inc/i18n.php';
// /////////////////////////////////////////////////
/* Variables */
/* --------- */
/* Services */
/*----------*/
$logService = new LogService();
$purgeService = new PurgeService($connect, $logService);
$securityService = new SecurityService();
$inputService = new InputService();
/* Action */
/* ------ */
$logService->log('CRON PURGE', 'Cron purge starting…');
$ended = false;
$iterationCount = 0;
$totalCount = 0;
while (!$ended)
{
$count = $purgeService->purgeOldPolls();
$logService->log('CRON PURGE', 'count='.$count);
if ($count == 0)
{
$ended = true;
}
else
{
$iterationCount += 1;
$totalCount += $count;
}
}
$logService->log('CRON PURGE', 'Purged '.$totalCount.' poll(s) in '.$iterationCount.' iterations.');
$logService->log('CRON PURGE', 'Cron purge done.');
/* PAGE */
/* ---- */
echo date("Y-m-d H:i:s").": cron purge done.\n"
?>

View File

@ -28,8 +28,8 @@ if (is_file(CONF_FILENAME)) {
}
$error = null;
$result['details'] = null;
$installService = new InstallService();
$result['details'] = null;
if (!empty($_POST)) {
$installService->updateFields($_POST);

View File

@ -17,64 +17,113 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Migration;
use Doctrine\DBAL\Migrations\OutputWriter;
use Doctrine\DBAL\Migrations\Tools\Console\Helper\MigrationStatusInfosHelper;
use Framadate\FramaDB;
use Framadate\Migration\AddColumn_hidden_In_poll_For_0_9;
use Framadate\Migration\AddColumn_receiveNewComments_For_0_9;
use Framadate\Migration\AddColumn_uniqId_In_vote_For_0_9;
use Framadate\Migration\AddColumn_ValueMax_In_poll_For_1_1;
use Framadate\Migration\AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9;
use Framadate\Migration\Alter_Comment_table_adding_date;
use Framadate\Migration\Alter_Comment_table_for_name_length;
use Framadate\Migration\Fix_MySQL_No_Zero_Date;
use Framadate\Migration\From_0_0_to_0_8_Migration;
use Framadate\Migration\From_0_8_to_0_9_Migration;
use Framadate\Migration\Generate_uniqId_for_old_votes;
use Framadate\Migration\Increase_pollId_size;
use Framadate\Migration\Migration;
use Framadate\Migration\RPadVotes_from_0_8;
use Framadate\Utils;
require_once __DIR__ . '/../app/inc/init.php';
include_once __DIR__ . '/../app/inc/init.php';
class MigrationLogger {
private $log;
set_time_limit(300);
public function __construct()
{
$this->log = '';
// List a Migration sub classes to execute
$migrations = [
new From_0_0_to_0_8_Migration(),
new From_0_8_to_0_9_Migration(),
new AddColumn_receiveNewComments_For_0_9(),
new AddColumn_uniqId_In_vote_For_0_9(),
new AddColumn_hidden_In_poll_For_0_9(),
new AddColumn_ValueMax_In_poll_For_1_1(),
new Generate_uniqId_for_old_votes(),
new RPadVotes_from_0_8(),
new Alter_Comment_table_for_name_length(),
new Alter_Comment_table_adding_date(),
new AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9(),
new Increase_pollId_size(),
new AddColumn_ValueMax_In_poll_For_1_1(),
new Fix_MySQL_No_Zero_Date(),
];
// ---------------------------------------
// Check if MIGRATION_TABLE already exists
/** @var FramaDB $connect */
$tables = $connect->allTables();
$pdo = $connect->getPDO();
$prefixedMigrationTable = Utils::table(MIGRATION_TABLE);
if (!in_array($prefixedMigrationTable, $tables, true)) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . $prefixedMigrationTable . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
`execute_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)
ENGINE = MyISAM
DEFAULT CHARSET = utf8;');
}
$selectStmt = $pdo->prepare('SELECT id FROM `' . $prefixedMigrationTable . '` WHERE name=?');
$insertStmt = $pdo->prepare('INSERT INTO `' . $prefixedMigrationTable . '` (name) VALUES (?)');
$countSucceeded = 0;
$countFailed = 0;
$countSkipped = 0;
// Loop on every Migration sub classes
$success = [];
$fail = [];
foreach ($migrations as $migration) {
$className = get_class($migration);
// Check if $className is a Migration sub class
if (!$migration instanceof Migration) {
$smarty->assign('error', 'The class ' . $className . ' is not a sub class of Framadate\\Migration\\Migration.');
$smarty->display('error.tpl');
exit;
}
public function addLine($message)
{
$this->log .= $message . "\n";
}
// Check if the Migration is already executed
$selectStmt->execute([$className]);
$executed = $selectStmt->rowCount();
$selectStmt->closeCursor();
public function getLog()
{
return $this->log;
if (!$executed && $migration->preCondition($pdo)) {
$migration->execute($pdo);
if ($insertStmt->execute([$className])) {
$countSucceeded++;
$success[] = $migration->description();
} else {
$countFailed++;
$fail[] = $migration->description();
}
} else {
$countSkipped++;
}
}
$executing = false;
$migration = null;
$output = '';
$countTotal = $countSucceeded + $countFailed + $countSkipped;
if (isset($_POST['execute'])) {
$executing = true;
}
$smarty->assign('success', $success);
$smarty->assign('fail', $fail);
$migrationsDirectory = __DIR__ . '/../app/classes/Framadate/Migrations';
$log = new MigrationLogger();
$smarty->assign('countSucceeded', $countSucceeded);
$smarty->assign('countFailed', $countFailed);
$smarty->assign('countSkipped', $countSkipped);
$smarty->assign('countTotal', $countTotal);
$smarty->assign('time', $total_time = round((microtime(true)-$_SERVER['REQUEST_TIME_FLOAT']), 4));
$configuration = new Configuration($connect, new OutputWriter(function ($message) use ($log) {
$log->addLine($message);
}));
$configuration->setMigrationsTableName(Utils::table(MIGRATION_TABLE) . '_new');
$configuration->setMigrationsDirectory($migrationsDirectory);
$configuration->setMigrationsNamespace('DoctrineMigrations');
$configuration->registerMigrationsFromDirectory($migrationsDirectory);
if ($executing) {
$migration = new Migration($configuration);
$migration->migrate();
$output = trim(strip_tags($log->getLog()));
}
$infos = (new MigrationStatusInfosHelper($configuration))->getMigrationsInfos();
$smarty->assign('countTotal', $infos['Available Migrations']);
$smarty->assign('countExecuted', $infos['Executed Migrations']);
$smarty->assign('countWaiting', $infos['New Migrations']);
$smarty->assign('executing', $executing);
$smarty->assign('title', __('Admin', 'Migration'));
$smarty->assign('output', $output);
$smarty->assign('time', round((microtime(true)-$_SERVER['REQUEST_TIME_FLOAT']), 4));
$smarty->display('admin/migration.tpl');

View File

@ -17,6 +17,10 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Services\AdminPollService;
use Framadate\Services\LogService;
use Framadate\Services\PollService;
use Framadate\Services\SecurityService;
use Framadate\Services\SuperAdminService;
include_once __DIR__ . '/../app/inc/init.php';
@ -45,10 +49,11 @@ $poll_to_delete = null;
/* Services */
/*----------*/
$adminPollService = Services::adminPoll();
$pollService = Services::poll();
$securityService = Services::security();
$logService = new LogService();
$pollService = new PollService($logService);
$adminPollService = new AdminPollService($connect, $pollService, $logService);
$superAdminService = new SuperAdminService();
$securityService = new SecurityService();
/* GET */
/*-----*/

View File

@ -17,6 +17,11 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\PurgeService;
use Framadate\Services\SecurityService;
include_once __DIR__ . '/../app/inc/init.php';
include_once __DIR__ . '/../bandeaux.php';
@ -28,14 +33,15 @@ $message = null;
/* Services */
/*----------*/
$inputService = Services::input();
$purgeService = Services::purge();
$securityService = Services::security();
$logService = new LogService();
$purgeService = new PurgeService($logService);
$securityService = new SecurityService();
$inputService = new InputService();
/* POST */
/*-----*/
$action = $inputService->filterName(isset($_POST['action']) ? $_POST['action'] : null);
$action = $inputService->filterName($_POST['action'] ?? null);
/* PAGE */
/* ---- */

View File

@ -16,17 +16,20 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Doctrine\DBAL\DBALException;
use Framadate\Editable;
use Framadate\Exception\AlreadyExistsException;
use Framadate\Exception\ConcurrentEditionException;
use Framadate\Exception\ConcurrentVoteException;
use Framadate\Exception\MomentAlreadyExistsException;
use Framadate\Exception\SlotAlreadyExistsException;
use Framadate\Message;
use Framadate\Security\PasswordHasher;
use Framadate\Services\AdminPollService;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\NotificationService;
use Framadate\Services\PollService;
use Framadate\Services\SessionService;
use Framadate\Utils;
include_once __DIR__ . '/app/inc/init.php';
@ -43,11 +46,13 @@ $editingVoteId = 0;
/* Services */
/*----------*/
$adminPollService = Services::adminPoll();
$inputService = Services::input();
$notificationService = Services::notification();
$pollService = Services::poll();
$sessionService = Services::session();
$logService = new LogService();
$pollService = new PollService($logService);
$adminPollService = new AdminPollService($connect, $pollService, $logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$notificationService = new NotificationService($mailService);
$sessionService = new SessionService();
/* PAGE */
/* ---- */
@ -62,7 +67,7 @@ if (!empty($_GET['poll'])) {
if ($poll) {
$poll_id = $poll->id;
} else {
$smarty->assign('error', __('Error', "This poll doesn't exist!"));
$smarty->assign('error', __('Error', 'This poll doesn\'t exist !'));
$smarty->display('error.tpl');
exit;
}
@ -76,7 +81,7 @@ $messagePollCreated = $sessionService->get("Framadate", "messagePollCreated", FA
if ($messagePollCreated) {
$sessionService->remove("Framadate", "messagePollCreated");
$message = new Message('success', __('adminstuds', 'The poll was created.'));
$message = new Message('success', __('adminstuds', 'The poll is created.'));
}
// -------------------------------
@ -108,7 +113,7 @@ if (isset($_POST['update_poll_info'])) {
$updated = true;
}
} elseif ($field === 'rules') {
$rules = strip_tags($_POST['rules']);
$rules = (int) strip_tags($_POST['rules']);
switch ($rules) {
case 0:
$poll->active = false;
@ -132,38 +137,41 @@ if (isset($_POST['update_poll_info'])) {
break;
}
} elseif ($field === 'expiration_date') {
$expiration_date = $inputService->filterDate($_POST['expiration_date']);
if ($expiration_date) {
$poll->end_date = $expiration_date;
$givenExpirationDate = $inputService->parseDate($_POST['expiration_date']);
$expiration_date = $inputService->validateDate($givenExpirationDate, $pollService->minExpiryDate(), $pollService->maxExpiryDate());
if ($poll->end_date !== $expiration_date->format('Y-m-d H:i:s')) {
$poll->end_date = $expiration_date->format('Y-m-d H:i:s');
$updated = true;
}
} elseif ($field === 'name') {
$admin_name = $inputService->filterName($_POST['name']);
$admin_name = $_POST['name'];
$admin_name = mb_substr($admin_name, 0, 32);
$admin_name = $inputService->filterName($admin_name);
if ($admin_name) {
$poll->admin_name = $admin_name;
$updated = true;
}
} elseif ($field === 'hidden') {
$hidden = isset($_POST['hidden']) ? $inputService->filterBoolean($_POST['hidden']) : false;
$hidden = isset($_POST['hidden']) && $inputService->filterBoolean($_POST['hidden']);
if ($hidden !== $poll->hidden) {
$poll->hidden = $hidden;
$poll->results_publicly_visible = false;
$updated = true;
}
} elseif ($field === 'removePassword') {
$removePassword = isset($_POST['removePassword']) ? $inputService->filterBoolean($_POST['removePassword']) : false;
$removePassword = isset($_POST['removePassword']) && $inputService->filterBoolean($_POST['removePassword']);
if ($removePassword) {
$poll->results_publicly_visible = false;
$poll->password_hash = null;
$updated = true;
}
} elseif ($field === 'password') {
$password = isset($_POST['password']) ? $_POST['password'] : null;
$password = $_POST['password'] ?? null;
/**
* Did the user choose results to be publicly visible ?
*/
$resultsPubliclyVisible = isset($_POST['resultsPubliclyVisible']) ? $inputService->filterBoolean($_POST['resultsPubliclyVisible']) : false;
$resultsPubliclyVisible = isset($_POST['resultsPubliclyVisible']) && $inputService->filterBoolean($_POST['resultsPubliclyVisible']);
/**
* If there's one, save the password
*/
@ -214,17 +222,12 @@ $selectedNewVotes = [];
if (!empty($_POST['save'])) { // Save edition of an old vote
$name = $inputService->filterName($_POST['name']);
if(empty($_POST['mail']) || $inputService->filterMail($_POST['mail'])===false) {
$mail = null;
} else {
$mail = $inputService->filterMail($_POST['mail']);
}
$editedVote = filter_input(INPUT_POST, 'save', FILTER_VALIDATE_INT);
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
$slots_hash = $inputService->filterMD5($_POST['control']);
if (empty($editedVote)) {
$message = new Message('danger', __('Error', 'Something has gone wrong...'));
$message = new Message('danger', __('Error', 'Something is going wrong...'));
}
if (count($choices) !== count($_POST['choices'])) {
$message = new Message('danger', __('Error', 'There is a problem with your choices'));
@ -233,14 +236,14 @@ if (!empty($_POST['save'])) { // Save edition of an old vote
if ($message === null) {
// Update vote
try {
$result = $pollService->updateVote($poll_id, $editedVote, $name, $choices, $slots_hash, $mail);
$result = $pollService->updateVote($poll_id, $editedVote, $name, $choices, $slots_hash);
if ($result) {
$message = new Message('success', __('adminstuds', 'Vote updated'));
} else {
$message = new Message('danger', __('Error', 'Update vote failed'));
}
} catch (AlreadyExistsException $aee) {
$message = new Message('danger', __('Error', "The name you've chosen already exists in this poll!"));
$message = new Message('danger', __('Error', 'The name you\'ve chosen already exist in this poll!'));
} catch (ConcurrentEditionException $cee) {
$message = new Message('danger', __('Error', 'Poll has been updated before you vote'));
} catch (ConcurrentVoteException $cve) {
@ -249,11 +252,6 @@ if (!empty($_POST['save'])) { // Save edition of an old vote
}
} elseif (isset($_POST['save'])) { // Add a new vote
$name = $inputService->filterName($_POST['name']);
if(empty($_POST['mail']) || $inputService->filterMail($_POST['mail'])===false) {
$mail = null;
} else {
$mail = $inputService->filterMail($_POST['mail']);
}
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
$slots_hash = $inputService->filterMD5($_POST['control']);
@ -267,7 +265,7 @@ if (!empty($_POST['save'])) { // Save edition of an old vote
if ($message === null) {
// Add vote
try {
$result = $pollService->addVote($poll_id, $name, $choices, $slots_hash, $mail);
$result = $pollService->addVote($poll_id, $name, $choices, $slots_hash);
if ($result) {
$message = new Message('success', __('adminstuds', 'Vote added'));
} else {
@ -397,43 +395,12 @@ if (isset($_GET['delete_column'])) {
}
if ($result) {
$message = new Message('success', __('adminstuds', 'Column deleted'));
$message = new Message('success', __('adminstuds', 'Column removed'));
} else {
$message = new Message('danger', __('Error', 'Failed to delete column'));
}
}
// -------------------------------
// Collect the mails of a column
// -------------------------------
if (isset($_GET['collect_mail'])) {
$column_str = strval(filter_input(INPUT_GET, 'collect_mail', FILTER_DEFAULT));
$column_str = strval(Utils::base64url_decode($column_str));
$column = intval($column_str);
$votes = $pollService->splitVotes($pollService->allVotesByPollId($poll_id));
$mails_yes = $mails_ifneedbe = $mails_no = [];
foreach ($votes as $vote) {
if (intval($vote->choices[$column]) === 2 && $vote->mail !== NULL) {
$mails_yes[] = $vote->mail;
} elseif (intval($vote->choices[$column]) === 1 && $vote->mail !== NULL) {
$mails_ifneedbe[] = $vote->mail;
} elseif($vote->mail !== NULL) {
$mails_no[] = $vote->mail;
}
}
$smarty->assign('poll_id', $poll_id);
$smarty->assign('admin_poll_id', $admin_poll_id);
$smarty->assign('admin', true);
$smarty->assign('title', __('Generic', 'Poll') . ' - ' . $poll->title . ' - ' . __('adminstuds', 'Collect the emails of the polled users for the choice'));
$smarty->assign('mails_yes', $mails_yes);
$smarty->assign('mails_ifneedbe', $mails_ifneedbe);
$smarty->assign('mails_no', $mails_no);
$smarty->display('display_mails.tpl');
exit;
}
// -------------------------------
// Add a slot
// -------------------------------
@ -460,22 +427,18 @@ if (isset($_POST['confirm_add_column'])) {
exit_displaying_add_column(new Message('danger', __('Error', "Can't create an empty column.")));
}
if ($poll->format === 'D') {
$date = $inputService->filterDate($_POST['newdate']);
$date = DateTime::createFromFormat(__('Date', 'datetime_parseformat'), $_POST['newdate'])->setTime(0, 0, 0);
$time = $date->getTimestamp();
$newmoment = strip_tags($_POST['newmoment']);
$newmoment = str_replace(',', '-', strip_tags($_POST['newmoment']));
$adminPollService->addDateSlot($poll_id, $time, $newmoment);
} else {
$newslot = strip_tags($_POST['choice']);
$newslot = str_replace(',', '-', strip_tags($_POST['choice']));
$adminPollService->addClassicSlot($poll_id, $newslot);
}
$message = new Message('success', __('adminstuds', 'Choice added'));
} catch (SlotAlreadyExistsException $e) {
exit_displaying_add_column(new Message('danger', __f('Error', 'The column %s already exists', $e->getSlot())));
} catch (MomentAlreadyExistsException $e) {
exit_displaying_add_column(new Message('danger', __f('Error', 'The column %s already exists with %s', $e->getSlot(), $e->getMoment())));
} catch (DBALException $e) {
exit_displaying_add_column(new Message('danger', __('Error', 'Error while adding a column')));
exit_displaying_add_column(new Message('danger', __('Error', 'The column already exists')));
}
}
@ -483,16 +446,14 @@ if (isset($_POST['confirm_add_column'])) {
$slots = $pollService->allSlotsByPoll($poll);
$votes = $pollService->allVotesByPollId($poll_id);
$comments = $pollService->allCommentsByPollId($poll_id);
$deletion_date = clone $poll->end_date;
$deletion_date->add(new DateInterval('P' . PURGE_DELAY . 'D'));
// Assign data to template
$smarty->assign('poll_id', $poll_id);
$smarty->assign('admin_poll_id', $admin_poll_id);
$smarty->assign('poll', $poll);
$smarty->assign('title', __('Generic', 'Poll') . ' - ' . $poll->title);
$smarty->assign('expired', $poll->end_date < new DateTime());
$smarty->assign('deletion_date', $deletion_date);
$smarty->assign('expired', strtotime($poll->end_date) < time());
$smarty->assign('deletion_date', strtotime($poll->end_date) + PURGE_DELAY * 86400);
$smarty->assign('slots', $poll->format === 'D' ? $pollService->splitSlots($slots) : $slots);
$smarty->assign('slots_hash', $pollService->hashSlots($slots));
$smarty->assign('votes', $pollService->splitVotes($votes));

View File

@ -1,54 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate;
use Doctrine\DBAL\Migrations\AbstractMigration as DoctrineAbstractMigration;
use Doctrine\DBAL\Schema\Schema;
abstract class AbstractMigration extends DoctrineAbstractMigration
{
/**
* @param Schema $schema
* @param $class
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @return bool
*/
public function legacyCheck(Schema $schema, $class)
{
/**
* If there's no legacy table, we can go on
*/
if (!$schema->hasTable(Utils::table(MIGRATION_TABLE))) {
return false;
}
$migration_table = $schema->getTable(Utils::table(MIGRATION_TABLE));
/**
* We check the migration table
*/
if ($migration_table->hasColumn('name')) {
/** @var $stmt \Doctrine\DBAL\Driver\Statement */
$stmt = $this->connection->prepare('SELECT * FROM ' . Utils::table(MIGRATION_TABLE) . ' WHERE name = ?');
$stmt->execute([$class]);
return $stmt->rowCount() > 0;
}
return false;
}
}

View File

@ -26,32 +26,32 @@ class Choice
private $name;
/**
* All available slots for this Choice.
* All availables slots for this Choice.
*/
private $slots;
public function __construct($name = '')
public function __construct($name='')
{
$this->name = $name;
$this->slots = [];
}
public function addSlot($slot)
public function addSlot($slot): void
{
$this->slots[] = $slot;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function getSlots()
public function getSlots(): array
{
return $this->slots;
}
public static function compare(Choice $a, Choice $b)
public static function compare(Choice $a, Choice $b): int
{
return strcmp($a->name, $b->name);
}

View File

@ -1,35 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate;
/**
* Class CollectMail
*
* Is used to specify the poll's edition permissions.
* @TODO : wait to use the SplEnum
*
* @package Framadate
*/
class CollectMail { // extends SplEnum
const NO_COLLECT = 0;
const COLLECT = 1;
const COLLECT_REQUIRED = 2;
const COLLECT_REQUIRED_VERIFIED = 3;
}

View File

@ -23,14 +23,13 @@ namespace Framadate;
* Class Editable
*
* Is used to specify the poll's edition permissions.
* @TODO : wait to use the SplEnum
*
* @package Framadate
*/
class Editable { // extends SplEnum
const __default = self::EDITABLE_BY_ALL;
const NOT_EDITABLE = 0;
const EDITABLE_BY_ALL = 1;
const EDITABLE_BY_OWN = 2;
}
public const NOT_EDITABLE = 0;
public const EDITABLE_BY_ALL = 1;
public const EDITABLE_BY_OWN = 2;
}

View File

@ -2,6 +2,4 @@
namespace Framadate\Exception;
class AlreadyExistsException extends \Exception {
function __construct() {
}
}

View File

@ -2,6 +2,4 @@
namespace Framadate\Exception;
class ConcurrentEditionException extends \Exception {
function __construct() {
}
}

View File

@ -7,6 +7,4 @@ namespace Framadate\Exception;
* Thrown when a poll has a maximum votes constraint for options, and a vote happened since the poll was rendered
*/
class ConcurrentVoteException extends \Exception {
function __construct() {
}
}

View File

@ -1,20 +1,5 @@
<?php
namespace Framadate\Exception;
class MomentAlreadyExistsException extends SlotAlreadyExistsException {
public $moment;
public function __construct($slot, $moment, $message = '')
{
parent::__construct($slot, $message);
$this->moment = $moment;
}
/**
* @return mixed
*/
public function getMoment()
{
return $this->moment;
}
class MomentAlreadyExistsException extends \Exception {
}

View File

@ -0,0 +1,10 @@
<?php
namespace Framadate\Exception;
/**
* Class PollNotFoundException
*
* Thrown when a poll isn't found in a critical process
*/
class PollNotFoundException extends \Exception {
}

View File

@ -1,20 +0,0 @@
<?php
namespace Framadate\Exception;
class SlotAlreadyExistsException extends \Exception {
public $slot;
public function __construct($slot, $message = '')
{
parent::__construct($message);
$this->slot = $slot;
}
/**
* @return mixed
*/
public function getSlot()
{
return $this->slot;
}
}

View File

@ -18,8 +18,6 @@
*/
namespace Framadate;
use DateTime;
class Form
{
public $title;
@ -28,24 +26,13 @@ class Form
public $admin_name;
public $admin_mail;
public $format;
/**
* @var DateTime
*/
public $end_date;
/**
* @var DateTime
*/
public $creation_date;
public $choix_sondage;
public $ValueMax;
public $errors = [];
/**
* Tells if users can modify their choices.
* @var \Framadate\Editable
* @var int
*/
public $editable;
@ -95,29 +82,22 @@ class Form
*/
public $results_publicly_visible;
/**
* Tells if voters email addresses are collected or not.
* @var \Framadate\CollectMail
*/
public $collect_users_mail;
/**
* List of available choices
*/
private $choices;
public function __construct()
{
public function __construct(){
$this->editable = Editable::EDITABLE_BY_ALL;
$this->collect_users_mail = CollectMail::NO_COLLECT;
$this->clearChoices();
}
public function clearChoices() {
public function clearChoices(): void
{
$this->choices = [];
}
public function addChoice(Choice $choice)
public function addChoice(Choice $choice): void
{
$this->choices[] = $choice;
}
@ -127,8 +107,8 @@ class Form
return $this->choices;
}
public function sortChoices()
public function sortChoices(): void
{
usort($this->choices, ['Framadate\Choice', 'compare']);
usort($this->choices, [Choice::class, 'compare']);
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate;
use PDO;
class FramaDB {
/**
* PDO Object, connection to database.
* @var PDO
*/
private $pdo;
public function __construct(string $connection_string, string $user, string $password) {
$this->pdo = new PDO($connection_string, $user, $password);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
/**
* @return PDO Connection to database
*/
public function getPDO(): PDO
{
return $this->pdo;
}
/**
* Find all tables in database.
*
* @return array The array of table names
*/
public function allTables(): array
{
return $this->pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @return \PDOStatement|false
*/
public function prepare(string $sql) {
return $this->pdo->prepare($sql);
}
public function beginTransaction(): void
{
$this->pdo->beginTransaction();
}
public function commit(): void
{
$this->pdo->commit();
}
public function rollback(): void
{
$this->pdo->rollback();
}
public function errorCode(): ?string {
return $this->pdo->errorCode();
}
public function errorInfo(): array
{
return $this->pdo->errorInfo();
}
/**
* @return \PDOStatement|false
*/
public function query($sql) {
return $this->pdo->query($sql);
}
public function lastInsertId(): string {
return $this->pdo->lastInsertId();
}
}

View File

@ -1,38 +1,37 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate;
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate;
class Message {
var $type;
var $message;
var $link;
var $linkTitle;
var $linkIcon;
var $includeTemplate;
function __construct($type, $message, $link=null, $linkTitle=null, $linkIcon=null, $includeTemplate=null) {
$this->type = $type;
$this->message = $message;
$this->link = $link;
$this->linkTitle = $linkTitle;
$this->linkIcon = $linkIcon;
var $type;
var $message;
var $link;
var $linkTitle;
var $linkIcon;
var $includeTemplate;
public function __construct($type, $message, $link=null, $linkTitle=null, $linkIcon=null, $includeTemplate=null) {
$this->type = $type;
$this->message = $message;
$this->link = $link;
$this->linkTitle = $linkTitle;
$this->linkIcon = $linkIcon;
$this->includeTemplate = $includeTemplate;
}
}
}

View File

@ -16,11 +16,10 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
namespace Framadate\Migration;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field Value_Max on the poll table.
@ -28,38 +27,46 @@ use Framadate\Utils;
* @package Framadate\Migration
* @version 0.9
*/
class Version20180220000000 extends AbstractMigration
{
class AddColumn_ValueMax_In_poll_For_1_1 implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
public function description():string {
return 'Add column "ValueMax" in table "vote" for version 0.9';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\DBALException
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumn_ValueMax_In_poll_For_1_1'), 'Migration has been executed in an earlier database migration system');
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->addColumn('ValueMax', 'smallint', ['default' => null, 'notnull' => false]);
public function preCondition(PDO $pdo): bool {
return true;
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function down(Schema $schema)
public function execute(PDO $pdo): bool {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->dropColumn('ValueMax');
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `ValueMax` TINYINT NULL;');
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field hidden on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class AddColumn_hidden_In_poll_For_0_9 implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string
{
return 'Add column "hidden" in table "vote" for version 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool
{
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool
{
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `hidden` TINYINT( 1 ) NOT NULL DEFAULT "0"');
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field receiveNewComments on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class AddColumn_receiveNewComments_For_0_9 implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string
{
return 'Add column "receiveNewComments" for version 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool
{
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool
{
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `receiveNewComments` TINYINT(1) DEFAULT \'0\'
AFTER `receiveNewVotes`');
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field uniqId on the vote table.
*
* @package Framadate\Migration
* @version 0.9
*/
class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string {
return 'Add column "uniqId" in table "vote" for version 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('vote') . '`
ADD `uniqId` CHAR(16) NOT NULL
AFTER `id`,
ADD INDEX (`uniqId`) ;');
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the fields password_hash and results_publicly_visible on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
function description(): string {
return 'Add columns "password_hash" and "results_publicly_visible" in table "vote" for version 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `password_hash` VARCHAR(255) NULL DEFAULT NULL ,
ADD `results_publicly_visible` TINYINT(1) NULL DEFAULT NULL');
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration alter the comment table to add a date column.
*
* @package Framadate\Migration
* @version 1.0
*/
class Alter_Comment_table_adding_date implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description():string {
return 'Alter the comment table to add a date column.';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
return true;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$this->alterCommentTable($pdo);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
ADD `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ;');
}
}

View File

@ -16,12 +16,10 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
namespace Framadate\Migration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use Framadate\AbstractMigration;
use Framadate\Utils;
use PDO;
/**
* This migration alter the comment table to set a length to the name column.
@ -29,43 +27,46 @@ use Framadate\Utils;
* @package Framadate\Migration
* @version 1.0
*/
class Version20151012075900 extends AbstractMigration
{
class Alter_Comment_table_for_name_length implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
public function description(): string {
return 'Alter the comment table to set a length to the name column.';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\Alter_Comment_table_for_name_length'), 'Migration has been executed in an earlier database migration system');
$commentTable = $schema->getTable(Utils::table('comment'));
$commentTable->changeColumn('name', ['default' => null, 'notnull' => false]);
$commentTable->changeColumn('name', ['type' => Type::getType('string'), 'length' => 64, 'notnull' => true]);
public function preCondition(PDO $pdo): bool {
return true;
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function down(Schema $schema)
{
$commentTable = $schema->getTable(Utils::table('comment'));
public function execute(PDO $pdo): bool {
$this->alterCommentTable($pdo);
$commentTable->changeColumn('name', ['type' => Type::getType('string')]);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
CHANGE `name` `name` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ;');
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration sets Poll.end_date to NULL by default
*
* @package Framadate\Migration
* @version 1.1
*/
class Fix_MySQL_No_Zero_Date implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string {
return 'Sets Poll end_date to NULL by default (work around MySQL NO_ZERO_DATE)';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true if the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->prepare("SELECT Column_Default from Information_Schema.Columns where Table_Name = ? AND Column_Name = ?;");
$stmt->bindValue(1, Utils::table('poll'));
$stmt->bindValue(2, 'end_date');
$stmt->execute();
$default = $stmt->fetch(PDO::FETCH_COLUMN);
$driver_name = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
return $default !== null && $driver_name === 'mysql';
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool if the execution succeeded
*/
public function execute(PDO $pdo): bool {
$pdo->exec('ALTER TABLE ' . Utils::table('poll') . ' MODIFY end_date TIMESTAMP NULL DEFAULT NULL;');
return true;
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* Class From_0_0_to_0_8_Migration
*
* @package Framadate\Migration
* @version 0.8
*/
class From_0_0_to_0_8_Migration implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string {
return 'First installation of the Framadate application (v0.8)';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES like \'' . TABLENAME_PREFIX . '%\''); //issue187 : pouvoir installer framadate dans une base contenant d'autres tables.
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if there is no tables but the MIGRATION_TABLE one
$diff = array_diff($tables, [Utils::table(MIGRATION_TABLE)]);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `sondage` (
`id_sondage` char(16) NOT NULL,
`commentaires` text,
`mail_admin` varchar(128) DEFAULT NULL,
`nom_admin` varchar(64) DEFAULT NULL,
`titre` text,
`id_sondage_admin` char(24) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`date_fin` timestamp NULL DEFAULT NULL,
`format` varchar(2) DEFAULT NULL,
`mailsonde` tinyint(1) DEFAULT \'0\',
`statut` int(11) NOT NULL DEFAULT \'1\' COMMENT \'1 = actif ; 0 = inactif ; \',
UNIQUE KEY `id_sondage` (`id_sondage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;');
$pdo->exec('
CREATE TABLE IF NOT EXISTS `sujet_studs` (
`id_sondage` char(16) NOT NULL,
`sujet` text,
KEY `id_sondage` (`id_sondage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;');
$pdo->exec('
CREATE TABLE IF NOT EXISTS `comments` (
`id_comment` int(11) unsigned NOT NULL AUTO_INCREMENT,
`id_sondage` char(16) NOT NULL,
`comment` text NOT NULL,
`usercomment` text,
PRIMARY KEY (`id_comment`),
KEY `id_sondage` (`id_sondage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;');
$pdo->exec('
CREATE TABLE IF NOT EXISTS `user_studs` (
`id_users` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nom` varchar(64) NOT NULL,
`id_sondage` char(16) NOT NULL,
`reponses` text NOT NULL,
PRIMARY KEY (`id_users`),
KEY `id_sondage` (`id_sondage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;');
return true;
}
}

View File

@ -0,0 +1,304 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This class executes the aciton in database to migrate data from version 0.8 to 0.9.
*
* @package Framadate\Migration
* @version 0.9
*/
class From_0_8_to_0_9_Migration implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string {
return 'From 0.8 to 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.8 are presents
$diff = array_diff(['sondage', 'sujet_studs', 'comments', 'user_studs'], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$this->createPollTable($pdo);
$this->createCommentTable($pdo);
$this->createSlotTable($pdo);
$this->createVoteTable($pdo);
$pdo->beginTransaction();
$this->migrateFromSondageToPoll($pdo);
$this->migrateFromCommentsToComment($pdo);
$this->migrateFromSujetStudsToSlot($pdo);
$this->migrateFromUserStudsToVote($pdo);
$pdo->commit();
$this->dropOldTables($pdo);
return true;
}
private function createPollTable(PDO $pdo): void
{
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('poll') . '` (
`id` CHAR(16) NOT NULL,
`admin_id` CHAR(24) NOT NULL,
`title` TEXT NOT NULL,
`description` TEXT,
`admin_name` VARCHAR(64) DEFAULT NULL,
`admin_mail` VARCHAR(128) DEFAULT NULL,
`creation_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`end_date` TIMESTAMP NULL DEFAULT NULL,
`format` VARCHAR(1) DEFAULT NULL,
`editable` TINYINT(1) DEFAULT \'0\',
`receiveNewVotes` TINYINT(1) DEFAULT \'0\',
`active` TINYINT(1) DEFAULT \'1\',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8');
}
private function migrateFromSondageToPoll(PDO $pdo): void
{
$select = $pdo->query('
SELECT
`id_sondage`,
`id_sondage_admin`,
`titre`,
`commentaires`,
`nom_admin`,
`mail_admin`,
`date_creation`,
`date_fin`,
SUBSTR(`format`, 1, 1) AS `format`,
CASE SUBSTR(`format`, 2, 1)
WHEN \'+\' THEN 1
ELSE 0 END AS `editable`,
`mailsonde`,
CASE SUBSTR(`format`, 2, 1)
WHEN \'-\' THEN 0
ELSE 1 END AS `active`
FROM sondage');
$insert = $pdo->prepare('
INSERT INTO `' . Utils::table('poll') . '`
(`id`, `admin_id`, `title`, `description`, `admin_name`, `admin_mail`, `creation_date`, `end_date`, `format`, `editable`, `receiveNewVotes`, `active`)
VALUE (?,?,?,?,?,?,?,?,?,?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$row->id_sondage_admin,
$this->unescape($row->titre),
$this->unescape($row->commentaires),
$this->unescape($row->nom_admin),
$this->unescape($row->mail_admin),
$row->date_creation,
$row->date_fin,
$row->format,
$row->editable,
$row->mailsonde,
$row->active
]);
}
}
private function createSlotTable(PDO $pdo): void
{
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('slot') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`poll_id` CHAR(16) NOT NULL,
`title` TEXT,
`moments` TEXT,
PRIMARY KEY (`id`),
KEY `poll_id` (`poll_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8');
}
private function migrateFromSujetStudsToSlot(PDO $pdo): void
{
$stmt = $pdo->query('SELECT * FROM sujet_studs');
$sujets = $stmt->fetchAll();
$slots = [];
foreach ($sujets as $sujet) {
$newSlots = $this->transformSujetToSlot($sujet);
foreach ($newSlots as $newSlot) {
$slots[] = $newSlot;
}
}
$prepared = $pdo->prepare('INSERT INTO ' . Utils::table('slot') . ' (`poll_id`, `title`, `moments`) VALUE (?,?,?)');
foreach ($slots as $slot) {
$prepared->execute([
$slot->poll_id,
$this->unescape($slot->title),
!empty($slot->moments) ? $this->unescape($slot->moments) : null
]);
}
}
private function createCommentTable(PDO $pdo): void
{
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('comment') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`poll_id` CHAR(16) NOT NULL,
`name` TEXT,
`comment` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `poll_id` (`poll_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8');
}
private function migrateFromCommentsToComment(PDO $pdo): void
{
$select = $pdo->query('
SELECT
`id_sondage`,
`usercomment`,
`comment`
FROM `comments`');
$insert = $pdo->prepare('
INSERT INTO `' . Utils::table('comment') . '` (`poll_id`, `name`, `comment`)
VALUE (?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->usercomment),
$this->unescape($row->comment)
]);
}
}
private function createVoteTable(PDO $pdo): void
{
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('vote') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`poll_id` CHAR(16) NOT NULL,
`name` VARCHAR(64) NOT NULL,
`choices` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `poll_id` (`poll_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8');
}
private function migrateFromUserStudsToVote(PDO $pdo): void
{
$select = $pdo->query('
SELECT
`id_sondage`,
`nom`,
REPLACE(REPLACE(REPLACE(`reponses`, 1, \'X\'), 2, 1), \'X\', 2) reponses
FROM `user_studs`');
$insert = $pdo->prepare('
INSERT INTO `' . Utils::table('vote') . '` (`poll_id`, `name`, `choices`)
VALUE (?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->nom),
$row->reponses
]);
}
}
private function transformSujetToSlot($sujet): array
{
$slots = [];
$ex = explode(',', $sujet->sujet);
$isDatePoll = strpos($sujet->sujet, '@');
$lastSlot = null;
foreach ($ex as $atomicSlot) {
if ($isDatePoll === false) { // Classic poll
$slot = new \stdClass();
$slot->poll_id = $sujet->id_sondage;
$slot->title = $atomicSlot;
$slots[] = $slot;
} else { // Date poll
$values = explode('@', $atomicSlot);
if ($lastSlot === null || $lastSlot->title !== $values[0]) {
$lastSlot = new \stdClass();
$lastSlot->poll_id = $sujet->id_sondage;
$lastSlot->title = $values[0];
$lastSlot->moments = count($values) === 2 ? $values[1] : '-';
$slots[] = $lastSlot;
} else {
$lastSlot->moments .= ',' . (count($values) === 2 ? $values[1] : '-');
}
}
}
return $slots;
}
private function dropOldTables(PDO $pdo): void
{
$pdo->exec('DROP TABLE `comments`');
$pdo->exec('DROP TABLE `sujet_studs`');
$pdo->exec('DROP TABLE `user_studs`');
$pdo->exec('DROP TABLE `sondage`');
}
private function unescape(string $value): string
{
return stripslashes(html_entity_decode($value, ENT_QUOTES));
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Security\Token;
use Framadate\Utils;
use PDO;
/**
* This migration generate uniqId for all legacy votes.
*
* @package Framadate\Migration
* @version 0.9
*/
class Generate_uniqId_for_old_votes implements Migration {
public function __construct() {
}
public function description(): string {
return 'Generate "uniqId" in "vote" table for all legacy votes';
}
public function preCondition(PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
$pdo->beginTransaction();
$this->generateUniqIdsForEmptyOnes($pdo);
$pdo->commit();
return true;
}
private function generateUniqIdsForEmptyOnes(PDO $pdo): void
{
$select = $pdo->query('
SELECT `id`
FROM `' . Utils::table('vote') . '`
WHERE `uniqid` = \'\'');
$update = $pdo->prepare('
UPDATE `' . Utils::table('vote') . '`
SET `uniqid` = :uniqid
WHERE `id` = :id');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
$token = Token::getToken(16);
$update->execute([
'uniqid' => $token,
'id' => $row->id
]);
}
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
class Increase_pollId_size implements Migration {
public function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string {
return 'Increase the size of id column in poll table';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true if the Migration should be executed
*/
public function preCondition(PDO $pdo): bool {
return true;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true if the execution succeeded
*/
public function execute(PDO $pdo): bool {
$this->alterCommentTable($pdo);
$this->alterPollTable($pdo);
$this->alterSlotTable($pdo);
$this->alterVoteTable($pdo);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterPollTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
CHANGE `id` `id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterSlotTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('slot') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterVoteTable(PDO $pdo): void
{
$pdo->exec('
ALTER TABLE `' . Utils::table('vote') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
}

View File

@ -16,45 +16,32 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
namespace Framadate\Migration;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the column mail in the vote table
*
* @package Framadate\Migration
* @version 1.2
*/
class Version20180419190000 extends AbstractMigration
{
interface Migration {
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Remove the old migration table';
}
public function description(): string;
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param PDO $pdo The connection to database
* @return bool true if the Migration should be executed
*/
public function up(Schema $schema)
{
$this->skipIf(!$schema->hasTable(Utils::table(MIGRATION_TABLE)), "The old migration table wasn't created, no need to delete it.");
$schema->dropTable(Utils::table(MIGRATION_TABLE));
}
public function preCondition(PDO $pdo): bool;
/**
* @param Schema $schema
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool true if the execution succeeded
*/
public function down(Schema $schema)
{
// No need to recreate legacy migration table
}
public function execute(PDO $pdo): bool;
}

View File

@ -0,0 +1,67 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphael DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphael DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
/**
* This migration RPad votes from version 0.8.
* Because some votes does not have enough values for their poll.
*
* @package Framadate\Migration
* @version 0.9
*/
class RPadVotes_from_0_8 implements Migration {
public function description(): string {
return 'RPad votes from version 0.8.';
}
public function preCondition(\PDO $pdo): bool {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(\PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
public function execute(\PDO $pdo): bool {
$pdo->beginTransaction();
$this->rpadVotes($pdo);
$pdo->commit();
return true;
}
private function rpadVotes(\PDO $pdo): void
{
$pdo->exec('UPDATE ' . Utils::table('vote') . ' fv
INNER JOIN (
SELECT v.id, RPAD(v.choices, inn.slots_count, \'0\') new_choices
FROM ' . Utils::table('vote') . ' v
INNER JOIN
(SELECT s.poll_id, SUM(IFNULL(LENGTH(s.moments) - LENGTH(REPLACE(s.moments, \',\', \'\')) + 1, 1)) slots_count
FROM ' . Utils::table('slot') . ' s
GROUP BY s.poll_id
ORDER BY s.poll_id) inn ON inn.poll_id = v.poll_id
WHERE LENGTH(v.choices) != inn.slots_count
) computed ON fv.id = computed.id
SET fv.choices = computed.new_choices');
}
}

View File

@ -1,99 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
/**
* Class From_0_0_to_0_8_Migration
*
* @package Framadate\Migration
* @version 0.8
*/
class Version20150101000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'First installation of the Framadate application (v0.8)';
}
/**
* This method is called only one time in the migration page.
*
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @return void true is the execution succeeded
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema,'Framadate\Migration\From_0_0_to_0_8_Migration'), 'Migration has been executed in an earlier database migration system');
$sondage = $schema->createTable('sondage');
$sondage->addColumn('id_sondage', 'string');
$sondage->addColumn('commentaires', 'text');
$sondage->addColumn('mail_admin', 'string', ['notnull' => false]);
$sondage->addColumn('nom_admin', 'string', ['notnull' => false]);
$sondage->addColumn('titre', 'text');
$sondage->addColumn('id_sondage_admin', 'string', ['notnull' => false]);
$sondage->addColumn('date_creation', 'datetime', ['default' => (new \DateTime())->format('Y-m-d H:i:s')]);
$sondage->addColumn('date_fin', 'datetime', ['notnull' => false]);
$sondage->addColumn('format', 'string', ['notnull' => false]);
$sondage->addColumn('mailsonde', 'boolean', ['default' => false]);
$sondage->addColumn('statut', 'integer', ['default' => '1']);
$sondage->addUniqueIndex(['id_sondage'], 'sondage_index_id_sondage');
$sujetStuds = $schema->createTable('sujet_studs');
$sujetStuds->addColumn('id_sondage', 'string');
$sujetStuds->addColumn('sujet', 'text');
$sujetStuds->addIndex(['id_sondage'], 'sujet_studs_index_id_sondage');
$comments = $schema->createTable('comments');
$schema->createSequence('comments_seq');
$comments->addColumn('id_comment', 'integer', ['autoincrement' => true]);
$comments->addColumn('id_sondage', 'string');
$comments->addColumn('comment', 'text');
$comments->addColumn('usercomment', 'text', ['notnull' => false]);
$comments->addUniqueIndex(['id_comment'], 'comments_index_id_comment');
$comments->addIndex(['id_sondage'], 'comments_index_id_sondage');
$userStuds = $schema->createTable('user_studs');
$schema->createSequence('user_studs_seq');
$userStuds->addColumn('id_users', 'integer', ['autoincrement' => true]);
$userStuds->addColumn('nom', 'string');
$userStuds->addColumn('id_sondage', 'string');
$userStuds->addColumn('reponses', 'text');
$userStuds->addUniqueIndex(['id_users'], 'user_studs_index_id_users');
$userStuds->addIndex(['id_sondage'], 'user_studs_index_id_sondage');
}
public function down(Schema $schema)
{
$this->addSql('DROP TABLE sondage');
$this->addSql('DROP TABLE sujet_studs');
$this->addSql('DROP TABLE comments');
$this->addSql('DROP TABLE user_studs');
}
}

View File

@ -1,160 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This class executes the aciton in database to migrate data from version 0.8 to 0.9.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150102000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'From 0.8 to 0.9 first part';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema,'Framadate\Migration\From_0_8_to_0_9_Migration'), 'Migration has been executed in an earlier database migration system');
foreach (['sondage', 'sujet_studs', 'comments', 'user_studs'] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$this->createPollTable($schema);
$this->createCommentTable($schema);
$this->createSlotTable($schema);
$this->createVoteTable($schema);
}
public function down(Schema $schema)
{
$sondage = $schema->createTable('sondage');
$sondage->addColumn('id_sondage', 'string');
$sondage->addColumn('commentaires', 'text');
$sondage->addColumn('mail_admin', 'string', ['notnull' => false]);
$sondage->addColumn('nom_admin', 'string', ['notnull' => false]);
$sondage->addColumn('titre', 'text');
$sondage->addColumn('id_sondage_admin', 'string', ['notnull' => false]);
$sondage->addColumn('date_creation', 'datetime', ['default' => (new \DateTime())->format('Y-m-d H:i:s')]);
$sondage->addColumn('date_fin', 'datetime', ['notnull' => false]);
$sondage->addColumn('format', 'string', ['notnull' => false]);
$sondage->addColumn('mailsonde', 'boolean', ['default' => false]);
$sondage->addColumn('statut', 'integer', ['default' => '1']);
$sondage->addUniqueIndex(['id_sondage'], 'sondage_index_id_sondage');
$sujetStuds = $schema->createTable('sujet_studs');
$sujetStuds->addColumn('id_sondage', 'string');
$sujetStuds->addColumn('sujet', 'text');
$sujetStuds->addIndex(['id_sondage'], 'sujet_studs_index_id_sondage');
$comments = $schema->createTable('comments');
$schema->createSequence('comments_seq');
$comments->addColumn('id_comment', 'integer', ['autoincrement' => true]);
$comments->addColumn('id_sondage', 'string');
$comments->addColumn('comment', 'text');
$comments->addColumn('usercomment', 'text', ['notnull' => false]);
$comments->addUniqueIndex(['id_comment'], 'comments_index_id_comment');
$comments->addIndex(['id_sondage'], 'comments_index_id_sondage');
$userStuds = $schema->createTable('user_studs');
$schema->createSequence('user_studs_seq');
$userStuds->addColumn('id_users', 'integer', ['autoincrement' => true]);
$userStuds->addColumn('nom', 'string');
$userStuds->addColumn('id_sondage', 'string');
$userStuds->addColumn('reponses', 'text');
$userStuds->addUniqueIndex(['id_users'], 'user_studs_index_id_users');
$userStuds->addIndex(['id_sondage'], 'user_studs_index_id_sondage');
$schema->dropTable(Utils::table('poll'));
$schema->dropTable(Utils::table('comment'));
$schema->dropTable(Utils::table('vote'));
$schema->dropTable(Utils::table('slot'));
}
private function createPollTable(Schema $schema)
{
$poll = $schema->createTable(Utils::table('poll'));
$poll->addColumn('id', 'string');
$poll->addColumn('admin_id', 'string');
$poll->addColumn('title', 'text');
$poll->addColumn('description', 'text', ['notnull' => false]);
$poll->addColumn('admin_name', 'string');
$poll->addColumn('admin_mail', 'string', ['notnull' => false]);
$poll->addColumn('creation_date', 'datetime', ['default' => (new \DateTime())->format('Y-m-d H:i:s')]);
$poll->addColumn('end_date', 'datetime', ['notnull' => false]);
$poll->addColumn('format', 'string', ['default' => null, 'notnull' => false]);
$poll->addColumn('editable', 'integer', ['default' => 0]);
$poll->addColumn('receiveNewVotes', 'boolean', ['default' => false]);
$poll->addColumn('active', 'boolean', ['default' => true]);
$poll->addUniqueIndex(['id'], 'poll_index_id');
}
private function createSlotTable(Schema $schema)
{
$slot = $schema->createTable(Utils::table('slot'));
$schema->createSequence('slot_seq');
$slot->addColumn('id', 'integer', ['autoincrement' => true]);
$slot->addColumn('poll_id', 'string');
$slot->addColumn('title', 'text');
$slot->addColumn('moments', 'text', ['notnull' => false]);
$slot->addUniqueIndex(['id'], 'slot_index_id');
$slot->addIndex(['poll_id'], 'slot_index_poll_id');
}
private function createCommentTable(Schema $schema)
{
$comment = $schema->createTable(Utils::table('comment'));
$schema->createSequence('comment_seq');
$comment->addColumn('id', 'integer', ['autoincrement' => true]);
$comment->addColumn('poll_id', 'string');
$comment->addColumn('name', 'text', ['notnull' => false]);
$comment->addColumn('comment', 'text');
$comment->addUniqueIndex(['id'], 'comment_index_id');
$comment->addIndex(['poll_id'], 'comment_index_poll_id');
}
private function createVoteTable(Schema $schema)
{
$vote = $schema->createTable(Utils::table('vote'));
$schema->createSequence('vote_seq');
$vote->addColumn('id', 'integer', ['autoincrement' => true]);
$vote->addColumn('poll_id', 'string');
$vote->addColumn('name', 'string');
$vote->addColumn('choices', 'string');
$vote->addUniqueIndex(['id'], 'vote_index_id');
$vote->addIndex(['poll_id'], 'vote_index_poll_id');
}
}

View File

@ -1,263 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This class executes the aciton in database to migrate data from version 0.8 to 0.9.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150102100000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'From 0.8 to 0.9 second part';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema,'Framadate\Migration\From_0_8_to_0_9_Migration'), 'Migration has been executed in an earlier database migration system');
foreach ([Utils::table('poll'), Utils::table('comment'), Utils::table('slot'), Utils::table('vote')] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$this->migrateFromSondageToPoll();
$this->migrateFromCommentsToComment();
$this->migrateFromSujetStudsToSlot();
//$this->migrateFromUserStudsToVote();
$this->dropOldTables($schema);
}
public function down(Schema $schema)
{
$sondage = $schema->createTable('sondage');
$sondage->addColumn('id_sondage', 'string');
$sondage->addColumn('commentaires', 'text');
$sondage->addColumn('mail_admin', 'string', ['notnull' => false]);
$sondage->addColumn('nom_admin', 'string', ['notnull' => false]);
$sondage->addColumn('titre', 'text');
$sondage->addColumn('id_sondage_admin', 'string', ['notnull' => false]);
$sondage->addColumn('date_creation', 'datetime', ['default' => (new \DateTime())->format('Y-m-d H:i:s')]);
$sondage->addColumn('date_fin', 'datetime', ['notnull' => false]);
$sondage->addColumn('format', 'string', ['notnull' => false]);
$sondage->addColumn('mailsonde', 'boolean', ['default' => false]);
$sondage->addColumn('statut', 'integer', ['default' => '1']);
$sondage->addUniqueIndex(['id_sondage'], 'sondage_index_id_sondage');
$sujetStuds = $schema->createTable('sujet_studs');
$sujetStuds->addColumn('id_sondage', 'string');
$sujetStuds->addColumn('sujet', 'text');
$sujetStuds->addIndex(['id_sondage'], 'sujet_studs_index_id_sondage');
$comments = $schema->createTable('comments');
$schema->createSequence('comments_seq');
$comments->addColumn('id_comment', 'integer', ['autoincrement' => true]);
$comments->addColumn('id_sondage', 'string');
$comments->addColumn('comment', 'text');
$comments->addColumn('usercomment', 'text', ['notnull' => false]);
$comments->addUniqueIndex(['id_comment'], 'comments_index_id_comment');
$comments->addIndex(['id_sondage'], 'comments_index_id_sondage');
$userStuds = $schema->createTable('user_studs');
$schema->createSequence('user_studs_seq');
$userStuds->addColumn('id_users', 'integer', ['autoincrement' => true]);
$userStuds->addColumn('nom', 'string');
$userStuds->addColumn('id_sondage', 'string');
$userStuds->addColumn('reponses', 'text');
$userStuds->addUniqueIndex(['id_users'], 'user_studs_index_id_users');
$userStuds->addIndex(['id_sondage'], 'user_studs_index_id_sondage');
$schema->dropTable(Utils::table('poll'));
$schema->dropTable(Utils::table('comment'));
$schema->dropTable(Utils::table('vote'));
$schema->dropTable(Utils::table('slot'));
}
private function migrateFromSondageToPoll()
{
$select = $this->connection->query('
SELECT
id_sondage,
id_sondage_admin,
titre,
commentaires,
nom_admin,
mail_admin,
date_creation,
date_fin,
SUBSTR(format, 1, 1) AS format,
CASE SUBSTR(format, 2, 1)
WHEN \'+\' THEN 1
ELSE 0 END AS editable,
mailsonde,
CASE SUBSTR(format, 2, 1)
WHEN \'-\' THEN 0
ELSE 1 END AS active
FROM sondage');
$insert = $this->connection->prepare('
INSERT INTO ' . Utils::table('poll') . '
(id, admin_id, title, description, admin_name, admin_mail, creation_date, end_date, format, editable, receiveNewVotes, active)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)');
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$row->id_sondage_admin,
$this->unescape($row->titre),
$this->unescape($row->commentaires),
$this->unescape($row->nom_admin),
$this->unescape($row->mail_admin),
$row->date_creation,
$row->date_fin,
$row->format,
$row->editable,
$row->mailsonde,
$row->active
]);
}
}
private function migrateFromSujetStudsToSlot()
{
$stmt = $this->connection->query('SELECT * FROM sujet_studs');
$sujets = $stmt->fetchAll();
$slots = [];
foreach ($sujets as $sujet) {
$newSlots = $this->transformSujetToSlot($sujet);
foreach ($newSlots as $newSlot) {
$slots[] = $newSlot;
}
}
$prepared = $this->connection->prepare('INSERT INTO ' . Utils::table('slot') . ' (poll_id, title, moments) VALUES (?,?,?)');
foreach ($slots as $slot) {
$prepared->execute([
$slot->poll_id,
$this->unescape($slot->title),
!empty($slot->moments) ? $this->unescape($slot->moments) : null
]);
}
}
private function migrateFromCommentsToComment()
{
$select = $this->connection->query('
SELECT
id_sondage,
usercomment,
comment
FROM comments');
$insert = $this->connection->prepare('
INSERT INTO ' . Utils::table('comment') . ' (poll_id, name, comment)
VALUES (?,?,?)');
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->usercomment),
$this->unescape($row->comment)
]);
}
}
private function migrateFromUserStudsToVote()
{
$select = $this->connection->query('
SELECT
id_sondage,
nom,
REPLACE(REPLACE(REPLACE(reponses, 1, \'X\'), 2, 1), \'X\', 2) reponses
FROM user_studs');
$insert = $this->connection->prepare('
INSERT INTO ' . Utils::table('vote') . ' (poll_id, name, choices)
VALUES (?,?,?)');
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->nom),
$row->reponses
]);
}
}
private function transformSujetToSlot($sujet)
{
$slots = [];
$ex = explode(',', $sujet->sujet);
$isDatePoll = strpos($sujet->sujet, '@');
$lastSlot = null;
foreach ($ex as $atomicSlot) {
if ($isDatePoll === false) { // Classic poll
$slot = new \stdClass();
$slot->poll_id = $sujet->id_sondage;
$slot->title = $atomicSlot;
$slots[] = $slot;
} else { // Date poll
$values = explode('@', $atomicSlot);
if ($lastSlot === null || $lastSlot->title !== $values[0]) {
$lastSlot = new \stdClass();
$lastSlot->poll_id = $sujet->id_sondage;
$lastSlot->title = $values[0];
$lastSlot->moments = count($values) === 2 ? $values[1] : '-';
$slots[] = $lastSlot;
} else {
$lastSlot->moments .= ',' . (count($values) === 2 ? $values[1] : '-');
}
}
}
return $slots;
}
private function dropOldTables(Schema $schema)
{
$schema->dropTable('comments');
$schema->dropTable('sujet_studs');
$schema->dropTable('user_studs');
$schema->dropTable('sondage');
}
private function unescape($value)
{
return stripslashes(html_entity_decode($value, ENT_QUOTES));
}
}

View File

@ -1,70 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the field receiveNewComments on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150117000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add column "receiveNewComments" for version 0.9';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema,'Framadate\Migration\AddColumn_receiveNewComments_For_0_9'), 'Migration has been executed in an earlier database migration system');
foreach ([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$pollTable = $schema->getTable(Utils::table('poll'));
$this->skipIf($pollTable->hasColumn('receiveNewComments'), 'Column receiveNewComments already exists');
$pollTable->addColumn('receiveNewComments', 'boolean', ['default' => false]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->dropColumn('receiveNewComments');
}
}

View File

@ -1,76 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the field uniqId on the vote table.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150402000000 extends AbstractMigration
{
private $indexUniqIdName = 'IDX_vote_uniqId';
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add column "uniqId" in table "vote" for version 0.9';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumn_uniqId_In_vote_For_0_9'), 'Migration has been executed in an earlier database migration system');
foreach ([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$voteTable = $schema->getTable(Utils::table('vote'));
$this->skipIf($voteTable->hasColumn('uniqId'), 'Column uniqId already existing');
$voteTable->addColumn('uniqId', 'string', ['length' => 16]);
$voteTable->addIndex(['uniqId'], $this->indexUniqIdName);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$voteTable = $schema->getTable(Utils::table('vote'));
$voteTable->dropIndex($this->indexUniqIdName);
$voteTable->dropColumn('uniqId');
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the field hidden on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150405000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add column "hidden" in table "vote" for version 0.9';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumn_hidden_In_poll_For_0_9'), 'Migration has been executed in an earlier database migration system');
foreach ([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$pollTable = $schema->getTable(Utils::table('poll'));
$this->skipIf($pollTable->hasColumn('hidden'), 'Column hidden already existing in table poll');
$pollTable->addColumn('hidden', 'boolean', ['default' => false, 'notnull' => true]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->dropColumn('hidden');
}
}

View File

@ -1,78 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Security\Token;
use Framadate\Utils;
/**
* This migration generate uniqId for all legacy votes.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150624000000 extends AbstractMigration
{
public function description()
{
return 'Generate "uniqId" in "vote" table for all legacy votes';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\Generate_uniqId_for_old_votes'), 'Migration has been executed in an earlier database migration system');
foreach ([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$this->connection->beginTransaction();
$select = $this->connection->query('
SELECT id
FROM ' . Utils::table('vote') . '
WHERE uniqid = \'\'');
$update = $this->connection->prepare('
UPDATE ' . Utils::table('vote') . '
SET uniqid = :uniqid
WHERE id = :id');
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$token = Token::getToken(16);
$update->execute([
'uniqid' => $token,
'id' => $row->id
]);
}
$this->connection->commit();
}
public function down(Schema $schema)
{
// TODO: Implement down() method.
}
}

View File

@ -1,102 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Rapha<EFBFBD>l DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est r<EFBFBD>gi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Rapha<EFBFBD>l DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration RPad votes from version 0.8.
* Because some votes does not have enough values for their poll.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20150918000000 extends AbstractMigration
{
public function description()
{
return 'RPad votes from version 0.8.';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function up(Schema $schema)
{
$this->skipIf(
$this->legacyCheck($schema, 'Framadate\Migration\RPadVotes_from_0_8'),
'Migration has been executed in an earlier database migration system'
);
foreach ([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table(
'comment'
)] as $table) {
$this->skipIf(!$schema->hasTable($table), 'Missing table ' . $table);
}
$driver_name = $this->connection->getDatabasePlatform()->getName();
switch ($driver_name) {
case 'mysql':
$this->addSql(
'UPDATE ' . Utils::table('vote') . ' fv
INNER JOIN (
SELECT v.id, RPAD(v.choices, inn.slots_count, \'0\') new_choices
FROM ' . Utils::table('vote') . ' v
INNER JOIN
(SELECT s.poll_id, SUM(IFNULL(LENGTH(s.moments) - LENGTH(REPLACE(s.moments, \',\', \'\')) + 1, 1)) slots_count
FROM ' . Utils::table('slot') . ' s
GROUP BY s.poll_id
ORDER BY s.poll_id) inn ON inn.poll_id = v.poll_id
WHERE LENGTH(v.choices) != inn.slots_count
) computed ON fv.id = computed.id
SET fv.choices = computed.new_choices'
);
break;
case 'postgresql':
$this->addSql(
"UPDATE " . Utils::table('vote') . " fv
SET choices = computed.new_choices
FROM (
SELECT v.id, RPAD(v.choices::text, inn.slots_count::int, '0') new_choices
FROM " . Utils::table('vote') . " v
INNER JOIN
(SELECT s.poll_id, SUM(coalesce(LENGTH(s.moments) - LENGTH(REPLACE(s.moments, ',', '')) + 1, 1)) slots_count
FROM " . Utils::table('slot') . " s
GROUP BY s.poll_id
ORDER BY s.poll_id) inn ON inn.poll_id = v.poll_id
WHERE LENGTH(v.choices) != inn.slots_count
) computed WHERE fv.id = computed.id"
);
break;
default:
$this->skipIf(true, "Not on MySQL or PostgreSQL");
break;
}
}
public function down(Schema $schema)
{
// TODO: Implement down() method.
}
}

View File

@ -1,73 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration alter the comment table to add a date column.
*
* @package Framadate\Migration
* @version 1.0
*/
class Version20151012082600 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Alter the comment table to add a date column.';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\Alter_Comment_table_adding_date'), 'Migration has been executed in an earlier database migration system');
$commentTable = $schema->getTable(Utils::table('comment'));
$this->skipIf($commentTable->hasColumn('date'), 'Column date in comment table already exists');
// The default for datetime types is only available for MySQL 5.6
// See the "Functionality Added or Changed" section,
// "Important Change: In MySQL, the TIMESTAMP data type" element, on
// https://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-6.html
$commentTable->addColumn('date', 'datetime', ['default' => 0]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$commentTable = $schema->getTable(Utils::table('comment'));
$commentTable->dropColumn('comment');
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the fields password_hash and results_publicly_visible on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class Version20151028000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add columns "password_hash" and "results_publicly_visible" in table "vote" for version 0.9';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9'), 'Migration has been executed in an earlier database migration system');
$pollTable = $schema->getTable(Utils::table('poll'));
$this->skipIf($pollTable->hasColumn('password_hash'), 'Column password_hash in table poll already exists');
$this->skipIf($pollTable->hasColumn('results_publicly_visible'), 'Column results_publicly_visible in table poll already exists');
$pollTable->addColumn('password_hash', 'string', ['notnull' => false]);
$pollTable->addColumn('results_publicly_visible', 'boolean', ['notnull' => false]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->dropColumn('password_hash');
$pollTable->dropColumn('results_publicly_visible');
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use Framadate\AbstractMigration;
use Framadate\Utils;
class Version20151205000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Increase the size of id column in poll table';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\Increase_pollId_size'), 'Migration has been executed in an earlier database migration system');
$commentTable = $schema->getTable(Utils::table('comment'));
$commentTable->changeColumn('poll_id', ['type' => Type::getType('string'), 'length' => 64, 'notnull' => true]);
$pollTable = $schema->getTable(Utils::table('poll'));
$pollTable->changeColumn('id', ['type' => Type::getType('string'), 'length' => 64, 'notnull' => true]);
$slotTable = $schema->getTable(Utils::table('slot'));
$slotTable->changeColumn('poll_id', ['type' => Type::getType('string'), 'length' => 64, 'notnull' => true]);
$voteTable = $schema->getTable(Utils::table('vote'));
$voteTable->changeColumn('poll_id', ['type' => Type::getType('string'), 'length' => 64, 'notnull' => true]);
}
public function down(Schema $schema)
{
// TODO: Implement down() method.
}
}

View File

@ -1,88 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration sets Poll.end_date to NULL by default
*
* @package Framadate\Migration
* @version 1.1
*/
class Version20180411000000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Sets Poll end_date to NULL by default (work around MySQL NO_ZERO_DATE)';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param Connection|\PDO $connection The connection to database
* @return bool true if the Migration should be executed.
*/
public function preCondition(Connection $connection)
{
$driver_name = $connection->getWrappedConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ($driver_name === 'mysql') {
$stmt = $connection->prepare(
"SELECT Column_Default from Information_Schema.Columns where Table_Name = ? AND Column_Name = ?;"
);
$stmt->bindValue(1, Utils::table('poll'));
$stmt->bindValue(2, 'end_date');
$stmt->execute();
$default = $stmt->fetch(\PDO::FETCH_COLUMN);
return $default === null;
}
return true;
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
// We don't disable this migration even if legacy because it wasn't working correctly before
// $this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\Fix_MySQL_No_Zero_Date'), 'Migration has been executed in an earlier database migration system');
$this->skipIf($this->preCondition($this->connection), "Database server isn't MySQL or poll end_date default value was already NULL");
$poll = $schema->getTable(Utils::table('poll'));
$poll->changeColumn('end_date', ['default' => null, 'notnull' => false]);
}
public function down(Schema $schema)
{
// nothing
}
}

View File

@ -1,65 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the column collect_users_mail in the poll table
*
* @package Framadate\Migration
* @version 1.2
*/
class Version20180419170000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add column collect_users_mail in table poll';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumn_collect_mail_In_poll'), 'Migration has been executed in an earlier database migration system');
$poll = $schema->getTable(Utils::table('poll'));
$poll->addColumn('collect_users_mail', 'boolean', ['default' => false]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$poll = $schema->getTable(Utils::table('poll'));
$poll->dropColumn('collect_users_mail');
}
}

View File

@ -1,65 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the column mail in the vote table
*
* @package Framadate\Migration
* @version 1.2
*/
class Version20180419180000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Add column mail in table vote';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\Migrations\SkipMigrationException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$this->skipIf($this->legacyCheck($schema, 'Framadate\Migration\AddColumn_collect_mail_In_poll'), 'Migration has been executed in an earlier database migration system');
$vote = $schema->getTable(Utils::table('vote'));
$vote->addColumn('mail', 'string', ['default' => null, 'notnull' => false]);
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$vote = $schema->getTable(Utils::table('vote'));
$vote->dropColumn('mail');
}
}

View File

@ -1,100 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Framadate\AbstractMigration;
use Framadate\Utils;
/**
* This migration adds the column collect_users_mail in the poll table
*
* @package Framadate\Migration
* @version 1.2
*/
class Version20180525110000 extends AbstractMigration
{
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description()
{
return 'Change column collect_users_mail in table poll from boolean to smallint';
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
* @throws \Doctrine\DBAL\DBALException
*/
public function up(Schema $schema)
{
$poll = $schema->getTable(Utils::table('poll'));
$poll->addColumn('collect_users_mail_integer', 'smallint', ['default' => 0]);
}
/**
* @param Schema $schema
*/
public function postUp(Schema $schema)
{
$this->addSql('UPDATE ' . Utils::table('poll') . ' SET collect_users_mail_integer = collect_users_mail');
$this->addSql('ALTER TABLE ' . Utils::table('poll') . ' DROP COLUMN collect_users_mail');
if ($this->connection->getDatabasePlatform()->getName() === 'mysql') {
$this->addSql(
'ALTER TABLE ' . Utils::table('poll') . ' CHANGE collect_users_mail_integer collect_users_mail SMALLINT'
);
} else {
$this->addSql(
'ALTER TABLE ' . Utils::table('poll') . ' RENAME COLUMN collect_users_mail_integer to collect_users_mail'
);
}
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function down(Schema $schema)
{
$poll = $schema->getTable(Utils::table('poll'));
$poll->addColumn('collect_users_mail_boolean', 'boolean', ['default' => false]);
}
/**
* @param Schema $schema
*/
public function postDown(Schema $schema)
{
$this->addSql('UPDATE ' . Utils::table('poll') . ' SET collect_users_mail_boolean = collect_users_mail > 0');
$this->addSql('ALTER TABLE ' . Utils::table('poll') . ' DROP COLUMN collect_users_mail');
if ($this->connection->getDatabasePlatform()->getName() === 'mysql') {
$this->addSql(
'ALTER TABLE ' . Utils::table('poll') . ' CHANGE collect_users_mail_boolean collect_users_mail SMALLINT'
);
} else {
$this->addSql(
'ALTER TABLE ' . Utils::table('poll') . ' RENAME COLUMN collect_users_mail_boolean to collect_users_mail'
);
}
}
}

View File

@ -1,82 +1,52 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Query\QueryBuilder;
use PDOStatement;
use Framadate\FramaDB;
abstract class AbstractRepository {
/**
* @var Connection
* @var FramaDB
*/
protected $connect;
private $connect;
/**
* PollRepository constructor.
* @param Connection $connect
* @param FramaDB $connect
*/
public function __construct(Connection $connect) {
public function __construct(FramaDB $connect) {
$this->connect = $connect;
$this->connect->setFetchMode(\PDO::FETCH_OBJ);
}
public function beginTransaction()
public function beginTransaction(): void
{
$this->connect->beginTransaction();
}
/**
* @throws ConnectionException
*/
public function commit()
public function commit(): void
{
$this->connect->commit();
}
/**
* @throws ConnectionException
*/
public function rollback()
public function rollback(): void
{
$this->connect->rollback();
}
/**
* @return QueryBuilder
* @return \PDOStatement|false
*/
public function createQueryBuilder()
{
return $this->connect->createQueryBuilder();
}
/**
* @param string $sql
*@throws DBALException
* @return bool|Statement|PDOStatement
*/
public function prepare($sql)
{
public function prepare(string $sql) {
return $this->connect->prepare($sql);
}
/**
* @param string $sql
*@throws DBALException
* @return bool|Statement|PDOStatement
* @return \PDOStatement|false
*/
public function query($sql)
{
public function query($sql) {
return $this->connect->query($sql);
}
/**
* @return string
*/
public function lastInsertId()
{
public function lastInsertId(): string {
return $this->connect->lastInsertId();
}
}

View File

@ -1,78 +1,57 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type;
use Framadate\Utils;
class CommentRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws DBALException
* @return array
* @return array|false
*/
public function findAllByPollId($poll_id) {
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('comment') . ' WHERE poll_id = ? ORDER BY id');
public function findAllByPollId(string $poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('comment') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
$comments = $prepared->fetchAll();
/**
* Hack to make date a proper DateTime
*/
return array_map(function($comment) {
$comment->date = Type::getType(Type::DATETIME)->convertToPhpValue($comment->date, $this->connect->getDatabasePlatform());
return $comment;
}, $comments);
return $prepared->fetchAll();
}
/**
* Insert a new comment.
*
* @param $poll_id
* @param $name
* @param $comment
* @param string $poll_id
* @param string $name
* @param string $comment
* @return bool
*/
public function insert($poll_id, $name, $comment)
public function insert(string $poll_id, string $name, string $comment): bool
{
return $this->connect->insert(Utils::table('comment'), ['poll_id' => $poll_id, 'name' => $name, 'comment' => $comment]) > 0;
$prepared = $this->prepare('INSERT INTO `' . Utils::table('comment') . '` (poll_id, name, comment) VALUES (?,?,?)');
return $prepared->execute([$poll_id, $name, $comment]);
}
/**
* @param $poll_id
* @param $comment_id
* @throws InvalidArgumentException
* @return bool
*/
public function deleteById($poll_id, $comment_id)
public function deleteById(string $poll_id, int $comment_id): bool
{
return $this->connect->delete(Utils::table('comment'), ['poll_id' => $poll_id, 'id' => $comment_id]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('comment') . '` WHERE poll_id = ? AND id = ?');
return $prepared->execute([$poll_id, $comment_id]);
}
/**
* Delete all comments of a given poll.
*
* @param $poll_id int The ID of the given poll.
* @throws InvalidArgumentException
* @return bool true if action succeeded.
* @param string $poll_id The ID of the given poll.
* @return bool|null true if action succeeded.
*/
public function deleteByPollId($poll_id)
public function deleteByPollId(string $poll_id): ?bool
{
return $this->connect->delete(Utils::table('comment'), ['poll_id' => $poll_id]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('comment') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);
}
/**
* @param $poll_id
* @param $name
* @param $comment
* @throws DBALException
* @return bool
*/
public function exists($poll_id, $name, $comment)
public function exists(string $poll_id, string $name, string $comment): bool
{
$prepared = $this->prepare('SELECT 1 FROM ' . Utils::table('comment') . ' WHERE poll_id = ? AND name = ? AND comment = ?');
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('comment') . '` WHERE poll_id = ? AND name = ? AND comment = ?');
$prepared->execute([$poll_id, $name, $comment]);
return $prepared->rowCount() > 0;

View File

@ -1,148 +1,81 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type;
use Exception;
use Framadate\Form;
use Framadate\FramaDB;
use Framadate\Utils;
use PDO;
class PollRepository extends AbstractRepository {
/**
* @param $poll_id
* @param $admin_poll_id
* @param Form $form
* @throws Exception
*/
public function insertPoll($poll_id, $admin_poll_id, Form $form)
public function insertPoll(string $poll_id, string $admin_poll_id, $form): void
{
$this->connect->insert(Utils::table('poll'), [
'id' => $poll_id,
'admin_id' => $admin_poll_id,
'title' => $form->title,
'description' => $form->description,
'admin_name' => $form->admin_name,
'admin_mail' => $form->admin_mail,
'end_date' => $form->end_date->format('Y-m-d H:i:s'),
'format' => $form->format,
'editable' => ($form->editable>=0 && $form->editable<=2) ? $form->editable : 0,
'receiveNewVotes' => $form->receiveNewVotes ? 1 : 0,
'receiveNewComments' => $form->receiveNewComments ? 1 : 0,
'hidden' => $form->hidden ? 1 : 0,
'password_hash' => $form->password_hash,
'results_publicly_visible' => $form->results_publicly_visible ? 1 : 0,
'ValueMax' => $form->ValueMax,
'collect_users_mail' => ($form->collect_users_mail >= 0 && $form->collect_users_mail <= 3) ? $form->collect_users_mail : 0,
]);
$sql = 'INSERT INTO `' . Utils::table('poll') . '`
(id, admin_id, title, description, admin_name, admin_mail, end_date, format, editable, receiveNewVotes, receiveNewComments, hidden, password_hash, results_publicly_visible,ValueMax)
VALUES (?,?,?,?,?,?,FROM_UNIXTIME(?),?,?,?,?,?,?,?,?)';
$prepared = $this->prepare($sql);
$prepared->execute([$poll_id, $admin_poll_id, $form->title, $form->description, $form->admin_name, $form->admin_mail, $form->end_date, $form->format, ($form->editable>=0 && $form->editable<=2) ? $form->editable : 0, $form->receiveNewVotes ? 1 : 0, $form->receiveNewComments ? 1 : 0, $form->hidden ? 1 : 0, $form->password_hash, $form->results_publicly_visible ? 1 : 0,$form->ValueMax]);
}
/**
* @param $poll_id
* @throws DBALException
* @return mixed
*/
public function findById($poll_id)
{
$prepared = $this->connect->executeQuery('SELECT * FROM ' . Utils::table('poll') . ' WHERE id = ?', [$poll_id]);
public function findById(string $poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE id = ?');
$prepared->execute([$poll_id]);
$poll = $prepared->fetch();
$prepared->closeCursor();
/**
* Hack to make date a proper DateTime
*/
$poll->creation_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->creation_date, $this->connect->getDatabasePlatform());
$poll->end_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->end_date, $this->connect->getDatabasePlatform());
return $poll;
}
/**
* @param $admin_poll_id
* @throws DBALException
* @return mixed
*/
public function findByAdminId($admin_poll_id) {
$prepared = $this->connect->executeQuery('SELECT * FROM ' . Utils::table('poll') . ' WHERE admin_id = ?', [$admin_poll_id]);
public function findByAdminId(string $admin_poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE admin_id = ?');
$prepared->execute([$admin_poll_id]);
$poll = $prepared->fetch();
$prepared->closeCursor();
$poll->creation_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->creation_date, $this->connect->getDatabasePlatform());
$poll->end_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->end_date, $this->connect->getDatabasePlatform());
return $poll;
}
/**
* @param $poll_id
* @throws DBALException
* @return bool
*/
public function existsById($poll_id) {
$prepared = $this->prepare('SELECT 1 FROM ' . Utils::table('poll') . ' WHERE id = ?');
public function existsById(string $poll_id): bool
{
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('poll') . '` WHERE id = ?');
$prepared->execute([$poll_id]);
return $prepared->rowCount() > 0;
}
/**
* @param $admin_poll_id
* @throws DBALException
* @return bool
*/
public function existsByAdminId($admin_poll_id) {
$prepared = $this->prepare('SELECT 1 FROM ' . Utils::table('poll') . ' WHERE admin_id = ?');
public function existsByAdminId(string $admin_poll_id): bool
{
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('poll') . '` WHERE admin_id = ?');
$prepared->execute([$admin_poll_id]);
return $prepared->rowCount() > 0;
}
/**
* @param $poll
* @return bool
*/
public function update($poll)
public function update($poll): bool
{
return $this->connect->update(Utils::table('poll'), [
'title' => $poll->title,
'admin_name' => $poll->admin_name,
'admin_mail' => $poll->admin_mail,
'description' => $poll->description,
'end_date' => $poll->end_date->format('Y-m-d H:i:s'), # TODO : Harmonize dates between here and insert
'active' => $poll->active,
'editable' => $poll->editable >= 0 && $poll->editable <= 2 ? $poll->editable : 0,
'hidden' => $poll->hidden ? 1 : 0,
'password_hash' => $poll->password_hash,
'results_publicly_visible' => $poll->results_publicly_visible ? 1 : 0
], [
'id' => $poll->id
]) > 0;
$prepared = $this->prepare('UPDATE `' . Utils::table('poll') . '` SET title=?, admin_name=?, admin_mail=?, description=?, end_date=?, active=?, editable=?, hidden=?, password_hash=?, results_publicly_visible=? WHERE id = ?');
return $prepared->execute([$poll->title, $poll->admin_name, $poll->admin_mail, $poll->description, $poll->end_date, $poll->active ? 1 : 0, ($poll->editable>=0 && $poll->editable<=2) ? $poll->editable : 0, $poll->hidden ? 1 : 0, $poll->password_hash, $poll->results_publicly_visible ? 1 : 0, $poll->id]);
}
/**
* @param $poll_id
* @throws InvalidArgumentException
* @return bool
*/
public function deleteById($poll_id)
public function deleteById($poll_id): bool
{
return $this->connect->delete(Utils::table('poll'), ['id' => $poll_id]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('poll') . '` WHERE id = ?');
return $prepared->execute([$poll_id]);
}
/**
* Find old polls. Limit: 20.
*
* @throws DBALException
* @return array Array of old polls
*/
public function findOldPolls()
public function findOldPolls(): array
{
$prepared = $this->connect->executeQuery('SELECT * FROM ' . Utils::table('poll') . ' WHERE DATE_ADD(end_date, INTERVAL ? DAY) < NOW() AND end_date != 0 LIMIT 20', [PURGE_DELAY]);
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE DATE_ADD(`end_date`, INTERVAL ' . PURGE_DELAY . ' DAY) < NOW() AND `end_date` != 0 LIMIT 20');
$prepared->execute([]);
return $prepared->fetchAll();
}
@ -153,16 +86,15 @@ class PollRepository extends AbstractRepository {
* @param array $search Array of search : ['id'=>..., 'title'=>..., 'name'=>..., 'mail'=>...]
* @param int $start The number of first entry to select
* @param int $limit The number of entries to find
* @throws DBALException
* @return array The found polls
*/
public function findAll($search, $start, $limit) {
public function findAll(array $search, int $start, int $limit): array
{
// Polls
$request = "";
$request .= "SELECT p.*,";
$request .= " (SELECT count(1) FROM " . Utils::table('vote') . " v WHERE p.id=v.poll_id) votes";
$request .= " FROM " . Utils::table('poll') . " p";
$request = "SELECT p.*,";
$request .= " (SELECT count(1) FROM `" . Utils::table('vote') . "` v WHERE p.id=v.poll_id) votes";
$request .= " FROM `" . Utils::table('poll') . "` p";
$request .= " WHERE 1";
$values = [];
@ -185,7 +117,7 @@ class PollRepository extends AbstractRepository {
}
$request .= " AND p.$columnName LIKE :$searchKey";
$values[$searchKey] = "%{$search[$searchKey]}%";
$values[$searchKey] = "%$search[$searchKey]%";
}
$request .= " ORDER BY p.title ASC";
@ -209,11 +141,11 @@ class PollRepository extends AbstractRepository {
* Find all polls that are created with the given admin mail.
*
* @param string $mail Email address of the poll admin
* @throws DBALException
* @return array The list of matching polls
*/
public function findAllByAdminMail($mail) {
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('poll') . ' WHERE admin_mail = :admin_mail');
public function findAllByAdminMail(string $mail): array
{
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE admin_mail = :admin_mail');
$prepared->execute(['admin_mail' => $mail]);
return $prepared->fetchAll();
@ -222,15 +154,15 @@ class PollRepository extends AbstractRepository {
/**
* Get the total number of polls in database.
*
* @param array $search Array of search : ['id'=>..., 'title'=>..., 'name'=>...]
* @throws DBALException
* @param array|null $search Array of search : ['id'=>..., 'title'=>..., 'name'=>...]
* @return int The number of polls
*/
public function count($search = null) {
public function count(array $search = null): int
{
// Total count
$prepared = $this->prepare('
SELECT count(1) nb
FROM ' . Utils::table('poll') . ' p
FROM `' . Utils::table('poll') . '` p
WHERE (:id = "" OR p.id LIKE :id)
AND (:title = "" OR p.title LIKE :title)
AND (:name = "" OR p.admin_name LIKE :name)
@ -244,13 +176,7 @@ SELECT count(1) nb
$prepared->bindParam(':name', $name, PDO::PARAM_STR);
$prepared->execute();
$count = $prepared->fetch();
/*echo '---';
print_r($count);
echo '---';
exit;*/
return $count->nb;
return $prepared->fetch()->nb;
}
}

View File

@ -18,7 +18,7 @@
*/
namespace Framadate\Repositories;
use Doctrine\DBAL\Connection;
use Framadate\FramaDB;
class RepositoryFactory {
private static $connect;
@ -29,16 +29,17 @@ class RepositoryFactory {
private static $commentRepository;
/**
* @param Connection $connect
* @param FramaDB $connect
*/
static function init(Connection $connect) {
public static function init(FramaDB $connect): void {
self::$connect = $connect;
}
/**
* @return PollRepository The singleton of PollRepository
*/
static function pollRepository() {
public static function pollRepository(): PollRepository
{
if (self::$pollRepository === null) {
self::$pollRepository = new PollRepository(self::$connect);
}
@ -49,7 +50,8 @@ class RepositoryFactory {
/**
* @return SlotRepository The singleton of SlotRepository
*/
static function slotRepository() {
public static function slotRepository(): SlotRepository
{
if (self::$slotRepository === null) {
self::$slotRepository = new SlotRepository(self::$connect);
}
@ -60,7 +62,8 @@ class RepositoryFactory {
/**
* @return VoteRepository The singleton of VoteRepository
*/
static function voteRepository() {
public static function voteRepository(): VoteRepository
{
if (self::$voteRepository === null) {
self::$voteRepository = new VoteRepository(self::$connect);
}
@ -71,7 +74,8 @@ class RepositoryFactory {
/**
* @return CommentRepository The singleton of CommentRepository
*/
static function commentRepository() {
public static function commentRepository(): CommentRepository
{
if (self::$commentRepository === null) {
self::$commentRepository = new CommentRepository(self::$connect);
}

View File

@ -4,35 +4,37 @@
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Rapha<EFBFBD>l DROZ
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphael DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est r<EFBFBD>gi par la licence CeCILL-B. Si une copie de cette licence
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Rapha<EFBFBD>l DROZ
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphael DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Repositories;
use Framadate\Choice;
use Framadate\FramaDB;
use Framadate\Utils;
class SlotRepository extends AbstractRepository {
/**
* Insert a bulk of slots.
*
* @param int $poll_id
* @param string $poll_id
* @param array $choices
*/
public function insertSlots($poll_id, $choices) {
public function insertSlots(string $poll_id, array $choices): void
{
$prepared = $this->prepare('INSERT INTO `' . Utils::table('slot') . '` (poll_id, title, moments) VALUES (?, ?, ?)');
foreach ($choices as $choice) {
/** @var Choice $choice */
// We prepared the slots (joined by comas)
$joinedSlots = null;
$joinedSlots = '';
$first = true;
foreach ($choice->getSlots() as $slot) {
if ($first) {
@ -44,22 +46,19 @@ class SlotRepository extends AbstractRepository {
}
// We execute the insertion
$this->connect->insert(Utils::table('slot'), [
'poll_id' => $poll_id,
'title' => $choice->getName(),
'moments' => $joinedSlots
]);
if (empty($joinedSlots)) {
$prepared->execute([$poll_id, $choice->getName(), null]);
} else {
$prepared->execute([$poll_id, $choice->getName(), $joinedSlots]);
}
}
}
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @return array
* @return array|false
*/
public function listByPollId($poll_id)
{
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('slot') . ' WHERE poll_id = ? ORDER BY id');
public function listByPollId(string $poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('slot') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
return $prepared->fetchAll();
@ -68,13 +67,12 @@ class SlotRepository extends AbstractRepository {
/**
* Find the slot into poll for a given datetime.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $datetime int The datetime of the slot
* @throws \Doctrine\DBAL\DBALException
* @return mixed Object The slot found, or null
*/
function findByPollIdAndDatetime($poll_id, $datetime) {
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('slot') . ' WHERE poll_id = ? AND SUBSTRING_INDEX(title, \'@\', 1) = ?');
public function findByPollIdAndDatetime(string $poll_id, $datetime) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('slot') . '` WHERE poll_id = ? AND SUBSTRING_INDEX(title, \'@\', 1) = ?');
$prepared->execute([$poll_id, $datetime]);
$slot = $prepared->fetch();
@ -86,49 +84,49 @@ class SlotRepository extends AbstractRepository {
/**
* Insert a new slot into a given poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $title mixed The title of the slot
* @param $moments mixed|null The moments joined with ","
* @return bool true if action succeeded
*/
function insert($poll_id, $title, $moments)
public function insert(string $poll_id, string $title, ?string $moments): bool
{
return $this->connect->insert(Utils::table('slot'), ['poll_id' => $poll_id, 'title' => $title, 'moments' => $moments]) > 0;
$prepared = $this->prepare('INSERT INTO `' . Utils::table('slot') . '` (poll_id, title, moments) VALUES (?,?,?)');
return $prepared->execute([$poll_id, $title, $moments]);
}
/**
* Update a slot into a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $datetime int The datetime of the slot to update
* @param $newMoments mixed The new moments
* @return bool|null true if action succeeded.
*/
function update($poll_id, $datetime, $newMoments)
public function update(string $poll_id, $datetime, $newMoments): ?bool
{
return $this->connect->update(Utils::table('slot'), ['moments' => $newMoments], ['poll_id' => $poll_id, 'title' => $datetime]) > 0;
$prepared = $this->prepare('UPDATE `' . Utils::table('slot') . '` SET moments = ? WHERE poll_id = ? AND title = ?');
return $prepared->execute([$newMoments, $poll_id, $datetime]);
}
/**
* Delete a entire slot from a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id int The ID of the poll
* @param $datetime mixed The datetime of the slot
* @throws \Doctrine\DBAL\DBALException
* @return bool
*/
public function deleteByDateTime($poll_id, $datetime)
public function deleteByDateTime(string $poll_id, $datetime): void
{
return $this->connect->delete(Utils::table('slot'), ['poll_id' => $poll_id, 'title' => $datetime]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('slot') . '` WHERE poll_id = ? AND title = ?');
$prepared->execute([$poll_id, $datetime]);
}
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @return bool
*/
public function deleteByPollId($poll_id)
public function deleteByPollId(string $poll_id): bool
{
return $this->connect->delete(Utils::table('slot'), ['poll_id' => $poll_id]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('slot') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);
}
}

View File

@ -1,48 +1,30 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Query\QueryBuilder;
use Framadate\FramaDB;
use Framadate\Utils;
class VoteRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws DBALException
* @return array
* @return array|false
*/
public function allUserVotesByPollId($poll_id)
{
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('vote') . ' WHERE poll_id = ? ORDER BY id');
public function allUserVotesByPollId(string $poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('vote') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
return $prepared->fetchAll();
}
/**
* Insert default values for slots
*
* @param string $poll_id
* @param integer $insert_position
* @return Statement|int
*/
public function insertDefault($poll_id, $insert_position)
public function insertDefault(string $poll_id, int $insert_position): bool
{
$qb = $this->createQueryBuilder();
$sql = $this->connect->getDatabasePlatform()->getName() === 'sqlite' ?
$this->generateSQLForInsertDefaultSQLite($qb, $insert_position) :
$this->generateSQLForInsertDefault($qb, $insert_position)
;
$query = $qb->update(Utils::table('vote'))
->set('choices', $sql)
->where($qb->expr()->eq('poll_id', $qb->createNamedParameter($poll_id)))
;
return $query->execute();
$prepared = $this->prepare('UPDATE `' . Utils::table('vote') . '` SET choices = CONCAT(SUBSTRING(choices, 1, ?), " ", SUBSTRING(choices, ?)) WHERE poll_id = ?'); //#51 : default value for unselected vote
return $prepared->execute([$insert_position, $insert_position + 1, $poll_id]);
}
function insert($poll_id, $name, $choices, $token, $mail) {
$this->connect->insert(Utils::table('vote'), ['poll_id' => $poll_id, 'name' => $name, 'choices' => $choices, 'uniqId' => $token, 'mail' => $mail]);
public function insert(string $poll_id, string $name, string $choices, string $token): \stdClass {
$prepared = $this->prepare('INSERT INTO `' . Utils::table('vote') . '` (poll_id, name, choices, uniqId) VALUES (?,?,?,?)');
$prepared->execute([$poll_id, $name, $choices, $token]);
$newVote = new \stdClass();
$newVote->poll_id = $poll_id;
@ -50,90 +32,61 @@ class VoteRepository extends AbstractRepository {
$newVote->name = $name;
$newVote->choices = $choices;
$newVote->uniqId = $token;
$newVote->mail=$mail;
return $newVote;
}
/**
* @param $poll_id
* @param $vote_id
* @throws DBALException
* @return bool
*/
public function deleteById($poll_id, $vote_id)
public function deleteById(string $poll_id, int $vote_id): bool
{
return $this->connect->delete(Utils::table('vote'), ['poll_id' => $poll_id, 'id' => $vote_id]) > 0;
}
$prepared = $this->prepare('DELETE FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND id = ?');
public function deleteOldVotesByPollId($poll_id, $votesToDelete) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('vote') . '` WHERE poll_id = ? ORDER BY `poll_id` ASC LIMIT ' . $votesToDelete);
return $prepared->execute([$poll_id]);
return $prepared->execute([$poll_id, $vote_id]);
}
/**
* Delete all votes of a given poll.
*
* @param $poll_id int The ID of the given poll.
* @throws DBALException
* @param string $poll_id The ID of the given poll.
* @return bool|null true if action succeeded.
*/
public function deleteByPollId($poll_id)
public function deleteByPollId(string $poll_id): ?bool
{
return $this->connect->delete(Utils::table('vote'), ['poll_id' => $poll_id]) > 0;
$prepared = $this->prepare('DELETE FROM `' . Utils::table('vote') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);
}
/**
* Delete all votes made on given moment index.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $index int The index of the vote into the poll
* @return bool|null true if action succeeded.
*/
public function deleteByIndex($poll_id, $index)
public function deleteByIndex(string $poll_id, int $index): ?bool
{
$qb = $this->createQueryBuilder();
$sql = $this->connect->getDatabasePlatform()->getName() === 'sqlite' ?
$this->generateSQLForInsertDefaultSQLite($qb, $index, true) :
$this->generateSQLForInsertDefault($qb, $index, true)
;
$query = $qb->update(Utils::table('vote'))
->set('choices', $sql)
->where($qb->expr()->eq('poll_id', $qb->createNamedParameter($poll_id)))
;
return $query->execute();
$prepared = $this->prepare('UPDATE `' . Utils::table('vote') . '` SET choices = CONCAT(SUBSTR(choices, 1, ?), SUBSTR(choices, ?)) WHERE poll_id = ?');
return $prepared->execute([$index, $index + 2, $poll_id]);
}
/**
* @param $poll_id
* @param $vote_id
* @param $name
* @param $choices
* @return bool
*/
public function update($poll_id, $vote_id, $name, $choices, $mail)
public function update(string $poll_id, string $vote_id, string $name, $choices): bool
{
return $this->connect->update(Utils::table('vote'), [
'choices' => $choices,
'name' => $name,
'mail' => $mail,
], [
'poll_id' => $poll_id,
'id' => $vote_id,
]) > 0;
$prepared = $this->prepare('UPDATE `' . Utils::table('vote') . '` SET choices = ?, name = ? WHERE poll_id = ? AND id = ?');
return $prepared->execute([$choices, $name, $poll_id, $vote_id]);
}
/**
* Check if name is already used for the given poll.
*
* @param int $poll_id ID of the poll
* @param string $poll_id ID of the poll
* @param string $name Name of the vote
* @throws DBALException
* @return bool true if vote already exists
*/
public function existsByPollIdAndName($poll_id, $name) {
$prepared = $this->prepare('SELECT 1 FROM ' . Utils::table('vote') . ' WHERE poll_id = ? AND name = ?');
public function existsByPollIdAndName(string $poll_id, string $name): bool
{
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND name = ?');
$prepared->execute([$poll_id, $name]);
return $prepared->rowCount() > 0;
}
@ -141,48 +94,16 @@ class VoteRepository extends AbstractRepository {
/**
* Check if name is already used for the given poll and another vote.
*
* @param int $poll_id ID of the poll
* @param string $poll_id ID of the poll
* @param string $name Name of the vote
* @param int $vote_id ID of the current vote
* @throws DBALException
* @return bool true if vote already exists
*/
public function existsByPollIdAndNameAndVoteId($poll_id, $name, $vote_id) {
$prepared = $this->prepare('SELECT 1 FROM ' . Utils::table('vote') . ' WHERE poll_id = ? AND name = ? AND id != ?');
public function existsByPollIdAndNameAndVoteId(string $poll_id, string $name, int $vote_id): bool
{
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND name = ? AND id != ?');
$prepared->execute([$poll_id, $name, $vote_id]);
return $prepared->rowCount() > 0;
}
/**
* @param QueryBuilder $qb
* @param $insert_position
* @param bool $delete
* @return string
*/
private function generateSQLForInsertDefaultSQLite($qb, $insert_position, $delete = false)
{
$position = $insert_position + ($delete ? 2 : 1);
return 'SUBSTR(choices, 1, '
. $qb->createNamedParameter($insert_position)
. ') || " " || SUBSTR(choices, '
. $qb->createNamedParameter($position)
. ')';
}
/**
* @param QueryBuilder $qb
* @param int $insert_position
* @param bool $delete
* @return string
*/
private function generateSQLForInsertDefault($qb, $insert_position, $delete = false)
{
$position = $insert_position + ($delete ? 2 : 1);
return 'CONCAT(SUBSTR(choices, 1, '
. $qb->createNamedParameter($insert_position)
. '), " ", SUBSTR(choices, '
. $qb->createNamedParameter($position)
. '))';
}
}

View File

@ -16,7 +16,7 @@ class PasswordHasher {
* @param string $password the password to hash.
* @return false|string the hashed password, or false on failure. The used algorithm, cost and salt are returned as part of the hash.
*/
public static function hash($password) {
public static function hash(string $password) {
return password_hash($password, PASSWORD_DEFAULT);
}
@ -27,7 +27,8 @@ class PasswordHasher {
* @param string $hash the hash to compare.
* @return bool
*/
public static function verify($password, $hash) {
public static function verify(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
}
}

View File

@ -2,31 +2,35 @@
namespace Framadate\Security;
class Token {
const DEFAULT_LENGTH = 64;
public const DEFAULT_LENGTH = 64;
private $time;
private $value;
private $length;
private static $codeAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
function __construct($length = self::DEFAULT_LENGTH) {
public function __construct($length = self::DEFAULT_LENGTH) {
$this->length = $length;
$this->time = time() + TOKEN_TIME;
$this->value = $this->generate();
}
public function getTime() {
public function getTime(): int
{
return $this->time;
}
public function getValue() {
public function getValue(): string
{
return $this->value;
}
public function isGone() {
public function isGone(): bool
{
return $this->time < time();
}
public function check($value) {
public function check($value): bool
{
return $value === $this->value;
}
@ -37,7 +41,8 @@ class Token {
* @param bool $crypto_strong If passed, tells if the token is "cryptographically strong" or not.
* @return string
*/
public static function getToken($length = self::DEFAULT_LENGTH, &$crypto_strong = false) {
public static function getToken(int $length = self::DEFAULT_LENGTH, bool &$crypto_strong = false): string
{
if (function_exists('openssl_random_pseudo_bytes')) {
openssl_random_pseudo_bytes(1, $crypto_strong); // Fake use to see if the algorithm used was "cryptographically strong"
return self::getSecureToken($length);
@ -45,7 +50,8 @@ class Token {
return self::getUnsecureToken($length);
}
public static function getUnsecureToken($length) {
public static function getUnsecureToken(int $length): string
{
$string = '';
mt_srand();
for ($i = 0; $i < $length; $i++) {
@ -58,7 +64,8 @@ class Token {
/**
* @author http://stackoverflow.com/a/13733588
*/
public static function getSecureToken($length){
public static function getSecureToken(int $length): string
{
$token = "";
for($i=0;$i<$length;$i++){
$token .= self::$codeAlphabet[self::crypto_rand_secure(0,strlen(self::$codeAlphabet))];
@ -66,25 +73,33 @@ class Token {
return $token;
}
private function generate() {
private function generate(): string
{
return self::getToken($this->length);
}
/**
* @author http://us1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322
*
* @param int $max
*
* @psalm-param 0 $min
* @psalm-param 0|positive-int $max
*/
private static function crypto_rand_secure($min, $max) {
private static function crypto_rand_secure(int $min, $max): int {
$range = $max - $min;
if ($range < 0) return $min; // not so random...
// not so random...
if ($range < 0) {
return $min;
}
$log = log($range, 2);
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter; // discard irrelevant bits
$rnd &= $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
}

View File

@ -1,13 +1,10 @@
<?php
namespace Framadate\Services;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Framadate\Exception\MomentAlreadyExistsException;
use Framadate\Form;
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
use stdClass;
use Framadate\Utils;
/**
* Class AdminPollService
@ -24,7 +21,7 @@ class AdminPollService {
private $voteRepository;
private $commentRepository;
function __construct(Connection $connect, PollService $pollService, LogService $logService) {
public function __construct(FramaDB $connect, PollService $pollService, LogService $logService) {
$this->connect = $connect;
$this->pollService = $pollService;
$this->logService = $logService;
@ -34,15 +31,14 @@ class AdminPollService {
$this->commentRepository = RepositoryFactory::commentRepository();
}
/**
* @param Form $poll
* @return bool
*/
public function updatePoll($poll) {
public function updatePoll($poll): bool
{
global $config;
if ($poll->end_date < $poll->creation_date) {
$poll->end_date = $poll->creation_date;
$poll->end_date = $poll->creation_date;
} elseif ($poll->end_date > $this->pollService->maxExpiryDate()) {
$poll->end_date = $this->pollService->maxExpiryDate();
$poll->end_date = $this->pollService->maxExpiryDate();
}
return $this->pollRepository->update($poll);
@ -51,21 +47,22 @@ class AdminPollService {
/**
* Delete a comment from a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $comment_id int The ID of the comment
* @return mixed true is action succeeded
*/
function deleteComment($poll_id, $comment_id) {
public function deleteComment(string $poll_id, int $comment_id) {
return $this->commentRepository->deleteById($poll_id, $comment_id);
}
/**
* Remove all comments of a poll.
*
* @param $poll_id int The ID a the poll
* @param string $poll_id The ID a the poll
* @return bool|null true is action succeeded
*/
function cleanComments($poll_id) {
public function cleanComments(string $poll_id): ?bool
{
$this->logService->log("CLEAN_COMMENTS", "id:$poll_id");
return $this->commentRepository->deleteByPollId($poll_id);
}
@ -73,21 +70,23 @@ class AdminPollService {
/**
* Delete a vote from a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $vote_id int The ID of the vote
* @return mixed true is action succeeded
* @return bool true is action succeeded
*/
function deleteVote($poll_id, $vote_id) {
public function deleteVote(string $poll_id, int $vote_id): bool
{
return $this->voteRepository->deleteById($poll_id, $vote_id);
}
/**
* Remove all votes of a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @return bool|null true is action succeeded
*/
function cleanVotes($poll_id) {
public function cleanVotes(string $poll_id): ?bool
{
$this->logService->log('CLEAN_VOTES', 'id:' . $poll_id);
return $this->voteRepository->deleteByPollId($poll_id);
}
@ -95,20 +94,13 @@ class AdminPollService {
/**
* Delete the entire given poll.
*
* @param $poll_id int The ID of the poll
* @param $poll_id string The ID of the poll
* @return bool true is action succeeded
*/
function deleteEntirePoll($poll_id) {
public function deleteEntirePoll(string $poll_id): bool
{
$poll = $this->pollRepository->findById($poll_id);
$this->logService->logEntries(
'DELETE_POLL',
[
"id:$poll->id",
"format:$poll->format",
"admin:$poll->admin_name",
"mail:$poll->admin_mail"
]
);
$this->logService->log('DELETE_POLL', "id:$poll->id, format:$poll->format, admin:$poll->admin_name, mail:$poll->admin_mail");
// Delete the entire poll
$this->voteRepository->deleteByPollId($poll_id);
@ -126,11 +118,9 @@ class AdminPollService {
* @param object $slot The slot informations (datetime + moment)
* @return bool true if action succeeded
*/
public function deleteDateSlot($poll, $slot) {
$this->logService->logEntries(
'DELETE_SLOT',
["id:$poll->id", 'slot:' . json_encode($slot)]
);
public function deleteDateSlot(object $poll, object $slot): bool
{
$this->logService->log('DELETE_SLOT', 'id:' . $poll->id . ', slot:' . json_encode($slot));
$datetime = $slot->title;
$moment = $slot->moment;
@ -140,7 +130,9 @@ class AdminPollService {
// We can't delete the last slot
if ($poll->format === 'D' && count($slots) === 1 && strpos($slots[0]->moments, ',') === false) {
return false;
} elseif ($poll->format === 'A' && count($slots) === 1) {
}
if ($poll->format === 'A' && count($slots) === 1) {
return false;
}
@ -177,10 +169,9 @@ class AdminPollService {
return true;
}
public function deleteClassicSlot($poll, $slot_title) {
$this->logService->logEntries(
['DELETE_SLOT', "id:$poll->id", "slot:$slot_title"]
);
public function deleteClassicSlot($poll, string $slot_title): bool
{
$this->logService->log('DELETE_SLOT', 'id:' . $poll->id . ', slot:' . $slot_title);
$slots = $this->pollService->allSlotsByPoll($poll);
@ -215,54 +206,41 @@ class AdminPollService {
* <li>Create a new moment if a slot already exists for the given date</li>
* </ul>
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @param $datetime int The datetime
* @param $new_moment string The moment's name
* @throws MomentAlreadyExistsException When the moment to add already exists in database
* @throws ConnectionException
*/
public function addDateSlot($poll_id, $datetime, $new_moment) {
$this->logService->logEntries(
'ADD_COLUMN',
["id:$poll_id", "datetime:$datetime", "moment:$new_moment"]
);
public function addDateSlot(string $poll_id, int $datetime, string $new_moment): void
{
$this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', datetime:' . $datetime . ', moment:' . $new_moment);
try {
$slots = $this->slotRepository->listByPollId($poll_id);
$result = $this->findInsertPosition($slots, $datetime);
} catch (DBALException $e) {
$this->logService->log('ERROR', "Database error, couldn't find slot insert position" . $e->getMessage());
return;
}
$slots = $this->slotRepository->listByPollId($poll_id);
$result = $this->findInsertPosition($slots, $datetime);
try {
// Begin transaction
$this->connect->beginTransaction();
// Begin transaction
$this->connect->beginTransaction();
if ($result->slot !== null) {
$slot = $result->slot;
$moments = explode(',', $slot->moments);
if ($result->slot !== null) {
$slot = $result->slot;
$moments = explode(',', $slot->moments);
// Check if moment already exists (maybe not necessary)
if (in_array($new_moment, $moments, true)) {
throw new MomentAlreadyExistsException($slot, $new_moment);
}
// Update found slot
$moments[] = $new_moment;
$this->slotRepository->update($poll_id, $datetime, implode(',', $moments));
} else {
$this->slotRepository->insert($poll_id, $datetime, $new_moment);
// Check if moment already exists (maybe not necessary)
if (in_array($new_moment, $moments, true)) {
throw new MomentAlreadyExistsException();
}
$this->voteRepository->insertDefault($poll_id, $result->insert);
// Commit transaction
$this->connect->commit();
} catch (DBALException $e) {
$this->logService->log('ERROR', "Database error, couldn't insert date slot" . $e->getMessage());
$this->connect->rollBack();
// Update found slot
$moments[] = $new_moment;
$this->slotRepository->update($poll_id, $datetime, implode(',', $moments));
} else {
$this->slotRepository->insert($poll_id, $datetime, $new_moment);
}
$this->voteRepository->insertDefault($poll_id, $result->insert);
// Commit transaction
$this->connect->commit();
}
/**
@ -271,22 +249,18 @@ class AdminPollService {
* <li>Create a new slot if no one exists for the given title</li>
* </ul>
*
* @param $poll_id int The ID of the poll
* @param $title int The title
* @param string $poll_id The ID of the poll
* @param string $title The title
* @throws MomentAlreadyExistsException When the moment to add already exists in database
* @throws ConnectionException
* @throws \Doctrine\DBAL\DBALException
*/
public function addClassicSlot($poll_id, $title) {
$this->logService->logEntries(
'ADD_COLUMN',
["id:$poll_id", "title:$title"]
);
public function addClassicSlot(string $poll_id, string $title): void
{
$this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', title:' . $title);
$slots = $this->slotRepository->listByPollId($poll_id);
// Check if slot already exists
$titles = array_map(function ($slot) {
$titles = array_map(static function ($slot) {
return $slot->title;
}, $slots);
if (in_array($title, $titles, true)) {
@ -313,10 +287,10 @@ class AdminPollService {
*
* @param $slots array All the slots of the poll
* @param $datetime int The datetime of the new slot
* @return stdClass An object like this one: {insert:X, slot:Y} where Y can be null.
* @return \stdClass An object like this one: {insert:X, slot:Y} where Y can be null.
*/
private function findInsertPosition($slots, $datetime) {
$result = new stdClass();
private function findInsertPosition(array $slots, int $datetime) {
$result = new \stdClass();
$result->slot = null;
$result->insert = 0;
@ -325,7 +299,7 @@ class AdminPollService {
// Search where to insert new column
foreach ($slots as $k=>$slot) {
$rowDatetime = $slot->title;
$rowDatetime = (int) $slot->title;
$moments = explode(',', $slot->moments);
if ($datetime === $rowDatetime) {

View File

@ -0,0 +1,182 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use DateTime;
use Framadate\Utils;
use Sabre\VObject;
class ICalService {
/**
* Creates an ical-File and initiates the download. If possible, the provided time is used, else an all day event is created.
*/
public function getEvent($poll, string $start_day, string $start_time): void
{
if(!$this->dayIsReadable($start_day)) {
return;
}
$ical_text = "";
$elements = explode("-", $start_time);
$end_time = null;
if(count($elements) === 2) {
$start_time = trim($elements[0]);
$end_time = trim($elements[1]);
}
$start_time = $this->reviseTimeString($start_time);
if($end_time !== null) {
$end_time = $this->reviseTimeString($end_time);
}
if($start_time !== null) {
if($end_time !== null) {
$ical_text = $this->getTimedEvent($poll, $start_day . " " . $start_time, $start_day . " " . $end_time);
} else {
$ical_text = $this->getTimedEvent1Hour($poll, $start_day . " " . $start_time);
}
}
else {
$date = DateTime::createFromFormat('d-m-Y', $start_day);
$day = $date->format('Ymd');
$ical_text = $this->getAllDayEvent($poll, $day);
}
$this->provideFile($poll->title, $ical_text);
}
/**
* Calls getTimedEvent with one hour as a time slot, starting at $start_daytime
*/
public function getTimedEvent1Hour($poll, string $start_daytime): string
{
$end_daytime = date(DATE_ATOM, strtotime('+1 hours', strtotime($start_daytime)));
return $this->getTimedEvent($poll, $start_daytime, $end_daytime);
}
/**
* Generates the text for an ical event including the time
*/
public function getTimedEvent($poll, string $start_daytime, string $end_daytime): string
{
$vcalendar = new VObject\Component\VCalendar([
'VEVENT' => [
'SUMMARY' => $poll->title,
'DESCRIPTION' => $this->stripMD($poll->description),
'DTSTART' => new DateTime($start_daytime),
'DTEND' => new DateTime($end_daytime)
],
'PRODID' => ICAL_PRODID
]);
return $vcalendar->serialize();
}
/**
* Generates the text for an ical event if the time is not known
*/
public function getAllDayEvent($poll, string $day): string
{
$vcalendar = new VObject\Component\VCalendar();
$vevent = $vcalendar->add('VEVENT');
$vevent->add('SUMMARY', $poll->title);
$vevent->add('DESCRIPTION', $this->stripMD($poll->description));
$dtstart = $vevent->add('DTSTART', $day);
$dtstart['VALUE'] = 'DATE';
unset($vcalendar->PRODID);
$vcalendar->add('PRODID', ICAL_PRODID);
return $vcalendar->serialize();
}
/**
* Creates a file and initiates the download
* @param string $title
* @param string $ical_text
*/
public function provideFile(string $title, string $ical_text): void
{
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename=' . $this->stripTitle($title) . ICAL_ENDING);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/calendar");
echo $ical_text;
exit;
}
/**
* Reformats a string value into a time readable by DateTime
* @param string $time
* @return string the corrected value, null if the format is unknown
*/
public function reviseTimeString(string $time): ?string
{
// 24-hour clock / international format
if (preg_match('/^\d\d(:)\d\d$/', $time)) {
return $time;
}
// 12-hour clock / using am and pm
if (preg_match('/^\d[0-2]?:?\d{0,2}\s?[aApP][mM]$/', $time)) {
return $this->formatTime($time);
}
// french format HHhMM or HHh
if (preg_match('/^\d\d?[hH]\d?\d?$/', $time)) {
return $this->formatTime(str_pad(str_ireplace("H", ":", $time), 5, "0"));
}
// Number only
if (preg_match('/^\d{1,4}$/', $time)) {
return $this->formatTime(str_pad(str_pad($time, 2, "0", STR_PAD_LEFT), 4, "0"));
}
return null;
}
/**
* @param string $day
* @return false|int 1 if the day string can be parsed, 0 if not and false if an error occured
*/
public function dayIsReadable(string $day) {
return preg_match('/^\d{2}-\d{2}-\d{4}$/', $day);
}
/**
* @param string $time
* @return string date string in format H:i (e.g. 19:00)
*/
public function formatTime(string $time): string
{
return date("H:i", strtotime($time));
}
/**
* Converts MD Code to HTML, then strips HTML away
*/
public function stripMD(string $string): string
{
return strip_tags(Utils::markdown($string));
}
/**
* Strips a string so it's usable as a file name (only digits, letters and underline allowed)
*
* @return null|string
*/
public function stripTitle(string $string): ?string {
return preg_replace('/[^a-z0-9_]+/', '-', strtolower($string));
}
}

View File

@ -17,26 +17,28 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use function __;
use DateTime;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use InvalidArgumentException;
use o80\i18n\CantLoadDictionaryException;
/**
* This class helps to clean all inputs from the users or external services.
*/
class InputService {
function __construct() {}
public function __construct() {}
/**
* This method filter an array calling "filter_var" on each items.
* Only items validated are added at their own indexes, the others are not returned.
* @param array $arr The array to filter
* @param int $type The type of filter to apply
* @param array|null $options The associative array of options
* @param array|int $options The associative array of options
* @return array The filtered array
*/
function filterArray(array $arr, $type, $options = null) {
public function filterArray(array $arr, int $type, $options = 0): array
{
$newArr = [];
foreach($arr as $id=>$item) {
@ -49,24 +51,32 @@ class InputService {
return $newArr;
}
function filterAllowedValues($value, array $allowedValues) {
public function filterAllowedValues($value, array $allowedValues) {
return in_array($value, $allowedValues, true) ? $value : null;
}
public function filterTitle($title) {
public function filterTitle($title): ?string
{
return $this->returnIfNotBlank($title);
}
/**
* @return false|string
*/
public function filterId($id) {
$filtered = filter_var($id, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
return $filtered ? substr($filtered, 0, 64) : false;
}
public function filterName($name) {
public function filterName($name): ?string
{
$filtered = trim($name);
return $this->returnIfNotBlank($filtered);
}
/**
* @return false|string
*/
public function filterMail($mail) {
///////////////////////////////////////////////////////////////////////////////////////
// formatting
@ -90,51 +100,67 @@ class InputService {
return $resultat;
}
public function filterDescription($description) {
$description = str_replace("\r\n", "\n", $description);
return $description;
public function filterDescription($description): string {
return str_replace("\r\n", "\n", $description);
}
/**
* @return false|string
*/
public function filterMD5($control) {
return filter_var($control, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => MD5_REGEX]]);
}
/**
* @return false|int
*/
public function filterInteger($int) {
return filter_var($int, FILTER_VALIDATE_INT);
}
/**
* @return false|int
*/
public function filterValueMax($int)
{
return $this->filterInteger($int) >= 1 ? $this->filterInteger($int) : false;
}
public function filterBoolean($boolean) {
return !!filter_var($boolean, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_TRUE_REGEX]]);
public function filterBoolean($boolean): bool
{
return (bool)filter_var($boolean, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_TRUE_REGEX]]);
}
/**
* @return false|string
*/
public function filterEditable($editable) {
return filter_var($editable, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => EDITABLE_CHOICE_REGEX]]);
}
public function filterCollectMail($collectMail) {
return filter_var($collectMail, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => COLLECT_MAIL_CHOICE_REGEX]]);
}
public function filterComment($comment) {
public function filterComment($comment): ?string
{
$comment = str_replace("\r\n", "\n", $comment);
return $this->returnIfNotBlank($comment);
}
/**
* @param string $date
* @return DateTime
*/
public function filterDate($date) {
$dDate = parse_translation_date($date);
if ($dDate) {
return $dDate;
public function validateDate(DateTime $date, DateTime $minDate, DateTime $maxDate): DateTime {
if ($date < $minDate) {
return $minDate;
}
throw new InvalidArgumentException('Invalid date');
if ($maxDate < $date) {
return $maxDate;
}
return $date;
}
/**
* @throws CantLoadDictionaryException
* @return DateTime|false
*/
public function parseDate(string $date) {
return DateTime::createFromFormat(__('Date', 'datetime_parseformat'), $date)->setTime(0, 0);
}
/**
@ -143,7 +169,8 @@ class InputService {
* @param string $filtered The value
* @return string|null
*/
private function returnIfNotBlank($filtered) {
private function returnIfNotBlank(string $filtered): ?string
{
if ($filtered) {
$withoutSpaces = str_replace(' ', '', $filtered);
if (!empty($withoutSpaces)) {

View File

@ -9,7 +9,7 @@
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* Ce logiciel est r<EFBFBD>gi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
@ -17,10 +17,10 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;
use function __f;
use Exception;
use Framadate\Utils;
use PDO;
use Smarty;
/**
@ -36,34 +36,34 @@ class InstallService {
'cleanUrl' => true,
// Database configuration
'dbName' => 'framadate',
'dbPort' => 3306,
'dbHost' => 'localhost',
'dbConnectionString' => 'mysql:host=<HOST>;dbname=<SCHEMA>;port=3306',
'dbUser' => 'root',
'dbPassword' => '',
'dbPrefix' => 'fd_',
'migrationTable' => 'framadate_migration'
];
function __construct() {}
public function __construct() {}
public function updateFields($data) {
public function updateFields($data): void
{
foreach ($data as $field => $value) {
$this->fields[$field] = $value;
}
}
public function install(Smarty &$smarty) {
public function install(Smarty &$smarty): array
{
// Check values are present
if (empty($this->fields['appName']) || empty($this->fields['appMail']) || empty($this->fields['defaultLanguage']) || empty($this->fields['dbName']) || empty($this->fields['dbHost']) || empty($this->fields['dbPort']) || empty($this->fields['dbUser'])) {
return $this->error('Missing values');
if (empty($this->fields['appName']) || empty($this->fields['appMail']) || empty($this->fields['defaultLanguage']) || empty($this->fields['dbConnectionString']) || empty($this->fields['dbUser'])) {
return $this->error('MISSING_VALUES');
}
// Connect to database
try {
$connect = $this->connectTo($this->fields);
} catch(\Doctrine\DBAL\DBALException $e) {
return $this->error('Unable to connect to database', $e->getMessage());
$connect = $this->connectTo($this->fields['dbConnectionString'], $this->fields['dbUser'], $this->fields['dbPassword']);
} catch(Exception $e) {
return $this->error('CANT_CONNECT_TO_DATABASE', $e->getMessage());
}
// Write configuration to conf.php file
@ -75,23 +75,25 @@ class InstallService {
}
/**
* @param $fields
* @return \Doctrine\DBAL\Connection|null
* Connect to PDO compatible source
*
* @param string $connectionString
* @param string $user
* @param string $password
* @return PDO
*/
function connectTo($fields) {
$doctrineConfig = new Configuration();
$connectionParams = [
'dbname' => $fields['dbName'],
'user' => $fields['dbUser'],
'password' => $fields['dbPassword'],
'host' => $fields['dbHost'],
'driver' => $fields['dbDriver'],
'charset' => $fields['dbDriver'] === 'pdo_mysql' ? 'utf8mb4' : 'utf8',
];
return DriverManager::getConnection($connectionParams, $doctrineConfig);
public function connectTo(string $connectionString, string $user, string $password): PDO
{
$pdo = @new PDO($connectionString, $user, $password);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
function writeConfiguration(Smarty &$smarty) {
/**
* @return false|int
*/
public function writeConfiguration(Smarty &$smarty) {
foreach($this->fields as $field=>$value) {
$smarty->assign($field, $value);
}
@ -103,16 +105,17 @@ class InstallService {
/**
* @param $content
* @return bool|int
* @return false|int
*/
function writeToFile($content) {
public function writeToFile(string $content) {
return @file_put_contents(CONF_FILENAME, $content);
}
/**
* @return array
*/
function ok() {
public function ok(): array
{
return [
'status' => 'OK',
'msg' => __f('Installation', 'Ended', Utils::get_server_name())
@ -120,10 +123,12 @@ class InstallService {
}
/**
* @param $msg
* @param string $msg
* @param string $details
* @return array
*/
function error($msg, $details = '') {
public function error(string $msg, string $details = ''): array
{
return [
'status' => 'ERROR',
'code' => $msg,
@ -131,7 +136,8 @@ class InstallService {
];
}
public function getFields() {
public function getFields(): array
{
return $this->fields;
}
}

View File

@ -7,7 +7,7 @@ namespace Framadate\Services;
* @package Framadate\Services
*/
class LogService {
function __construct() {
public function __construct() {
}
/**
@ -16,22 +16,8 @@ class LogService {
* @param $tag string A tag is used to quickly found a message when reading log file
* @param $message string some message
*/
function log($tag, $message) {
error_log(date('Ymd His') . ' [' . $tag . '] ' . $message . "\n", 3, ROOT_DIR . LOG_FILE);
}
/**
* Log a list of entries as a single message to the log file.
*
* @param $tag string A tag is used to quickly found a message when reading log file
* @param $entries array some entries to join with comma into a single message
*/
function logEntries($tag, $entries) {
$escapeCommas = function($value) {
return str_replace(',', '-', $value);
};
$message = join(', ', array_map($escapeCommas, $entries));
public function log(string $tag, string $message): void
{
error_log(date('Ymd His') . ' [' . $tag . '] ' . $message . "\n", 3, ROOT_DIR . LOG_FILE);
}
}

View File

@ -1,58 +1,41 @@
<?php
namespace Framadate\Services;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;
class MailService {
const DELAY_BEFORE_RESEND = 300;
public const DELAY_BEFORE_RESEND = 300;
const MAILSERVICE_KEY = 'mailservice';
public const MAILSERVICE_KEY = 'mailservice';
/**
* @var bool
*/
private $smtp_allowed;
/**
* @var array
*/
private $smtp_options = [];
/**
* @var bool
*/
private $use_sendmail;
/**
* @var LogService
*/
private $logService;
/**
* MailService constructor.
* @param $smtp_allowed
* @param array $smtp_options
* @param bool $use_sendmail
*/
public function __construct($smtp_allowed, $smtp_options = [], $use_sendmail = false) {
public function __construct($smtp_allowed, $smtp_options = []) {
$this->logService = new LogService();
$this->smtp_allowed = $smtp_allowed;
if (true === is_array($smtp_options)) {
$this->smtp_options = $smtp_options;
}
$this->use_sendmail = $use_sendmail;
}
/**
* @return false|string
*/
public function isValidEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
public function isEnabled() {
return $this->smtp_allowed === true;
}
public function send($to, $subject, $body, $msgKey = null) {
if ($this->isEnabled() && $this->canSendMsg($msgKey)) {
/**
* @throws Exception
*/
public function send(string $to, string $subject, string $body, ?string $msgKey = null): void
{
if ($this->smtp_allowed === true && $this->canSendMsg($msgKey)) {
$mail = new PHPMailer(true);
$this->configureMailer($mail);
@ -70,16 +53,15 @@ class MailService {
$mail->Subject = $subject;
// Bodies
$body = $body . ' <br/><br/>' . __('Mail', 'Thank you for your trust.') . ' <br/>' . NOMAPPLICATION . ' <hr/>' . __('Mail', "\"The road is long, but the way is clear…\"<br/>Framasoft lives only by your donations.<br/>Thank you in advance for your support https://soutenir.framasoft.org");
$body .= ' <br/><br/>' . __('Mail', 'Thanks for your trust.') . ' <br/>' . NOMAPPLICATION . ' <hr/>' . __('Mail', 'FOOTER');
$mail->isHTML(true);
$mail->CharSet = PHPMailer::CHARSET_UTF8;
$mail->msgHTML($body, ROOT_DIR, function ($html) use ($mail) {
return $this->html2text($mail, $html);
});
$mail->msgHTML($body, ROOT_DIR, true);
// Build headers
$mail->CharSet = 'UTF-8';
$mail->addCustomHeader('Auto-Submitted', 'auto-generated');
$mail->addCustomHeader('Return-Path', '<>');
$mail->XMailer = ' ';
// Send mail
$mail->send();
@ -88,19 +70,25 @@ class MailService {
$this->logService->log('MAIL', 'Mail sent to: ' . $to . ', key: ' . $msgKey);
// Store the mail sending date
$this->initializeSession();
$_SESSION[self::MAILSERVICE_KEY][$msgKey] = time();
}
}
public function canSendMsg($msgKey) {
public function canSendMsg(?string $msgKey): bool
{
if ($msgKey === null) {
return true;
}
$this->initializeSession();
return !isset($_SESSION[self::MAILSERVICE_KEY][$msgKey]) || time() - $_SESSION[self::MAILSERVICE_KEY][$msgKey] > self::DELAY_BEFORE_RESEND;
}
private function initializeSession(): void {
if (!isset($_SESSION[self::MAILSERVICE_KEY])) {
$_SESSION[self::MAILSERVICE_KEY] = [];
}
return !isset($_SESSION[self::MAILSERVICE_KEY][$msgKey]) || time() - $_SESSION[self::MAILSERVICE_KEY][$msgKey] > self::DELAY_BEFORE_RESEND;
}
/**
@ -108,16 +96,14 @@ class MailService {
*
* @param PHPMailer $mailer
*/
private function configureMailer(PHPMailer $mailer) {
if ($this->use_sendmail) {
$mailer->isSendmail();
} else {
$mailer->isSMTP();
}
private function configureMailer(PHPMailer $mailer): void
{
$mailer->isSMTP();
$available_options = [
'host' => 'Host',
'auth' => 'SMTPAuth',
'authtype' => 'AuthType',
'username' => 'Username',
'password' => 'Password',
'secure' => 'SMTPSecure',
@ -130,18 +116,4 @@ class MailService {
}
}
}
/**
* Custom "advanced" callback to pass to msgHTML function
*
* @param PHPMailer $mailer a PHPMailer instance
* @param string $html the HTML body of an email
*/
private function html2text(PHPMailer $mailer, $html) {
$html = preg_replace('/<a[^>]*href="([^"]+)"[^>]*>(.*?)<\/a>/si', '${2}: ${1}', $html);
$html = preg_replace('/<br\/>/', "\n", $html);
return $mailer->html2text($html);
}
}

View File

@ -3,30 +3,23 @@
namespace Framadate\Services;
use \stdClass;
use Framadate\Services\MailService;
use function __;
use function __f;
use Framadate\Utils;
use o80\i18n\CantLoadDictionaryException;
use PHPMailer\PHPMailer\Exception;
class NotificationService {
const UPDATE_VOTE = 1;
const ADD_VOTE = 2;
const ADD_COMMENT = 3;
const UPDATE_POLL = 10;
const DELETED_POLL = 11;
const TEMPLATES_MAPPING = [
self::UPDATE_VOTE => 'mail/updated_vote.html.tpl',
self::ADD_VOTE => 'mail/added_vote.html.tpl',
self::ADD_COMMENT => 'mail/added_comment.html.tpl',
self::UPDATE_POLL => 'mail/updated_poll.html.tpl',
self::DELETED_POLL => 'mail/deleted_poll.html.tpl'
];
public const UPDATE_VOTE = 1;
public const ADD_VOTE = 2;
public const ADD_COMMENT = 3;
public const UPDATE_POLL = 10;
public const DELETED_POLL = 11;
private $mailService;
private $smarty;
function __construct(MailService $mailService, \Smarty $smarty) {
public function __construct(MailService $mailService) {
$this->mailService = $mailService;
$this->smarty = $smarty;
}
/**
@ -35,8 +28,10 @@ class NotificationService {
* @param $poll stdClass The poll
* @param $name string The name user who triggered the notification
* @param $type int cf: Constants on the top of this page
* @throws Exception|CantLoadDictionaryException
*/
function sendUpdateNotification(stdClass $poll, $type, $name='') {
public function sendUpdateNotification($poll, int $type, string $name=''): void
{
if (!isset($_SESSION['mail_sent'])) {
$_SESSION['mail_sent'] = [];
}
@ -46,68 +41,52 @@ class NotificationService {
$isOtherType = $type !== self::UPDATE_VOTE && $type !== self::ADD_VOTE && $type !== self::ADD_COMMENT;
if ($isVoteAndCanSendIt || $isCommentAndCanSendIt || $isOtherType) {
if (self::isParticipation($type)) {
$translationString = 'Poll participation: %s';
if ($this->isParticipation($type)) {
$translationString = 'Poll\'s participation: %s';
} else {
$translationString = 'Notification of poll: %s';
}
$subject = '[' . NOMAPPLICATION . '] ' . __f('Mail', $translationString, $poll->title);
$this->smarty->assign('username', $name);
$this->smarty->assign('poll_title', $poll->title);
$this->smarty->assign('poll_url', Utils::getUrlSondage($poll->admin_id, true));
$message = '';
$template_name = self::TEMPLATES_MAPPING[$type];
$urlSondage = Utils::getUrlSondage($poll->admin_id, true);
$link = '<a href="' . $urlSondage . '">' . $urlSondage . '</a>' . "\n\n";
if (!is_null($template_name)) {
$message = $this->smarty->fetch($template_name);
} else {
$message = '';
switch ($type) {
case self::UPDATE_VOTE:
$message .= $name . ' ';
$message .= __('Mail', "updated a vote.\nYou can find your poll at the link") . " :\n\n";
$message .= $link;
break;
case self::ADD_VOTE:
$message .= $name . ' ';
$message .= __('Mail', "filled a vote.\nYou can find your poll at the link") . " :\n\n";
$message .= $link;
break;
case self::ADD_COMMENT:
$message .= $name . ' ';
$message .= __('Mail', "wrote a comment.\nYou can find your poll at the link") . " :\n\n";
$message .= $link;
break;
case self::UPDATE_POLL:
$message = __f('Mail', 'Someone just change your poll available at the following link %s.', Utils::getUrlSondage($poll->admin_id, true)) . "\n\n";
break;
case self::DELETED_POLL:
$message = __f('Mail', 'Someone just delete your poll %s.', Utils::htmlEscape($poll->title)) . "\n\n";
break;
}
$messageTypeKey = $type . '-' . $poll->id;
$this->mailService->send($poll->admin_mail, $subject, $message, $messageTypeKey);
if ($poll->admin_mail) {
$this->mailService->send($poll->admin_mail, $subject, $message, $messageTypeKey);
}
}
}
function isParticipation($type)
public function isParticipation(int $type): bool
{
return $type >= self::UPDATE_POLL;
}
function sendPollCreationMails($creator_mail, $creator_name, $poll_name, $poll_id, $admin_poll_id) {
if (!$this->mailService->isEnabled() || !$this->mailService->isValidEmail($creator_mail)) {
return null;
}
$this->smarty->assign('poll_creator_name', Utils::htmlMailEscape($creator_name));
$this->smarty->assign('poll_name', Utils::htmlMailEscape($poll_name));
$this->smarty->assign('poll_url', Utils::getUrlSondage($poll_id));
$message_participants = $this->smarty->fetch('mail/participants_forward_email.html.tpl');
$this->mailService->send($creator_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Participant link') . '] ' . __('Generic', 'Poll') . ': ' . $poll_name, $message_participants);
$this->smarty->assign('poll_admin_url', Utils::getUrlSondage($admin_poll_id, true));
$message_admin = $this->smarty->fetch('mail/creation_notification_email.html.tpl');
$this->mailService->send($creator_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Message for the author') . '] ' . __('Generic', 'Poll') . ': ' . $poll_name, $message_admin);
}
function sendEditedVoteNotification($email, &$poll, $poll_id, $edited_vote_id) {
$url = Utils::getUrlSondage($poll_id, false, $edited_vote_id);
$this->smarty->assign('poll', $poll);
$this->smarty->assign('poll_id', $poll_id);
$this->smarty->assign('editedVoteUniqueId', $edited_vote_id);
$body = $this->smarty->fetch('mail/remember_edit_link.tpl');
$subject = '[' . NOMAPPLICATION . '][' . __('EditLink', 'REMINDER') . '] ' . __f('EditLink', 'Edit link for poll "%s"', $poll->title);
$this->mailService->send($email, $subject, $body);
}
function sendFindPollsByMailNotification($mail, &$polls) {
$this->smarty->assign('polls', $polls);
$body = $this->smarty->fetch('mail/find_polls.tpl');
$this->mailService->send($mail, __('FindPolls', 'List of your polls') . ' - ' . NOMAPPLICATION, $body, 'SEND_POLLS');
}
}

View File

@ -20,47 +20,26 @@ namespace Framadate\Services;
use DateInterval;
use DateTime;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Exception;
use Framadate\Exception\AlreadyExistsException;
use Framadate\Exception\ConcurrentEditionException;
use Framadate\Exception\ConcurrentVoteException;
use Framadate\Exception\PollNotFoundException;
use Framadate\Form;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Security\Token;
use RuntimeException;
use stdClass;
class PollService {
private $logService;
private $pollRepository;
private $slotRepository;
private $voteRepository;
private $commentRepository;
/**
* @var NotificationService
*/
private $notificationService;
/**
* @var SessionService
*/
private $sessionService;
/**
* @var PurgeService
*/
private $purgeService;
/**
* @var LogService
*/
private $logService;
public function __construct(LogService $logService, NotificationService $notificationService, SessionService $sessionService, PurgeService $purgeService) {
public function __construct(LogService $logService) {
$this->logService = $logService;
$this->notificationService = $notificationService;
$this->sessionService = $sessionService;
$this->purgeService = $purgeService;
$this->pollRepository = RepositoryFactory::pollRepository();
$this->slotRepository = RepositoryFactory::slotRepository();
$this->voteRepository = RepositoryFactory::voteRepository();
@ -70,56 +49,34 @@ class PollService {
/**
* Find a poll from its ID.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @return stdClass|null The found poll, or null
*/
function findById($poll_id) {
try {
if (preg_match(POLL_REGEX, $poll_id)) {
return $this->pollRepository->findById($poll_id);
}
} catch (DBALException $e) {
$this->logService->log('ERROR', 'Database error : ' . $e->getMessage());
public function findById(string $poll_id) {
if (preg_match(POLL_REGEX, $poll_id)) {
return $this->pollRepository->findById($poll_id);
}
return null;
}
/**
* @param $admin_poll_id
* @return mixed|null
*/
public function findByAdminId($admin_poll_id) {
try {
if (preg_match(ADMIN_POLL_REGEX, $admin_poll_id)) {
return $this->pollRepository->findByAdminId($admin_poll_id);
}
} catch (DBALException $e) {
$this->logService->log('ERROR', 'Database error : ' . $e->getMessage());
public function findByAdminId(string $admin_poll_id) {
if (preg_match(ADMIN_POLL_REGEX, $admin_poll_id)) {
return $this->pollRepository->findByAdminId($admin_poll_id);
}
return null;
}
/**
* @param $poll_id
* @return array
*/
public function allCommentsByPollId($poll_id)
{
try {
return $this->commentRepository->findAllByPollId($poll_id);
} catch (DBALException $e) {
$this->logService->log('error', $e->getMessage());
return null;
}
public function allCommentsByPollId(string $poll_id) {
return $this->commentRepository->findAllByPollId($poll_id);
}
function allVotesByPollId($poll_id) {
public function allVotesByPollId(string $poll_id) {
return $this->voteRepository->allUserVotesByPollId($poll_id);
}
function allSlotsByPoll($poll) {
public function allSlotsByPoll(stdClass $poll) {
$slots = $this->slotRepository->listByPollId($poll->id);
if ($poll->format === 'D') {
$this->sortSlorts($slots);
@ -128,46 +85,45 @@ class PollService {
}
/**
* @param $poll_id
* @param $vote_id
* @param $name
* @param $choices
* @param $slots_hash
* @param string $mail
* @param string $poll_id
* @param int $vote_id
* @param string $name
* @param array $choices
* @param string $slots_hash
* @throws AlreadyExistsException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @return bool
*/
public function updateVote($poll_id, $vote_id, $name, $choices, $slots_hash, $mail) {
public function updateVote(string $poll_id, int $vote_id, string $name, array $choices, string $slots_hash): bool
{
$this->checkVoteConstraints($choices, $poll_id, $slots_hash, $name, $vote_id);
// Update vote
$choices = implode($choices);
return $this->voteRepository->update($poll_id, $vote_id, $name, $choices, $mail);
return $this->voteRepository->update($poll_id, $vote_id, $name, implode($choices));
}
/**
* @param $poll_id
* @param $name
* @param $choices
* @param $slots_hash
* @param string $mail
* @throws AlreadyExistsException
* @param string $poll_id
* @param string $name
* @param array $choices
* @param string $slots_hash
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @throws PollNotFoundException
* @throws AlreadyExistsException
* @return stdClass
*/
function addVote($poll_id, $name, $choices, $slots_hash, $mail) {
public function addVote(string $poll_id, string $name, array $choices, string $slots_hash): stdClass
{
$this->checkVoteConstraints($choices, $poll_id, $slots_hash, $name);
// Insert new vote
$choices = implode($choices);
$token = $this->random(16);
return $this->voteRepository->insert($poll_id, $name, $choices, $token, $mail);
return $this->voteRepository->insert($poll_id, $name, implode($choices), $this->random(16));
}
function addComment($poll_id, $name, $comment) {
public function addComment($poll_id, $name, $comment): bool
{
if ($this->commentRepository->exists($poll_id, $name, $comment)) {
return true;
}
@ -176,109 +132,38 @@ class PollService {
}
/**
* Main entry point for poll creation. This action does all the following:
* - Validate input data
* - Create the poll in database
* - Send emails
* - Cleanups post creation (session and purges)
*
* @param Form $form
* @param string $end_date
* @return string|null returns the admin_id if the poll was created
*/
function doPollCreation(Form $form, $end_date) {
// Min/Max archive date
$min_expiry_time = $this->minExpiryDate();
$max_expiry_time = $this->maxExpiryDate();
if (!empty($end_date)) {
$registredate = explode('/', $end_date);
if (is_array($registredate) && count($registredate) === 3) {
$time = mktime(0, 0, 0, $registredate[1], $registredate[0], $registredate[2]);
if ($time < $min_expiry_time) {
$form->end_date = $min_expiry_time;
} elseif ($max_expiry_time < $time) {
$form->end_date = $max_expiry_time;
} else {
$form->end_date = $time;
}
}
}
if (empty($form->end_date)) {
// By default, expiration date is 6 months after last day
$form->end_date = $max_expiry_time;
}
// Insert poll in database
list($poll_id, $admin_poll_id) = $this->createPoll($form);
// Send confirmation by mail if enabled
if (!is_null($ids)) {
// Everything went well
$this->notificationService->sendPollCreationMails($form->admin_mail, $form->admin_name, $form->title, $poll_id, $admin_poll_id);
// Clean Form data in session
$this->sessionService->removeAll('form');
// Creation message
$this->sessionService->set("Framadate", "messagePollCreated", TRUE);
// Delete old polls
$this->purgeService->repeatedCleanings();
} else {
// Add an error in the form
$form->errors = [
__('Error', 'GenericErrorPollCreation')
];
// TODO: change this to use sessionService function
$_SESSION['form'] = serialize($form);
}
return $admin_poll_id;
}
/**
* @param Form $form
* @throws ConnectionException
* @return array
*/
public function createPoll(Form $form) {
public function createPoll(Form $form): array
{
// Generate poll IDs, loop while poll ID already exists
$this->pollRepository->beginTransaction();
try {
if (empty($form->id)) { // User want us to generate an id for him
do {
$poll_id = $this->random(16);
} while ($this->pollRepository->existsById($poll_id));
$admin_poll_id = $poll_id . $this->random(8);
} else { // User have chosen the poll id
$poll_id = $form->id;
do {
$admin_poll_id = $this->random(24);
} while ($this->pollRepository->existsByAdminId($admin_poll_id));
}
// Insert poll + slots
$this->pollRepository->insertPoll($poll_id, $admin_poll_id, $form);
$this->slotRepository->insertSlots($poll_id, $form->getChoices());
$this->pollRepository->commit();
$this->logService->log(
'CREATE_POLL',
'id:' . $poll_id . ', title: ' . $form->title . ', format:' . $form->format . ', admin:' . $form->admin_name . ', mail:' . $form->admin_mail
);
return [$poll_id, $admin_poll_id];
} catch (DBALException $e) {
$this->pollRepository->rollback();
$this->logService->log('ERROR', "Poll couldn't be saved : " . $e->getMessage());
return null;
if (empty($form->id)) { // User want us to generate an id for him
do {
$poll_id = $this->random(16);
} while ($this->pollRepository->existsById($poll_id));
$admin_poll_id = $poll_id . $this->random(8);
} else { // User have choosen the poll id
$poll_id = $form->id;
do {
$admin_poll_id = $this->random(24);
} while ($this->pollRepository->existsByAdminId($admin_poll_id));
}
// Insert poll + slots
$this->pollRepository->beginTransaction();
$this->pollRepository->insertPoll($poll_id, $admin_poll_id, $form);
$this->slotRepository->insertSlots($poll_id, $form->getChoices());
$this->pollRepository->commit();
$this->logService->log('CREATE_POLL', 'id:' . $poll_id . ', title: ' . $form->title . ', format:' . $form->format . ', admin:' . $form->admin_name . ', mail:' . $form->admin_mail);
return [$poll_id, $admin_poll_id];
}
public function findAllByAdminMail($mail) {
public function findAllByAdminMail($mail): array
{
return $this->pollRepository->findAllByAdminMail($mail);
}
@ -287,7 +172,8 @@ class PollService {
* @param stdClass $poll
* @return array
*/
public function computeBestChoices($votes, $poll) {
public function computeBestChoices(array $votes, $poll): array
{
if (0 === count($votes)) {
return $this->computeEmptyBestChoices($poll);
}
@ -313,11 +199,12 @@ class PollService {
return $result;
}
function splitSlots($slots) {
public function splitSlots($slots): array
{
$splitted = [];
foreach ($slots as $slot) {
$obj = new stdClass();
$obj->day = (new DateTime())->setTimestamp((int) $slot->title);
$obj->day = $slot->title;
$obj->moments = explode(',', $slot->moments);
$splitted[] = $obj;
@ -330,13 +217,15 @@ class PollService {
* @param $slots array The slots to hash
* @return string The hash
*/
public function hashSlots($slots) {
return md5(array_reduce($slots, function($carry, $item) {
public function hashSlots(array $slots): string
{
return md5(array_reduce($slots, static function($carry, $item) {
return $carry . $item->id . '@' . $item->moments . ';';
}));
}
function splitVotes($votes) {
public function splitVotes(array $votes): array
{
$splitted = [];
foreach ($votes as $vote) {
$obj = new stdClass();
@ -344,7 +233,6 @@ class PollService {
$obj->name = $vote->name;
$obj->uniqId = $vote->uniqId;
$obj->choices = str_split($vote->choices);
$obj->mail = $vote->mail;
$splitted[] = $obj;
}
@ -354,32 +242,30 @@ class PollService {
/**
* @throws Exception
* @return DateTime The max timestamp allowed for expiry date
* @return DateTime The max date allowed for expiry date
*/
public function maxExpiryDate() {
try {
global $config;
$default_poll_duration = isset($config['default_poll_duration']) ? $config['default_poll_duration'] : 60;
return (new DateTime())->add(new DateInterval('P' . $default_poll_duration . 'D'));
} catch (Exception $e) {
throw new RuntimeException('Configuration Exception');
}
public function maxExpiryDate(): DateTime {
global $config;
return (new DateTime())->add(new DateInterval('P' . $config['default_poll_duration'] . 'D'));
}
/**
* @throws Exception
* @return DateTime The min timestamp allowed for expiry date
* @return DateTime The min date allowed for expiry date
*/
public function minExpiryDate() {
public function minExpiryDate(): DateTime
{
return (new DateTime())->add(new DateInterval('P1D'));
}
/**
* @return mixed
*/
public function sortSlorts(&$slots) {
uasort($slots, function ($a, $b) {
return $a->title > $b->title;
public function sortSlorts(array &$slots): array {
uasort($slots, static function ($a, $b) {
if ($a->title === $b->title) {
return 0;
}
return $a->title > $b->title ? 1 : -1;
});
return $slots;
}
@ -388,7 +274,7 @@ class PollService {
* @param stdClass $poll
* @return array
*/
private function computeEmptyBestChoices($poll)
private function computeEmptyBestChoices($poll): array
{
$result = ['y' => [], 'inb' => []];
// if there is no votes, calculates the number of slot
@ -398,7 +284,7 @@ class PollService {
if ($poll->format === 'A') {
// poll format classic
for ($i = 0; $i < count($slots); $i++) {
for ($i = 0, $iMax = count($slots); $i < $iMax; $i++) {
$result['y'][] = 0;
$result['inb'][] = 0;
}
@ -408,7 +294,7 @@ class PollService {
$slots = $this->splitSlots($slots);
foreach ($slots as $slot) {
for ($i = 0; $i < count($slot->moments); $i++) {
for ($i = 0, $iMax = count($slot->moments); $i < $iMax; $i++) {
$result['y'][] = 0;
$result['inb'][] = 0;
}
@ -417,23 +303,26 @@ class PollService {
return $result;
}
private function random($length) {
private function random(int $length): string
{
return Token::getToken($length);
}
/**
* @param $choices
* @param $poll_id
* @param $slots_hash
* @param $name
* @param string $vote_id
* @param array $choices
* @param string $poll_id
* @param string $slots_hash
* @param string $name
* @param bool|int $vote_id
* @throws AlreadyExistsException
* @throws ConcurrentVoteException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @throws PollNotFoundException
*/
private function checkVoteConstraints($choices, $poll_id, $slots_hash, $name, $vote_id = FALSE) {
private function checkVoteConstraints(array $choices, string $poll_id, string $slots_hash, string $name, $vote_id = false): void
{
// Check if vote already exists with the same name
if (FALSE === $vote_id) {
if (false === $vote_id) {
$exists = $this->voteRepository->existsByPollIdAndName($poll_id, $name);
} else {
$exists = $this->voteRepository->existsByPollIdAndNameAndVoteId($poll_id, $name, $vote_id);
@ -445,6 +334,10 @@ class PollService {
$poll = $this->findById($poll_id);
if (!$poll) {
throw new PollNotFoundException();
}
// Check that no-one voted in the meantime and it conflicts the maximum votes constraint
$this->checkMaxVotes($choices, $poll, $poll_id);
@ -459,7 +352,8 @@ class PollService {
* @param $slots_hash string The hash sent by the user
* @throws ConcurrentEditionException Thrown when hashes are differents
*/
private function checkThatSlotsDidntChanged($poll, $slots_hash) {
private function checkThatSlotsDidntChanged(stdClass $poll, string $slots_hash): void
{
$slots = $this->allSlotsByPoll($poll);
if ($slots_hash !== $this->hashSlots($slots)) {
throw new ConcurrentEditionException();
@ -474,7 +368,8 @@ class PollService {
* @param string $poll_id
* @throws ConcurrentVoteException
*/
private function checkMaxVotes($user_choice, $poll, $poll_id) {
private function checkMaxVotes(array $user_choice, $poll, string $poll_id): void
{
$votes = $this->allVotesByPollId($poll_id);
if (count($votes) <= 0) {
return;

View File

@ -1,7 +1,6 @@
<?php
namespace Framadate\Services;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
/**
@ -16,7 +15,7 @@ class PurgeService {
private $voteRepository;
private $commentRepository;
function __construct(Connection $connect, LogService $logService) {
public function __construct(LogService $logService) {
$this->logService = $logService;
$this->pollRepository = RepositoryFactory::pollRepository();
$this->slotRepository = RepositoryFactory::slotRepository();
@ -24,89 +23,51 @@ class PurgeService {
$this->commentRepository = RepositoryFactory::commentRepository();
}
public function repeatedCleanings() {
$this->purgeOldPolls();
if (0 === time() % 10) {
$this->cleanDemoPoll();
}
}
/**
* This methode purges all old polls (the ones with end_date in past).
*
* @return bool true is action succeeded
* @return int number of purged polls
*/
public function purgeOldPolls() {
try {
$oldPolls = $this->pollRepository->findOldPolls();
$count = count($oldPolls);
public function purgeOldPolls(): int
{
$oldPolls = $this->pollRepository->findOldPolls();
$count = count($oldPolls);
if ($count > 0) {
$this->logService->log('EXPIRATION', 'Going to purge ' . $count . ' poll(s)...');
if ($count > 0) {
$this->logService->log('EXPIRATION', 'Going to purge ' . $count . ' poll(s)...');
foreach ($oldPolls as $poll) {
if ($this->purgePollById($poll->id)) {
$this->logService->log(
'EXPIRATION_SUCCESS',
'id: ' . $poll->id . ', title:' . $poll->title . ', format: ' . $poll->format . ', admin: ' . $poll->admin_name
);
} else {
$this->logService->log(
'EXPIRATION_FAILED',
'id: ' . $poll->id . ', title:' . $poll->title . ', format: ' . $poll->format . ', admin: ' . $poll->admin_name
);
}
foreach ($oldPolls as $poll) {
if ($this->purgePollById($poll->id)) {
$this->logService->log('EXPIRATION_SUCCESS', 'id: ' . $poll->id . ', title:' . $poll->title . ', format: ' . $poll->format . ', admin: ' . $poll->admin_name);
} else {
$this->logService->log('EXPIRATION_FAILED', 'id: ' . $poll->id . ', title:' . $poll->title . ', format: ' . $poll->format . ', admin: ' . $poll->admin_name);
}
}
return $count;
} catch (DBALException $e) {
$this->logService->log('ERROR', $e->getMessage());
return false;
}
return $count;
}
public function cleanDemoPoll() {
if (!defined("DEMO_POLL_ID") || !defined("DEMO_POLL_NUMBER_VOTES")) {
return;
}
$this->voteRepository->beginTransaction();
$demoVotes = $this->voteRepository->allUserVotesByPollId(DEMO_POLL_ID);
$votesToDelete = count($demoVotes) - DEMO_POLL_NUMBER_VOTES;
if ($votesToDelete > 0) {
$this->voteRepository->deleteOldVotesByPollId(DEMO_POLL_ID, $votesToDelete);
}
$this->voteRepository->commit();
}
/**
* This methode delete all data about a poll.
*
* @param $poll_id int The ID of the poll
* @param string $poll_id The ID of the poll
* @return bool true is action succeeded
*/
private function purgePollById($poll_id) {
public function purgePollById(string $poll_id): bool
{
$done = true;
try {
$this->pollRepository->beginTransaction();
$done &= $this->commentRepository->deleteByPollId($poll_id);
$done &= $this->voteRepository->deleteByPollId($poll_id);
$done &= $this->slotRepository->deleteByPollId($poll_id);
$done &= $this->pollRepository->deleteById($poll_id);
$this->pollRepository->beginTransaction();
$done &= $this->commentRepository->deleteByPollId($poll_id);
$done &= $this->voteRepository->deleteByPollId($poll_id);
$done &= $this->slotRepository->deleteByPollId($poll_id);
$done &= $this->pollRepository->deleteById($poll_id);
if ($done) {
$this->pollRepository->commit();
} else {
$this->pollRepository->rollback();
}
} catch (DBALException $e) {
$this->logService->log('ERROR', $e->getMessage());
if ($done) {
$this->pollRepository->commit();
} else {
$this->pollRepository->rollback();
}
return $done;

View File

@ -5,7 +5,7 @@ use Framadate\Security\PasswordHasher;
use Framadate\Security\Token;
class SecurityService {
function __construct() {
public function __construct() {
}
/**
@ -18,9 +18,10 @@ class SecurityService {
* </ul>
*
* @param $tokan_name string The name of the CSRF token
* @return Token The token
* @return string The token
*/
function getToken($tokan_name) {
function getToken(string $tokan_name): string
{
if (!isset($_SESSION['tokens'])) {
$_SESSION['tokens'] = [];
}
@ -38,7 +39,8 @@ class SecurityService {
* @param $csrf string Value to check
* @return bool true if the token is well checked
*/
public function checkCsrf($tokan_name, $csrf) {
public function checkCsrf(string $tokan_name, string $csrf): bool
{
$checked = $_SESSION['tokens'][$tokan_name]->getValue() === $csrf;
if($checked) {
@ -54,17 +56,18 @@ class SecurityService {
* @param $poll \stdClass The poll which we seek access
* @return bool true if the current session can access this poll
*/
public function canAccessPoll($poll) {
public function canAccessPoll($poll): bool
{
if (is_null($poll->password_hash)) {
return true;
}
$this->ensureSessionPollSecurityIsCreated();
$currentPassword = isset($_SESSION['poll_security'][$poll->id]) ? $_SESSION['poll_security'][$poll->id] : null;
$currentPassword = $_SESSION['poll_security'][$poll->id] ?? null;
if (!empty($currentPassword) && PasswordHasher::verify($currentPassword, $poll->password_hash)) {
return true;
}
}
unset($_SESSION['poll_security'][$poll->id]);
return false;
}
@ -75,17 +78,18 @@ class SecurityService {
* @param $poll \stdClass The poll which we seek access
* @param $password string the password to compare
*/
public function submitPollAccess($poll, $password) {
public function submitPollAccess($poll, string $password): void
{
if (!empty($password)) {
$this->ensureSessionPollSecurityIsCreated();
$_SESSION['poll_security'][$poll->id] = $password;
}
}
private function ensureSessionPollSecurityIsCreated() {
private function ensureSessionPollSecurityIsCreated(): void
{
if (!isset($_SESSION['poll_security'])) {
$_SESSION['poll_security'] = [];
}
}
}

View File

@ -17,12 +17,7 @@ class SessionService {
$this->initSectionIfNeeded($section);
$returnValue = $defaultValue;
if (isset($_SESSION[$section][$key])) {
$returnValue = $_SESSION[$section][$key];
}
return $returnValue;
return $_SESSION[$section][$key] ?? $defaultValue;
}
/**
@ -32,7 +27,8 @@ class SessionService {
* @param $key
* @param $value
*/
public function set($section, $key, $value) {
public function set($section, $key, $value): void
{
assert(!empty($key));
assert(!empty($section));
@ -47,27 +43,18 @@ class SessionService {
* @param $section
* @param $key
*/
public function remove($section, $key) {
public function remove($section, $key): void
{
assert(!empty($key));
assert(!empty($section));
unset($_SESSION[$section][$key]);
}
/**
* Remove a session section
*
* @param $section
*/
public function removeAll($section) {
assert(!empty($section));
unset($_SESSION[$section]);
}
private function initSectionIfNeeded($section) {
private function initSectionIfNeeded($section): void
{
if (!isset($_SESSION[$section])) {
$_SESSION[$section] = [];
}
}
}
}

View File

@ -11,7 +11,7 @@ use Framadate\Repositories\RepositoryFactory;
class SuperAdminService {
private $pollRepository;
function __construct() {
public function __construct() {
$this->pollRepository = RepositoryFactory::pollRepository();
}
@ -23,7 +23,8 @@ class SuperAdminService {
* @param int $limit The limit size
* @return array ['polls' => The {$limit} polls, 'count' => Entries found by the query, 'total' => Total count]
*/
public function findAllPolls($search, $page, $limit) {
public function findAllPolls(array $search, int $page, int $limit): array
{
$start = $page * $limit;
$polls = $this->pollRepository->findAll($search, $start, $limit);
$count = $this->pollRepository->count($search);
@ -32,4 +33,3 @@ class SuperAdminService {
return ['polls' => $polls, 'count' => $count, 'total' => $total];
}
}

View File

@ -24,31 +24,24 @@ class Utils {
/**
* @return string Server name
*/
public static function get_server_name() {
$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$serverPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : '';
$scheme = (
(defined('FORCE_HTTPS') && FORCE_HTTPS ) ||
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
) ? 'https' : 'http';
$domain = defined('APP_URL') ? APP_URL : $serverName;
$port = in_array($serverPort, ['80', '443'], true) ? '' : ':' . $serverPort;
public static function get_server_name(): string
{
$scheme = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) ? 'https' : 'http';
$port = in_array($_SERVER['SERVER_PORT'], ['80', '443'], true) ? '' : ':' . $_SERVER['SERVER_PORT'];
$dirname = dirname($_SERVER['SCRIPT_NAME']);
$dirname = $dirname === '\\' ? '/' : $dirname . '/';
$dirname = str_replace('/admin', '', $dirname);
$dirname = str_replace('/action', '', $dirname);
$dirname = str_replace(['/admin', '/action'], '', $dirname);
$server_name = (defined('APP_URL') ? APP_URL : $_SERVER['SERVER_NAME']) . $port . $dirname;
return $scheme . '://' . preg_replace('#//+#', '/', $domain . $port . $dirname);
return $scheme . '://' . preg_replace('#//+#', '/', $server_name);
}
/**
* @param string $title
*
* @deprecated
*/
public static function print_header($title = '') {
public static function print_header($title = ''): void {
global $locale;
echo '<!DOCTYPE html>
@ -68,17 +61,17 @@ class Utils {
<link rel="stylesheet" href="' . self::get_server_name() . 'css/style.css" />
<link rel="stylesheet" href="' . self::get_server_name() . 'css/frama.css" />
<link rel="stylesheet" href="' . self::get_server_name() . 'css/print.css" media="print" />
<script type="text/javascript" src="' . self::get_server_name() . 'js/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="' . self::get_server_name() . 'js/bootstrap.min.js"></script>
<script type="text/javascript" src="' . self::get_server_name() . 'js/bootstrap-datepicker.js"></script>';
<script src="' . self::get_server_name() . 'js/jquery-3.6.0.min.js"></script>
<script src="' . self::get_server_name() . 'js/bootstrap.min.js"></script>
<script src="' . self::get_server_name() . 'js/bootstrap-datepicker.js"></script>';
if ('en' !== $locale) {
echo '
<script type="text/javascript" src="' . self::get_server_name() . 'js/locales/bootstrap-datepicker.' . $locale . '.js"></script>';
<script src="' . self::get_server_name() . 'js/locales/bootstrap-datepicker.' . $locale . '.js"></script>';
}
echo '
<script type="text/javascript" src="' . self::get_server_name() . 'js/core.js"></script>';
<script src="' . self::get_server_name() . 'js/core.js"></script>';
if (is_file($_SERVER['DOCUMENT_ROOT'] . "/nav/nav.js")) {
echo '<script src="/nav/nav.js" id="nav_js" type="text/javascript" charset="utf-8"></script><!-- /Framanav -->';
echo '<script src="/nav/nav.js" id="nav_js" charset="utf-8"></script><!-- /Framanav -->';
}
echo '
@ -89,16 +82,17 @@ class Utils {
/**
* Function allowing to generate poll's url
* @param string $id The poll's id
* @param bool $admin True to generate an admin URL, false for a public one
* @param string $vote_id (optional) The vote's unique id
* @param null $action
* @param null $action_value
* @return string The poll's URL.
* @param string $id The poll's id
* @param bool $admin True to generate an admin URL, false for a public one
* @param string $vote_id (optional) The vote's unique id
* @param string|null $action
* @param string|null $action_value
* @return string The poll's URL.
*/
public static function getUrlSondage($id, $admin = false, $vote_id = '', $action = null, $action_value = null) {
public static function getUrlSondage(string $id, bool $admin = false, string $vote_id = '', string $action = null, string $action_value = null): string
{
// URL-Encode $action_value
$action_value = $action_value ? Utils::base64url_encode($action_value) : null;
$action_value = $action_value ? self::base64url_encode($action_value) : null;
if (URL_PROPRE) {
if ($admin === true) {
@ -140,17 +134,20 @@ class Utils {
*
* @param mixed $object The object to print.
*/
public static function debug($object) {
public static function debug($object): void
{
echo '<pre>';
print_r($object);
echo '</pre>';
}
public static function table($tableName) {
public static function table(string $tableName): string
{
return TABLENAME_PREFIX . $tableName;
}
public static function markdown($md, $clear=false, $line=true) {
public static function markdown(string $md, bool $clear=false, bool $line=true): string
{
$parseDown = new Parsedown();
$parseDown
@ -163,7 +160,7 @@ class Utils {
} else {
$md = preg_replace_callback(
'#( ){2,}#',
function ($m) {
static function ($m) {
return str_repeat('&nbsp;', strlen($m[0]));
},
$md
@ -176,39 +173,38 @@ class Utils {
return $clear ? $text : $html;
}
public static function htmlEscape($html) {
public static function htmlEscape(string $html): string {
return htmlentities($html, ENT_HTML5 | ENT_QUOTES);
}
public static function htmlMailEscape($html) {
public static function htmlMailEscape(string $html): string
{
return htmlspecialchars($html, ENT_HTML5 | ENT_QUOTES);
}
public static function csvEscape($text) {
$escaped = str_replace('"', '""', $text);
$escaped = str_replace("\r\n", '', $escaped);
$escaped = str_replace("\n", '', $escaped);
public static function csvEscape(string $text): string
{
$escaped = str_replace(['"', "\r\n", "\n"], ['""', '', ''], $text);
$escaped = preg_replace("/^(=|\+|\-|\@)/", "'$1", $escaped);
return '"' . $escaped . '"';
}
public static function cleanFilename($title) {
public static function cleanFilename(string $title): string {
$cleaned = preg_replace('[^a-zA-Z0-9._-]', '_', $title);
$cleaned = preg_replace(' {2,}', ' ', $cleaned);
return $cleaned;
return preg_replace(' {2,}', ' ', $cleaned);
}
public static function fromPostOrDefault($postKey, $default = '') {
public static function fromPostOrDefault(string $postKey, ?string $default = '') {
return !empty($_POST[$postKey]) ? $_POST[$postKey] : $default;
}
public static function base64url_encode($input) {
public static function base64url_encode(string $input): string
{
return rtrim(strtr(base64_encode($input), '+/', '-_'), '=');
}
public static function base64url_decode($input) {
public static function base64url_decode(string $input): string {
return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT), true);
}
}

View File

@ -1,117 +0,0 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
// Fully qualified domain name of your webserver.
// If this is unset or empty, the servername is determined automatically.
// You *have to set this* if you are running Framadate behind a reverse proxy.
// const APP_URL = '<www.mydomain.fr>';
// Application name
const NOMAPPLICATION = 'Framadate';
// Database administrator email
const ADRESSEMAILADMIN = 'admin@app.tld';
// Email for automatic responses (you should set it to "no-reply")
const ADRESSEMAILREPONSEAUTO = 'no@reply';
// Database driver
const DB_DRIVER = FRAMADATE_DB_DRIVER;
// Database name
const DB_NAME = FRAMADATE_DB_NAME;
// Database host
const DB_HOST = FRAMADATE_DB_HOST;
// Database port
const DB_PORT = FRAMADATE_DB_PORT;
// Database user
const DB_USER = FRAMADATE_DB_USER;
// Database password
const DB_PASSWORD = FRAMADATE_DB_PASSWORD;
// Table name prefix
const TABLENAME_PREFIX = 'fd_';
// Name of the table that stores migration script already executed
const MIGRATION_TABLE = 'framadate_migration';
// Default Language
const DEFAULT_LANGUAGE = 'fr';
// List of supported languages, fake constant as arrays can be used as constants only in PHP >=5.6
$ALLOWED_LANGUAGES = [
'fr' => 'Français',
'en' => 'English',
'oc' => 'Occitan',
'es' => 'Español',
'de' => 'Deutsch',
'nl' => 'Dutch',
'it' => 'Italiano',
'br' => 'Brezhoneg',
'hu' => 'Magyar',
];
// Path to image file with the title
const IMAGE_TITRE = 'images/logo-framadate.png';
// Clean URLs, boolean
const URL_PROPRE = false;
// Use REMOTE_USER data provided by web server
const USE_REMOTE_USER = true;
// Path to the log file
const LOG_FILE = 'admin/stdout.log';
// Days (after expiration date) before purging a poll
const PURGE_DELAY = 60;
// Max slots per poll
const MAX_SLOTS_PER_POLL = 366;
// Number of seconds before we allow to resend an "Remember Edit Link" email.
const TIME_EDIT_LINK_EMAIL = 60;
// Config
$config = [
/* general config */
'use_smtp' => false, // use email for polls creation/modification/responses notification
'smtp_options' => [
'host' => 'localhost', // SMTP server (you could add many servers (main and backup for example) : use ";" like separator
'auth' => false, // Enable SMTP authentication
'username' => '', // SMTP username
'password' => '', // SMTP password
'secure' => '', // Enable encryption (false, tls or ssl)
'port' => 25, // TCP port to connect to
],
/* home */
'show_what_is_that' => true, // display "how to use" section
'show_the_software' => true, // display technical information about the software
'show_cultivate_your_garden' => true, // display "development and administration" information
/* create_classic_poll.php / create_date_poll.php */
'default_poll_duration' => 180, // default values for the new poll duration (number of days).
/* create_classic_poll.php */
'user_can_add_img_or_link' => true, // user can add link or URL when creating his poll.
'markdown_editor_by_default' => true, // The markdown editor for the description is enabled by default
'provide_fork_awesome' => true, // Whether the build-in fork-awesome should be provided
];

View File

@ -18,10 +18,10 @@
*/
// FRAMADATE version
const VERSION = '1.2.0-alpha.1';
const VERSION = '1.1.19';
// PHP Needed version
const PHP_NEEDED_VERSION = '5.6';
const PHP_NEEDED_VERSION = '7.3';
// Config constants
const COMPILE_DIR = '/tpl_c/';
@ -33,7 +33,6 @@ const CHOICE_REGEX = '/^[ 012]$/';
const BOOLEAN_REGEX = '/^(on|off|true|false|1|0)$/i';
const BOOLEAN_TRUE_REGEX = '/^(on|true|1)$/i';
const EDITABLE_CHOICE_REGEX = '/^[0-2]$/';
const COLLECT_MAIL_CHOICE_REGEX = '/^[0-3]$/';
const BASE64_REGEX = '/^[A-Za-z0-9]+$/';
const MD5_REGEX = '/^[A-Fa-f0-9]{32}$/';
@ -43,3 +42,6 @@ const SESSION_EDIT_LINK_TIME = "EditLinkMail";
// CSRF (300s = 5min)
const TOKEN_TIME = 300;
const ICAL_ENDING = ".ics";
const ICAL_PRODID = "-//Framasoft//Framadate//EN";

View File

@ -17,134 +17,31 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
const DATE_FORMAT_FULL = 'EEEE d MMMM y';
const DATE_FORMAT_SHORT = 'EEEE d MMMM y';
const DATE_FORMAT_DAY = 'E d';
const DATE_FORMAT_DATE = 'dd-MM-y';
const DATE_FORMAT_MONTH_YEAR = 'MMMM y';
const DATE_FORMAT_DATETIME_SHORT = 'EEEE d';
// Prepare I18N instance
use o80\i18n\I18N;
// Change session language when requested
if (isset($_REQUEST['lang'])
&& array_key_exists($_REQUEST['lang'], $ALLOWED_LANGUAGES)) {
$_SESSION['lang'] = $_REQUEST['lang'];
$i18n = I18N::instance();
$i18n->setDefaultLang(DEFAULT_LANGUAGE);
$i18n->setPath(__DIR__ . '/../../locale');
// Change language when user asked for it
if (isset($_POST['lang']) && is_string($_POST['lang']) && array_key_exists($_POST['lang'], $ALLOWED_LANGUAGES)) {
$_SESSION['lang'] = $_POST['lang'];
}
// Try the user-specified locale, or the browser-specified locale.
if (isset($_SESSION['lang'])) {
$wanted_locale = $_SESSION['lang'];
} else {
$wanted_locale = locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE']);
}
// Use the best available locale.
$locale = locale_lookup(array_keys($ALLOWED_LANGUAGES), $wanted_locale, false, DEFAULT_LANGUAGE);
/* <html lang="$locale"> */
$i18n->get('', 'Something, just to load the dictionary');
$locale = str_replace('_', '-', $i18n->getLoadedLang());
/**
* Formats a DateTime according to the IntlDateFormatter
*
* @param DateTime $date
* @param string $pattern
* @param $forceLocale
* @return string
*/
function date_format_intl(DateTime $date, $pattern = DATE_FORMAT_FULL, $forceLocale = null) {
global $locale;
$local_locale = $forceLocale || $locale;
$dateFormatter = IntlDateFormatter::create(
$local_locale,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
date_default_timezone_get(),
IntlDateFormatter::GREGORIAN,
$pattern
);
return $dateFormatter->format($date);
}
/**
* Formats a DateTime according to a translated format
*
* @param DateTime $date
* @param string $pattern
* @return string
*/
function date_format_translation(DateTime $date, $pattern = 'Y-m-d') {
return $date->format(__('Date', $pattern));
}
/**
* Converts a string into a DateTime according to the IntlDateFormatter
*
* @param $dateString
* @param string $pattern
* @param string|null $forceLocale
* @return DateTime|null
*/
function parse_intl_date($dateString, $pattern = DATE_FORMAT_DATE, $forceLocale = null) {
global $locale;
$local_locale = $forceLocale || $locale;
$dateFormatter = IntlDateFormatter::create(
$local_locale,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
date_default_timezone_get(),
IntlDateFormatter::GREGORIAN,
$pattern
);
$timestamp = $dateFormatter->parse($dateString);
try {
return (new DateTime())->setTimestamp($timestamp);
} catch (Exception $e) {
return null;
/* Date Format */
$date_format['txt_full'] = __('Date', 'FULL'); //summary in create_date_poll.php and removal date in choix_(date|autre).php
$date_format['txt_short'] = __('Date', 'SHORT'); // radio title
$date_format['txt_day'] = __('Date', 'DAY');
$date_format['txt_date'] = __('Date', 'DATE');
$date_format['txt_month_year'] = __('Date', 'MONTH_YEAR');
$date_format['txt_datetime_short'] = __('Date', 'DATETIME');
if (PHP_OS_FAMILY === 'Windows') { //%e can't be used on Windows platform, use %#d instead
foreach ($date_format as $k => $v) {
$date_format[$k] = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $v); //replace %e by %#d for windows
}
}
/**
* Converts a string into a DateTime according to a translated format
*
* @param string $dateString
* @param string $pattern
* @return DateTime
*/
function parse_translation_date($dateString, $pattern = 'Y-m-d') {
return DateTime::createFromFormat(__('Date', $pattern), $dateString);
}
/* i18n helper functions */
use Symfony\Component\Translation\Loader\PoFileLoader;
use Symfony\Component\Translation\Translator;
class __i18n {
private static $translator;
private static $fallbacktranslator;
public static function init($locale) {
self::$translator = new Translator($locale);
self::$translator->addLoader('pofile', new PoFileLoader());
self::$translator->addResource('pofile', ROOT_DIR . "po/{$locale}.po", $locale);
# Fallback:
# For Symfony/Translation, empty strings are valid, but in po files, untranslated strings are "".
# This means we cannot use the standard $translator->setFallbackLocales() mechanism :(
self::$fallbacktranslator = new Translator(DEFAULT_LANGUAGE);
self::$fallbacktranslator->addLoader('pofile', new PoFileLoader());
self::$fallbacktranslator->addResource('pofile', ROOT_DIR . "po/" . DEFAULT_LANGUAGE . ".po", DEFAULT_LANGUAGE);
}
public static function translate($key) {
return self::$translator->trans($key)
?: self::$fallbacktranslator->trans($key);
}
}
__i18n::init($locale);
function __($section, $key) {
return __i18n::translate($key);
}
function __f($section, $key, $args) {
$msg = __i18n::translate($key);
$args = array_slice(func_get_args(), 2);
return vsprintf($msg, $args);
}

View File

@ -16,15 +16,13 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Services\LogService;
use Framadate\Utils;
// Autoloading of dependencies with Composer
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../vendor/o80/i18n/src/shortcuts.php';
if (session_id() === '') {
session_start();
@ -35,47 +33,23 @@ if (ini_get('date.timezone') === '') {
}
define('ROOT_DIR', __DIR__ . '/../../');
$path = '/app/inc/config.php';
if (getenv('APP_ENV') === 'test') {
$path = '/app/inc/config.test.php';
// Read DB connection params from Environment Variables if possible
define('FRAMADATE_DB_DRIVER', getenv('FRAMADATE_DB_DRIVER') ? getenv('FRAMADATE_DB_DRIVER') : 'pdo_sqlite');
define('FRAMADATE_DB_NAME', getenv('FRAMADATE_DB_NAME') ? getenv('FRAMADATE_DB_NAME') : 'framadate');
define('FRAMADATE_DB_HOST', getenv('FRAMADATE_DB_HOST') ? getenv('FRAMADATE_DB_HOST') : '');
define('FRAMADATE_DB_PORT', getenv('FRAMADATE_DB_PORT') ? getenv('FRAMADATE_DB_PORT') : '');
define('FRAMADATE_DB_USER', getenv('FRAMADATE_DB_USER') ? getenv('FRAMADATE_DB_USER') : '');
define('FRAMADATE_DB_PASSWORD', getenv('FRAMADATE_DB_PASSWORD') ? getenv('FRAMADATE_DB_PASSWORD') : '');
}
define('CONF_FILENAME', ROOT_DIR . $path);
define('CONF_FILENAME', ROOT_DIR . '/app/inc/config.php');
require_once __DIR__ . '/constants.php';
if (is_file(CONF_FILENAME)) {
@include_once CONF_FILENAME;
// Connection to database
$doctrineConfig = new Configuration();
$connectionParams = [
'dbname' => DB_NAME,
'user' => DB_USER,
'password' => DB_PASSWORD,
'host' => DB_HOST,
'driver' => DB_DRIVER,
'charset' => DB_DRIVER === 'pdo_mysql' ? 'utf8mb4' : 'utf8',
];
if (DB_DRIVER === 'pdo_sqlite') {
$connectionParams['path'] = ROOT_DIR . '/test_database.sqlite';
}
@include_once __DIR__ . '/config.php';
try {
$connect = DriverManager::getConnection($connectionParams, $doctrineConfig);
// Connection to database
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
$err = 0;
} catch (DBALException $e) {
$logger = new LogService();
$logger->log('ERROR', $e->getMessage());
} catch (PDOException $e) {
if ($_SERVER['SCRIPT_NAME'] !== '/maintenance.php') {
header(('Location: ' . Utils::get_server_name() . 'maintenance.php'));
exit;
}
$error = $e->getMessage();
}
} else {
define('NOMAPPLICATION', 'Framadate');
@ -89,12 +63,10 @@ if (is_file(CONF_FILENAME)) {
'de' => 'Deutsch',
'it' => 'Italiano',
'br' => 'Brezhoneg',
'ca' => 'Català'
];
}
require_once __DIR__ . '/i18n.php';
// Smarty
require_once __DIR__ . '/smarty.php';
require_once __DIR__ . '/services.php';
Services::init($connect, $smarty);

View File

@ -1,94 +0,0 @@
<?php
use Doctrine\DBAL\Connection;
use Framadate\Services\AdminPollService;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\NotificationService;
use Framadate\Services\PollService;
use Framadate\Services\PurgeService;
use Framadate\Services\SecurityService;
use Framadate\Services\SessionService;
class Services {
private static $connect;
private static $smarty;
private static $adminPollService;
private static $inputService;
private static $logService;
private static $mailService;
private static $notificationService;
private static $pollService;
private static $purgeService;
private static $securityService;
private static $sessionService;
public static function init(Connection $connect, \Smarty $smarty) {
self::$connect = $connect;
self::$smarty = $smarty;
}
public static function adminPoll() {
if (self::$adminPollService === null) {
self::$adminPollService = new AdminPollService(self::$connect, self::poll(), self::log());
}
return self::$adminPollService;
}
public static function input() {
if (self::$inputService === null) {
self::$inputService = new InputService();
}
return self::$inputService;
}
public static function log() {
if (self::$logService === null) {
self::$logService = new LogService();
}
return self::$logService;
}
public static function mail() {
if (self::$mailService === null) {
self::$mailService = new MailService($config['use_smtp'], $config['smtp_options'], $config['use_sendmail']);
}
return self::$mailService;
}
public static function notification() {
if (self::$notificationService === null) {
self::$notificationService = new NotificationService(self::mail(), self::$smarty);
}
return self::$notificationService;
}
public static function poll() {
if (self::$pollService === null) {
self::$pollService = new PollService(self::log(), self::notification(), self::session(), self::purge());
}
return self::$pollService;
}
public static function purge() {
if (self::$purgeService === null) {
self::$purgeService = new PurgeService(self::$connect, self::log());
}
return self::$purgeService;
}
public static function session() {
if (self::$sessionService === null) {
self::$sessionService = new SessionService();
}
return self::$sessionService;
}
public static function security() {
if (self::$securityService === null) {
self::$securityService = new SecurityService();
}
return self::$securityService;
}
}

View File

@ -25,18 +25,15 @@ $smarty->setCompileDir(ROOT_DIR . COMPILE_DIR);
$smarty->setCacheDir(ROOT_DIR . '/cache/');
$smarty->caching = false;
$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$smarty->assign('APPLICATION_NAME', NOMAPPLICATION);
$smarty->assign('APPLICATION_VERSION', VERSION);
$smarty->assign('SERVER_URL', Utils::get_server_name());
$smarty->assign('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']);
$smarty->assign('TITLE_IMAGE', IMAGE_TITRE);
$smarty->assign('use_nav_js', strstr($serverName, 'framadate.org'));
$smarty->assign('use_nav_js', strstr($_SERVER['SERVER_NAME'], 'framadate.org'));
$smarty->assign('provide_fork_awesome', !isset($config['provide_fork_awesome']) || $config['provide_fork_awesome']);
$smarty->assign('locale', $locale);
$smarty->assign('langs', $ALLOWED_LANGUAGES);
$smarty->assign('date_format', $date_format);
if (isset($config['tracking_code'])) {
$smarty->assign('tracking_code', $config['tracking_code']);
}
@ -45,8 +42,7 @@ if (defined('FAVICON')) {
}
// Dev Mode
if (php_sapi_name() === 'cli-server') {
$smarty->caching = 0;
if (isset($_SERVER['FRAMADATE_DEVMODE']) && $_SERVER['FRAMADATE_DEVMODE']) {
$smarty->force_compile = true;
$smarty->compile_check = true;
} else {
@ -54,9 +50,10 @@ if (php_sapi_name() === 'cli-server') {
$smarty->compile_check = false;
}
function smarty_function_poll_url($params, Smarty_Internal_Template $template) {
function smarty_function_poll_url($params, Smarty_Internal_Template $template): string
{
$poll_id = filter_var($params['id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
$admin = (isset($params['admin']) && $params['admin']) ? true : false;
$admin = isset($params['admin']) && $params['admin'];
$action = (isset($params['action']) && !empty($params['action'])) ? Utils::htmlEscape($params['action']) : false;
$action_value = (isset($params['action_value']) && !empty($params['action_value'])) ? $params['action_value'] : false;
$vote_unique_id = isset($params['vote_id']) ? filter_var($params['vote_id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]) : '';
@ -66,41 +63,40 @@ function smarty_function_poll_url($params, Smarty_Internal_Template $template) {
return Utils::getUrlSondage($poll_id, $admin, $vote_unique_id, $action, $action_value);
}
function smarty_modifier_markdown($md, $clear = false, $inline=true) {
function smarty_modifier_markdown(string $md, bool $clear = false, bool $inline=true): string
{
return Utils::markdown($md, $clear, $inline);
}
function smarty_modifier_resource($link) {
function smarty_modifier_resource(string $link): string
{
return Utils::get_server_name() . $link;
}
function smarty_modifier_addslashes_single_quote($string) {
function smarty_modifier_addslashes_single_quote(string $string): string
{
return addcslashes($string, '\\\'');
}
function smarty_modifier_html($html) {
function smarty_modifier_addslashes(string $string): string
{
return addslashes($string);
}
function smarty_modifier_html(?string $html): string
{
if (!$html) {
return '';
}
return Utils::htmlEscape($html);
}
/**
* markdown_to_text
* Retrieves a markdown string and tries to make a plain text value
*
* @param array $options
* @return string
*/
function smarty_function_markdown_to_text($options, Smarty_Internal_Template $template)
function smarty_modifier_html_special_chars(string $html): string
{
global $locale;
$text = strip_tags(Parsedown::instance()->text($options['markdown']));
$number_letters = (new NumberFormatter($locale, NumberFormatter::ORDINAL))->format($options['id'] + 1);
return $text !== '' ? $text : __f('Poll results', '%s option', $number_letters);
}
function smarty_modifier_html_special_chars($html) {
return Utils::htmlMailEscape($html);
}
function smarty_modifier_datepicker_path($lang) {
function smarty_modifier_datepicker_path(string $lang): string
{
$i = 0;
while (!is_file(path_for_datepicker_locale($lang)) && $i < 3) {
$lang_arr = explode('-', $lang);
@ -109,12 +105,13 @@ function smarty_modifier_datepicker_path($lang) {
} else {
$lang = 'en';
}
$i += 1;
++$i;
}
return 'js/locales/bootstrap-datepicker.' . $lang . '.js';
}
function smarty_modifier_locale_2_lang($locale) {
function smarty_modifier_locale_2_lang(string $locale): string
{
$lang_arr = explode('-', $locale);
if ($lang_arr && count($lang_arr) > 1) {
return $lang_arr[0];
@ -122,41 +119,10 @@ function smarty_modifier_locale_2_lang($locale) {
return $locale;
}
function path_for_datepicker_locale($lang) {
function path_for_datepicker_locale(string $lang): string
{
return __DIR__ . '/../../js/locales/bootstrap-datepicker.' . $lang . '.js';
}
/**
* @param $date
* @param string $pattern
* @return string
*/
function smarty_modifier_date_format_intl(DateTime $date, $pattern) {
return date_format_intl($date, $pattern);
}
/**
* @param DateTime $date
* @return int
*/
function smarty_modifier_date_to_timestamp(DateTime $date) {
return $date->getTimestamp();
}
/**
* @param integer $timestamp
* @throws Exception
* @return DateTime
*/
function smarty_modifier_timestamp_to_date($timestamp) {
return (new DateTime())->setTimestamp((int) $timestamp);
}
/**
* @param DateTime $date
* @param string $pattern
* @return bool|DateTime
*/
function smarty_modifier_date_format_translation(DateTime $date, $pattern = 'Y-m-d') {
return date_format_translation($date, $pattern);
}
# Customization #4871 par Didier le 28/08/2021.
$smarty->assign('VERSION',VERSION);

View File

@ -4,11 +4,12 @@ namespace Framadate;
use PHPUnit\Framework\TestCase;
abstract class FramaTestCase extends TestCase {
protected function getTestResourcePath($resourcepath) {
protected function getTestResourcePath(string $resourcepath): string
{
return __DIR__ . '/../resources/' . $resourcepath;
}
protected function readTestResource($resourcepath) {
protected function readTestResource(string $resourcepath) {
return file_get_contents($this->getTestResourcePath($resourcepath));
}

View File

@ -1,35 +1,34 @@
<?php
namespace Framadate\Services;
use Framadate\FramaTestCase;
class InputServiceUnitTest extends FramaTestCase
{
public function liste_emails()
public function liste_emails(): array
{
return [
// valids addresses
"valid address" => ["example@example.com", "example@example.com"],
"local address" => ["test@localhost", "test@localhost"],
"IP address" => ["ip.email@127.0.0.1", "ip.email@127.0.0.1"],
"with spaces arround" => [" with@spaces ", "with@spaces"],
"unicode caracters" => ["unicode.éà@idn-œ.com", "unicode.éà@idn-œ.com"],
"valid address" => ["example@example.com", "example@example.com"],
"local address" => ["test@localhost", "test@localhost"],
"IP address" => ["ip.email@127.0.0.1", "ip.email@127.0.0.1"],
"with spaces arround" => [" with@spaces ", "with@spaces"],
"unicode caracters" => ["unicode.éà@idn-œ.com", "unicode.éà@idn-œ.com"],
// invalids addresses
"without domain" => ["without-domain", FALSE],
"space inside" => ["example example@example.com", FALSE],
"forbidden chars" => ["special_chars.@example.com", FALSE],
"without domain" => ["without-domain", FALSE],
"space inside" => ["example example@example.com", FALSE],
"forbidden chars" => ["special_chars.@example.com", FALSE],
];
}
/**
* @dataProvider liste_emails
*/
public function test_filterMail($email, $expected)
/**
* @dataProvider liste_emails
*/
public function test_filterMail($email, $expected): void
{
$inputService = new InputService();
$filtered = $inputService->filterMail($email);
$inputService = new InputService();
$filtered = $inputService->filterMail($email);
$this->assertSame($expected, $filtered);
}
$this->assertSame($expected, $filtered);
}
}

View File

@ -4,9 +4,10 @@ namespace Framadate\Services;
use Framadate\FramaTestCase;
class MailServiceUnitTest extends FramaTestCase {
const MSG_KEY = '666';
public const MSG_KEY = '666';
public function test_should_send_a_2nd_mail_after_a_good_interval() {
public function test_should_send_a_2nd_mail_after_a_good_interval(): void
{
// Given
$mailService = new MailService(true);
$_SESSION[MailService::MAILSERVICE_KEY] = [self::MSG_KEY => time() - 1000];
@ -15,10 +16,11 @@ class MailServiceUnitTest extends FramaTestCase {
$canSendMsg = $mailService->canSendMsg(self::MSG_KEY);
// Then
$this->assertSame(true, $canSendMsg);
$this->assertTrue($canSendMsg);
}
public function test_should_not_send_2_mails_in_a_short_interval() {
public function test_should_not_send_2_mails_in_a_short_interval(): void
{
// Given
$mailService = new MailService(true);
$_SESSION[MailService::MAILSERVICE_KEY] = [self::MSG_KEY => time()];
@ -27,6 +29,6 @@ class MailServiceUnitTest extends FramaTestCase {
$canSendMsg = $mailService->canSendMsg(self::MSG_KEY);
// Then
$this->assertSame(false, $canSendMsg);
$this->assertFalse($canSendMsg);
}
}

View File

@ -28,11 +28,11 @@ function bandeau_titre($titre)
echo '
<header role="banner">';
if(count($ALLOWED_LANGUAGES) > 1){
echo '<form method="post" action="" class="hidden-print">
echo '<form method="post" class="hidden-print">
<div class="input-group input-group-sm pull-right col-md-2 col-xs-4">
<select name="lang" class="form-control" title="' . __('Language selector', 'Select language') . '" >' . liste_lang() . '</select>
<select name="lang" class="form-control" title="' . __('Language selector', 'Select the language') . '" >' . liste_lang() . '</select>
<span class="input-group-btn">
<button type="submit" class="btn btn-default btn-sm" title="' . __('Language selector', 'Change language') . '">OK</button>
<button type="submit" class="btn btn-default btn-sm" title="' . __('Language selector', 'Change the language') . '">OK</button>
</span>
</div>
</form>';
@ -43,16 +43,25 @@ function bandeau_titre($titre)
<hr class="trait" role="presentation" />
</header>
<main role="main">';
global $connect;
$tables = $connect->allTables();
$diff = array_diff([Utils::table('comment'), Utils::table('poll'), Utils::table('slot'), Utils::table('vote')], $tables);
if (0 !== count($diff)) {
echo '<div class="alert alert-danger">' . __('Error', 'Framadate is not properly installed, please check the "INSTALL" to setup the database before continuing.') . '</div>';
bandeau_pied();
die();
}
}
function liste_lang()
function liste_lang(): string
{
global $ALLOWED_LANGUAGES; global $locale;
$str = '';
foreach ($ALLOWED_LANGUAGES as $k => $v ) {
if (substr($k,0,2)===$locale) {
if (strpos($k, $locale) === 0) {
$str .= '<option lang="' . substr($k,0,2) . '" selected value="' . $k . '">' . $v . '</option>' . "\n" ;
} else {
$str .= '<option lang="' . substr($k,0,2) . '" value="' . $k . '">' . $v . '</option>' . "\n" ;

View File

@ -1,59 +0,0 @@
#!/usr/bin/env php
<?php
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Framadate\Utils;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
try {
require_once __DIR__ . '/../app/inc/init.php';
$input = new ArgvInput();
$output = new ConsoleOutput();
$style = new SymfonyStyle($input, $output);
if ($connect === null) {
throw new \Exception("Undefined database connection\n");
}
// replace the ConsoleRunner::run() statement with:
$cli = new Application('Doctrine Command Line Interface', VERSION);
$cli->setCatchExceptions(true);
$helperSet = new HelperSet(
[
'db' => new ConnectionHelper($connect),
'question' => new QuestionHelper(),
]
);
$cli->setHelperSet($helperSet);
$migrateCommand = new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand();
$statusCommand = new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand();
$migrationsDirectory = __DIR__ . '/../app/classes/Framadate/Migrations';
$configuration = new Configuration($connect);
$configuration->setMigrationsTableName(Utils::table(MIGRATION_TABLE) . '_new');
$configuration->setMigrationsDirectory($migrationsDirectory);
$configuration->setMigrationsNamespace('DoctrineMigrations');
$configuration->registerMigrationsFromDirectory($migrationsDirectory);
$migrateCommand->setMigrationConfiguration($configuration);
$statusCommand->setMigrationConfiguration($configuration);
// Register All Doctrine Commands
$cli->addCommands([$migrateCommand, $statusCommand]);
// Runs console application
$cli->run($input, $output);
} catch (\Exception $e) {
$style->error($e->getMessage());
exit(1);
}

46
buildlang.php Normal file
View File

@ -0,0 +1,46 @@
<?php
include_once __DIR__ . '/app/inc/init.php';
?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body><pre><?php
$goodLang = $_GET['good'];
$otherLang = $_GET['other'];
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$other = json_decode(file_get_contents(__DIR__ . '/locale/' . $otherLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
foreach ($good as $sectionName => $section) {
foreach ($section as $key => $value) {
$good[$sectionName][$key] = getFromOther($other, $key, $value, $otherLang);
}
}
echo json_encode($good, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | ~(JSON_ERROR_UTF8 | JSON_HEX_QUOT | JSON_HEX_APOS));
function getFromOther($other, $goodKey, $default, $otherLang): string {
foreach ($other as $sectionName => $section) {
foreach ($section as $key => $value) {
if (
strtolower($key) === strtolower($goodKey) ||
stripos($key, strtolower($goodKey)) === 0 ||
strtolower(trim($key)) === strtolower($goodKey) ||
strtolower(trim(substr(trim($key), 0, strlen($key) - 1))) === strtolower($goodKey)
) {
return $value;
}
}
}
echo '[-]' . $goodKey . "\n";
return strtoupper($otherLang) . '_' . $default;
}
?>
</pre>
</body>
</html>

68
compare.php Normal file
View File

@ -0,0 +1,68 @@
<?php
include_once __DIR__ . '/app/inc/init.php';
?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body><pre><?php
$goodLang = $_GET['good'];
$testLang = $_GET['test'];
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$test = json_decode(file_get_contents(__DIR__ . '/locale/' . $testLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$diffSection = false;
foreach ($good as $sectionName => $section) {
if (!isset($test[$sectionName])) {
echo '- section: ' . $sectionName . "\n";
$diffSection = true;
}
}
foreach ($test as $sectionName => $section) {
if (!isset($good[$sectionName])) {
echo '+ section: ' . $sectionName . "\n";
$diffSection = true;
}
}
if (!$diffSection and array_keys($good)!==array_keys($test)) {
var_dump(array_keys($good));
var_dump(array_keys($test));
} else {
echo 'All sections are in two langs.' . "\n";
}
$diff = [];
foreach ($good as $sectionName => $section) {
$diffSection = false;
foreach($section as $key=>$value) {
if (!isset($test[$sectionName][$key])) {
$diff[$sectionName]['-'][] = $key;
$diffSection = true;
}
}
if (!$diffSection and array_keys($section) !== array_keys($test[$sectionName])) {
$diff[$sectionName]['order_good'] = array_keys($section);
$diff[$sectionName]['order_test'] = array_keys($test[$sectionName]);
}
}
foreach ($test as $sectionName => $section) {
foreach($section as $key=>$value) {
if (!isset($good[$sectionName][$key])) {
$diff[$sectionName]['+'][] = $key;
}
}
}
if (count($diff) > 0) {
var_dump($diff);
}
?>
</pre>
</body>
</html>

View File

@ -2,10 +2,12 @@
"name": "framasoft/framadate",
"description": "Application to facilitate the schedule of events or classic polls",
"homepage": "https://framadate.org/",
"keywords": ["poll", "framadate"],
"version": "1.2.0-alpha.1",
"keywords": [
"poll",
"framadate"
],
"version": "0.9.0",
"license": "CECILL-B",
"type": "project",
"support": {
"issues": "https://framagit.org/framasoft/framadate/issues"
@ -52,37 +54,43 @@
"email": "raphael.droz@gmail.com"
}
],
"scripts": {
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -not -path './tests/integration/vendor/*' -print0 | xargs -0 -n1 php -l"
},
"require": {
"php": ">=5.6.0",
"php": ">=7.3.0",
"ext-pdo": "*",
"ext-intl": "*",
"smarty/smarty": "^3.1",
"phpmailer/phpmailer": "~6.0",
"ext-json": "*",
"smarty/smarty": "^4.0",
"o80/i18n": "dev-develop",
"phpmailer/phpmailer": "~6.2",
"ircmaxell/password-compat": "dev-master",
"roave/security-advisories": "dev-master",
"erusev/parsedown": "^1.7",
"egulias/email-validator": "~2.1",
"doctrine/dbal": "^2.5",
"doctrine/migrations": "^1.5",
"sensiolabs/ansi-to-html": "^1.1",
"symfony/translation": "^3.4"
"egulias/email-validator": "^3.1",
"sabre/vobject": "~4.1"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
"friendsofphp/php-cs-fixer": "~2.0",
"roave/security-advisories": "dev-master"
"phpunit/phpunit": "^9",
"friendsofphp/php-cs-fixer": "^3.2",
"vimeo/psalm": "^4.15"
},
"repositories": [
{
"type": "git",
"url": "https://framagit.org/framasoft/framadate/o80-i18n"
}
],
"autoload": {
"psr-4": {
"Framadate\\": "app/classes/Framadate/"
}
},
"config": {
"platform": {
"php": "5.6.0"
"php": "7.3.0"
}
}
}

5868
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,100 +17,113 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Choice;
use Framadate\Form;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\PollService;
use Framadate\Services\PurgeService;
use Framadate\Services\SessionService;
use Framadate\Utils;
include_once __DIR__ . '/app/inc/init.php';
$pollService = Services::poll();
/* Service */
/*---------*/
$logService = new LogService();
$pollService = new PollService($logService);
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$purgeService = new PurgeService($logService);
$sessionService = new SessionService();
$inputService = new InputService();
if (is_file('bandeaux_local.php')) {
include_once('bandeaux_local.php');
} else {
include_once 'bandeaux.php';
include_once('bandeaux.php');
}
$max_expiry_time = $pollService->maxExpiryDate();
$form = unserialize($_SESSION['form']);
$form = isset($_SESSION['form']) ? unserialize($_SESSION['form']) : null;
if ($form === null || !($form instanceof Form)) {
// Step 1/4 : error if $_SESSION from info_sondage are not valid
if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && empty($form->admin_mail))) {
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation, or your session has expired.'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation.'));
$smarty->display('error.tpl');
exit;
}
// The poll format is other (A) if we are in this file
if (!isset($form->format)) {
$form->format = 'A';
}
// If we come from another format, we need to clear choices
if (isset($form->format) && $form->format !== 'A') {
$form->format = 'A';
$form->clearChoices();
}
// The poll format is AUTRE (other) if we are in this file
if (!isset($form->format)) {
$form->format = 'A';
}
// Step 4 : Data prepare before insert in DB
if (isset($_POST['confirmation'])) {
// Define expiration date
$expiration_date = $inputService->parseDate($_POST['enddate']);
$form->end_date = $inputService->validateDate($expiration_date, $pollService->minExpiryDate(), $pollService->maxExpiryDate())->getTimestamp();
// The poll format is AUTRE (other)
if ($form->format !== 'A') {
$form->format = 'A';
$form->clearChoices();
}
// Insert poll in database
$ids = $pollService->createPoll($form);
$poll_id = $ids[0];
$admin_poll_id = $ids[1];
if (!isset($form->title) || !isset($form->admin_name) || ($config['use_smtp'] && !isset($form->admin_mail))) {
$step = 1;
} elseif (isset($_POST['confirmation'])) {
$step = 4;
} elseif (empty($form->errors) && empty($_POST['fin_sondage_autre']) ) {
$step = 2;
} else {
$step = 3;
}
// Send confirmation by mail if enabled
if ($config['use_smtp'] === true) {
$message = __('Mail', "This is the message you have to send to the people you want to poll. \nNow, you have to send this message to everyone you want to poll.");
$message .= '<br/><br/>';
$message .= Utils::htmlMailEscape($form->admin_name) . ' ' . __('Mail', 'hast just created a poll called') . ' : "' . Utils::htmlMailEscape($form->title) . '".<br/>';
$message .= sprintf(__('Mail', 'Thanks for filling the poll at the link above') . ' :<br/><br/><a href="%1$s">%1$s</a>', Utils::getUrlSondage($poll_id));
switch ($step) {
case 2: // Step 2/4 : Select choices of the poll
$choices = $form->getChoices();
$nb_choices = max( 5- count($choices), 0);
while ($nb_choices-- > 0) {
$c = new Choice('');
$form->addChoice($c);
}
$message_admin = __('Mail', "This message should NOT be sent to the polled people. It is private for the poll's creator.\n\nYou can now modify it at the link above");
$message_admin .= sprintf(' :<br/><br/><a href="%1$s">%1$s</a>', Utils::getUrlSondage($admin_poll_id, true));
$_SESSION['form'] = serialize($form);
// Display step 2
$smarty->assign('title', __('Step 2 classic', 'Poll options (2 of 3)'));
$smarty->assign('choices', $form->getChoices());
$smarty->assign('allowMarkdown', $config['user_can_add_img_or_link']);
$smarty->assign('error', null);
$smarty->display('create_classic_poll_step_2.tpl');
exit;
case 3: // Step 3/4 : Confirm poll creation and choose a removal date
// Handle Step2 submission
if (!empty($_POST['choices'])) {
// remove empty choices
$_POST['choices'] = array_filter($_POST['choices'], function ($c) {
return !empty($c);
});
$form->clearChoices();
// store choices in $_SESSION
foreach ($_POST['choices'] as $c) {
$c = strip_tags($c);
$choice = new Choice($c);
$form->addChoice($choice);
if ($mailService->isValidEmail($form->admin_mail)) {
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
}
}
// Expiry date is initialised with config parameter. Value will be modified in step 4 if user has defined an other date
$form->end_date = $max_expiry_time;
// Clean Form data in $_SESSION
unset($_SESSION['form']);
// Delete old polls
$purgeService->purgeOldPolls();
// creation message
$sessionService->set("Framadate", "messagePollCreated", TRUE);
// Redirect to poll administration
header('Location:' . Utils::getUrlSondage($admin_poll_id, true));
exit;
} // Step 3/4 : Confirm poll creation and choose a removal date
else if (isset($_POST['fin_sondage_autre'])) {
// Store choices in $_SESSION
if (isset($_POST['choices'])) {
$form->clearChoices();
foreach ($_POST['choices'] as $c) {
if (!empty($c)) {
$c = strip_tags($c);
$choice = new Choice($c);
$form->addChoice($choice);
}
}
}
// Expiration date is initialised with config parameter. Value will be modified in step 4 if user has defined an other date
$form->end_date = $pollService->maxExpiryDate()->format('Y-m-d H:i:s');
// Summary
$summary = '<ol>';
foreach ($form->getChoices() as $i => $choice) {
/** @var Choice $choice */
foreach ($form->getChoices() as $i=>$choice) {
preg_match_all('/\[!\[(.*?)\]\((.*?)\)\]\((.*?)\)/', $choice->getName(), $md_a_img); // Markdown [![alt](src)](href)
preg_match_all('/!\[(.*?)\]\((.*?)\)/', $choice->getName(), $md_img); // Markdown ![alt](src)
preg_match_all('/\[(.*?)\]\((.*?)\)/', $choice->getName(), $md_a); // Markdown [text](href)
if (isset($md_a_img[2][0]) && $md_a_img[2][0] !== '' && isset($md_a_img[3][0]) && $md_a_img[3][0] !== '') { // [![alt](src)](href)
if (isset($md_a_img[2][0], $md_a_img[3][0]) && $md_a_img[2][0] !== '' && $md_a_img[3][0] !== '') { // [![alt](src)](href)
$li_subject_text = (isset($md_a_img[1][0]) && $md_a_img[1][0] !== '') ? stripslashes($md_a_img[1][0]) : __('Generic', 'Choice') . ' ' . ($i + 1);
$li_subject_html = '<a href="' . $md_a_img[3][0] . '"><img src="' . $md_a_img[2][0] . '" class="img-responsive" alt="' . $li_subject_text . '" /></a>';
} elseif (isset($md_img[2][0]) && $md_img[2][0] !== '') { // ![alt](src)
@ -128,43 +141,101 @@ switch ($step) {
}
$summary .= '</ol>';
$end_date_str = date_format_intl($max_expiry_time); //textual date
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $pollService->maxExpiryDate()->getTimestamp())); //textual date
$_SESSION['form'] = serialize($form);
$smarty->assign('title', __('Step 3', 'Removal date and confirmation (3 of 3)'));
$smarty->assign('title', __('Step 3', 'Removal date and confirmation (3 on 3)'));
$smarty->assign('summary', $summary);
$smarty->assign('end_date_str', $end_date_str);
$smarty->assign('default_poll_duration', $config['default_poll_duration']);
$smarty->assign('use_smtp', $config['use_smtp']);
$smarty->assign('errors', $form->errors);
$smarty->display('create_poll_step_3.tpl');
exit;
$smarty->display('create_classic_poll_step3.tpl');
case 4:
// Step 4 : Do the poll creation
// Step 2/4 : Select choices of the poll
} else {
Utils::print_header(__('Step 2 classic', 'Poll subjects (2 on 3)'));
bandeau_titre(__('Step 2 classic', 'Poll subjects (2 on 3)'));
// Read expiration date passed in POST parameters
$end_date = filter_input(INPUT_POST, 'enddate', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '#^[0-9]{2}/[0-9]{2}/[0-9]{4}$#']]);
$admin_poll_id = $pollService->doPollCreation($form, $end_date);
if (!is_null($admin_poll_id)) {
// Redirect to poll administration
header('Location:' . Utils::getUrlSondage($admin_poll_id, true));
} else {
// Redirect to current page
$referer = $_SERVER['HTTP_REFERER'];
header("Location: $referer");
echo '
<form name="formulaire" action="' . Utils::get_server_name() . 'create_classic_poll.php" method="POST" class="form-horizontal">
<div class="row">
<div class="col-md-8 col-md-offset-2">';
echo '
<div class="alert alert-info">
<p>' . __('Step 2 classic', 'To make a generic poll you need to propose at least two choices between differents subjects.') . '</p>
<p>' . __('Step 2 classic', 'You can add or remove additional choices with the buttons') . ' <span class="glyphicon glyphicon-minus text-info"></span><span class="sr-only">' . __('Generic', 'Remove') . '</span> <span class="glyphicon glyphicon-plus text-success"></span><span class="sr-only">' . __('Generic', 'Add') . '</span></p>';
if ($config['user_can_add_img_or_link']) {
echo ' <p>' . __('Step 2 classic', 'It\'s possible to propose links or images by using') . ' <a href="http://' . $locale . '.wikipedia.org/wiki/Markdown">' . __('Step 2 classic', 'the Markdown syntax') . '</a>.</p>';
}
exit;
echo ' </div>' . "\n";
case 1:
default:
// Step 1/4 : error if $_SESSION from info_sondage are not valid
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation, or your session has expired.'));
$smarty->display('error.tpl');
exit;
}
// Fields choices : 5 by default
$choices = $form->getChoices();
$nb_choices = max(count($choices), 5);
for ($i = 0; $i < $nb_choices; $i++) {
$choice = $choices[$i] ?? new Choice();
echo '
<div class="form-group choice-field">
<label for="choice' . $i . '" class="col-sm-2 control-label">' . __('Generic', 'Choice') . ' ' . ($i + 1) . '</label>
<div class="col-sm-10 input-group">
<input type="text" class="form-control" name="choices[]" size="40" value="' . $choice->getName() . '" id="choice' . $i . '" />';
if ($config['user_can_add_img_or_link']) {
echo '<span class="input-group-addon btn-link md-a-img" title="' . __('Step 2 classic', 'Add a link or an image') . ' - ' . __('Generic', 'Choice') . ' ' . ($i + 1) . '" ><span class="glyphicon glyphicon-picture"></span> <span class="glyphicon glyphicon-link"></span></span>';
}
echo '
</div>
</div>' . "\n";
}
echo '
<div class="col-md-4">
<div class="btn-group btn-group">
<button type="button" id="remove-a-choice" class="btn btn-default" title="' . __('Step 2 classic', 'Remove a choice') . '"><span class="glyphicon glyphicon-minus text-info"></span><span class="sr-only">' . __('Generic', 'Remove') . '</span></button>
<button type="button" id="add-a-choice" class="btn btn-default" title="' . __('Step 2 classic', 'Add a choice') . '"><span class="glyphicon glyphicon-plus text-success"></span><span class="sr-only">' . __('Generic', 'Add') . '</span></button>
</div>
</div>
<div class="col-md-8 text-right">
<a class="btn btn-default" href="' . Utils::get_server_name() . 'create_poll.php?type=classic" title="' . __('Step 2', 'Back to step 1') . '">' . __('Generic', 'Back') . '</a>
<button name="fin_sondage_autre" value="' . __('Generic', 'Next') . '" type="submit" class="btn btn-success disabled" title="' . __('Step 2', 'Go to step 3') . '">' . __('Generic', 'Next') . '</button>
</div>
</div>
</div>
<div class="modal fade" id="md-a-imgModal" tabindex="-1" role="dialog" aria-labelledby="md-a-imgModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">' . __('Generic', 'Close') . '</span></button>
<p class="modal-title" id="md-a-imgModalLabel">' . __('Step 2 classic', 'Add a link or an image') . '</p>
</div>
<div class="modal-body">
<p class="alert alert-info">' . __('Step 2 classic', 'These fields are optional. You can add a link, an image or both.') . '</p>
<div class="form-group">
<label for="md-img"><span class="glyphicon glyphicon-picture"></span> ' . __('Step 2 classic', 'URL of the image') . '</label>
<input id="md-img" type="text" placeholder="http://…" class="form-control" size="40" />
</div>
<div class="form-group">
<label for="md-a"><span class="glyphicon glyphicon-link"></span> ' . __('Generic', 'Link') . '</label>
<input id="md-a" type="text" placeholder="http://…" class="form-control" size="40" />
</div>
<div class="form-group">
<label for="md-text">' . __('Step 2 classic', 'Alternative text') . '</label>
<input id="md-text" type="text" class="form-control" size="40" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">' . __('Generic', 'Cancel') . '</button>
<button type="button" class="btn btn-primary">' . __('Generic', 'Add') . '</button>
</div>
</div>
</div>
</div>
</form>
<script src="js/app/framadatepicker.js"></script>
<script src="js/app/classic_poll.js"></script>
' . "\n";
bandeau_pied();
}

View File

@ -17,50 +17,59 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft https://framagit.org/framasoft/framadate/)
*/
use Framadate\Choice;
use Framadate\Form;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\PollService;
use Framadate\Services\PurgeService;
use Framadate\Services\SessionService;
use Framadate\Utils;
include_once __DIR__ . '/app/inc/init.php';
$inputService = Services::input();
$pollService = Services::poll();
/* Service */
/*---------*/
$logService = new LogService();
$pollService = new PollService($logService);
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$purgeService = new PurgeService($logService);
$inputService = new InputService();
$sessionService = new SessionService();
if (is_readable('bandeaux_local.php')) {
include_once('bandeaux_local.php');
}
$max_expiry_time = $pollService->maxExpiryDate();
$form = isset($_SESSION['form']) ? unserialize($_SESSION['form']) : null;
if ($form === null || !($form instanceof Form)) {
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation, or your session has expired.'));
$smarty->display('error.tpl');
exit;
}
$form = unserialize($_SESSION['form']);
// The poll format is DATE if we are in this file
if (!isset($form->format)) {
$form->format = 'D';
}
// If we come from another format, we need to clear choices
if ($form->format !== 'D') {
if (isset($form->format) && $form->format !== 'D') {
$form->format = 'D';
$form->clearChoices();
}
if (!isset($form->title) || !isset($form->admin_name) || ($config['use_smtp'] && !isset($form->admin_mail))) {
if (!isset($form->title, $form->admin_name) || ($config['use_smtp'] && !isset($form->admin_mail))) {
$step = 1;
} else if (!empty($_POST['confirmation'])) {
$step = 4;
} else if (empty($form->errors) && (empty($_POST['choixheures']) || isset($form->totalchoixjour))) {
} else if (empty($_POST['choixheures']) || isset($form->totalchoixjour)) {
$step = 2;
} else {
$step = 3;
}
switch ($step) {
case 1:
// Step 1/4 : error if $_SESSION from info_sondage are not valid
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation.'));
$smarty->display('error.tpl');
exit;
case 2:
// Step 2/4 : Select dates of the poll
@ -85,7 +94,7 @@ switch ($step) {
$_SESSION['form'] = serialize($form);
// Display step 2
$smarty->assign('title', __('Step 2 date', 'Poll dates (2 of 3)'));
$smarty->assign('title', __('Step 2 date', 'Poll dates (2 on 3)'));
$smarty->assign('choices', $form->getChoices());
$smarty->assign('error', null);
@ -98,14 +107,14 @@ switch ($step) {
// Handle Step2 submission
if (!empty($_POST['days'])) {
// Remove empty dates
$_POST['days'] = array_filter($_POST['days'], function ($d) {
$_POST['days'] = array_filter($_POST['days'], static function ($d) {
return !empty($d);
});
// Check if there are at most MAX_SLOTS_PER_POLL slots
if (count($_POST['days']) > MAX_SLOTS_PER_POLL) {
// Display step 2
$smarty->assign('title', __('Step 2 date', 'Poll dates (2 of 3)'));
$smarty->assign('title', __('Step 2 date', 'Poll dates (2 on 3)'));
$smarty->assign('choices', $form->getChoices());
$smarty->assign('error', __f('Error', 'You can\'t select more than %d dates', MAX_SLOTS_PER_POLL));
@ -126,18 +135,18 @@ switch ($step) {
$i++;
}
for ($i = 0; $i < count($_POST['days']); $i++) {
for ($i = 0, $iMax = count($_POST['days']); $i < $iMax; $i++) {
$day = $_POST['days'][$i];
if (!empty($day)) {
// Add choice to Form data
$date = DateTime::createFromFormat(__('Date', 'Y-m-d'), $_POST['days'][$i])->setTime(0, 0, 0);
$time = (string) $date->getTimestamp();
$date = DateTime::createFromFormat(__('Date', 'datetime_parseformat'), $_POST['days'][$i])->setTime(0, 0, 0);
$time = $date->getTimestamp();
$choice = new Choice($time);
$form->addChoice($choice);
$schedules = $inputService->filterArray($moments[$i], FILTER_DEFAULT);
for ($j = 0; $j < count($schedules); $j++) {
for ($j = 0, $jMax = count($schedules); $j < $jMax; $j++) {
if (!empty($schedules[$j])) {
$choice->addSlot(strip_tags($schedules[$j]));
}
@ -151,9 +160,7 @@ switch ($step) {
$summary = '<ul>';
$choices = $form->getChoices();
foreach ($choices as $choice) {
/** @var Choice $choice */
$date = (new DateTime())->setTimestamp((int) $choice->getName());
$summary .= '<li>' . $end_date_str = date_format_intl($date); //textual date
$summary .= '<li>' . strftime($date_format['txt_full'], $choice->getName());
$first = true;
foreach ($choice->getSlots() as $slots) {
$summary .= $first ? ': ' : ', ';
@ -164,43 +171,60 @@ switch ($step) {
}
$summary .= '</ul>';
$end_date_str = date_format_intl($max_expiry_time); //textual date
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $pollService->maxExpiryDate()->getTimestamp())); // textual date
$_SESSION['form'] = serialize($form);
$smarty->assign('title', __('Step 3', 'Removal date and confirmation (3 of 3)'));
$smarty->assign('title', __('Step 3', 'Removal date and confirmation (3 on 3)'));
$smarty->assign('summary', $summary);
$smarty->assign('end_date_str', $end_date_str);
$smarty->assign('default_poll_duration', $config['default_poll_duration']);
$smarty->assign('use_smtp', $config['use_smtp']);
$smarty->assign('errors', $form->errors);
$smarty->display('create_poll_step_3.tpl');
$smarty->display('create_classic_poll_step3.tpl');
exit;
case 4:
// Step 4 : Do the poll creation
// Step 4 : Data prepare before insert in DB
// Read expiration date passed in POST parameters
$end_date = filter_input(INPUT_POST, 'enddate', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '#^[0-9]{2}/[0-9]{2}/[0-9]{4}$#']]);
// Define expiration date
$expiration_date = $inputService->parseDate($_POST['enddate']);
$form->end_date = $inputService->validateDate($expiration_date, $pollService->minExpiryDate(), $pollService->maxExpiryDate())->getTimestamp();
$admin_poll_id = $pollService->doPollCreation($form, $end_date);
// Insert poll in database
$ids = $pollService->createPoll($form);
$poll_id = $ids[0];
$admin_poll_id = $ids[1];
if (!is_null($admin_poll_id)) {
// Redirect to poll administration
header('Location:' . Utils::getUrlSondage($admin_poll_id, true));
} else {
// Redirect to current page
$referer = $_SERVER['HTTP_REFERER'];
header("Location: $referer");
// Send confirmation by mail if enabled
if ($config['use_smtp'] === true) {
$message = __('Mail', "This is the message you have to send to the people you want to poll. \nNow, you have to send this message to everyone you want to poll.");
$message .= '<br/><br/>';
$message .= Utils::htmlEscape($form->admin_name) . ' ' . __('Mail', 'hast just created a poll called') . ' : "' . Utils::htmlEscape($form->title) . '".<br/>';
$message .= __('Mail', 'Thanks for filling the poll at the link above') . ' :<br/><br/><a href="%1$s">%1$s</a>';
$message_admin = __('Mail', "This message should NOT be sent to the polled people. It is private for the poll's creator.\n\nYou can now modify it at the link above");
$message_admin .= ' :<br/><br/><a href="%1$s">%1$s</a>';
$message = sprintf($message, Utils::getUrlSondage($poll_id));
$message_admin = sprintf($message_admin, Utils::getUrlSondage($admin_poll_id, true));
if ($mailService->isValidEmail($form->admin_mail)) {
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
}
}
exit;
case 1:
default:
// Step 1/4 : error if $_SESSION from info_sondage are not valid
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation, or your session has expired.'));
$smarty->display('error.tpl');
// Clean Form data in $_SESSION
unset($_SESSION['form']);
// Delete old polls
$purgeService->purgeOldPolls();
// creation message
$sessionService->set("Framadate", "messagePollCreated", TRUE);
// Redirect to poll administration
header('Location:' . Utils::getUrlSondage($admin_poll_id, true));
exit;
}

Some files were not shown because too many files have changed in this diff Show More