1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Object; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use DateTimeInterface; |
7
|
|
|
use Exception; |
8
|
|
|
use InvalidArgumentException; |
9
|
|
|
|
10
|
|
|
// From `pimple/pimple` |
11
|
|
|
use Pimple\Container; |
12
|
|
|
|
13
|
|
|
// From `charcoal-core` |
14
|
|
|
use Charcoal\Model\AbstractModel; |
15
|
|
|
|
16
|
|
|
// From `charcoal-translation` |
17
|
|
|
use Charcoal\Translator\TranslatorAwareTrait; |
18
|
|
|
|
19
|
|
|
// From `charcoal-object` |
20
|
|
|
use Charcoal\Object\UserDataInterface; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* User Data is a base model for objects typically submitted by the end-user of the application. |
24
|
|
|
*/ |
25
|
|
|
class UserData extends AbstractModel implements |
26
|
|
|
UserDataInterface |
27
|
|
|
{ |
28
|
|
|
use TranslatorAwareTrait; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Client IP address of the end-user. |
32
|
|
|
* |
33
|
|
|
* @var integer|null |
34
|
|
|
*/ |
35
|
|
|
private $ip; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Language of the end-user or source URI. |
39
|
|
|
* |
40
|
|
|
* @var string|null |
41
|
|
|
*/ |
42
|
|
|
private $lang; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Source URL or identifier of end-user submission. |
46
|
|
|
* |
47
|
|
|
* @var string|null |
48
|
|
|
*/ |
49
|
|
|
private $origin; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Creation timestamp of submission. |
53
|
|
|
* |
54
|
|
|
* @var DateTimeInterface|null |
55
|
|
|
*/ |
56
|
|
|
private $ts; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Dependencies |
60
|
|
|
* @param Container $container DI Container. |
61
|
|
|
* @return void |
62
|
|
|
*/ |
63
|
|
|
public function setDependencies(Container $container) |
64
|
|
|
{ |
65
|
|
|
parent::setDependencies($container); |
66
|
|
|
|
67
|
|
|
$this->setTranslator($container['translator']); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Set the client IP address. |
73
|
|
|
* |
74
|
|
|
* @param integer|null $ip The remote IP at object creation. |
75
|
|
|
* @return UserDataInterface Chainable |
76
|
|
|
*/ |
77
|
|
|
public function setIp($ip) |
78
|
|
|
{ |
79
|
|
|
if ($ip === null) { |
80
|
|
|
$this->ip = null; |
81
|
|
|
return $this; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
if (is_string($ip)) { |
85
|
|
|
$ip = ip2long($ip); |
86
|
|
|
} elseif (is_numeric($ip)) { |
87
|
|
|
$ip = (int)$ip; |
88
|
|
|
} else { |
89
|
|
|
$ip = 0; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
$this->ip = $ip; |
93
|
|
|
|
94
|
|
|
return $this; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Retrieve the client IP address. |
99
|
|
|
* |
100
|
|
|
* @return integer|null |
101
|
|
|
*/ |
102
|
|
|
public function ip() |
103
|
|
|
{ |
104
|
|
|
return $this->ip; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Set the origin language. |
109
|
|
|
* |
110
|
|
|
* @param string $lang The language code. |
111
|
|
|
* @throws InvalidArgumentException If the argument is not a string. |
112
|
|
|
* @return UserDataInterface Chainable |
113
|
|
|
*/ |
114
|
|
View Code Duplication |
public function setLang($lang) |
|
|
|
|
115
|
|
|
{ |
116
|
|
|
if ($lang !== null) { |
117
|
|
|
if (!is_string($lang)) { |
118
|
|
|
throw new InvalidArgumentException( |
119
|
|
|
'Language must be a string' |
120
|
|
|
); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$this->lang = $lang; |
125
|
|
|
|
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Retrieve the language. |
131
|
|
|
* |
132
|
|
|
* @return string |
133
|
|
|
*/ |
134
|
|
|
public function lang() |
135
|
|
|
{ |
136
|
|
|
return $this->lang; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Set the origin of the object submission. |
141
|
|
|
* |
142
|
|
|
* @param string $origin The source URL or identifier of the submission. |
143
|
|
|
* @throws InvalidArgumentException If the argument is not a string. |
144
|
|
|
* @return UserDataInterface Chainable |
145
|
|
|
*/ |
146
|
|
View Code Duplication |
public function setOrigin($origin) |
|
|
|
|
147
|
|
|
{ |
148
|
|
|
if ($origin !== null) { |
149
|
|
|
if (!is_string($origin)) { |
150
|
|
|
throw new InvalidArgumentException( |
151
|
|
|
'Origin must be a string.' |
152
|
|
|
); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$this->origin = $origin; |
157
|
|
|
|
158
|
|
|
return $this; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Resolve the origin of the user data. |
163
|
|
|
* |
164
|
|
|
* @todo Use PSR-7 Request, if available, instead of PHP's environment variables. |
165
|
|
|
* @return string |
166
|
|
|
*/ |
167
|
|
|
public function resolveOrigin() |
|
|
|
|
168
|
|
|
{ |
169
|
|
|
$origin = ''; |
170
|
|
|
if (PHP_SAPI == 'cli') { |
171
|
|
|
$argv = $GLOBALS['argv']; |
172
|
|
|
|
173
|
|
|
/** Assume its a Charcoal script */ |
174
|
|
|
if (isset($argv[0])) { |
175
|
|
|
if (strpos($argv[0], 'bin/charcoal') !== false) { |
176
|
|
|
$origin = array_slice($argv, 1); |
177
|
|
|
$origin = implode(' ', $origin); |
178
|
|
|
} else { |
179
|
|
|
$origin = implode(' ', $argv); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
} else { |
183
|
|
|
$host = getenv('HTTP_HOST'); |
184
|
|
|
if ($host) { |
185
|
|
|
$origin = 'http'; |
186
|
|
|
|
187
|
|
|
if (getenv('HTTPS') === 'on') { |
188
|
|
|
$origin .= 's'; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$origin .= '://'.$host; |
192
|
|
|
} |
193
|
|
|
$origin .= getenv('REQUEST_URI'); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $origin; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Retrieve the origin of the object submission. |
201
|
|
|
* |
202
|
|
|
* @return string |
203
|
|
|
*/ |
204
|
|
|
public function origin() |
205
|
|
|
{ |
206
|
|
|
return $this->origin; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Set when the object was created. |
211
|
|
|
* |
212
|
|
|
* @param DateTime|string|null $timestamp The timestamp at object's creation. |
213
|
|
|
* NULL is accepted and instances of DateTimeInterface are recommended; |
214
|
|
|
* any other value will be converted (if possible) into one. |
215
|
|
|
* @throws InvalidArgumentException If the timestamp is invalid. |
216
|
|
|
* @return UserDataInterface Chainable |
217
|
|
|
*/ |
218
|
|
View Code Duplication |
public function setTs($timestamp) |
|
|
|
|
219
|
|
|
{ |
220
|
|
|
if ($timestamp === null) { |
221
|
|
|
$this->ts = null; |
222
|
|
|
return $this; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if (is_string($timestamp)) { |
226
|
|
|
try { |
227
|
|
|
$timestamp = new DateTime($timestamp); |
228
|
|
|
} catch (Exception $e) { |
229
|
|
|
throw new InvalidArgumentException(sprintf( |
230
|
|
|
'Invalid timestamp: %s', |
231
|
|
|
$e->getMessage() |
232
|
|
|
), 0, $e); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
if (!$timestamp instanceof DateTimeInterface) { |
237
|
|
|
throw new InvalidArgumentException( |
238
|
|
|
'Invalid timestamp value. Must be a date/time string or a DateTime object.' |
239
|
|
|
); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
$this->ts = $timestamp; |
243
|
|
|
|
244
|
|
|
return $this; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Retrieve the creation timestamp. |
249
|
|
|
* |
250
|
|
|
* @return DateTime|null |
251
|
|
|
*/ |
252
|
|
|
public function ts() |
253
|
|
|
{ |
254
|
|
|
return $this->ts; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Event called before _creating_ the object. |
259
|
|
|
* |
260
|
|
|
* @see Charcoal\Source\StorableTrait::preSave() For the "create" Event. |
261
|
|
|
* @return boolean |
262
|
|
|
*/ |
263
|
|
|
public function preSave() |
264
|
|
|
{ |
265
|
|
|
$result = parent::preSave(); |
266
|
|
|
|
267
|
|
|
$this->setTs('now'); |
268
|
|
|
|
269
|
|
|
if (getenv('REMOTE_ADDR')) { |
270
|
|
|
$this->setIp(getenv('REMOTE_ADDR')); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
if (!isset($this->origin)) { |
274
|
|
|
$this->setOrigin($this->resolveOrigin()); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return $result; |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.