vendor/rollbar/rollbar/src/DataBuilder.php line 367

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Rollbar;
  3. use Rollbar\Payload\Context;
  4. use Rollbar\Payload\Message;
  5. use Rollbar\Payload\Body;
  6. use Rollbar\Payload\Level;
  7. use Rollbar\Payload\Person;
  8. use Rollbar\Payload\Server;
  9. use Rollbar\Payload\Request;
  10. use Rollbar\Payload\Data;
  11. use Rollbar\Payload\Trace;
  12. use Rollbar\Payload\Frame;
  13. use Rollbar\Payload\TraceChain;
  14. use Rollbar\Payload\ExceptionInfo;
  15. use Rollbar\Rollbar;
  16. use Stringable;
  17. use Throwable;
  18. class DataBuilder implements DataBuilderInterface
  19. {
  20.     const ANONYMIZE_IP 'anonymize';
  21.     
  22.     protected static $defaults;
  23.     protected $environment;
  24.     protected $messageLevel;
  25.     protected $exceptionLevel;
  26.     protected $psrLevels;
  27.     protected $errorLevels;
  28.     protected $codeVersion;
  29.     protected $platform;
  30.     protected $framework;
  31.     protected $context;
  32.     protected $requestParams;
  33.     protected $requestBody;
  34.     protected $requestExtras;
  35.     protected $host;
  36.     protected $person;
  37.     protected $personFunc;
  38.     protected $serverRoot;
  39.     protected $serverBranch;
  40.     protected $serverCodeVersion;
  41.     protected $serverExtras;
  42.     protected $custom;
  43.     protected $customDataMethod;
  44.     protected $fingerprint;
  45.     protected $title;
  46.     protected $notifier;
  47.     protected $baseException;
  48.     protected $includeCodeContext;
  49.     protected $includeExcCodeContext;
  50.     protected $sendMessageTrace;
  51.     protected $rawRequestBody;
  52.     protected $localVarsDump;
  53.     protected $captureErrorStacktraces;
  54.     protected $captureIP;
  55.     protected $captureEmail;
  56.     protected $captureUsername;
  57.     
  58.     /**
  59.      * @var Utilities
  60.      */
  61.     protected $utilities;
  62.     /**
  63.      * Initializes the data builder from the Rollbar configs.
  64.      *
  65.      * @param array $config The configuration array.
  66.      */
  67.     public function __construct(array $config)
  68.     {
  69.         self::$defaults Defaults::get();
  70.         
  71.         $this->setUtilities($config);
  72.         
  73.         $this->setEnvironment($config);
  74.         $this->setRawRequestBody($config);
  75.         $this->setDefaultMessageLevel($config);
  76.         $this->setDefaultExceptionLevel($config);
  77.         $this->setDefaultPsrLevels($config);
  78.         $this->setErrorLevels($config);
  79.         $this->setCodeVersion($config);
  80.         $this->setPlatform($config);
  81.         $this->setFramework($config);
  82.         $this->setContext($config);
  83.         $this->setRequestParams($config);
  84.         $this->setRequestBody($config);
  85.         $this->setRequestExtras($config);
  86.         $this->setHost($config);
  87.         $this->setPerson($config);
  88.         $this->setPersonFunc($config);
  89.         $this->setServerRoot($config);
  90.         $this->setServerBranch($config);
  91.         $this->setServerCodeVersion($config);
  92.         $this->setServerExtras($config);
  93.         $this->setCustom($config);
  94.         $this->setFingerprint($config);
  95.         $this->setTitle($config);
  96.         $this->setNotifier($config);
  97.         $this->setBaseException($config);
  98.         $this->setIncludeCodeContext($config);
  99.         $this->setIncludeExcCodeContext($config);
  100.         $this->setSendMessageTrace($config);
  101.         $this->setLocalVarsDump($config);
  102.         $this->setCaptureErrorStacktraces($config);
  103.         $this->setCaptureEmail($config);
  104.         $this->setCaptureUsername($config);
  105.         $this->setCaptureIP($config);
  106.         $this->setCustomDataMethod($config);
  107.     }
  108.     protected function setCaptureIP($config)
  109.     {
  110.         $fromConfig $config['capture_ip'] ?? null;
  111.         $this->captureIP self::$defaults->captureIP($fromConfig);
  112.     }
  113.     
  114.     protected function setCaptureEmail($config)
  115.     {
  116.         $fromConfig $config['capture_email'] ?? null;
  117.         $this->captureEmail self::$defaults->captureEmail($fromConfig);
  118.     }
  119.     
  120.     protected function setCaptureUsername($config)
  121.     {
  122.         $fromConfig $config['capture_username'] ?? null;
  123.         $this->captureUsername self::$defaults->captureUsername($fromConfig);
  124.     }
  125.     protected function setEnvironment($config)
  126.     {
  127.         $fromConfig $config['environment'] ?? self::$defaults->get()->environment();
  128.         $this->utilities->validateString($fromConfig"config['environment']"nullfalse);
  129.         $this->environment $fromConfig;
  130.     }
  131.     protected function setDefaultMessageLevel($config)
  132.     {
  133.         $fromConfig $config['messageLevel'] ?? null;
  134.         $this->messageLevel self::$defaults->messageLevel($fromConfig);
  135.     }
  136.     protected function setDefaultExceptionLevel($config)
  137.     {
  138.         $fromConfig $config['exceptionLevel'] ?? null;
  139.         $this->exceptionLevel self::$defaults->exceptionLevel($fromConfig);
  140.     }
  141.     protected function setDefaultPsrLevels($config)
  142.     {
  143.         $fromConfig $config['psrLevels'] ?? null;
  144.         $this->psrLevels self::$defaults->psrLevels($fromConfig);
  145.     }
  146.     protected function setErrorLevels($config)
  147.     {
  148.         $fromConfig $config['errorLevels'] ?? null;
  149.         $this->errorLevels self::$defaults->errorLevels($fromConfig);
  150.     }
  151.     protected function setSendMessageTrace($config)
  152.     {
  153.         $fromConfig $config['send_message_trace'] ?? null;
  154.         $this->sendMessageTrace self::$defaults->sendMessageTrace($fromConfig);
  155.     }
  156.     
  157.     protected function setRawRequestBody($config)
  158.     {
  159.         $fromConfig $config['include_raw_request_body'] ?? null;
  160.         $this->rawRequestBody self::$defaults->rawRequestBody($fromConfig);
  161.     }
  162.     protected function setLocalVarsDump($config)
  163.     {
  164.         $fromConfig $config['local_vars_dump'] ?? null;
  165.         $this->localVarsDump self::$defaults->localVarsDump($fromConfig);
  166.         if ($this->localVarsDump && !empty(ini_get('zend.exception_ignore_args'))) {
  167.             ini_set('zend.exception_ignore_args''0');
  168.             assert(empty(ini_get('zend.exception_ignore_args')) || ini_get('zend.exception_ignore_args') == "0");
  169.         }
  170.     }
  171.     
  172.     protected function setCaptureErrorStacktraces($config)
  173.     {
  174.         $fromConfig $config['capture_error_stacktraces'] ?? null;
  175.         $this->captureErrorStacktraces self::$defaults->captureErrorStacktraces($fromConfig);
  176.     }
  177.     protected function setCodeVersion($config)
  178.     {
  179.         $fromConfig $config['codeVersion'] ?? null;
  180.         if (!isset($fromConfig)) {
  181.             $fromConfig $config['code_version'] ?? null;
  182.         }
  183.         $this->codeVersion self::$defaults->codeVersion($fromConfig);
  184.     }
  185.     protected function setPlatform($config)
  186.     {
  187.         $fromConfig $config['platform'] ?? null;
  188.         $this->platform self::$defaults->platform($fromConfig);
  189.     }
  190.     protected function setFramework($config)
  191.     {
  192.         $this->framework $config['framework'] ?? null;
  193.     }
  194.     protected function setContext($config)
  195.     {
  196.         $this->context $config['context'] ?? null;
  197.     }
  198.     protected function setRequestParams($config)
  199.     {
  200.         $this->requestParams $config['requestParams'] ?? null;
  201.     }
  202.     /*
  203.      * @SuppressWarnings(PHPMD.Superglobals)
  204.      */
  205.     protected function setRequestBody($config)
  206.     {
  207.         
  208.         $this->requestBody $config['requestBody'] ?? null;
  209.         
  210.         if (!$this->requestBody && $this->rawRequestBody) {
  211.             $this->requestBody file_get_contents("php://input");
  212.         }
  213.     }
  214.     protected function setRequestExtras($config)
  215.     {
  216.         $this->requestExtras $config["requestExtras"] ?? null;
  217.     }
  218.     protected function setPerson($config)
  219.     {
  220.         $this->person $config['person'] ?? null;
  221.     }
  222.     protected function setPersonFunc($config)
  223.     {
  224.         $this->personFunc $config['person_fn'] ?? null;
  225.     }
  226.     protected function setServerRoot($config)
  227.     {
  228.         $fromConfig $config['serverRoot'] ?? null;
  229.         if (!isset($fromConfig)) {
  230.             $fromConfig $config['root'] ?? null;
  231.         }
  232.         $this->serverRoot self::$defaults->serverRoot($fromConfig);
  233.     }
  234.     protected function setServerBranch($config)
  235.     {
  236.         $fromConfig $config['serverBranch'] ?? null;
  237.         if (!isset($fromConfig)) {
  238.             $fromConfig $config['branch'] ?? null;
  239.         }
  240.             
  241.         $this->serverBranch self::$defaults->branch($fromConfig);
  242.         
  243.         if ($this->serverBranch === null) {
  244.             $autodetectBranch $config['autodetect_branch'] ?? self::$defaults->autodetectBranch();
  245.             
  246.             if ($autodetectBranch) {
  247.                 $allowExec $config['allow_exec'] ?? self::$defaults->allowExec();
  248.                     
  249.                 $this->serverBranch $this->detectGitBranch($allowExec);
  250.             }
  251.         }
  252.     }
  253.     protected function setServerCodeVersion($config)
  254.     {
  255.         $this->serverCodeVersion $config['serverCodeVersion'] ?? null;
  256.     }
  257.     protected function setServerExtras($config)
  258.     {
  259.         $this->serverExtras $config['serverExtras'] ?? null;
  260.     }
  261.     /**
  262.      * Stores the 'custom' key from the $config array. The 'custom' key should hold an array of key / value pairs to be
  263.      * sent to Rollbar with each request.
  264.      *
  265.      * @param array $config The configuration array.
  266.      *
  267.      * @return void
  268.      */
  269.     public function setCustom(array $config): void
  270.     {
  271.         $this->custom $config['custom'] ?? \Rollbar\Defaults::get()->custom();
  272.     }
  273.     
  274.     public function setCustomDataMethod($config)
  275.     {
  276.         $this->customDataMethod $config['custom_data_method'] ?? \Rollbar\Defaults::get()->customDataMethod();
  277.     }
  278.     protected function setFingerprint($config)
  279.     {
  280.         $this->fingerprint $config['fingerprint'] ?? null;
  281.     }
  282.     protected function setTitle($config)
  283.     {
  284.         $this->title $config['title'] ?? null;
  285.     }
  286.     protected function setNotifier($config)
  287.     {
  288.         $fromConfig $config['notifier'] ?? null;
  289.         $this->notifier self::$defaults->notifier($fromConfig);
  290.     }
  291.     protected function setBaseException($config)
  292.     {
  293.         $fromConfig $config['baseException'] ?? null;
  294.         $this->baseException self::$defaults->baseException($fromConfig);
  295.     }
  296.     protected function setIncludeCodeContext($config)
  297.     {
  298.         $fromConfig $config['include_error_code_context'] ?? null;
  299.         $this->includeCodeContext self::$defaults->includeCodeContext($fromConfig);
  300.     }
  301.     protected function setIncludeExcCodeContext($config)
  302.     {
  303.         $fromConfig =
  304.             $config['include_exception_code_context'] ?? null;
  305.         $this->includeExcCodeContext self::$defaults->includeExcCodeContext($fromConfig);
  306.     }
  307.     
  308.     protected function setUtilities($config)
  309.     {
  310.         $this->utilities $config['utilities'] ?? null;
  311.         if (!$this->utilities) {
  312.             throw new \InvalidArgumentException(
  313.                 'Missing dependency: Utilities not provided to the DataBuilder.'
  314.             );
  315.         }
  316.     }
  317.     protected function setHost($config)
  318.     {
  319.         $this->host $config['host'] ?? self::$defaults->host();
  320.     }
  321.     /**
  322.      * Creates the {@see Data} object from an exception or log message. This method respects the PSR-3 standard on
  323.      * handling exceptions in the context https://www.php-fig.org/psr/psr-3/#13-context.
  324.      *
  325.      * @param string                      $level   The severity log level for the item being logged.
  326.      * @param Throwable|string|Stringable $toLog   The exception or message to be logged.
  327.      * @param array                       $context Any additional context data.
  328.      *
  329.      * @return Data
  330.      */
  331.     public function makeData(string $levelThrowable|string|Stringable $toLog, array $context): Data
  332.     {
  333.         $env  $this->getEnvironment();
  334.         $body $this->getBody($toLog$context);
  335.         $data = new Data($env$body);
  336.         $data->setLevel($this->getLevel($level$toLog))
  337.             ->setTimestamp($this->getTimestamp())
  338.             ->setCodeVersion($this->getCodeVersion())
  339.             ->setPlatform($this->getPlatform())
  340.             ->setLanguage($this->getLanguage())
  341.             ->setFramework($this->getFramework())
  342.             ->setContext($this->getContext())
  343.             ->setRequest($this->getRequest())
  344.             ->setPerson($this->getPerson())
  345.             ->setServer($this->getServer())
  346.             ->setCustom($this->getCustomForPayload($toLog$context))
  347.             ->setFingerprint($this->getFingerprint($context))
  348.             ->setTitle($this->getTitle())
  349.             ->setUuid($this->getUuid())
  350.             ->setNotifier($this->getNotifier());
  351.         return $data;
  352.     }
  353.     public function getEnvironment()
  354.     {
  355.         return $this->environment;
  356.     }
  357.     protected function getBody(Throwable|string|Stringable $toLog, array $context): Body
  358.     {
  359.         $baseException $this->getBaseException();
  360.         // Get the exception from either the $message or $context['exception']. See
  361.         // https://www.php-fig.org/psr/psr-3/#13-context for a description of $context['exception'].
  362.         if (isset($context['exception']) && $context['exception'] instanceof $baseException) {
  363.             $message null;
  364.             if (!$toLog instanceof Throwable) {
  365.                 $message = (string) $toLog;
  366.             }
  367.             $content $this->getExceptionTrace($context['exception'], $message);
  368.         } elseif ($toLog instanceof ErrorWrapper) {
  369.             $content $this->getErrorTrace($toLog);
  370.         } elseif ($toLog instanceof $baseException) {
  371.             $content $this->getExceptionTrace($toLog);
  372.         } else {
  373.             $content $this->getMessage($toLog);
  374.         }
  375.         return new Body($content$context);
  376.     }
  377.     public function getErrorTrace(ErrorWrapper $error)
  378.     {
  379.         return $this->makeTrace($error$this->includeCodeContext$error->getClassName());
  380.     }
  381.     /**
  382.      * @param Throwable              $exc
  383.      * @param string|Stringable|null $message
  384.      *
  385.      * @return Trace|TraceChain
  386.      */
  387.     public function getExceptionTrace(Throwable $excstring|Stringable $message null): Trace|TraceChain
  388.     {
  389.         $chain   = array();
  390.         $chain[] = $this->makeTrace($exc$this->includeExcCodeContextmessage$message);
  391.         $previous $exc->getPrevious();
  392.         $baseException $this->getBaseException();
  393.         while ($previous instanceof $baseException) {
  394.             $chain[] = $this->makeTrace($previous$this->includeExcCodeContext);
  395.             if ($previous->getPrevious() === $previous) {
  396.                 break;
  397.             }
  398.             $previous $previous->getPrevious();
  399.         }
  400.         if (count($chain) > 1) {
  401.             return new TraceChain($chain);
  402.         }
  403.         return $chain[0];
  404.     }
  405.     /**
  406.      * @param Throwable              $exception
  407.      * @param bool                   $includeContext whether or not to include context
  408.      * @param string|null            $classOverride
  409.      * @param string|Stringable|null $message
  410.      *
  411.      * @return Trace
  412.      */
  413.     public function makeTrace(
  414.         Throwable $exception,
  415.         bool $includeContext,
  416.         ?string $classOverride null,
  417.         string|Stringable $message null,
  418.     ): Trace {
  419.         $frames = array();
  420.         if ($this->captureErrorStacktraces) {
  421.             $frames $this->makeFrames($exception$includeContext);
  422.         }
  423.         
  424.         $exceptionMessage $exception->getMessage();
  425.         $description $message;
  426.         // If a message is explicitly set when calling the logger, use that. Otherwise, use the exception's message.
  427.         if (null !== $message) {
  428.             $exceptionMessage $message;
  429.             $description $exception->getMessage();
  430.         }
  431.         return new Trace($frames, new ExceptionInfo(
  432.             $classOverride ?: get_class($exception),
  433.             $exceptionMessage,
  434.             $description,
  435.         ));
  436.     }
  437.     public function makeFrames($exception$includeContext)
  438.     {
  439.         $frames = array();
  440.         
  441.         foreach ($this->getTrace($exception) as $frameInfo) {
  442.             // filename and lineno may be missing in pathological cases, like
  443.             // register_shutdown_function(fn() => var_dump(debug_backtrace()));
  444.             $filename $frameInfo['file'] ?? null;
  445.             $lineno $frameInfo['line'] ?? null;
  446.             $method $frameInfo['function'] ?? null;
  447.             if (isset($frameInfo['class'])) {
  448.                 $method $frameInfo['class'] . "::" $method;
  449.             }
  450.             $args $frameInfo['args'] ?? null;
  451.             $frame = new Frame($filename);
  452.             $frame->setLineno($lineno)
  453.                 ->setMethod($method);
  454.                 
  455.             if ($this->localVarsDump && $args !== null) {
  456.                 $frame->setArgs($args);
  457.             }
  458.             if ($includeContext) {
  459.                 $this->addCodeContextToFrame($frame$filename$lineno);
  460.             }
  461.             $frames[] = $frame;
  462.         }
  463.         
  464.         $frames array_reverse($frames);
  465.         return $frames;
  466.     }
  467.     private function addCodeContextToFrame(Frame $frame$filename$line)
  468.     {
  469.         if (null === $filename || !file_exists($filename)) {
  470.             return;
  471.         }
  472.         $source $this->getSourceLines($filename);
  473.         $total count($source);
  474.         $line max($line 10);
  475.         $frame->setCode($source[$line]);
  476.         $offset 6;
  477.         $min max($line $offset0);
  478.         $pre null;
  479.         $post null;
  480.         if ($min !== $line) {
  481.             $pre array_slice($source$min$line $min);
  482.         }
  483.         $max min($line $offset$total);
  484.         if ($max !== $line) {
  485.             $post array_slice($source$line 1$max $line);
  486.         }
  487.         $frame->setContext(new Context($pre$post));
  488.     }
  489.     private function getTrace($exc)
  490.     {
  491.         if ($exc instanceof ErrorWrapper) {
  492.             return $exc->getBacktrace();
  493.         } else {
  494.             $trace $exc->getTrace();
  495.             
  496.             // Add the Exception's file and line as the last frame of the trace
  497.             array_unshift($trace, array('file' => $exc->getFile(), 'line' => $exc->getLine()));
  498.             
  499.             return $trace;
  500.         }
  501.     }
  502.     protected function getMessage($toLog)
  503.     {
  504.         return new Message(
  505.             (string)$toLog,
  506.             $this->sendMessageTrace ?
  507.             debug_backtrace($this->localVarsDump DEBUG_BACKTRACE_IGNORE_ARGS) :
  508.             null
  509.         );
  510.     }
  511.     protected function getLevel($level$toLog)
  512.     {
  513.         // resolve null level to default values, if we can
  514.         if ($level === null) {
  515.             if ($toLog instanceof ErrorWrapper) {
  516.                 $level $this->errorLevels[$toLog->errorLevel] ?? null;
  517.             } elseif ($toLog instanceof \Exception) {
  518.                 $level $this->exceptionLevel;
  519.             } else {
  520.                 $level $this->messageLevel;
  521.             }
  522.         }
  523.         if ($level !== null) {
  524.             $level strtolower($level);
  525.             $level $this->psrLevels[$level] ?? null;
  526.             if ($level !== null) {
  527.                 // this is a well-known PSR level: "error", "notice", "info", etc.
  528.                 return LevelFactory::fromName($level);
  529.             }
  530.         }
  531.         return null;
  532.     }
  533.     protected function getTimestamp()
  534.     {
  535.         return time();
  536.     }
  537.     protected function getCodeVersion()
  538.     {
  539.         return $this->codeVersion;
  540.     }
  541.     protected function getPlatform()
  542.     {
  543.         return $this->platform;
  544.     }
  545.     protected function getLanguage()
  546.     {
  547.         return "php";
  548.         // TODO: once the backend understands a more informative language value
  549.         // return "PHP " . phpversion();
  550.     }
  551.     protected function getFramework()
  552.     {
  553.         return $this->framework;
  554.     }
  555.     protected function getContext()
  556.     {
  557.         return $this->context;
  558.     }
  559.     /*
  560.      * @SuppressWarnings(PHPMD.Superglobals)
  561.      */
  562.     protected function getRequest()
  563.     {
  564.         $request = new Request();
  565.         $request->setUrl($this->getUrl())
  566.             ->setHeaders($this->getHeaders())
  567.             ->setParams($this->getRequestParams())
  568.             ->setBody($this->getRequestBody())
  569.             ->setUserIp($this->getUserIp());
  570.       
  571.         if (isset($_SERVER)) {
  572.             $request->setMethod($_SERVER['REQUEST_METHOD'] ?? null)
  573.                 ->setQueryString($_SERVER['QUERY_STRING'] ?? null);
  574.         }
  575.       
  576.         if (isset($_GET)) {
  577.             $request->setGet($_GET);
  578.         }
  579.         if (isset($_POST)) {
  580.             $request->setPost($_POST);
  581.         }
  582.         
  583.         if ($request->getMethod() === 'PUT') {
  584.             $postData = array();
  585.             $body $request->getBody();
  586.             if ($body !== null) {
  587.                 // PHP reports warning if parse_str() detects more than max_input_vars items.
  588.                 @parse_str($body$postData);
  589.             }
  590.             $request->setPost($postData);
  591.         }
  592.         
  593.         $extras $this->getRequestExtras();
  594.         if (!$extras) {
  595.             $extras = array();
  596.         }
  597.         $request->setExtras($extras);
  598.         
  599.         if (isset($_SESSION) && is_array($_SESSION) && count($_SESSION) > 0) {
  600.             $request->setSession($_SESSION);
  601.         }
  602.         return $request;
  603.     }
  604.     
  605.     public function parseForwardedString($forwarded)
  606.     {
  607.         $result = array();
  608.         
  609.         // Remove Forwarded   = 1#forwarded-element header prefix
  610.         $parts trim(str_replace('Forwarded:'''$forwarded));
  611.         
  612.         /**
  613.          * Break up the forwarded-element =
  614.          *  [ forwarded-pair ] *( ";" [ forwarded-pair ] )
  615.          */
  616.         $parts explode(';'$parts);
  617.         
  618.         /**
  619.          * Parse forwarded pairs
  620.          */
  621.         foreach ($parts as $forwardedPair) {
  622.             $forwardedPair trim($forwardedPair);
  623.             
  624.             
  625.             if (stripos($forwardedPair'host=') !== false) {
  626.                 // Parse 'host' forwarded pair
  627.                 $result['host'] = substr($forwardedPairstrlen('host='));
  628.             } elseif (stripos($forwardedPair'proto=') !== false) {
  629.                 // Parse 'proto' forwarded pair
  630.                 $result['proto'] = substr($forwardedPairstrlen('proto='));
  631.             } else {
  632.                 // Parse 'for' and 'by' forwarded pairs which are comma separated
  633.                 $fpParts explode(','$forwardedPair);
  634.                 foreach ($fpParts as $fpPart) {
  635.                     $fpPart trim($fpPart);
  636.                     
  637.                     if (stripos($fpPart'for=') !== false) {
  638.                         // Parse 'for' forwarded pair
  639.                         $result['for'] = $result['for'] ?? array();
  640.                         $result['for'][] = substr($fpPartstrlen('for='));
  641.                     } elseif (stripos($fpPart'by=') !== false) {
  642.                         // Parse 'by' forwarded pair
  643.                         $result['by'] = $result['by'] ?? array();
  644.                         $result['by'][] = substr($fpPartstrlen('by='));
  645.                     }
  646.                 }
  647.             }
  648.         }
  649.         
  650.         return $result;
  651.     }
  652.     
  653.     /*
  654.      * @SuppressWarnings(PHPMD.Superglobals)
  655.      */
  656.     public function getUrlProto()
  657.     {
  658.         $proto '';
  659.         
  660.         if (!empty($_SERVER['HTTP_FORWARDED'])) {
  661.             extract($this->parseForwardedString($_SERVER['HTTP_FORWARDED']));
  662.         }
  663.         
  664.         if (empty($proto)) {
  665.             if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
  666.                 $proto explode(','strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']));
  667.                 $proto $proto[0];
  668.             } elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
  669.                 $proto 'https';
  670.             } else {
  671.                 $proto 'http';
  672.             }
  673.         }
  674.         
  675.         return $proto;
  676.     }
  677.     
  678.     /*
  679.      * @SuppressWarnings(PHPMD.Superglobals)
  680.      */
  681.     public function getUrlHost()
  682.     {
  683.         $host '';
  684.         
  685.         if (!empty($_SERVER['HTTP_FORWARDED'])) {
  686.             extract($this->parseForwardedString($_SERVER['HTTP_FORWARDED']));
  687.         }
  688.         
  689.         if (empty($host)) {
  690.             if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
  691.                 $host $_SERVER['HTTP_X_FORWARDED_HOST'];
  692.             } elseif (!empty($_SERVER['HTTP_HOST'])) {
  693.                 $parts explode(':'$_SERVER['HTTP_HOST']);
  694.                 $host $parts[0];
  695.             } elseif (!empty($_SERVER['SERVER_NAME'])) {
  696.                 $host $_SERVER['SERVER_NAME'];
  697.             } else {
  698.                 $host 'unknown';
  699.             }
  700.         }
  701.         
  702.         return $host;
  703.     }
  704.     
  705.     /*
  706.      * @SuppressWarnings(PHPMD.Superglobals)
  707.      */
  708.     public function getUrlPort($proto)
  709.     {
  710.         $port '';
  711.         
  712.         if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
  713.             $port $_SERVER['HTTP_X_FORWARDED_PORT'];
  714.         } elseif (!empty($_SERVER['SERVER_PORT'])) {
  715.             $port $_SERVER['SERVER_PORT'];
  716.         } elseif ($proto === 'https') {
  717.             $port 443;
  718.         } else {
  719.             $port 80;
  720.         }
  721.         
  722.         return $port;
  723.     }
  724.     /*
  725.      * @SuppressWarnings(PHPMD.Superglobals)
  726.      */
  727.     public function getUrl()
  728.     {
  729.         $proto $this->getUrlProto();
  730.         $host $this->getUrlHost();
  731.         $port $this->getUrlPort($proto);
  732.         
  733.         $url $proto '://' $host;
  734.         if (($proto == 'https' && $port != 443) || ($proto == 'http' && $port != 80)) {
  735.             $url .= ':' $port;
  736.         }
  737.         if (isset($_SERVER)) {
  738.             $path $_SERVER['REQUEST_URI'] ?? '/';
  739.             $url .= $path;
  740.         }
  741.         if ($host == 'unknown') {
  742.             $url null;
  743.         }
  744.         return $url;
  745.     }
  746.     /*
  747.      * @SuppressWarnings(PHPMD.Superglobals)
  748.      */
  749.     public function getHeaders()
  750.     {
  751.         $headers = array();
  752.         if (isset($_SERVER)) {
  753.             foreach ($_SERVER as $key => $val) {
  754.                 if (substr($key05) == 'HTTP_') {
  755.                     // convert HTTP_CONTENT_TYPE to Content-Type, HTTP_HOST to Host, etc.
  756.                     $name strtolower(substr($key5));
  757.                     $name str_replace(' ''-'ucwords(str_replace('_'' '$name)));
  758.                     $headers[$name] = $val;
  759.                 }
  760.             }
  761.         }
  762.         if (count($headers) > 0) {
  763.             return $headers;
  764.         } else {
  765.             return null;
  766.         }
  767.     }
  768.     
  769.     protected function getRequestParams()
  770.     {
  771.         return $this->requestParams;
  772.     }
  773.     protected function getRequestBody()
  774.     {
  775.         return $this->requestBody;
  776.     }
  777.     /**
  778.      * Get the user's IP, by inspecting the http header X-Real-IP, or if not
  779.      * that first address from the http header X-Forwarded-For, and if not that
  780.      * then the remote IP connecting to the web server, if available.
  781.      *
  782.      * @SuppressWarnings(PHPMD.Superglobals)
  783.      */
  784.     protected function getUserIp(): ?string
  785.     {
  786.         if (!isset($_SERVER) || $this->captureIP === false) {
  787.             return null;
  788.         }
  789.         
  790.         $ipAddress $_SERVER['REMOTE_ADDR'] ?? null;
  791.         
  792.         $forwardFor $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
  793.         if ($forwardFor) {
  794.             // return everything until the first comma
  795.             $parts explode(','$forwardFor);
  796.             $ipAddress $parts[0];
  797.         }
  798.         $realIp $_SERVER['HTTP_X_REAL_IP'] ?? null;
  799.         if ($realIp) {
  800.             $ipAddress $realIp;
  801.         }
  802.         if ($ipAddress === null) {
  803.             return null;
  804.         }
  805.         
  806.         if ($this->captureIP === DataBuilder::ANONYMIZE_IP) {
  807.             if (filter_var($ipAddressFILTER_VALIDATE_IPFILTER_FLAG_IPV4)) {
  808.                 $parts explode('.'$ipAddress);
  809.                 $ipAddress $parts[0] . '.' $parts[1] . '.' $parts[2] . '.0';
  810.             } elseif (filter_var($ipAddressFILTER_VALIDATE_IPFILTER_FLAG_IPV6)) {
  811.                 $parts explode(':'$ipAddress);
  812.                 $ipAddress =
  813.                     $parts[0] . ':' .
  814.                     $parts[1] . ':' .
  815.                     $parts[2] . ':' .
  816.                     '0000:0000:0000:0000:0000';
  817.             }
  818.         }
  819.         
  820.         return $ipAddress;
  821.     }
  822.     protected function getRequestExtras()
  823.     {
  824.         return $this->requestExtras;
  825.     }
  826.     /**
  827.      * @return Person
  828.      */
  829.     protected function getPerson()
  830.     {
  831.         $personData $this->person;
  832.         if (!isset($personData) && is_callable($this->personFunc)) {
  833.             try {
  834.                 $personData = ($this->personFunc)();
  835.             } catch (\Exception $exception) {
  836.                 Rollbar::scope(array('person_fn' => null))->
  837.                     log(Level::ERROR$exception);
  838.             }
  839.         }
  840.         if (!isset($personData['id'])) {
  841.             return null;
  842.         }
  843.         $identifier = (string)$personData['id'];
  844.         $email null;
  845.         if ($this->captureEmail && isset($personData['email'])) {
  846.             $email $personData['email'];
  847.         }
  848.         $username null;
  849.         if ($this->captureUsername && isset($personData['username'])) {
  850.             $username $personData['username'];
  851.         }
  852.         unset($personData['id'], $personData['email'], $personData['username']);
  853.         return new Person($identifier$username$email$personData);
  854.     }
  855.     /*
  856.      * @SuppressWarnings(PHPMD.Superglobals)
  857.      */
  858.     protected function getServer()
  859.     {
  860.         $server = new Server();
  861.         $server->setHost($this->getHost())
  862.             ->setRoot($this->getServerRoot())
  863.             ->setBranch($this->getServerBranch())
  864.             ->setCodeVersion($this->getServerCodeVersion());
  865.         $extras $this->getServerExtras();
  866.         if (!$extras) {
  867.             $extras = array();
  868.         }
  869.         $server->setExtras($extras);
  870.         if (isset($_SERVER) && array_key_exists('argv'$_SERVER)) {
  871.             $server->setArgv($_SERVER['argv']);
  872.         }
  873.         return $server;
  874.     }
  875.     protected function getHost()
  876.     {
  877.         return $this->host ?? gethostname();
  878.     }
  879.     protected function getServerRoot()
  880.     {
  881.         return $this->serverRoot;
  882.     }
  883.     protected function getServerBranch()
  884.     {
  885.         return $this->serverBranch;
  886.     }
  887.     protected function getServerCodeVersion()
  888.     {
  889.         return $this->serverCodeVersion;
  890.     }
  891.     protected function getServerExtras()
  892.     {
  893.         return $this->serverExtras;
  894.     }
  895.     /**
  896.      * Returns the array of key / value pairs that will be sent with the payload to Rollbar.
  897.      *
  898.      * @return array|null
  899.      */
  900.     public function getCustom(): ?array
  901.     {
  902.         return $this->custom;
  903.     }
  904.     
  905.     public function getCustomDataMethod()
  906.     {
  907.         return $this->customDataMethod;
  908.     }
  909.     /**
  910.      * Returns the processed custom value to be sent with the payload. If there
  911.      * is no custom value an empty array is returned.
  912.      *
  913.      * @param Throwable|string $toLog
  914.      * @param array            $context
  915.      *
  916.      * @return array
  917.      */
  918.     protected function getCustomForPayload(Throwable|string $toLog, array $context): array
  919.     {
  920.         $custom $this->resolveCustomContent($this->getCustom());
  921.         if ($customDataMethod $this->getCustomDataMethod()) {
  922.             $customDataMethodContext $context['custom_data_method_context'] ?? null;
  923.             $customDataMethodResult $customDataMethod($toLog$customDataMethodContext);
  924.             $custom array_merge($custom$customDataMethodResult);
  925.         }
  926.         unset($context['custom_data_method_context']);
  927.         return $custom;
  928.     }
  929.     /**
  930.      * This method transforms $custom into an array that can be processed and sent in the payload.
  931.      *
  932.      * @param mixed $custom
  933.      *
  934.      * @return array
  935.      * @throws \Exception If Serializable::serialize() returns an invalid type an exception can be thrown.
  936.      *                    This can be removed once support for Serializable has been removed.
  937.      */
  938.     protected function resolveCustomContent(mixed $custom): array
  939.     {
  940.         // This check is placed first because it should return a string|null, and we want to return an array.
  941.         if ($custom instanceof \Serializable) {
  942.             // We don't return this value instead we run it through the rest of the checks. The same is true for the
  943.             // next check.
  944.             if (method_exists($custom'__serialize')) {
  945.                 $custom $custom->__serialize();
  946.             } else {
  947.                 trigger_error("Using the Serializable interface has been deprecated."E_USER_DEPRECATED);
  948.                 $custom $custom->serialize();
  949.             }
  950.         } else {
  951.             if ($custom instanceof SerializerInterface) {
  952.                 $custom $custom->serialize();
  953.             }
  954.         }
  955.         // Values that resolve to false e.g. null, false, 0, 0.0, "", and [], we will ignore.
  956.         // Otherwise, after all other checks we will assign it to the "message" key.
  957.         if (!$custom) {
  958.             return [];
  959.         }
  960.         if (is_object($custom)) {
  961.             trigger_error(
  962.                 "Using an object that does not implement the "
  963.                 "Rollbar\SerializerInterface interface has been deprecated.",
  964.                 E_USER_DEPRECATED
  965.             );
  966.             return get_object_vars($custom);
  967.         }
  968.         if (is_array($custom)) {
  969.             return $custom;
  970.         }
  971.         return ['message' => $custom];
  972.     }
  973.     /**
  974.      * Adds a new key / value pair that will be sent with the payload to Rollbar. If the key already exists in the
  975.      * custom data array the existing value will be overwritten.
  976.      *
  977.      * @param string $key  The key to store this value in the custom array.
  978.      * @param mixed  $data The value that is going to be stored. Must be a primitive or JSON serializable.
  979.      *
  980.      * @return void
  981.      */
  982.     public function addCustom(string $keymixed $data): void
  983.     {
  984.         if (!is_array($this->custom)) {
  985.             $this->custom = array();
  986.         }
  987.         
  988.         $this->custom[$key] = $data;
  989.     }
  990.     /**
  991.      * Removes a key from the custom data array that is sent with the payload to Rollbar.
  992.      *
  993.      * @param string $key The key to remove.
  994.      *
  995.      * @return void
  996.      */
  997.     public function removeCustom(string $key): void
  998.     {
  999.         unset($this->custom[$key]);
  1000.     }
  1001.     protected function getFingerprint($context)
  1002.     {
  1003.         return $context['fingerprint'] ?? $this->fingerprint;
  1004.     }
  1005.     protected function getTitle()
  1006.     {
  1007.         return $this->title;
  1008.     }
  1009.     protected function getUuid()
  1010.     {
  1011.         return $this->utilities->uuid4();
  1012.     }
  1013.     protected function getNotifier()
  1014.     {
  1015.         return $this->notifier;
  1016.     }
  1017.     protected function getBaseException()
  1018.     {
  1019.         return $this->baseException;
  1020.     }
  1021.     /**
  1022.      * Parses an array of code lines from source file with given filename.
  1023.      *
  1024.      * Attempts to automatically detect the line break character used in the file.
  1025.      *
  1026.      * @param string $filename
  1027.      * @return string[] An array of lines of code from the given source file.
  1028.      */
  1029.     private function getSourceLines($filename)
  1030.     {
  1031.         $rawSource file_get_contents($filename);
  1032.         $source explode(PHP_EOL$rawSource);
  1033.         if (count($source) === 1) {
  1034.             if (substr_count($rawSource"\n") > substr_count($rawSource"\r")) {
  1035.                 $source explode("\n"$rawSource);
  1036.             } else {
  1037.                 $source explode("\r"$rawSource);
  1038.             }
  1039.         }
  1040.         $source str_replace(array("\n""\t""\r"), ''$source);
  1041.         return $source;
  1042.     }
  1043.     /**
  1044.      * Wrap a PHP error in the {@see ErrorWrapper} class and add stacktrace information.
  1045.      *
  1046.      * @param int         $errno   The level of the error raised.
  1047.      * @param string      $errstr  The error message.
  1048.      * @param string|null $errfile The filename that the error was raised in.
  1049.      * @param int|null    $errline The line number where the error was raised.
  1050.      *
  1051.      * @return ErrorWrapper
  1052.      */
  1053.     public function generateErrorWrapper(int $errnostring $errstr, ?string $errfile, ?int $errline): ErrorWrapper
  1054.     {
  1055.         return new ErrorWrapper(
  1056.             $errno,
  1057.             $errstr,
  1058.             $errfile,
  1059.             $errline,
  1060.             $this->buildErrorTrace($errfile$errline),
  1061.             $this->utilities
  1062.         );
  1063.     }
  1064.     
  1065.     /**
  1066.      * Fetches the stack trace for fatal and regular errors.
  1067.      *
  1068.      * @param string|null $errfile The filename that the error was raised in.
  1069.      * @param int|null    $errline The line number where the error was raised.
  1070.      *
  1071.      * @return array
  1072.      */
  1073.     protected function buildErrorTrace(?string $errfile, ?int $errline): array
  1074.     {
  1075.         if ($this->captureErrorStacktraces) {
  1076.             $backTrace $this->fetchErrorTrace();
  1077.             
  1078.             $backTrace $this->stripShutdownFrames($backTrace);
  1079.             
  1080.             // Add the final frame
  1081.             array_unshift(
  1082.                 $backTrace,
  1083.                 array('file' => $errfile'line' => $errline)
  1084.             );
  1085.         } else {
  1086.             $backTrace = array();
  1087.         }
  1088.         
  1089.         return $backTrace;
  1090.     }
  1091.     /**
  1092.      * Check if this PHP install has the `xdebug_get_function_stack` function.
  1093.      *
  1094.      * @return bool
  1095.      */
  1096.     private function hasXdebugGetFunctionStack()
  1097.     {
  1098.         // TBD: allow the consumer to disable use of Xdebug even if it's
  1099.         // TBD: installed in the consumer's runtime environment?
  1100.         // if the function doesn't exist, we're obviously unable to use it
  1101.         if (! function_exists('xdebug_get_function_stack')) {
  1102.             return false;
  1103.         }
  1104.         // if the function's not provided by Xdebug, we can't guarantee an
  1105.         // API conformance so we refuse to use it
  1106.         $version phpversion('xdebug');
  1107.         if (false === $version) {
  1108.             return false;
  1109.         }
  1110.         // in XDebug 2 and prior, existence of the function implied usability
  1111.         if (version_compare($version'3.0.0''<')) {
  1112.             return true;
  1113.         }
  1114.         // in XDebug 3 and later, the function is defined but disabled unless
  1115.         // the xdebug mode parameter includes it
  1116.         return !str_contains(ini_get('xdebug.mode'), 'develop') ? false true;
  1117.     }
  1118.     
  1119.     private function fetchErrorTrace()
  1120.     {
  1121.         if ($this->hasXdebugGetFunctionStack()) {
  1122.             return array_reverse(\xdebug_get_function_stack());
  1123.         } else {
  1124.             return debug_backtrace($this->localVarsDump DEBUG_BACKTRACE_IGNORE_ARGS);
  1125.         }
  1126.     }
  1127.     
  1128.     private function stripShutdownFrames($backTrace)
  1129.     {
  1130.         foreach ($backTrace as $index => $frame) {
  1131.             extract($frame);
  1132.             
  1133.             $fatalHandlerMethod = (isset($method)
  1134.                                     && $method === 'Rollbar\\Handlers\\FatalHandler::handle');
  1135.                                     
  1136.             $fatalHandlerClassAndFunction = (isset($class)
  1137.                                                 && $class === 'Rollbar\\Handlers\\FatalHandler'
  1138.                                                 && isset($function)
  1139.                                                 && $function === 'handle');
  1140.             
  1141.             $errorHandlerMethod = (isset($method)
  1142.                                     && $method === 'Rollbar\\Handlers\\ErrorHandler::handle');
  1143.                                     
  1144.             $errorHandlerClassAndFunction = (isset($class)
  1145.                                                 && $class === 'Rollbar\\Handlers\\ErrorHandler'
  1146.                                                 && isset($function)
  1147.                                                 && $function === 'handle');
  1148.             
  1149.             if ($fatalHandlerMethod ||
  1150.                  $fatalHandlerClassAndFunction ||
  1151.                  $errorHandlerMethod ||
  1152.                  $errorHandlerClassAndFunction) {
  1153.                 return array_slice($backTrace$index+1);
  1154.             }
  1155.         }
  1156.         
  1157.         return $backTrace;
  1158.     }
  1159.     
  1160.     public function detectGitBranch($allowExec true): ?string
  1161.     {
  1162.         if ($allowExec) {
  1163.             static $cachedValue;
  1164.             static $hasExecuted false;
  1165.             if (!$hasExecuted) {
  1166.                 $cachedValue self::getGitBranch();
  1167.                 $hasExecuted true;
  1168.             }
  1169.             return $cachedValue;
  1170.         }
  1171.         return null;
  1172.     }
  1173.     
  1174.     private static function getGitBranch(): ?string
  1175.     {
  1176.         if (function_exists('shell_exec')) {
  1177.             $stdRedirCmd Utilities::isWindows() ? ' > NUL' ' 2> /dev/null';
  1178.             $output shell_exec('git rev-parse --abbrev-ref HEAD' $stdRedirCmd);
  1179.             if (is_string($output)) {
  1180.                 return rtrim($output);
  1181.             }
  1182.         }
  1183.         return null;
  1184.     }
  1185. }