]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Links.php
Scrutinizer Auto-Fixes (#59)
[galette.git] / galette / lib / Galette / Core / Links.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Temporay links for galette, to send direct links to invoices, recipts,
7 * and member cards directly by email
8 *
9 * PHP version 5
10 *
11 * Copyright © 2020 The Galette Team
12 *
13 * This file is part of Galette (http://galette.tuxfamily.org).
14 *
15 * Galette is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * Galette is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
27 *
28 * @category Core
29 * @package Galette
30 *
31 * @author Johan Cwiklinski <johan@x-tnd.be>
32 * @copyright 2020 The Galette Team
33 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.9.4 - 2020-03-11
36 */
37
38 namespace Galette\Core;
39
40 use Analog\Analog;
41 use Galette\Entity\Adherent;
42 use Galette\Entity\Contribution;
43
44 /**
45 * Temporary password managment
46 *
47 * @category Core
48 * @name Links
49 * @package Galette
50 * @author Johan Cwiklinski <johan@x-tnd.be>
51 * @copyright 2020 The Galette Team
52 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
53 * @link http://galette.tuxfamily.org
54 * @since Available since 0.9.4 - 2020-03-11
55 */
56
57 class Links
58 {
59 const TABLE = 'tmplinks';
60 const PK = 'hash';
61
62 const TARGET_MEMBERCARD = 1;
63 const TARGET_INVOICE = 2;
64 const TARGET_RECEIPT = 3;
65
66 private $zdb;
67
68 /**
69 * Default constructor
70 *
71 * @param Db $zdb Database instance:
72 * @param boolean $clean Whether we should clean expired links in database
73 */
74 public function __construct(Db $zdb, $clean = true)
75 {
76 $this->zdb = $zdb;
77 if ($clean === true) {
78 $this->cleanExpired();
79 }
80 }
81
82 /**
83 * Remove all old entry
84 *
85 * @param int $target Target (one of self::TARGET_* constants)
86 * @param int $id Target identifier
87 *
88 * @return boolean
89 */
90 private function removeOldEntry($target, $id)
91 {
92 try {
93 $delete = $this->zdb->delete(self::TABLE);
94 $delete->where([
95 'target' => $target,
96 'id' => $id
97 ]);
98
99 $del = $this->zdb->execute($delete);
100 if ($del) {
101 Analog::log(
102 'Temporary link for `' . $target . '-' . $id . '` has been removed.',
103 Analog::DEBUG
104 );
105 }
106 } catch (\Exception $e) {
107 Analog::log(
108 'An error has occurred removing old temporary link ' .
109 $e->getMessage(),
110 Analog::ERROR
111 );
112 throw $e;
113 }
114 }
115
116 /**
117 * Generates a new link for specified target
118 *
119 * @param int $target Target (one of self::TARGET_* constants)
120 * @param int $id Target identifier
121 *
122 * @return false|string
123 */
124 public function generateNewLink($target, $id)
125 {
126 //first of all, we'll remove all existant entries for specified id
127 $this->removeOldEntry($target, $id);
128
129 //second, generate a new hash and store it in the database
130 try {
131 $select = $this->zdb->select(Adherent::TABLE);
132 $select->columns([Adherent::PK, 'email_adh']);
133 $id_adh = null;
134 if ($target === Links::TARGET_MEMBERCARD) {
135 $id_adh = $id;
136 } else {
137 //get member id from contribution
138 $cselect = $this->zdb->select(Contribution::TABLE);
139 $cselect->columns([Adherent::PK])->where([Contribution::PK => $id]);
140 $cresults = $this->zdb->execute($cselect);
141 $cresult = $cresults->current();
142 $id_adh = $cresult->id_adh;
143 }
144
145 $select->where([Adherent::PK => $id_adh]);
146 $results = $this->zdb->execute($select);
147 $result = $results->current();
148 $code = $result->email_adh;
149 $hash = password_hash($code, PASSWORD_BCRYPT);
150
151 $values = array(
152 'target' => $target,
153 'id' => $id,
154 'creation_date' => date('Y-m-d H:i:s'),
155 'hash' => $hash
156 );
157
158 $insert = $this->zdb->insert(self::TABLE);
159 $insert->values($values);
160
161 $add = $this->zdb->execute($insert);
162 if ($add) {
163 Analog::log(
164 'New temporary link set for `' . $target . '-' . $id . '`.',
165 Analog::DEBUG
166 );
167 return base64_encode($hash);
168 }
169 return false;
170 } catch (\Exception $e) {
171 Analog::log(
172 "An error occurred trying to add temporary link entry. " .
173 $e->getMessage(),
174 Analog::ERROR
175 );
176 throw $e;
177 }
178 }
179
180 /**
181 * Get expiration date
182 *
183 * @return \DateTime
184 */
185 private function getExpirationDate()
186 {
187 $date = new \DateTime();
188 $date->sub(new \DateInterval('P1W'));
189 return $date;
190 }
191
192 /**
193 * Remove expired links queries (older than 1 week)
194 *
195 * @return boolean
196 */
197 protected function cleanExpired()
198 {
199 try {
200 $date = $this->getExpirationDate();
201 $delete = $this->zdb->delete(self::TABLE);
202 $delete->where->lessThan(
203 'creation_date',
204 $date->format('Y-m-d H:i:s')
205 );
206 $del = $this->zdb->execute($delete);
207 if ($del) {
208 Analog::log(
209 'Expired temporary links has been deleted.',
210 Analog::DEBUG
211 );
212 return true;
213 }
214 return false;
215 } catch (\Exception $e) {
216 Analog::log(
217 'An error occurred deleting expired temporary links. ' .
218 $e->getMessage(),
219 Analog::WARNING
220 );
221 throw $e;
222 }
223 }
224
225 /**
226 * Check if requested hash is valid
227 *
228 * @param string $hash the hash, base64 encoded
229 * @param string $code Code sent to validate link
230 *
231 * @return false if hash is not valid, array otherwise
232 */
233 public function isHashValid($hash, $code)
234 {
235 try {
236 $hash = base64_decode($hash);
237 $select = $this->zdb->select(self::TABLE);
238 $select->where(array('hash' => $hash));
239
240 $date = $this->getExpirationDate();
241 $select->where->greaterThanOrEqualTo(
242 'creation_date',
243 $date->format('Y-m-d')
244 );
245
246 $results = $this->zdb->execute($select);
247
248 if ($results->count() > 0) {
249 $result = $results->current();
250 if (password_verify($code, $result->hash)) {
251 return [(int)$result->target, (int)$result->id];
252 }
253 }
254 return false;
255 } catch (\Exception $e) {
256 Analog::log(
257 'An error occurred getting requested hash. ' . $e->getMessage(),
258 Analog::WARNING
259 );
260 throw $e;
261 }
262 }
263 }