From 33885d4e8faabe0d9fdec788a9ca5040a17e9df8 Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Wed, 17 Jan 2024 14:45:45 -0600 Subject: [PATCH] GuzzleMiddleware issue with `Guzzle\Promise\Create::rejectionFor()` Additional file-size & complexity reduction HttpMessage/Utility/Uri::parseUrl now accepts a UriInterface (why not) Teams - can now pass a UriInterface as $url param --- phpcs.xml.dist | 2 + src/Debug/Collector/GuzzleMiddleware.php | 6 +- src/Debug/Framework/Yii1_1/UserInfo.php | 7 +- src/Debug/Route/AbstractErrorRoute.php | 122 ++++++++++ src/Debug/Route/Discord.php | 62 ++--- src/Debug/Route/ErrorThrottleTrait.php | 60 ----- src/Debug/Route/Html.php | 45 ++-- src/Debug/Route/Slack.php | 60 +---- src/Debug/Route/Teams.php | 73 ++---- src/Debug/Utility/Html.php | 287 ++--------------------- src/Debug/Utility/HtmlBuild.php | 185 +++++++++++++++ src/Debug/Utility/HtmlParse.php | 120 ++++++++++ src/Debug/Utility/PhpDoc/Parsers.php | 165 ++++++++----- src/HttpMessage/Utility/Uri.php | 20 +- src/Slack/AbstractBlockFactory.php | 4 +- src/Slack/BlockElementsFactory.php | 53 +++-- src/Teams/Actions/AbstractAction.php | 5 +- src/Teams/Actions/OpenUrl.php | 9 +- src/Teams/CardUtilityTrait.php | 71 ++---- src/Teams/Cards/AdaptiveCard.php | 5 +- src/Teams/Cards/HeroCard.php | 5 +- src/Teams/Cards/MessageCard.php | 15 +- src/Teams/Elements/CommonTrait.php | 6 +- src/Teams/Elements/Image.php | 9 +- src/Teams/Elements/Media.php | 5 +- src/Teams/Elements/MediaSource.php | 11 +- src/Teams/Section.php | 19 +- tests/Debug/Plugin/Method/TableTest.php | 1 + tests/Debug/Route/DiscordTest.php | 1 + tests/Debug/Route/SlackTest.php | 1 + tests/Debug/Route/TeamsTest.php | 1 + tests/Debug/Utility/HtmlTest.php | 2 + tests/HttpMessage/Utility/UriTest.php | 20 ++ 33 files changed, 789 insertions(+), 668 deletions(-) create mode 100644 src/Debug/Route/AbstractErrorRoute.php delete mode 100644 src/Debug/Route/ErrorThrottleTrait.php create mode 100644 src/Debug/Utility/HtmlBuild.php create mode 100644 src/Debug/Utility/HtmlParse.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index d7d2f675..5878484c 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -52,6 +52,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/src/Debug/Collector/GuzzleMiddleware.php b/src/Debug/Collector/GuzzleMiddleware.php index e8e9f7d3..c12273d0 100644 --- a/src/Debug/Collector/GuzzleMiddleware.php +++ b/src/Debug/Collector/GuzzleMiddleware.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -96,7 +96,9 @@ public function onRejected(GuzzleException $reason, array $requestInfo) } $this->logResponse($response, $requestInfo, $reason); $this->debug->groupEnd($meta); - return Promise\Create::rejectionFor($reason); + return \class_exists('GuzzleHttp\\Promise\\Create') + ? Promise\Create::rejectionFor($reason) + : Promise\rejection_for($reason); } /** diff --git a/src/Debug/Framework/Yii1_1/UserInfo.php b/src/Debug/Framework/Yii1_1/UserInfo.php index d3c6c30f..7caa14f4 100644 --- a/src/Debug/Framework/Yii1_1/UserInfo.php +++ b/src/Debug/Framework/Yii1_1/UserInfo.php @@ -18,7 +18,6 @@ use CModel; use CWebApplication; use Exception; -use IWebUser; use Yii; /** @@ -69,12 +68,12 @@ public function log() /** * Log user attributes * - * @param IWebUser $user User instance - * @param Debug $debug Debug instance + * @param CApplicationComponent $user User instance (web or console) + * @param Debug $debug Debug instance * * @return void */ - private function logIdentityData(IWebUser $user, Debug $debug) + private function logIdentityData(CApplicationComponent $user, Debug $debug) { $identityData = $user->model->attributes; if ($user->model instanceof CModel) { diff --git a/src/Debug/Route/AbstractErrorRoute.php b/src/Debug/Route/AbstractErrorRoute.php new file mode 100644 index 00000000..fdb4139b --- /dev/null +++ b/src/Debug/Route/AbstractErrorRoute.php @@ -0,0 +1,122 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Route; + +use bdk\Debug; +use bdk\ErrorHandler; +use bdk\ErrorHandler\Error; + +/** + * common "shouldSend" method + */ +abstract class AbstractErrorRoute extends AbstractRoute +{ + protected $statsKey = ''; + + /** + * Constructor + * + * @param Debug $debug debug instance + */ + public function __construct(Debug $debug) + { + parent::__construct($debug); + $this->cfg = \array_merge($this->cfg, array( + 'errorMask' => E_ERROR | E_PARSE | E_COMPILE_ERROR | E_WARNING | E_USER_ERROR, + )); + $debug->errorHandler->setCfg('enableStats', true); + } + + /** + * {@inheritDoc} + */ + public function getSubscriptions() + { + return array( + ErrorHandler::EVENT_ERROR => array('onError', -1), + ); + } + + /** + * ErrorHandler::EVENT_ERROR event subscriber + * + * @param Error $error error/event object + * + * @return void + */ + public function onError(Error $error) + { + if ($this->shouldSend($error, $this->statsKey) === false) { + return; + } + $messages = $this->buildMessages($error); + $this->sendMessages($messages); + } + + /** + * Build messages to send to client + * + * @param Error $error Error instance + * + * @return array + */ + abstract protected function buildMessages(Error $error); + + /** + * Send messages to client (ie Discord, Slack, or Teams) + * + * @param array $messages array of message(s) to send to client + * + * @return void + */ + abstract protected function sendMessages(array $messages); + + /** + * Should we send a notification for this error? + * + * @param Error $error Error instance + * @param string $statsKey name under which we store stats + * + * @return bool + */ + private function shouldSend(Error $error, $statsKey) + { + if ($error['throw']) { + // subscriber that set throw *should have* stopped error propagation + return false; + } + if (($error['type'] & $this->cfg['errorMask']) !== $error['type']) { + return false; + } + if ($error['isFirstOccur'] === false) { + return false; + } + if ($error['inConsole']) { + return false; + } + $error['stats'] = \array_merge(array( + $statsKey => array( + 'countSince' => 0, + 'timestamp' => null, + ), + ), $error['stats'] ?: array()); + $tsCutoff = \time() - $this->cfg['throttleMin'] * 60; + if ($error['stats'][$statsKey]['timestamp'] > $tsCutoff) { + // This error was recently sent + $error['stats'][$statsKey]['countSince']++; + return false; + } + $error['stats'][$statsKey]['timestamp'] = \time(); + return true; + } +} diff --git a/src/Debug/Route/Discord.php b/src/Debug/Route/Discord.php index 290399a2..97635b64 100644 --- a/src/Debug/Route/Discord.php +++ b/src/Debug/Route/Discord.php @@ -14,7 +14,6 @@ use bdk\CurlHttpMessage\Client as CurlHttpMessageClient; use bdk\Debug; -use bdk\ErrorHandler; use bdk\ErrorHandler\Error; use RuntimeException; @@ -25,10 +24,8 @@ * * @see https://discord.com/developers/docs/resources/webhook#execute-webhook */ -class Discord extends AbstractRoute +class Discord extends AbstractErrorRoute { - use ErrorThrottleTrait; - protected $cfg = array( 'errorMask' => 0, 'onClientInit' => null, @@ -39,45 +36,17 @@ class Discord extends AbstractRoute /** @var CurlHttpMessageClient */ protected $client; + protected $statsKey = 'discord'; + /** - * Constructor - * - * @param Debug $debug debug instance + * {@inheritDoc} */ public function __construct(Debug $debug) { parent::__construct($debug); $this->cfg = \array_merge($this->cfg, array( - 'errorMask' => E_ERROR | E_PARSE | E_COMPILE_ERROR | E_WARNING | E_USER_ERROR, 'webhookUrl' => \getenv('DISCORD_WEBHOOK_URL'), )); - $debug->errorHandler->setCfg('enableStats', true); - } - - /** - * {@inheritDoc} - */ - public function getSubscriptions() - { - return array( - ErrorHandler::EVENT_ERROR => array('onError', -1), - ); - } - - /** - * ErrorHandler::EVENT_ERROR event subscriber - * - * @param Error $error error/event object - * - * @return void - */ - public function onError(Error $error) - { - if ($this->shouldSend($error, 'discord') === false) { - return; - } - $message = $this->buildMessage($error); - $this->sendMessage($message); } /** @@ -101,23 +70,20 @@ private function assertCfg() } /** - * Build Discord error message(s) - * - * @param Error $error Error instance - * - * @return array + * {@inheritDoc} */ - private function buildMessage(Error $error) + protected function buildMessages(Error $error) { $emoji = $error->isFatal() ? ':no_entry:' : ':warning:'; - return array( + $message = array( 'content' => $emoji . ' **' . $error['typeStr'] . '**' . "\n" . $this->getRequestMethodUri() . "\n" . $error->getMessageText() . "\n" . $error['fileAndLine'], ); + return array($message); } /** @@ -139,7 +105,17 @@ protected function getClient() } /** - * Send message to Discord + * {@inheritDoc} + */ + protected function sendMessages(array $messages) + { + foreach ($messages as $message) { + $this->sendMessage($message); + } + } + + /** + * Send message * * @param array $message Discord message * diff --git a/src/Debug/Route/ErrorThrottleTrait.php b/src/Debug/Route/ErrorThrottleTrait.php deleted file mode 100644 index f1f104f9..00000000 --- a/src/Debug/Route/ErrorThrottleTrait.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent - * @version v3.1 - */ - -namespace bdk\Debug\Route; - -use bdk\ErrorHandler\Error; - -/** - * common "shouldSend" method - */ -trait ErrorThrottleTrait -{ - /** - * Should we send a notification for this error? - * - * @param Error $error Error instance - * @param string $statsKey name under which we store stats - * - * @return bool - */ - private function shouldSend(Error $error, $statsKey) - { - if ($error['throw']) { - // subscriber that set throw *should have* stopped error propagation - return false; - } - if (($error['type'] & $this->cfg['errorMask']) !== $error['type']) { - return false; - } - if ($error['isFirstOccur'] === false) { - return false; - } - if ($error['inConsole']) { - return false; - } - $error['stats'] = \array_merge(array( - $statsKey => array( - 'countSince' => 0, - 'timestamp' => null, - ), - ), $error['stats'] ?: array()); - $tsCutoff = \time() - $this->cfg['throttleMin'] * 60; - if ($error['stats'][$statsKey]['timestamp'] > $tsCutoff) { - // This error was recently sent - $error['stats'][$statsKey]['countSince']++; - return false; - } - $error['stats'][$statsKey]['timestamp'] = \time(); - return true; - } -} diff --git a/src/Debug/Route/Html.php b/src/Debug/Route/Html.php index 84955b59..dcd94584 100644 --- a/src/Debug/Route/Html.php +++ b/src/Debug/Route/Html.php @@ -235,15 +235,14 @@ private function buildOutput() 'linkFilesTemplateDefault' => $lftDefault ?: null, 'tooltip' => $this->cfg['tooltip'], ), - )) . ">\n"; - $str .= $this->buildStyleTag(); - $str .= $this->buildScriptTag(); - $str .= $this->buildHeader(); - if ($this->cfg['outputScript']) { - $str .= '
Loading
' . "\n"; - } - $str .= $this->tabs->buildTabPanes(); - $str .= '' . "\n"; // close .debug + )) . ">\n" + . $this->buildStyleTag() + . $this->buildScriptTag() + . $this->buildHeader() + . $this->buildLoading() + . $this->tabs->buildTabPanes() + . '' . "\n"; // close .debug + $str = \preg_replace('#(]*>)\s+#', '$1', $str); // ugly, but want to be able to use :empty $str = \strtr($str, array( '{{channels}}' => \htmlspecialchars(\json_encode($this->buildChannelTree(), JSON_FORCE_OBJECT)), @@ -262,17 +261,17 @@ protected function buildChannelTree() $channelRoot = \reset($channels)->rootInstance; \ksort($channels, SORT_NATURAL | SORT_FLAG_CASE); $tree = array(); - foreach ($channels as $name => $channel) { + \array_walk($channels, static function (Debug $channel, $name) use ($channels, $channelRoot, &$tree) { $ref = &$tree; $path = \explode('.', $name); foreach ($path as $i => $k) { + // output may have only output general.foo + // we still need general + $pathFq = \implode('.', \array_slice($path, 0, $i + 1)); + $channel = isset($channels[$pathFq]) + ? $channels[$pathFq] + : $channelRoot->getChannel($pathFq); if (!isset($ref[$k])) { - // output may have only output general.foo - // we still need general - $pathFq = \implode('.', \array_slice($path, 0, $i + 1)); - $channel = isset($channels[$pathFq]) - ? $channels[$pathFq] - : $channelRoot->getChannel($pathFq); $ref[$k] = array( 'channels' => array(), 'options' => array( @@ -283,7 +282,7 @@ protected function buildChannelTree() } $ref = &$ref[$k]['channels']; } - } + }); return $tree; } @@ -302,6 +301,18 @@ private function buildHeader() . '' . "\n"; } + /** + * Build "loading" spinner + * + * @return string + */ + private function buildLoading() + { + return $this->cfg['outputScript'] + ? '
Loading
' . "\n" + : ''; + } + /** * Build