These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * PrivateBin |
||
4 | * |
||
5 | * a zero-knowledge paste bin |
||
6 | * |
||
7 | * @link https://github.com/PrivateBin/PrivateBin |
||
8 | * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) |
||
9 | * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License |
||
10 | * @version 1.1 |
||
11 | */ |
||
12 | |||
13 | namespace PrivateBin; |
||
14 | |||
15 | use Exception; |
||
16 | use PrivateBin\Persistence\ServerSalt; |
||
17 | use PrivateBin\Persistence\TrafficLimiter; |
||
18 | |||
19 | /** |
||
20 | * PrivateBin |
||
21 | * |
||
22 | * Controller, puts it all together. |
||
23 | */ |
||
24 | class PrivateBin |
||
25 | { |
||
26 | /** |
||
27 | * version |
||
28 | * |
||
29 | * @const string |
||
30 | */ |
||
31 | const VERSION = '1.1'; |
||
32 | |||
33 | /** |
||
34 | * minimal required PHP version |
||
35 | * |
||
36 | * @const string |
||
37 | */ |
||
38 | const MIN_PHP_VERSION = '5.4.0'; |
||
39 | |||
40 | /** |
||
41 | * show the same error message if the paste expired or does not exist |
||
42 | * |
||
43 | * @const string |
||
44 | */ |
||
45 | const GENERIC_ERROR = 'Paste does not exist, has expired or has been deleted.'; |
||
46 | |||
47 | /** |
||
48 | * configuration |
||
49 | * |
||
50 | * @access private |
||
51 | * @var Configuration |
||
52 | */ |
||
53 | private $_conf; |
||
54 | |||
55 | /** |
||
56 | * data |
||
57 | * |
||
58 | * @access private |
||
59 | * @var string |
||
60 | */ |
||
61 | private $_data = ''; |
||
62 | |||
63 | /** |
||
64 | * does the paste expire |
||
65 | * |
||
66 | * @access private |
||
67 | * @var bool |
||
68 | */ |
||
69 | private $_doesExpire = false; |
||
70 | |||
71 | /** |
||
72 | * error message |
||
73 | * |
||
74 | * @access private |
||
75 | * @var string |
||
76 | */ |
||
77 | private $_error = ''; |
||
78 | |||
79 | /** |
||
80 | * status message |
||
81 | * |
||
82 | * @access private |
||
83 | * @var string |
||
84 | */ |
||
85 | private $_status = ''; |
||
86 | |||
87 | /** |
||
88 | * JSON message |
||
89 | * |
||
90 | * @access private |
||
91 | * @var string |
||
92 | */ |
||
93 | private $_json = ''; |
||
94 | |||
95 | /** |
||
96 | * Factory of instance models |
||
97 | * |
||
98 | * @access private |
||
99 | * @var model |
||
100 | */ |
||
101 | private $_model; |
||
102 | |||
103 | /** |
||
104 | * request |
||
105 | * |
||
106 | * @access private |
||
107 | * @var request |
||
108 | */ |
||
109 | private $_request; |
||
110 | |||
111 | /** |
||
112 | * URL base |
||
113 | * |
||
114 | * @access private |
||
115 | * @var string |
||
116 | */ |
||
117 | private $_urlBase; |
||
118 | |||
119 | /** |
||
120 | * constructor |
||
121 | * |
||
122 | * initializes and runs PrivateBin |
||
123 | * |
||
124 | * @access public |
||
125 | * @throws Exception |
||
126 | */ |
||
127 | 96 | public function __construct() |
|
128 | { |
||
129 | 96 | if (version_compare(PHP_VERSION, self::MIN_PHP_VERSION) < 0) { |
|
130 | throw new Exception(I18n::_('%s requires php %s or above to work. Sorry.', I18n::_('PrivateBin'), self::MIN_PHP_VERSION), 1); |
||
131 | } |
||
132 | 96 | if (strlen(PATH) < 0 && substr(PATH, -1) !== DIRECTORY_SEPARATOR) { |
|
133 | throw new Exception(I18n::_('%s requires the PATH to end in a "%s". Please update the PATH in your index.php.', I18n::_('PrivateBin'), DIRECTORY_SEPARATOR), 5); |
||
134 | } |
||
135 | |||
136 | // load config from ini file, initialize required classes |
||
137 | 96 | $this->_init(); |
|
138 | |||
139 | 94 | switch ($this->_request->getOperation()) { |
|
140 | 94 | case 'create': |
|
141 | 44 | $this->_create(); |
|
142 | 44 | break; |
|
143 | 50 | case 'delete': |
|
144 | 18 | $this->_delete( |
|
145 | 18 | $this->_request->getParam('pasteid'), |
|
146 | 18 | $this->_request->getParam('deletetoken') |
|
147 | ); |
||
148 | 18 | break; |
|
149 | 32 | case 'read': |
|
150 | 19 | $this->_read($this->_request->getParam('pasteid')); |
|
151 | 19 | break; |
|
152 | 13 | case 'jsonld': |
|
153 | 5 | $this->_jsonld($this->_request->getParam('jsonld')); |
|
154 | 5 | return; |
|
155 | } |
||
156 | |||
157 | // output JSON or HTML |
||
158 | 89 | if ($this->_request->isJsonApiCall()) { |
|
159 | 55 | header('Content-type: ' . Request::MIME_JSON); |
|
160 | 55 | header('Access-Control-Allow-Origin: *'); |
|
161 | 55 | header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE'); |
|
162 | 55 | header('Access-Control-Allow-Headers: X-Requested-With, Content-Type'); |
|
163 | 55 | echo $this->_json; |
|
164 | } else { |
||
165 | 34 | $this->_view(); |
|
166 | } |
||
167 | 89 | } |
|
168 | |||
169 | /** |
||
170 | * initialize privatebin |
||
171 | * |
||
172 | * @access private |
||
173 | */ |
||
174 | 96 | private function _init() |
|
0 ignored issues
–
show
_init uses the super-global variable $_COOKIE which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
Loading history...
|
|||
175 | { |
||
176 | 96 | $this->_conf = new Configuration; |
|
177 | 94 | $this->_model = new Model($this->_conf); |
|
178 | 94 | $this->_request = new Request; |
|
179 | 94 | $this->_urlBase = array_key_exists('REQUEST_URI', $_SERVER) ? |
|
180 | 94 | htmlspecialchars($_SERVER['REQUEST_URI']) : '/'; |
|
181 | 94 | ServerSalt::setPath($this->_conf->getKey('dir', 'traffic')); |
|
182 | |||
183 | // set default language |
||
184 | 94 | $lang = $this->_conf->getKey('languagedefault'); |
|
185 | 94 | I18n::setLanguageFallback($lang); |
|
186 | // force default language, if language selection is disabled and a default is set |
||
187 | 94 | if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) { |
|
188 | 2 | $_COOKIE['lang'] = $lang; |
|
189 | 2 | setcookie('lang', $lang); |
|
190 | } |
||
191 | 94 | } |
|
192 | |||
193 | /** |
||
194 | * Store new paste or comment |
||
195 | * |
||
196 | * POST contains one or both: |
||
197 | * data = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) |
||
198 | * attachment = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) |
||
199 | * |
||
200 | * All optional data will go to meta information: |
||
201 | * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never) |
||
202 | * formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting) |
||
203 | * burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0) |
||
204 | * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0) |
||
205 | * attachmentname = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) |
||
206 | * nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) |
||
207 | * parentid (optional) = in discussion, which comment this comment replies to. |
||
208 | * pasteid (optional) = in discussion, which paste this comment belongs to. |
||
209 | * |
||
210 | * @access private |
||
211 | * @return string |
||
212 | */ |
||
213 | 44 | private function _create() |
|
214 | { |
||
215 | // Ensure last paste from visitors IP address was more than configured amount of seconds ago. |
||
216 | 44 | TrafficLimiter::setConfiguration($this->_conf); |
|
217 | 44 | if (!TrafficLimiter::canPass()) { |
|
218 | 2 | return $this->_return_message( |
|
219 | 2 | 1, I18n::_( |
|
220 | 2 | 'Please wait %d seconds between each post.', |
|
221 | 2 | $this->_conf->getKey('limit', 'traffic') |
|
222 | ) |
||
223 | ); |
||
224 | } |
||
225 | |||
226 | 44 | $data = $this->_request->getParam('data'); |
|
227 | 44 | $attachment = $this->_request->getParam('attachment'); |
|
228 | 44 | $attachmentname = $this->_request->getParam('attachmentname'); |
|
229 | |||
230 | // Ensure content is not too big. |
||
231 | 44 | $sizelimit = $this->_conf->getKey('sizelimit'); |
|
232 | if ( |
||
233 | 44 | strlen($data) + strlen($attachment) + strlen($attachmentname) > $sizelimit |
|
234 | ) { |
||
235 | 2 | return $this->_return_message( |
|
236 | 2 | 1, |
|
237 | 2 | I18n::_( |
|
238 | 2 | 'Paste is limited to %s of encrypted data.', |
|
239 | 2 | Filter::formatHumanReadableSize($sizelimit) |
|
240 | ) |
||
241 | ); |
||
242 | } |
||
243 | |||
244 | // Ensure attachment did not get lost due to webserver limits or Suhosin |
||
245 | 42 | if (strlen($attachmentname) > 0 && strlen($attachment) == 0) { |
|
246 | 2 | return $this->_return_message(1, 'Attachment missing in data received by server. Please check your webserver or suhosin configuration for maximum POST parameter limitations.'); |
|
247 | } |
||
248 | |||
249 | // The user posts a comment. |
||
250 | 40 | $pasteid = $this->_request->getParam('pasteid'); |
|
251 | 40 | $parentid = $this->_request->getParam('parentid'); |
|
252 | 40 | if (!empty($pasteid) && !empty($parentid)) { |
|
253 | 12 | $paste = $this->_model->getPaste($pasteid); |
|
254 | 12 | if ($paste->exists()) { |
|
255 | try { |
||
256 | 10 | $comment = $paste->getComment($parentid); |
|
257 | |||
258 | 8 | $nickname = $this->_request->getParam('nickname'); |
|
259 | 8 | if (!empty($nickname)) { |
|
260 | 8 | $comment->setNickname($nickname); |
|
261 | } |
||
262 | |||
263 | 6 | $comment->setData($data); |
|
264 | 6 | $comment->store(); |
|
265 | 8 | } catch (Exception $e) { |
|
266 | 8 | return $this->_return_message(1, $e->getMessage()); |
|
267 | } |
||
268 | 2 | $this->_return_message(0, $comment->getId()); |
|
269 | } else { |
||
270 | 4 | $this->_return_message(1, 'Invalid data.'); |
|
271 | } |
||
272 | } |
||
273 | // The user posts a standard paste. |
||
274 | else { |
||
275 | 28 | $this->_model->purge(); |
|
276 | 28 | $paste = $this->_model->getPaste(); |
|
277 | try { |
||
278 | 28 | $paste->setData($data); |
|
279 | |||
280 | 28 | if (!empty($attachment)) { |
|
281 | 2 | $paste->setAttachment($attachment); |
|
282 | 2 | if (!empty($attachmentname)) { |
|
283 | 2 | $paste->setAttachmentName($attachmentname); |
|
284 | } |
||
285 | } |
||
286 | |||
287 | 28 | $expire = $this->_request->getParam('expire'); |
|
288 | 28 | if (!empty($expire)) { |
|
289 | 6 | $paste->setExpiration($expire); |
|
290 | } |
||
291 | |||
292 | 28 | $burnafterreading = $this->_request->getParam('burnafterreading'); |
|
293 | 28 | if (!empty($burnafterreading)) { |
|
294 | 2 | $paste->setBurnafterreading($burnafterreading); |
|
295 | } |
||
296 | |||
297 | 26 | $opendiscussion = $this->_request->getParam('opendiscussion'); |
|
298 | 26 | if (!empty($opendiscussion)) { |
|
299 | 4 | $paste->setOpendiscussion($opendiscussion); |
|
300 | } |
||
301 | |||
302 | 24 | $formatter = $this->_request->getParam('formatter'); |
|
303 | 24 | if (!empty($formatter)) { |
|
304 | 2 | $paste->setFormatter($formatter); |
|
305 | } |
||
306 | |||
307 | 24 | $paste->store(); |
|
308 | 6 | } catch (Exception $e) { |
|
309 | 6 | return $this->_return_message(1, $e->getMessage()); |
|
310 | } |
||
311 | 22 | $this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken())); |
|
312 | } |
||
313 | 26 | } |
|
314 | |||
315 | /** |
||
316 | * Delete an existing paste |
||
317 | * |
||
318 | * @access private |
||
319 | * @param string $dataid |
||
320 | * @param string $deletetoken |
||
321 | */ |
||
322 | 18 | private function _delete($dataid, $deletetoken) |
|
323 | { |
||
324 | try { |
||
325 | 18 | $paste = $this->_model->getPaste($dataid); |
|
326 | 16 | if ($paste->exists()) { |
|
327 | // accessing this property ensures that the paste would be |
||
328 | // deleted if it has already expired |
||
329 | 14 | $burnafterreading = $paste->isBurnafterreading(); |
|
330 | if ( |
||
331 | 12 | ($burnafterreading && $deletetoken == 'burnafterreading') || |
|
332 | 12 | Filter::slowEquals($deletetoken, $paste->getDeleteToken()) |
|
333 | ) { |
||
334 | // Paste exists and deletion token is valid: Delete the paste. |
||
335 | 8 | $paste->delete(); |
|
336 | 8 | $this->_status = 'Paste was properly deleted.'; |
|
337 | } else { |
||
338 | 4 | if (!$burnafterreading && $deletetoken == 'burnafterreading') { |
|
339 | 2 | $this->_error = 'Paste is not of burn-after-reading type.'; |
|
340 | } else { |
||
341 | 12 | $this->_error = 'Wrong deletion token. Paste was not deleted.'; |
|
342 | } |
||
343 | } |
||
344 | } else { |
||
345 | 14 | $this->_error = self::GENERIC_ERROR; |
|
346 | } |
||
347 | 4 | } catch (Exception $e) { |
|
348 | 4 | $this->_error = $e->getMessage(); |
|
349 | } |
||
350 | 18 | View Code Duplication | if ($this->_request->isJsonApiCall()) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
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.
Loading history...
|
|||
351 | 6 | if (strlen($this->_error)) { |
|
352 | 2 | $this->_return_message(1, $this->_error); |
|
353 | } else { |
||
354 | 4 | $this->_return_message(0, $dataid); |
|
355 | } |
||
356 | } |
||
357 | 18 | } |
|
358 | |||
359 | /** |
||
360 | * Read an existing paste or comment |
||
361 | * |
||
362 | * @access private |
||
363 | * @param string $dataid |
||
364 | */ |
||
365 | 19 | private function _read($dataid) |
|
366 | { |
||
367 | try { |
||
368 | 19 | $paste = $this->_model->getPaste($dataid); |
|
369 | 17 | if ($paste->exists()) { |
|
370 | 13 | $data = $paste->get(); |
|
371 | 11 | $this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date'); |
|
372 | 11 | if (property_exists($data->meta, 'salt')) { |
|
373 | 11 | unset($data->meta->salt); |
|
374 | } |
||
375 | 11 | $this->_data = json_encode($data); |
|
376 | } else { |
||
377 | 15 | $this->_error = self::GENERIC_ERROR; |
|
378 | } |
||
379 | 4 | } catch (Exception $e) { |
|
380 | 4 | $this->_error = $e->getMessage(); |
|
381 | } |
||
382 | |||
383 | 19 | View Code Duplication | if ($this->_request->isJsonApiCall()) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
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.
Loading history...
|
|||
384 | 5 | if (strlen($this->_error)) { |
|
385 | 2 | $this->_return_message(1, $this->_error); |
|
386 | } else { |
||
387 | 3 | $this->_return_message(0, $dataid, json_decode($this->_data, true)); |
|
388 | } |
||
389 | } |
||
390 | 19 | } |
|
391 | |||
392 | /** |
||
393 | * Display PrivateBin frontend. |
||
394 | * |
||
395 | * @access private |
||
396 | */ |
||
397 | 34 | private function _view() |
|
398 | { |
||
399 | // set headers to disable caching |
||
400 | 34 | $time = gmdate('D, d M Y H:i:s \G\M\T'); |
|
401 | 34 | header('Cache-Control: no-store, no-cache, no-transform, must-revalidate'); |
|
402 | 34 | header('Pragma: no-cache'); |
|
403 | 34 | header('Expires: ' . $time); |
|
404 | 34 | header('Last-Modified: ' . $time); |
|
405 | 34 | header('Vary: Accept'); |
|
406 | 34 | header('Content-Security-Policy: ' . $this->_conf->getKey('cspheader')); |
|
407 | 34 | header('X-Xss-Protection: 1; mode=block'); |
|
408 | 34 | header('X-Frame-Options: DENY'); |
|
409 | 34 | header('X-Content-Type-Options: nosniff'); |
|
410 | |||
411 | // label all the expiration options |
||
412 | 34 | $expire = array(); |
|
413 | 34 | foreach ($this->_conf->getSection('expire_options') as $time => $seconds) { |
|
414 | 34 | $expire[$time] = ($seconds == 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time); |
|
415 | } |
||
416 | |||
417 | // translate all the formatter options |
||
418 | 34 | $formatters = array_map('PrivateBin\\I18n::_', $this->_conf->getSection('formatter_options')); |
|
419 | |||
420 | // set language cookie if that functionality was enabled |
||
421 | 34 | $languageselection = ''; |
|
422 | 34 | if ($this->_conf->getKey('languageselection')) { |
|
423 | 2 | $languageselection = I18n::getLanguage(); |
|
424 | 2 | setcookie('lang', $languageselection); |
|
425 | } |
||
426 | |||
427 | 34 | $page = new View; |
|
428 | 34 | $page->assign('NAME', $this->_conf->getKey('name')); |
|
429 | 34 | $page->assign('CIPHERDATA', $this->_data); |
|
430 | 34 | $page->assign('ERROR', I18n::_($this->_error)); |
|
431 | 34 | $page->assign('STATUS', I18n::_($this->_status)); |
|
432 | 34 | $page->assign('VERSION', self::VERSION); |
|
433 | 34 | $page->assign('DISCUSSION', $this->_conf->getKey('discussion')); |
|
434 | 34 | $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion')); |
|
435 | 34 | $page->assign('MARKDOWN', array_key_exists('markdown', $formatters)); |
|
436 | 34 | $page->assign('SYNTAXHIGHLIGHTING', array_key_exists('syntaxhighlighting', $formatters)); |
|
437 | 34 | $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme')); |
|
438 | 34 | $page->assign('FORMATTER', $formatters); |
|
439 | 34 | $page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter')); |
|
440 | 34 | $page->assign('NOTICE', I18n::_($this->_conf->getKey('notice'))); |
|
441 | 34 | $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected')); |
|
442 | 34 | $page->assign('PASSWORD', $this->_conf->getKey('password')); |
|
443 | 34 | $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload')); |
|
444 | 34 | $page->assign('ZEROBINCOMPATIBILITY', $this->_conf->getKey('zerobincompatibility')); |
|
445 | 34 | $page->assign('LANGUAGESELECTION', $languageselection); |
|
446 | 34 | $page->assign('LANGUAGES', I18n::getLanguageLabels(I18n::getAvailableLanguages())); |
|
447 | 34 | $page->assign('EXPIRE', $expire); |
|
448 | 34 | $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire')); |
|
449 | 34 | $page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire'))); |
|
450 | 34 | $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener')); |
|
451 | 34 | $page->draw($this->_conf->getKey('template')); |
|
452 | 34 | } |
|
453 | |||
454 | /** |
||
455 | * outputs requested JSON-LD context |
||
456 | * |
||
457 | * @access private |
||
458 | * @param string $type |
||
459 | */ |
||
460 | 5 | private function _jsonld($type) |
|
461 | { |
||
462 | if ( |
||
463 | 5 | $type !== 'paste' && $type !== 'comment' && |
|
464 | 5 | $type !== 'pastemeta' && $type !== 'commentmeta' |
|
465 | ) { |
||
466 | 1 | $type = ''; |
|
467 | } |
||
468 | 5 | $content = '{}'; |
|
469 | 5 | $file = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $type . '.jsonld'; |
|
470 | 5 | if (is_readable($file)) { |
|
471 | 4 | $content = str_replace( |
|
472 | 4 | '?jsonld=', |
|
473 | 4 | $this->_urlBase . '?jsonld=', |
|
474 | file_get_contents($file) |
||
475 | ); |
||
476 | } |
||
477 | |||
478 | 5 | header('Content-type: application/ld+json'); |
|
479 | 5 | header('Access-Control-Allow-Origin: *'); |
|
480 | 5 | header('Access-Control-Allow-Methods: GET'); |
|
481 | 5 | echo $content; |
|
482 | 5 | } |
|
483 | |||
484 | /** |
||
485 | * prepares JSON encoded status message |
||
486 | * |
||
487 | * @access private |
||
488 | * @param int $status |
||
489 | * @param string $message |
||
490 | * @param array $other |
||
491 | */ |
||
492 | 55 | private function _return_message($status, $message, $other = array()) |
|
493 | { |
||
494 | 55 | $result = array('status' => $status); |
|
495 | 55 | if ($status) { |
|
496 | 26 | $result['message'] = I18n::_($message); |
|
497 | } else { |
||
498 | 31 | $result['id'] = $message; |
|
499 | 31 | $result['url'] = $this->_urlBase . '?' . $message; |
|
500 | } |
||
501 | 55 | $result += $other; |
|
502 | 55 | $this->_json = json_encode($result); |
|
503 | 55 | } |
|
504 | } |
||
505 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: