PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 152
Lasso Lite – Affiliate Link Manager & Product Displays v152
157 155 156 154 153 152 151 150 149 148 trunk 0.9.9 104 105 106 107 108 109 110 111 112 113 114 115 116 All 56 releases
simple-urls / libs / Raven / Client.php

Client.php in Lasso Lite – Affiliate Link Manager & Product Displays 152, at libs/Raven/Client.php

1,493 lines 44.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * This file is part of Raven.
4 *
5 * (c) Sentry Team
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11 /**
12 * Raven PHP Client
13 *
14 * @package raven
15 */
16
17 class Raven_Client
18 {
19 const VERSION = '1.8.3';
20
21 const PROTOCOL = '6';
22
23 const DEBUG = 'debug';
24 const INFO = 'info';
25 const WARN = 'warning';
26 const WARNING = 'warning';
27 const ERROR = 'error';
28 const FATAL = 'fatal';
29
30 const MESSAGE_LIMIT = 1024;
31
32 public $breadcrumbs;
33 /**
34 * @var Raven_Context
35 */
36 public $context;
37 public $extra_data;
38 /**
39 * @var array|null
40 */
41 public $severity_map;
42 public $store_errors_for_bulk_send = false;
43
44 protected $error_handler;
45 protected $error_types;
46
47 /**
48 * @var Raven_Serializer
49 */
50 protected $serializer;
51 /**
52 * @var Raven_ReprSerializer
53 */
54 protected $reprSerializer;
55
56 /**
57 * @var string
58 */
59 protected $app_path;
60 /**
61 * @var string[]
62 */
63 protected $prefixes;
64 /**
65 * @var string[]|null
66 */
67 protected $excluded_app_paths;
68 /**
69 * @var Callable
70 */
71 protected $transport;
72
73 public $logger;
74 /**
75 * @var string Full URL to Sentry
76 */
77 public $server;
78 public $secret_key;
79 public $public_key;
80 public $project;
81 public $auto_log_stacks;
82 public $name;
83 public $site;
84 public $tags;
85 public $release;
86 public $environment;
87 public $sample_rate;
88 public $trace;
89 public $timeout;
90 public $message_limit;
91 public $exclude;
92 public $http_proxy;
93 protected $send_callback;
94 public $curl_method;
95 public $curl_path;
96 public $curl_ipv4;
97 public $ca_cert;
98 public $verify_ssl;
99 public $curl_ssl_version;
100 public $trust_x_forwarded_proto;
101 public $mb_detect_order;
102 /**
103 * @var Raven_Processor[]
104 */
105 public $processors;
106 /**
107 * @var string|int|null
108 */
109 public $_lasterror;
110 /**
111 * @var object|null
112 */
113 protected $_last_sentry_error;
114 public $_last_event_id;
115 public $_user;
116 public $_pending_events;
117 public $sdk;
118 /**
119 * @var Raven_CurlHandler
120 */
121 protected $_curl_handler;
122 /**
123 * @var resource|null
124 */
125 protected $_curl_instance;
126 /**
127 * @var bool
128 */
129 protected $_shutdown_function_has_been_set;
130
131 public function __construct($options_or_dsn = null, $options = array())
132 {
133 if (is_array($options_or_dsn)) {
134 $options = array_merge($options_or_dsn, $options);
135 }
136
137 if (!is_array($options_or_dsn) && !empty($options_or_dsn)) {
138 $dsn = $options_or_dsn;
139 } elseif (!empty($_SERVER['SENTRY_DSN'])) {
140 $dsn = @$_SERVER['SENTRY_DSN'];
141 } elseif (!empty($options['dsn'])) {
142 $dsn = $options['dsn'];
143 } else {
144 $dsn = null;
145 }
146
147 if (!empty($dsn)) {
148 $options = array_merge($options, self::parseDSN($dsn));
149 }
150
151 $this->logger = Raven_Util::get($options, 'logger', 'php');
152 $this->server = Raven_Util::get($options, 'server');
153 $this->secret_key = Raven_Util::get($options, 'secret_key');
154 $this->public_key = Raven_Util::get($options, 'public_key');
155 $this->project = Raven_Util::get($options, 'project', 1);
156 $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false);
157 $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname());
158 $this->site = Raven_Util::get($options, 'site', self::_server_variable('SERVER_NAME'));
159 $this->tags = Raven_Util::get($options, 'tags', array());
160 $this->release = Raven_Util::get($options, 'release', null);
161 $this->environment = Raven_Util::get($options, 'environment', null);
162 $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1);
163 $this->trace = (bool) Raven_Util::get($options, 'trace', true);
164 $this->timeout = Raven_Util::get($options, 'timeout', 2);
165 $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT);
166 $this->exclude = Raven_Util::get($options, 'exclude', array());
167 $this->severity_map = null;
168 $this->http_proxy = Raven_Util::get($options, 'http_proxy');
169 $this->extra_data = Raven_Util::get($options, 'extra', array());
170 $this->send_callback = Raven_Util::get($options, 'send_callback', null);
171 $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync');
172 $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl');
173 $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true);
174 $this->ca_cert = Raven_Util::get($options, 'ca_cert', static::get_default_ca_cert());
175 $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true);
176 $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version');
177 $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto');
178 $this->transport = Raven_Util::get($options, 'transport', null);
179 $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null);
180 $this->error_types = Raven_Util::get($options, 'error_types', null);
181
182 // app path is used to determine if code is part of your application
183 $this->setAppPath(Raven_Util::get($options, 'app_path', null));
184 $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null));
185 // a list of prefixes used to coerce absolute paths into relative
186 $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes()));
187 $this->processors = $this->setProcessorsFromOptions($options);
188
189 $this->_lasterror = null;
190 $this->_last_sentry_error = null;
191 $this->_curl_instance = null;
192 $this->_last_event_id = null;
193 $this->_user = null;
194 $this->_pending_events = array();
195 $this->context = new Raven_Context();
196 $this->breadcrumbs = new Raven_Breadcrumbs();
197 $this->_shutdown_function_has_been_set = false;
198
199 $this->sdk = Raven_Util::get($options, 'sdk', array(
200 'name' => 'sentry-php',
201 'version' => self::VERSION,
202 ));
203 $this->serializer = new Raven_Serializer($this->mb_detect_order);
204 $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order);
205
206 if ($this->curl_method == 'async') {
207 $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options());
208 }
209
210 $this->transaction = new Raven_TransactionStack();
211 if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) {
212 // @codeCoverageIgnoreStart
213 $this->transaction->push($_SERVER['PATH_INFO']);
214 // @codeCoverageIgnoreEnd
215 }
216
217 if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) {
218 $this->registerDefaultBreadcrumbHandlers();
219 }
220
221 if (Raven_Util::get($options, 'install_shutdown_handler', true)) {
222 $this->registerShutdownFunction();
223 }
224 }
225
226 public function __destruct()
227 {
228 // Force close curl resource
229 $this->close_curl_resource();
230 }
231
232 /**
233 * Destruct all objects contain link to this object
234 *
235 * This method can not delete shutdown handler
236 */
237 public function close_all_children_link()
238 {
239 $this->processors = array();
240 }
241
242 /**
243 * Installs any available automated hooks (such as error_reporting).
244 */
245 public function install()
246 {
247 if ($this->error_handler) {
248 throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this)));
249 }
250 $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types);
251 $this->error_handler->registerExceptionHandler();
252 $this->error_handler->registerErrorHandler();
253 $this->error_handler->registerShutdownFunction();
254 return $this;
255 }
256
257 public function getRelease()
258 {
259 return $this->release;
260 }
261
262 public function setRelease($value)
263 {
264 $this->release = $value;
265 return $this;
266 }
267
268 public function getEnvironment()
269 {
270 return $this->environment;
271 }
272
273 public function setEnvironment($value)
274 {
275 $this->environment = $value;
276 return $this;
277 }
278
279 private static function getDefaultPrefixes()
280 {
281 $value = get_include_path();
282 return explode(PATH_SEPARATOR, $value);
283 }
284
285 private static function _convertPath($value)
286 {
287 $path = @realpath($value);
288 if ($path === false) {
289 $path = $value;
290 }
291 // we need app_path to have a trailing slash otherwise
292 // base path detection becomes complex if the same
293 // prefix is matched
294 if ($path[0] === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) {
295 $path .= DIRECTORY_SEPARATOR;
296 }
297 return $path;
298 }
299
300 public function getAppPath()
301 {
302 return $this->app_path;
303 }
304
305 public function setAppPath($value)
306 {
307 if ($value) {
308 $this->app_path = static::_convertPath($value);
309 } else {
310 $this->app_path = null;
311 }
312 return $this;
313 }
314
315 public function getExcludedAppPaths()
316 {
317 return $this->excluded_app_paths;
318 }
319
320 public function setExcludedAppPaths($value)
321 {
322 if ($value) {
323 $excluded_app_paths = array();
324
325 // We should be able to exclude a php files
326 foreach ((array) $value as $path) {
327 $excluded_app_paths[] = substr($path, -4) !== '.php' ? self::_convertPath($path) : $path;
328 }
329 } else {
330 $excluded_app_paths = null;
331 }
332
333 $this->excluded_app_paths = $excluded_app_paths;
334
335 return $this;
336 }
337
338 public function getPrefixes()
339 {
340 return $this->prefixes;
341 }
342
343 /**
344 * @param array $value
345 * @return Raven_Client
346 */
347 public function setPrefixes($value)
348 {
349 $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value;
350 return $this;
351 }
352
353 public function getSendCallback()
354 {
355 return $this->send_callback;
356 }
357
358 public function setSendCallback($value)
359 {
360 $this->send_callback = $value;
361 return $this;
362 }
363
364 public function getTransport()
365 {
366 return $this->transport;
367 }
368
369 public function getServerEndpoint($value = '')
370 {
371 return $this->server;
372 }
373
374 public static function getUserAgent()
375 {
376 return 'sentry-php/' . self::VERSION;
377 }
378
379 /**
380 * Set a custom transport to override how Sentry events are sent upstream.
381 *
382 * The bound function will be called with ``$client`` and ``$data`` arguments
383 * and is responsible for encoding the data, authenticating, and sending
384 * the data to the upstream Sentry server.
385 *
386 * @param Callable $value Function to be called
387 * @return Raven_Client
388 */
389 public function setTransport($value)
390 {
391 $this->transport = $value;
392 return $this;
393 }
394
395 /**
396 * @return string[]|Raven_Processor[]
397 */
398 public static function getDefaultProcessors()
399 {
400 return array(
401 'Raven_Processor_SanitizeDataProcessor',
402 );
403 }
404
405 /**
406 * Sets the Raven_Processor sub-classes to be used when data is processed before being
407 * sent to Sentry.
408 *
409 * @param $options
410 * @return Raven_Processor[]
411 */
412 public function setProcessorsFromOptions($options)
413 {
414 $processors = array();
415 foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) {
416 /**
417 * @var Raven_Processor $new_processor
418 * @var Raven_Processor|string $processor
419 */
420 $new_processor = new $processor($this);
421
422 if (isset($options['processorOptions']) && is_array($options['processorOptions'])) {
423 if (isset($options['processorOptions'][$processor])
424 && method_exists($processor, 'setProcessorOptions')
425 ) {
426 $new_processor->setProcessorOptions($options['processorOptions'][$processor]);
427 }
428 }
429 $processors[] = $new_processor;
430 }
431 return $processors;
432 }
433
434 /**
435 * Parses a Raven-compatible DSN and returns an array of its values.
436 *
437 * @param string $dsn Raven compatible DSN
438 * @return array parsed DSN
439 *
440 * @doc http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn
441 */
442 public static function parseDSN($dsn)
443 {
444 switch (strtolower($dsn)) {
445 case '':
446 case 'false':
447 case '(false)':
448 case 'empty':
449 case '(empty)':
450 case 'null':
451 case '(null)':
452 return array();
453 }
454
455 $url = parse_url($dsn);
456 $scheme = (isset($url['scheme']) ? $url['scheme'] : '');
457 if (!in_array($scheme, array('http', 'https'))) {
458 throw new InvalidArgumentException(
459 'Unsupported Sentry DSN scheme: '.
460 (!empty($scheme) ? $scheme : '<not set>')
461 );
462 }
463 $netloc = (isset($url['host']) ? $url['host'] : null);
464 $netloc .= (isset($url['port']) ? ':'.$url['port'] : null);
465 $rawpath = (isset($url['path']) ? $url['path'] : null);
466 if ($rawpath) {
467 $pos = strrpos($rawpath, '/', 1);
468 if ($pos !== false) {
469 $path = substr($rawpath, 0, $pos);
470 $project = substr($rawpath, $pos + 1);
471 } else {
472 $path = '';
473 $project = substr($rawpath, 1);
474 }
475 } else {
476 $project = null;
477 $path = '';
478 }
479 $username = (isset($url['user']) ? $url['user'] : null);
480 $password = (isset($url['pass']) ? $url['pass'] : null);
481 if (empty($netloc) || empty($project) || empty($username) || empty($password)) {
482 throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn);
483 }
484
485 return array(
486 'server' => sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project),
487 'project' => $project,
488 'public_key' => $username,
489 'secret_key' => $password,
490 );
491 }
492
493 public function getLastError()
494 {
495 return $this->_lasterror;
496 }
497
498 /**
499 * Given an identifier, returns a Sentry searchable string.
500 *
501 * @param mixed $ident
502 * @return mixed
503 * @codeCoverageIgnore
504 */
505 public function getIdent($ident)
506 {
507 // XXX: We don't calculate checksums yet, so we only have the ident.
508 return $ident;
509 }
510
511 /**
512 * @param string $message The message (primary description) for the event.
513 * @param array $params params to use when formatting the message.
514 * @param string $level Log level group
515 * @param bool|array $stack
516 * @param mixed $vars
517 * @return string|null
518 * @deprecated
519 * @codeCoverageIgnore
520 */
521 public function message($message, $params = array(), $level = self::INFO,
522 $stack = false, $vars = null)
523 {
524 return $this->captureMessage($message, $params, $level, $stack, $vars);
525 }
526
527 /**
528 * @param Exception $exception
529 * @return string|null
530 * @deprecated
531 * @codeCoverageIgnore
532 */
533 public function exception($exception)
534 {
535 return $this->captureException($exception);
536 }
537
538 /**
539 * Log a message to sentry
540 *
541 * @param string $message The message (primary description) for the event.
542 * @param array $params params to use when formatting the message.
543 * @param array $data Additional attributes to pass with this event (see Sentry docs).
544 * @param bool|array $stack
545 * @param mixed $vars
546 * @return string|null
547 */
548 public function captureMessage($message, $params = array(), $data = array(),
549 $stack = false, $vars = null)
550 {
551 // Gracefully handle messages which contain formatting characters, but were not
552 // intended to be used with formatting.
553 if (!empty($params)) {
554 $formatted_message = vsprintf($message, $params);
555 } else {
556 $formatted_message = $message;
557 }
558
559 if ($data === null) {
560 $data = array();
561 // support legacy method of passing in a level name as the third arg
562 } elseif (!is_array($data)) {
563 $data = array(
564 'level' => $data,
565 );
566 }
567
568 $data['message'] = $formatted_message;
569 $data['sentry.interfaces.Message'] = array(
570 'message' => $message,
571 'params' => $params,
572 'formatted' => $formatted_message,
573 );
574
575 return $this->capture($data, $stack, $vars);
576 }
577
578 /**
579 * Log an exception to sentry
580 *
581 * @param \Throwable|\Exception $exception The Throwable/Exception object.
582 * @param array $data Additional attributes to pass with this event (see Sentry docs).
583 * @param mixed $logger
584 * @param mixed $vars
585 * @return string|null
586 */
587 public function captureException($exception, $data = null, $logger = null, $vars = null)
588 {
589 $has_chained_exceptions = PHP_VERSION_ID >= 50300;
590
591 if (in_array(get_class($exception), $this->exclude)) {
592 return null;
593 }
594
595 if ($data === null) {
596 $data = array();
597 }
598
599 $exc = $exception;
600
601 // Only tracking lasso plugin path
602 if(strpos($exc->getFile(), SIMPLE_URLS_PLUGIN_PATH) === false){
603 return null;
604 }
605
606 do {
607 $exc_data = array(
608 'value' => $this->serializer->serialize($exc->getMessage()),
609 'type' => get_class($exc),
610 );
611
612 /**'exception'
613 * Exception::getTrace doesn't store the point at where the exception
614 * was thrown, so we have to stuff it in ourselves. Ugh.
615 */
616 $trace = $exc->getTrace();
617 $frame_where_exception_thrown = array(
618 'file' => $exc->getFile(),
619 'line' => $exc->getLine(),
620 );
621
622 array_unshift($trace, $frame_where_exception_thrown);
623
624 // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149)
625 if (!class_exists('Raven_Stacktrace')) {
626 // @codeCoverageIgnoreStart
627 spl_autoload_call('Raven_Stacktrace');
628 // @codeCoverageIgnoreEnd
629 }
630
631 $exc_data['stacktrace'] = array(
632 'frames' => Raven_Stacktrace::get_stack_info(
633 $trace, $this->trace, $vars, $this->message_limit, $this->prefixes,
634 $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
635 ),
636 );
637
638 $exceptions[] = $exc_data;
639 } while ($has_chained_exceptions && $exc = $exc->getPrevious());
640
641 $data['exception'] = array(
642 'values' => array_reverse($exceptions),
643 );
644 if ($logger !== null) {
645 $data['logger'] = $logger;
646 }
647
648 if (empty($data['level'])) {
649 if (method_exists($exception, 'getSeverity')) {
650 $data['level'] = $this->translateSeverity($exception->getSeverity());
651 } else {
652 $data['level'] = self::ERROR;
653 }
654 }
655
656 return $this->capture($data, $trace, $vars);
657 }
658
659
660 /**
661 * Capture the most recent error (obtained with ``error_get_last``).
662 * @return string|null
663 */
664 public function captureLastError()
665 {
666 if (null === $error = error_get_last()) {
667 return null;
668 }
669
670 $e = new ErrorException(
671 @$error['message'], 0, @$error['type'],
672 @$error['file'], @$error['line']
673 );
674
675 return $this->captureException($e);
676 }
677
678 /**
679 * Log an query to sentry
680 *
681 * @param string|null $query
682 * @param string $level
683 * @param string $engine
684 */
685 public function captureQuery($query, $level = self::INFO, $engine = '')
686 {
687 $data = array(
688 'message' => $query,
689 'level' => $level,
690 'sentry.interfaces.Query' => array(
691 'query' => $query
692 )
693 );
694
695 if ($engine !== '') {
696 $data['sentry.interfaces.Query']['engine'] = $engine;
697 }
698 return $this->capture($data, false);
699 }
700
701 /**
702 * Return the last captured event's ID or null if none available.
703 */
704 public function getLastEventID()
705 {
706 return $this->_last_event_id;
707 }
708
709 protected function registerDefaultBreadcrumbHandlers()
710 {
711 $handler = new Raven_Breadcrumbs_ErrorHandler($this);
712 $handler->install();
713 }
714
715 protected function registerShutdownFunction()
716 {
717 if (!$this->_shutdown_function_has_been_set) {
718 $this->_shutdown_function_has_been_set = true;
719 register_shutdown_function(array($this, 'onShutdown'));
720 }
721 }
722
723 /**
724 * @return bool
725 * @codeCoverageIgnore
726 */
727 protected static function is_http_request()
728 {
729 return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli';
730 }
731
732 protected function get_http_data()
733 {
734 $headers = array();
735
736 foreach ($_SERVER as $key => $value) {
737 if (0 === strpos($key, 'HTTP_')) {
738 $header_key =
739 str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
740 $headers[$header_key] = $value;
741 } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') {
742 $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
743 $headers[$header_key] = $value;
744 }
745 }
746
747 $result = array(
748 'method' => self::_server_variable('REQUEST_METHOD'),
749 'url' => $this->get_current_url(),
750 'query_string' => self::_server_variable('QUERY_STRING'),
751 );
752
753 // dont set this as an empty array as PHP will treat it as a numeric array
754 // instead of a mapping which goes against the defined Sentry spec
755 if (!empty($_POST)) {
756 $result['data'] = $_POST;
757 }
758 if (!empty($_COOKIE)) {
759 $result['cookies'] = $_COOKIE;
760 }
761 if (!empty($headers)) {
762 $result['headers'] = $headers;
763 }
764
765 return array(
766 'request' => $result,
767 );
768 }
769
770 protected function get_user_data()
771 {
772 $user = $this->context->user;
773 if ($user === null) {
774 if (!function_exists('session_id') || !session_id()) {
775 return array();
776 }
777 $user = array(
778 'id' => session_id(),
779 );
780 if (!empty($_SERVER['REMOTE_ADDR'])) {
781 $user['ip_address'] = $_SERVER['REMOTE_ADDR'];
782 }
783 if (!empty($_SESSION)) {
784 $user['data'] = $_SESSION;
785 }
786 }
787 return array(
788 'user' => $user,
789 );
790 }
791
792 protected function get_extra_data()
793 {
794 return $this->extra_data;
795 }
796
797 public function get_default_data()
798 {
799 return array(
800 'server_name' => $this->name,
801 'project' => $this->project,
802 'site' => $this->site,
803 'logger' => $this->logger,
804 'tags' => $this->tags,
805 'platform' => 'php',
806 'sdk' => $this->sdk,
807 'culprit' => $this->transaction->peek(),
808 );
809 }
810
811 public function capture($data, $stack = null, $vars = null)
812 {
813 if (!isset($data['timestamp'])) {
814 $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
815 }
816 if (!isset($data['level'])) {
817 $data['level'] = self::ERROR;
818 }
819 if (!isset($data['tags'])) {
820 $data['tags'] = array();
821 }
822 if (!isset($data['extra'])) {
823 $data['extra'] = array();
824 }
825 if (!isset($data['event_id'])) {
826 $data['event_id'] = static::uuid4();
827 }
828
829 if (isset($data['message'])) {
830 $data['message'] = substr($data['message'], 0, $this->message_limit);
831 }
832
833 $data = array_merge($this->get_default_data(), $data);
834
835 if (static::is_http_request()) {
836 $data = array_merge($this->get_http_data(), $data);
837 }
838
839 $data = array_merge($this->get_user_data(), $data);
840
841 if ($this->release) {
842 $data['release'] = $this->release;
843 }
844 if ($this->environment) {
845 $data['environment'] = $this->environment;
846 }
847
848 $data['tags'] = array_merge(
849 $this->tags,
850 $this->context->tags,
851 $data['tags']);
852
853 $data['extra'] = array_merge(
854 $this->get_extra_data(),
855 $this->context->extra,
856 $data['extra']);
857
858 if (empty($data['extra'])) {
859 unset($data['extra']);
860 }
861 if (empty($data['tags'])) {
862 unset($data['tags']);
863 }
864 if (empty($data['user'])) {
865 unset($data['user']);
866 }
867 if (empty($data['request'])) {
868 unset($data['request']);
869 }
870
871 if (!$this->breadcrumbs->is_empty()) {
872 $data['breadcrumbs'] = $this->breadcrumbs->fetch();
873 }
874
875 if ((!$stack && $this->auto_log_stacks) || $stack === true) {
876 $stack = debug_backtrace();
877
878 // Drop last stack
879 array_shift($stack);
880 }
881
882 if (!empty($stack)) {
883 // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149)
884 if (!class_exists('Raven_Stacktrace')) {
885 // @codeCoverageIgnoreStart
886 spl_autoload_call('Raven_Stacktrace');
887 // @codeCoverageIgnoreEnd
888 }
889
890 if (!isset($data['stacktrace']) && !isset($data['exception'])) {
891 $data['stacktrace'] = array(
892 'frames' => Raven_Stacktrace::get_stack_info(
893 $stack, $this->trace, $vars, $this->message_limit, $this->prefixes,
894 $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
895 ),
896 );
897 }
898 }
899
900 $this->sanitize($data);
901 $this->process($data);
902
903 if (!$this->store_errors_for_bulk_send) {
904 $this->send($data);
905 } else {
906 $this->_pending_events[] = $data;
907 }
908
909 $this->_last_event_id = $data['event_id'];
910
911 return $data['event_id'];
912 }
913
914 public function sanitize(&$data)
915 {
916 // attempt to sanitize any user provided data
917 if (!empty($data['request'])) {
918 $data['request'] = $this->serializer->serialize($data['request']);
919 }
920 if (!empty($data['user'])) {
921 $data['user'] = $this->serializer->serialize($data['user'], 3);
922 }
923 if (!empty($data['extra'])) {
924 $data['extra'] = $this->serializer->serialize($data['extra']);
925 }
926 if (!empty($data['tags'])) {
927 foreach ($data['tags'] as $key => $value) {
928 $data['tags'][$key] = @(string)$value;
929 }
930 }
931 if (!empty($data['contexts'])) {
932 $data['contexts'] = $this->serializer->serialize($data['contexts'], 5);
933 }
934 if (!empty($data['breadcrumbs'])) {
935 $data['breadcrumbs'] = $this->serializer->serialize($data['breadcrumbs'], 5);
936 }
937 }
938
939 /**
940 * Process data through all defined Raven_Processor sub-classes
941 *
942 * @param array $data Associative array of data to log
943 */
944 public function process(&$data)
945 {
946 foreach ($this->processors as $processor) {
947 $processor->process($data);
948 }
949 }
950
951 public function sendUnsentErrors()
952 {
953 foreach ($this->_pending_events as $data) {
954 $this->send($data);
955 }
956 $this->_pending_events = array();
957 if ($this->store_errors_for_bulk_send) {
958 //in case an error occurs after this is called, on shutdown, send any new errors.
959 $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED');
960 }
961 }
962
963 /**
964 * @param array $data
965 * @return string|bool
966 */
967 public function encode(&$data)
968 {
969 $message = Raven_Compat::json_encode($data);
970 if ($message === false) {
971 if (function_exists('json_last_error_msg')) {
972 $this->_lasterror = json_last_error_msg();
973 } else {
974 // @codeCoverageIgnoreStart
975 $this->_lasterror = json_last_error();
976 // @codeCoverageIgnoreEnd
977 }
978 return false;
979 }
980
981 if (function_exists("gzcompress")) {
982 $message = gzcompress($message);
983 }
984
985 // PHP's builtin curl_* function are happy without this, but the exec method requires it
986 $message = base64_encode($message);
987
988 return $message;
989 }
990
991 /**
992 * Wrapper to handle encoding and sending data to the Sentry API server.
993 *
994 * @param array $data Associative array of data to log
995 */
996 public function send(&$data)
997 {
998 if (is_callable($this->send_callback)
999 && call_user_func_array($this->send_callback, array(&$data)) === false
1000 ) {
1001 // if send_callback returns false, end native send
1002 return;
1003 }
1004
1005 if (!$this->server) {
1006 return;
1007 }
1008
1009 if ($this->transport) {
1010 call_user_func($this->transport, $this, $data);
1011 return;
1012 }
1013
1014 // should this event be sampled?
1015 if (rand(1, 100) / 100.0 > $this->sample_rate) {
1016 return;
1017 }
1018
1019 $message = $this->encode($data);
1020
1021 $headers = array(
1022 'User-Agent' => static::getUserAgent(),
1023 'X-Sentry-Auth' => $this->getAuthHeader(),
1024 'Content-Type' => 'application/octet-stream'
1025 );
1026
1027 $this->send_remote($this->server, $message, $headers);
1028 }
1029
1030 /**
1031 * Send data to Sentry
1032 *
1033 * @param string $url Full URL to Sentry
1034 * @param array|string $data Associative array of data to log
1035 * @param array $headers Associative array of headers
1036 */
1037 protected function send_remote($url, $data, $headers = array())
1038 {
1039 $parts = parse_url($url);
1040 $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null);
1041 $this->send_http($url, $data, $headers);
1042 }
1043
1044 protected static function get_default_ca_cert()
1045 {
1046 return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem';
1047 }
1048
1049 /**
1050 * @return array
1051 * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006
1052 */
1053 protected function get_curl_options()
1054 {
1055 $options = array(
1056 CURLOPT_VERBOSE => false,
1057 CURLOPT_SSL_VERIFYHOST => 2,
1058 CURLOPT_SSL_VERIFYPEER => $this->verify_ssl,
1059 CURLOPT_CAINFO => $this->ca_cert,
1060 CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION,
1061 );
1062 if ($this->http_proxy) {
1063 $options[CURLOPT_PROXY] = $this->http_proxy;
1064 }
1065 if ($this->curl_ssl_version) {
1066 $options[CURLOPT_SSLVERSION] = $this->curl_ssl_version;
1067 }
1068 if ($this->curl_ipv4) {
1069 $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
1070 }
1071 if (defined('CURLOPT_TIMEOUT_MS')) {
1072 // MS is available in curl >= 7.16.2
1073 $timeout = max(1, ceil(1000 * $this->timeout));
1074
1075 // some versions of PHP 5.3 don't have this defined correctly
1076 if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) {
1077 //see stackoverflow link in the phpdoc
1078 define('CURLOPT_CONNECTTIMEOUT_MS', 156);
1079 }
1080
1081 $options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout;
1082 $options[CURLOPT_TIMEOUT_MS] = $timeout;
1083 } else {
1084 // fall back to the lower-precision timeout.
1085 $timeout = max(1, ceil($this->timeout));
1086 $options[CURLOPT_CONNECTTIMEOUT] = $timeout;
1087 $options[CURLOPT_TIMEOUT] = $timeout;
1088 }
1089 return $options;
1090 }
1091
1092 /**
1093 * Send the message over http to the sentry url given
1094 *
1095 * @param string $url URL of the Sentry instance to log to
1096 * @param array|string $data Associative array of data to log
1097 * @param array $headers Associative array of headers
1098 */
1099 protected function send_http($url, $data, $headers = array())
1100 {
1101 if ($this->curl_method == 'async') {
1102 $this->_curl_handler->enqueue($url, $data, $headers);
1103 } elseif ($this->curl_method == 'exec') {
1104 $this->send_http_asynchronous_curl_exec($url, $data, $headers);
1105 } else {
1106 $this->send_http_synchronous($url, $data, $headers);
1107 }
1108 }
1109
1110 protected function buildCurlCommand($url, $data, $headers)
1111 {
1112 // TODO(dcramer): support ca_cert
1113 $cmd = $this->curl_path.' -X POST ';
1114 foreach ($headers as $key => $value) {
1115 $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' ';
1116 }
1117 $cmd .= '-d ' . escapeshellarg($data) . ' ';
1118 $cmd .= escapeshellarg($url) . ' ';
1119 $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send)
1120 if (!$this->verify_ssl) {
1121 $cmd .= '-k ';
1122 }
1123 $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background
1124
1125 return $cmd;
1126 }
1127
1128 /**
1129 * Send the cURL to Sentry asynchronously. No errors will be returned from cURL
1130 *
1131 * @param string $url URL of the Sentry instance to log to
1132 * @param array|string $data Associative array of data to log
1133 * @param array $headers Associative array of headers
1134 * @return bool
1135 */
1136 protected function send_http_asynchronous_curl_exec($url, $data, $headers)
1137 {
1138 exec($this->buildCurlCommand($url, $data, $headers));
1139 return true; // The exec method is just fire and forget, so just assume it always works
1140 }
1141
1142 /**
1143 * Send a blocking cURL to Sentry and check for errors from cURL
1144 *
1145 * @param string $url URL of the Sentry instance to log to
1146 * @param array|string $data Associative array of data to log
1147 * @param array $headers Associative array of headers
1148 * @return bool
1149 */
1150 protected function send_http_synchronous($url, $data, $headers)
1151 {
1152 $new_headers = array();
1153 foreach ($headers as $key => $value) {
1154 array_push($new_headers, $key .': '. $value);
1155 }
1156 // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216)
1157 $new_headers[] = 'Expect:';
1158
1159 if (is_null($this->_curl_instance)) {
1160 $this->_curl_instance = curl_init($url);
1161 }
1162 curl_setopt($this->_curl_instance, CURLOPT_POST, 1);
1163 curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers);
1164 curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data);
1165 curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true);
1166
1167 $options = $this->get_curl_options();
1168 if (isset($options[CURLOPT_CAINFO])) {
1169 $ca_cert = $options[CURLOPT_CAINFO];
1170 unset($options[CURLOPT_CAINFO]);
1171 } else {
1172 $ca_cert = null;
1173 }
1174 curl_setopt_array($this->_curl_instance, $options);
1175
1176 $buffer = curl_exec($this->_curl_instance);
1177
1178 $errno = curl_errno($this->_curl_instance);
1179 // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
1180 if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) {
1181 curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert);
1182 $buffer = curl_exec($this->_curl_instance);
1183 }
1184 if ($errno != 0) {
1185 $this->_lasterror = curl_error($this->_curl_instance);
1186 $this->_last_sentry_error = null;
1187 return false;
1188 }
1189
1190 $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE);
1191 $success = ($code == 200);
1192 if ($success) {
1193 $this->_lasterror = null;
1194 $this->_last_sentry_error = null;
1195 } else {
1196 // It'd be nice just to raise an exception here, but it's not very PHP-like
1197 $this->_lasterror = curl_error($this->_curl_instance);
1198 $this->_last_sentry_error = @json_decode($buffer);
1199 }
1200
1201 return $success;
1202 }
1203
1204 /**
1205 * Generate a Sentry authorization header string
1206 *
1207 * @param string $timestamp Timestamp when the event occurred
1208 * @param string $client HTTP client name (not Raven_Client object)
1209 * @param string $api_key Sentry API key
1210 * @param string $secret_key Sentry API key
1211 * @return string
1212 */
1213 protected static function get_auth_header($timestamp, $client, $api_key, $secret_key)
1214 {
1215 $header = array(
1216 sprintf('sentry_timestamp=%F', $timestamp),
1217 "sentry_client={$client}",
1218 sprintf('sentry_version=%s', self::PROTOCOL),
1219 );
1220
1221 if ($api_key) {
1222 $header[] = "sentry_key={$api_key}";
1223 }
1224
1225 if ($secret_key) {
1226 $header[] = "sentry_secret={$secret_key}";
1227 }
1228
1229
1230 return sprintf('Sentry %s', implode(', ', $header));
1231 }
1232
1233 public function getAuthHeader()
1234 {
1235 $timestamp = microtime(true);
1236 return $this->get_auth_header(
1237 $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key
1238 );
1239 }
1240
1241 /**
1242 * Generate an uuid4 value
1243 *
1244 * @return string
1245 */
1246 protected static function uuid4()
1247 {
1248 $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
1249 // 32 bits for "time_low"
1250 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
1251
1252 // 16 bits for "time_mid"
1253 mt_rand(0, 0xffff),
1254
1255 // 16 bits for "time_hi_and_version",
1256 // four most significant bits holds version number 4
1257 mt_rand(0, 0x0fff) | 0x4000,
1258
1259 // 16 bits, 8 bits for "clk_seq_hi_res",
1260 // 8 bits for "clk_seq_low",
1261 // two most significant bits holds zero and one for variant DCE1.1
1262 mt_rand(0, 0x3fff) | 0x8000,
1263
1264 // 48 bits for "node"
1265 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
1266 );
1267
1268 return str_replace('-', '', $uuid);
1269 }
1270
1271 /**
1272 * Return the URL for the current request
1273 *
1274 * @return string|null
1275 */
1276 protected function get_current_url()
1277 {
1278 // When running from commandline the REQUEST_URI is missing.
1279 if (!isset($_SERVER['REQUEST_URI'])) {
1280 return null;
1281 }
1282
1283 // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0
1284 $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
1285 : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR']
1286 : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '')));
1287
1288 $httpS = $this->isHttps() ? 's' : '';
1289 return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}";
1290 }
1291
1292 /**
1293 * Was the current request made over https?
1294 *
1295 * @return bool
1296 */
1297 protected function isHttps()
1298 {
1299 if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
1300 return true;
1301 }
1302
1303 if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
1304 return true;
1305 }
1306
1307 if (!empty($this->trust_x_forwarded_proto) &&
1308 !empty($_SERVER['X-FORWARDED-PROTO']) &&
1309 $_SERVER['X-FORWARDED-PROTO'] === 'https') {
1310 return true;
1311 }
1312
1313 return false;
1314 }
1315
1316 /**
1317 * Get the value of a key from $_SERVER
1318 *
1319 * @param string $key Key whose value you wish to obtain
1320 * @return string Key's value
1321 */
1322 private static function _server_variable($key)
1323 {
1324 if (isset($_SERVER[$key])) {
1325 return $_SERVER[$key];
1326 }
1327
1328 return '';
1329 }
1330
1331 /**
1332 * Translate a PHP Error constant into a Sentry log level group
1333 *
1334 * @param string $severity PHP E_$x error constant
1335 * @return string Sentry log level group
1336 */
1337 public function translateSeverity($severity)
1338 {
1339 if (is_array($this->severity_map) && isset($this->severity_map[$severity])) {
1340 return $this->severity_map[$severity];
1341 }
1342 switch ($severity) {
1343 case E_ERROR: return Raven_Client::ERROR;
1344 case E_WARNING: return Raven_Client::WARN;
1345 case E_PARSE: return Raven_Client::ERROR;
1346 case E_NOTICE: return Raven_Client::INFO;
1347 case E_CORE_ERROR: return Raven_Client::ERROR;
1348 case E_CORE_WARNING: return Raven_Client::WARN;
1349 case E_COMPILE_ERROR: return Raven_Client::ERROR;
1350 case E_COMPILE_WARNING: return Raven_Client::WARN;
1351 case E_USER_ERROR: return Raven_Client::ERROR;
1352 case E_USER_WARNING: return Raven_Client::WARN;
1353 case E_USER_NOTICE: return Raven_Client::INFO;
1354 case E_STRICT: return Raven_Client::INFO;
1355 case E_RECOVERABLE_ERROR: return Raven_Client::ERROR;
1356 }
1357 if (PHP_VERSION_ID >= 50300) {
1358 switch ($severity) {
1359 case E_DEPRECATED: return Raven_Client::WARN;
1360 case E_USER_DEPRECATED: return Raven_Client::WARN;
1361 }
1362 }
1363 return Raven_Client::ERROR;
1364 }
1365
1366 /**
1367 * Provide a map of PHP Error constants to Sentry logging groups to use instead
1368 * of the defaults in translateSeverity()
1369 *
1370 * @param array $map
1371 */
1372 public function registerSeverityMap($map)
1373 {
1374 $this->severity_map = $map;
1375 }
1376
1377 /**
1378 * Convenience function for setting a user's ID and Email
1379 *
1380 * @deprecated
1381 * @param string $id User's ID
1382 * @param string|null $email User's email
1383 * @param array $data Additional user data
1384 * @codeCoverageIgnore
1385 */
1386 public function set_user_data($id, $email = null, $data = array())
1387 {
1388 $user = array('id' => $id);
1389 if (isset($email)) {
1390 $user['email'] = $email;
1391 }
1392 $this->user_context(array_merge($user, $data));
1393 }
1394
1395 public function onShutdown()
1396 {
1397 if (!defined('RAVEN_CLIENT_END_REACHED')) {
1398 define('RAVEN_CLIENT_END_REACHED', true);
1399 }
1400 $this->sendUnsentErrors();
1401 if ($this->curl_method == 'async') {
1402 $this->_curl_handler->join();
1403 }
1404 }
1405
1406 /**
1407 * Sets user context.
1408 *
1409 * @param array $data Associative array of user data
1410 * @param bool $merge Merge existing context with new context
1411 */
1412 public function user_context($data, $merge = true)
1413 {
1414 if ($merge && $this->context->user !== null) {
1415 // bail if data is null
1416 if (!$data) {
1417 return;
1418 }
1419 $this->context->user = array_merge($this->context->user, $data);
1420 } else {
1421 $this->context->user = $data;
1422 }
1423 }
1424
1425 /**
1426 * Appends tags context.
1427 *
1428 * @param array $data Associative array of tags
1429 */
1430 public function tags_context($data)
1431 {
1432 $this->context->tags = array_merge($this->context->tags, $data);
1433 }
1434
1435 /**
1436 * Appends additional context.
1437 *
1438 * @param array $data Associative array of extra data
1439 */
1440 public function extra_context($data)
1441 {
1442 $this->context->extra = array_merge($this->context->extra, $data);
1443 }
1444
1445 /**
1446 * @param array $processors
1447 */
1448 public function setProcessors(array $processors)
1449 {
1450 $this->processors = $processors;
1451 }
1452
1453 /**
1454 * @return object|null
1455 */
1456 public function getLastSentryError()
1457 {
1458 return $this->_last_sentry_error;
1459 }
1460
1461 /**
1462 * @return bool
1463 */
1464 public function getShutdownFunctionHasBeenSet()
1465 {
1466 return $this->_shutdown_function_has_been_set;
1467 }
1468
1469 public function close_curl_resource()
1470 {
1471 if (!is_null($this->_curl_instance)) {
1472 curl_close($this->_curl_instance);
1473 $this->_curl_instance = null;
1474 }
1475 }
1476
1477 /**
1478 * @param Raven_Serializer $serializer
1479 */
1480 public function setSerializer(Raven_Serializer $serializer)
1481 {
1482 $this->serializer = $serializer;
1483 }
1484
1485 /**
1486 * @param Raven_ReprSerializer $reprSerializer
1487 */
1488 public function setReprSerializer(Raven_ReprSerializer $reprSerializer)
1489 {
1490 $this->reprSerializer = $reprSerializer;
1491 }
1492 }
1493