friendship ended with social-app. php is my new best friend

HAHA YESSSS... YESSSSSSSS...

+2 -1
composer.json
···
"league/commonmark": "^2.7",
"chillerlan/php-oauth": "^1.0",
"fusonic/opengraph": "^3.0",
-
"tracy/tracy": "^2.10"
},
"require-dev": {
"flightphp/tracy-extensions": "^0.2.7"
···
"league/commonmark": "^2.7",
"chillerlan/php-oauth": "^1.0",
"fusonic/opengraph": "^3.0",
+
"tracy/tracy": "^2.10",
+
"contributte/logging": "^0.6.3"
},
"require-dev": {
"flightphp/tracy-extensions": "^0.2.7"
+83 -1
composer.lock
···
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
-
"content-hash": "921235a9d490b02f0d924d774577c6c1",
"packages": [
{
"name": "chillerlan/php-http-message-utils",
···
}
],
"time": "2025-08-13T16:46:21+00:00"
},
{
"name": "dflydev/dot-access-data",
···
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
+
"content-hash": "224b4f41bb3993dcb863319878caf9f4",
"packages": [
{
"name": "chillerlan/php-http-message-utils",
···
}
],
"time": "2025-08-13T16:46:21+00:00"
+
},
+
{
+
"name": "contributte/logging",
+
"version": "v0.6.3",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/contributte/logging.git",
+
"reference": "2cc959bcfbd05cf2946b6711432d14fc0deed418"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/contributte/logging/zipball/2cc959bcfbd05cf2946b6711432d14fc0deed418",
+
"reference": "2cc959bcfbd05cf2946b6711432d14fc0deed418",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=7.2",
+
"tracy/tracy": "~2.5.5|~2.6.2|~2.7.0|~2.8.0|~2.9.0|~2.10.0"
+
},
+
"conflict": {
+
"nette/di": "<3.0"
+
},
+
"require-dev": {
+
"ext-json": "*",
+
"nette/di": "^3.0.0",
+
"ninjify/nunjuck": "^0.4",
+
"ninjify/qa": "^0.12",
+
"phpstan/phpstan": "^1.0",
+
"phpstan/phpstan-deprecation-rules": "^1.0",
+
"phpstan/phpstan-nette": "^1.0",
+
"phpstan/phpstan-strict-rules": "^1.0",
+
"sentry/sdk": "^3.0.0"
+
},
+
"suggest": {
+
"nette/di": "to use TracyLoggingExtension",
+
"sentry/sdk": "to use SentryLoggingExtension"
+
},
+
"type": "library",
+
"extra": {
+
"branch-alias": {
+
"dev-master": "0.6.x-dev"
+
}
+
},
+
"autoload": {
+
"psr-4": {
+
"Contributte\\Logging\\": "src"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Milan Felix Šulc",
+
"homepage": "https://f3l1x.io"
+
}
+
],
+
"description": "Plug-in support logging for Tracy / Nette Framework",
+
"homepage": "https://github.com/contributte/logging",
+
"keywords": [
+
"logging",
+
"monolog",
+
"nette",
+
"plugins",
+
"tracy"
+
],
+
"support": {
+
"issues": "https://github.com/contributte/logging/issues",
+
"source": "https://github.com/contributte/logging/tree/v0.6.3"
+
},
+
"funding": [
+
{
+
"url": "https://contributte.org/partners.html",
+
"type": "custom"
+
},
+
{
+
"url": "https://github.com/f3l1x",
+
"type": "github"
+
}
+
],
+
"time": "2023-04-03T15:20:33+00:00"
},
{
"name": "dflydev/dot-access-data",
+6 -13
index.php
···
require_once('config.php');
require_once('lib/bskyToucher.php');
-
use Smallnest\Bsky\BskyToucher;
use League\CommonMark\CommonMarkConverter;
use React\Promise\Deferred;
use React\EventLoop\Loop;
use React\Promise\Promise;
use Tracy\Debugger;
$bskyToucher = new BskyToucher();
···
Debugger::enable();
// This where errors and exceptions will be logged. Make sure this directory exists and is writable.
Debugger::$logDirectory = __DIR__ . '/../log/';
-
Debugger::$strictMode = true; // display all errors
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices
if (Debugger::$showBar) {
// This is specific to the Tracy Extension for Flight if you've included that
···
Flight::set('frontpageFeed', FRONTPAGE_FEED);
Flight::set('defaultRelay', DEFAULT_RELAY);
Flight::set('userAuth', null);
-
Flight::set('flight.log_errors', true);
Flight::set('flight.content_length', false);
-
Flight::response()->addResponseBodyCallback(function($body) {
-
return gzencode($body, 9);
-
});
-
Flight::set('standardParams', [
'siteTitle' => SITE_TITLE,
'themes' => THEMES,
···
});
Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void {
-
global $bskyToucher, $data;
$bskyToucher = new BskyToucher();
-
$deferred = new React\Promise\Deferred();
-
$data = [];
$post = $bskyToucher->getPost($handle, $rkey, Flight::get('userAuth') === null);
-
$atUri = 'at://'.$post->did.'/app.bsky.feed.post/'.$rkey;
$likes = $bskyToucher->getLikeUsers($atUri);
$reposts = $bskyToucher->getRepostUsers($atUri);
$quotes = $bskyToucher->getQuoteRecords($atUri);
···
});
Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void {
-
global $log;
$bskyToucher = new BskyToucher();
$user = $bskyToucher->getUserInfo($handle);
$posts = $bskyToucher->getUserPosts($handle);
···
$bskyToucher = new BskyToucher();
$feedUrl = "at://".$did."/app.bsky.feed.generator/".$name;
$feedInfo = $bskyToucher->getFeedInfo($feedUrl);
-
$creatorInfo = $bskyToucher->getUserInfo($feedInfo->creator_did);
$posts = $bskyToucher->getFeed($feedUrl);
$latte = new Latte\Engine;
$latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [
···
require_once('config.php');
require_once('lib/bskyToucher.php');
use League\CommonMark\CommonMarkConverter;
use React\Promise\Deferred;
use React\EventLoop\Loop;
use React\Promise\Promise;
use Tracy\Debugger;
+
use Tracy\OutputDebugger;
$bskyToucher = new BskyToucher();
···
Debugger::enable();
// This where errors and exceptions will be logged. Make sure this directory exists and is writable.
Debugger::$logDirectory = __DIR__ . '/../log/';
+
//Debugger::$strictMode = true; // display all errors
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices
if (Debugger::$showBar) {
// This is specific to the Tracy Extension for Flight if you've included that
···
Flight::set('frontpageFeed', FRONTPAGE_FEED);
Flight::set('defaultRelay', DEFAULT_RELAY);
Flight::set('userAuth', null);
+
Flight::set('flight.log_errors', false);
+
Flight::set('flight.handle_errors', false);
Flight::set('flight.content_length', false);
Flight::set('standardParams', [
'siteTitle' => SITE_TITLE,
'themes' => THEMES,
···
});
Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void {
$bskyToucher = new BskyToucher();
$post = $bskyToucher->getPost($handle, $rkey, Flight::get('userAuth') === null);
+
$atUri = 'at://'.$post->author->did.'/app.bsky.feed.post/'.$rkey;
$likes = $bskyToucher->getLikeUsers($atUri);
$reposts = $bskyToucher->getRepostUsers($atUri);
$quotes = $bskyToucher->getQuoteRecords($atUri);
···
});
Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void {
$bskyToucher = new BskyToucher();
$user = $bskyToucher->getUserInfo($handle);
$posts = $bskyToucher->getUserPosts($handle);
···
$bskyToucher = new BskyToucher();
$feedUrl = "at://".$did."/app.bsky.feed.generator/".$name;
$feedInfo = $bskyToucher->getFeedInfo($feedUrl);
+
$creatorInfo = $bskyToucher->getUserInfo($feedInfo->creatorDid);
$posts = $bskyToucher->getFeed($feedUrl);
$latte = new Latte\Engine;
$latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [
+49 -32
lib/bskyToucher.php
···
<?php
-
namespace Smallnest\Bsky;
error_reporting(E_ALL);
ini_set('display_errors', 'On');
···
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Exception\ConnectException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Fusonic\OpenGraph\Consumer;
···
function getPost(string $identifier, string $rkey, $slingshot = false):object|bool {
$cache = \requestPostCache($rkey);
if ($cache) {
-
$ret = $cache;
-
$authorInfo = $this->getUserInfo($ret->did, 'did');
-
$ret->author = (object) [
-
'displayName' => $authorInfo->displayName,
-
'handle' => $authorInfo->handle,
-
'did' => $ret->did,
-
'avatar' => $authorInfo->avatar
-
];
-
return $ret;
}
$ret = $this->getSlingshotData($identifier, "app.bsky.feed.post", $rkey);
if (!$ret) return false;
-
$userInfo = $this->getUserInfo($identifier);
-
$author = (object) [
-
'displayName' => $userInfo->displayName,
-
'handle' => $userInfo->handle,
-
'did' => $userInfo->did,
-
'avatar' => $this->getMediaUrl($userInfo->pds, $userInfo->did, $userInfo->avatar)
-
];
-
$ret->author = $author;
$post = $this->sanitizePost($ret, true);
\updatePostCache($post->postId, $post->did, $post->content, $post->embedType, json_encode($post->embeds));
return $post;
···
'cursor' => $cursor
]);
if (!$postData) return false;
-
$bskyToucher = new BskyToucher;
-
$postData = array_map(function($p) use ($bskyToucher) {
$uriComponents = $this->splitAtUri($p->uri);
$cache = \requestPostCache($uriComponents->rkey);
-
if ($cache) return $cache;
-
return $bskyToucher->sanitizePost($p, true);
}, $postData->records);
if (!$postData) return false;
···
function getFeedInfo(string $atUri): object|bool {
$cache = \requestFeedCache($atUri);
-
if ($cache) return $cache;
$uriComponents = $this->splitAtUri($atUri);
if (!$uriComponents) return false;
···
$authorInfo = $this->getUserInfo($uriComponents->did, 'did');
\updateFeedCache($atUri, $feedData->value->displayName, $feedData->value->description, $feedData->value->avatar->ref->{'$link'}, $uriComponents->did);
return (object) [
-
'mainClass' => 'feed',
'title' => $feedData->value->displayName,
'url' => '/f/'.$uriComponents->did.'/'.$uriComponents->rkey,
'description' => $feedData->value->description,
···
'displayName' => $authorInfo->displayName,
'handle' => $authorInfo->handle,
'avatar' => $authorInfo->avatar,
-
'did' => $did
],
-
'profileLink' => '/u/'.$authorInfo->handle,
'uri' => $post->uri,
'pds' => $authorInfo->pds,
'postId' => $rkey,
···
'handle' => $authorData->handle,
'did' => $authorData->did,
'avatar' => $authorData->avatar,
],
-
'profileLink' => '/u/'.$authorData->handle,
'uri' => $post->uri,
'pds' => $authorData->pds,
'postId' => $rkeyMatch[2],
···
return $feed;
}
function sanitizeEmbeds(object $embeds, object $authorData):array|bool {
if ($embeds->{'$type'} === 'app.bsky.embed.images') {
return array_map(function ($im) use ($authorData) {
···
'post' => $sanitizedPost
]
];
-
} else if ($embeds->{'$type'} === 'app.bsky.something.external') {
-
$httpClient = new Client();
-
$httpRequestFactory = new RequestFactory();
-
$consumer = new Consumer($httpClient, $httpRequestFactory);
return [
(object) [
'uri' => $embeds->external->uri,
'title' => $embeds->external->title,
'description' => $embeds->external->description,
-
'thumb' => $graphData->images[0]
]
];
}
···
<?php
error_reporting(E_ALL);
ini_set('display_errors', 'On');
···
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Exception\ConnectException;
+
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Fusonic\OpenGraph\Consumer;
···
function getPost(string $identifier, string $rkey, $slingshot = false):object|bool {
$cache = \requestPostCache($rkey);
if ($cache) {
+
return $this->sanitizeCachedPost($cache);
}
$ret = $this->getSlingshotData($identifier, "app.bsky.feed.post", $rkey);
if (!$ret) return false;
$post = $this->sanitizePost($ret, true);
\updatePostCache($post->postId, $post->did, $post->content, $post->embedType, json_encode($post->embeds));
return $post;
···
'cursor' => $cursor
]);
if (!$postData) return false;
+
$postData = array_map(function($p) {
$uriComponents = $this->splitAtUri($p->uri);
$cache = \requestPostCache($uriComponents->rkey);
+
if ($cache) return $this->sanitizeCachedPost($cache);
}, $postData->records);
if (!$postData) return false;
···
function getFeedInfo(string $atUri): object|bool {
$cache = \requestFeedCache($atUri);
+
if ($cache) {
+
$uriComponents = $this->splitAtUri($cache->at_uri);
+
$authorInfo = $this->getUserInfo($cache->creator_did, 'did');
+
return (object) [
+
'title' => $cache->title,
+
'url' => '/f/'.$cache->creator_did.'/'.$uriComponents->rkey,
+
'description' => $cache->description,
+
'avatar' => $cache->avatar,
+
'creatorDisplay' => $authorInfo->displayName,
+
'creatorDid' => $uriComponents->did,
+
'creatorPds' => $authorInfo->pds,
+
'creatorHandle' => $authorInfo->handle
+
];
+
}
$uriComponents = $this->splitAtUri($atUri);
if (!$uriComponents) return false;
···
$authorInfo = $this->getUserInfo($uriComponents->did, 'did');
\updateFeedCache($atUri, $feedData->value->displayName, $feedData->value->description, $feedData->value->avatar->ref->{'$link'}, $uriComponents->did);
return (object) [
'title' => $feedData->value->displayName,
'url' => '/f/'.$uriComponents->did.'/'.$uriComponents->rkey,
'description' => $feedData->value->description,
···
'displayName' => $authorInfo->displayName,
'handle' => $authorInfo->handle,
'avatar' => $authorInfo->avatar,
+
'did' => $did,
+
'profileLink' => '/u/'.$authorInfo->handle,
],
'uri' => $post->uri,
'pds' => $authorInfo->pds,
'postId' => $rkey,
···
'handle' => $authorData->handle,
'did' => $authorData->did,
'avatar' => $authorData->avatar,
+
'profileLink' => '/u/'.$authorData->handle,
],
'uri' => $post->uri,
'pds' => $authorData->pds,
'postId' => $rkeyMatch[2],
···
return $feed;
}
+
function sanitizeCachedPost(object $post): object {
+
$uri = 'at://'.$post->did.'/app.bsky.feed.post/'.$post->rkey;
+
$authorData = $this->getUserInfo($post->did);
+
return (object) [
+
'author' => (object) [
+
'displayName' => $authorData->displayName,
+
'handle' => $authorData->handle,
+
'did' => $authorData->did,
+
'avatar' => $authorData->avatar,
+
'profileLink' => '/u/'.$authorData->handle,
+
],
+
'uri' => $uri,
+
'pds' => $authorData->pds,
+
'postId' => $post->rkey,
+
'postLink' => '/u/'.$authorData->handle.'/'.$post->rkey,
+
'content' => $post->text,
+
'createdAt' => '', // TODO: add created_at field to db table,
+
'likeCount' => $this->getLikes($uri),
+
'repostCount' => $this->getReposts($uri),
+
'quoteCount' => $this->getQuotes($uri),
+
'replyCount' => $this->getReplies($uri),
+
'embedType' => $post->embedType,
+
'embeds' => json_decode($post->embeds)
+
];
+
}
+
function sanitizeEmbeds(object $embeds, object $authorData):array|bool {
if ($embeds->{'$type'} === 'app.bsky.embed.images') {
return array_map(function ($im) use ($authorData) {
···
'post' => $sanitizedPost
]
];
+
} else if ($embeds->{'$type'} === 'app.bsky.embed.external') {
return [
(object) [
'uri' => $embeds->external->uri,
'title' => $embeds->external->title,
'description' => $embeds->external->description,
+
'thumb' => $this->getMediaUrl($authorData->pds, $authorData->did, $embeds->external->thumb->ref->{'$link'})
]
];
}
+3 -3
templates/_partials/post.latte
···
<div class="post">
<div class="postHeader">
<div class="avatar">
-
<a href="{$post->profileLink}"><img src="{$post->author->avatar}" alt="{$post->author->displayName}'s user icon" /></a>
</div>
-
<div class="displayName"><a href="{$post->profileLink}">{$post->author->displayName|noescape}</a></div>
-
<div class="handle"><a href="{$post->profileLink}">{$post->author->handle}</a></div>
<div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div>
</div>
<div class="postContent">{$post->content|noescape}</div>
···
<div class="post">
<div class="postHeader">
<div class="avatar">
+
<a href="{$post->author->profileLink}"><img src="{$post->author->avatar}" alt="{$post->author->displayName}'s user icon" /></a>
</div>
+
<div class="displayName"><a href="{$post->author->profileLink}">{$post->author->displayName|noescape}</a></div>
+
<div class="handle"><a href="{$post->author->profileLink}">{$post->author->handle}</a></div>
<div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div>
</div>
<div class="postContent">{$post->content|noescape}</div>
+1 -1
templates/feed.latte
···
{block title} | {$feedName}{/block}
{block content}
-
{include '_partials/feedHeader.latte', displayName: $feedName, description: $description, avatar: $avatar, creatorDisplay: $creatorDisplay, creatorHandle: $creatorHandle, creatorPds: $creatorPds, creatorDid: $creatorDid, feedAtUri: $feedAtUri}
{include '_partials/feedPosts.latte', posts: $posts}
{/block}
···
{block title} | {$feedName}{/block}
{block content}
+
{include '_partials/feedHeader.latte', displayName: $feedName, description: $description, avatar: $avatar, creatorDisplay: $feedAuthorName, creatorHandle: $feedAuthorHandle, creatorPds: $feedAuthorPds, creatorDid: $feedAuthorDid, feedAtUri: $feedAtUri}
{include '_partials/feedPosts.latte', posts: $posts}
{/block}
-1
templates/profile.latte
···
<div class="description">
{$user->description}
</div>
-
{print_r($user)}
<div class="actions">
<button type="button" action="follow">follow</button> <button type="button" action="getNotifs">get notifs</button> <button type="button" action="starterPack">add to starter pack</button> <button type="button" action="list">add to list</button>
</div>
···
<div class="description">
{$user->description}
</div>
<div class="actions">
<button type="button" action="follow">follow</button> <button type="button" action="getNotifs">get notifs</button> <button type="button" action="starterPack">add to starter pack</button> <button type="button" action="list">add to list</button>
</div>
+1 -1
vendor/composer/autoload_files.php
···
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'4cdafd4a5191caf078235e7dd119fdaf' => $vendorDir . '/flightphp/core/flight/autoload.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
-
'd507e002f7fce7f0c6dbf1f22edcb902' => $vendorDir . '/tracy/tracy/src/Tracy/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);
···
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+
'd507e002f7fce7f0c6dbf1f22edcb902' => $vendorDir . '/tracy/tracy/src/Tracy/functions.php',
'4cdafd4a5191caf078235e7dd119fdaf' => $vendorDir . '/flightphp/core/flight/autoload.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);
+1
vendor/composer/autoload_psr4.php
···
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Fusonic\\OpenGraph\\' => array($vendorDir . '/fusonic/opengraph/src'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
);
···
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Fusonic\\OpenGraph\\' => array($vendorDir . '/fusonic/opengraph/src'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
+
'Contributte\\Logging\\' => array($vendorDir . '/contributte/logging/src'),
);
+9 -1
vendor/composer/autoload_static.php
···
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'4cdafd4a5191caf078235e7dd119fdaf' => __DIR__ . '/..' . '/flightphp/core/flight/autoload.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
-
'd507e002f7fce7f0c6dbf1f22edcb902' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
);
···
'D' =>
array (
'Dflydev\\DotAccessData\\' => 22,
),
);
···
'Dflydev\\DotAccessData\\' =>
array (
0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src',
),
);
···
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+
'd507e002f7fce7f0c6dbf1f22edcb902' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/functions.php',
'4cdafd4a5191caf078235e7dd119fdaf' => __DIR__ . '/..' . '/flightphp/core/flight/autoload.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
);
···
'D' =>
array (
'Dflydev\\DotAccessData\\' => 22,
+
),
+
'C' =>
+
array (
+
'Contributte\\Logging\\' => 20,
),
);
···
'Dflydev\\DotAccessData\\' =>
array (
0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src',
+
),
+
'Contributte\\Logging\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/contributte/logging/src',
),
);
+85
vendor/composer/installed.json
···
"install-path": "../chillerlan/php-standard-utilities"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
"version_normalized": "3.0.3.0",
···
"install-path": "../chillerlan/php-standard-utilities"
},
{
+
"name": "contributte/logging",
+
"version": "v0.6.3",
+
"version_normalized": "0.6.3.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/contributte/logging.git",
+
"reference": "2cc959bcfbd05cf2946b6711432d14fc0deed418"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/contributte/logging/zipball/2cc959bcfbd05cf2946b6711432d14fc0deed418",
+
"reference": "2cc959bcfbd05cf2946b6711432d14fc0deed418",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=7.2",
+
"tracy/tracy": "~2.5.5|~2.6.2|~2.7.0|~2.8.0|~2.9.0|~2.10.0"
+
},
+
"conflict": {
+
"nette/di": "<3.0"
+
},
+
"require-dev": {
+
"ext-json": "*",
+
"nette/di": "^3.0.0",
+
"ninjify/nunjuck": "^0.4",
+
"ninjify/qa": "^0.12",
+
"phpstan/phpstan": "^1.0",
+
"phpstan/phpstan-deprecation-rules": "^1.0",
+
"phpstan/phpstan-nette": "^1.0",
+
"phpstan/phpstan-strict-rules": "^1.0",
+
"sentry/sdk": "^3.0.0"
+
},
+
"suggest": {
+
"nette/di": "to use TracyLoggingExtension",
+
"sentry/sdk": "to use SentryLoggingExtension"
+
},
+
"time": "2023-04-03T15:20:33+00:00",
+
"type": "library",
+
"extra": {
+
"branch-alias": {
+
"dev-master": "0.6.x-dev"
+
}
+
},
+
"installation-source": "dist",
+
"autoload": {
+
"psr-4": {
+
"Contributte\\Logging\\": "src"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Milan Felix Šulc",
+
"homepage": "https://f3l1x.io"
+
}
+
],
+
"description": "Plug-in support logging for Tracy / Nette Framework",
+
"homepage": "https://github.com/contributte/logging",
+
"keywords": [
+
"logging",
+
"monolog",
+
"nette",
+
"plugins",
+
"tracy"
+
],
+
"support": {
+
"issues": "https://github.com/contributte/logging/issues",
+
"source": "https://github.com/contributte/logging/tree/v0.6.3"
+
},
+
"funding": [
+
{
+
"url": "https://contributte.org/partners.html",
+
"type": "custom"
+
},
+
{
+
"url": "https://github.com/f3l1x",
+
"type": "github"
+
}
+
],
+
"install-path": "../contributte/logging"
+
},
+
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
"version_normalized": "3.0.3.0",
+11 -2
vendor/composer/installed.php
···
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => 'ab7848d3bf2d17ac5c167820c1a0c50b893defdf',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => 'ab7848d3bf2d17ac5c167820c1a0c50b893defdf',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'reference' => '5799d72f48bd4c36a4f3f668a879e5827e65736f',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-standard-utilities',
'aliases' => array(),
'dev_requirement' => false,
),
···
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
+
'reference' => '1232cb2ae76534eee11860f9d75a5ab171068e6e',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
+
'reference' => '1232cb2ae76534eee11860f9d75a5ab171068e6e',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'reference' => '5799d72f48bd4c36a4f3f668a879e5827e65736f',
'type' => 'library',
'install_path' => __DIR__ . '/../chillerlan/php-standard-utilities',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
+
'contributte/logging' => array(
+
'pretty_version' => 'v0.6.3',
+
'version' => '0.6.3.0',
+
'reference' => '2cc959bcfbd05cf2946b6711432d14fc0deed418',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../contributte/logging',
'aliases' => array(),
'dev_requirement' => false,
),
+10
vendor/contributte/logging/.github/.kodiak.toml
···
···
+
version = 1
+
+
[merge]
+
automerge_label = "automerge"
+
blacklist_title_regex = "^WIP.*"
+
blacklist_labels = ["WIP"]
+
method = "rebase"
+
delete_branch_on_merge = true
+
notify_on_conflict = true
+
optimistic_updates = false
+269
vendor/contributte/logging/.github/workflows/main.yaml
···
···
+
name: "build"
+
+
on:
+
pull_request:
+
paths-ignore:
+
- ".docs/**"
+
push:
+
branches:
+
- "*"
+
schedule:
+
- cron: "0 8 * * 1" # At 08:00 on Monday
+
+
env:
+
extensions: "json"
+
cache-version: "1"
+
composer-version: "v2"
+
composer-install: "composer update --no-interaction --no-progress --prefer-dist --prefer-stable"
+
+
jobs:
+
qa:
+
name: "Quality assurance"
+
runs-on: "${{ matrix.operating-system }}"
+
+
strategy:
+
matrix:
+
php-version: [ "7.4" ]
+
operating-system: [ "ubuntu-latest" ]
+
fail-fast: false
+
+
steps:
+
- name: "Checkout"
+
uses: "actions/checkout@v2"
+
+
- name: "Setup PHP cache environment"
+
id: "extcache"
+
uses: "shivammathur/cache-extensions@v1"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
key: "${{ env.cache-version }}"
+
+
- name: "Cache PHP extensions"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.extcache.outputs.dir }}"
+
key: "${{ steps.extcache.outputs.key }}"
+
restore-keys: "${{ steps.extcache.outputs.key }}"
+
+
- name: "Install PHP"
+
uses: "shivammathur/setup-php@v2"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
tools: "composer:${{ env.composer-version }} "
+
+
- name: "Setup problem matchers for PHP"
+
run: 'echo "::add-matcher::${{ runner.tool_cache }}/php.json"'
+
+
- name: "Get Composer cache directory"
+
id: "composercache"
+
run: 'echo "::set-output name=dir::$(composer config cache-files-dir)"'
+
+
- name: "Cache PHP dependencies"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.composercache.outputs.dir }}"
+
key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}"
+
restore-keys: "${{ runner.os }}-composer-"
+
+
- name: "Validate Composer"
+
run: "composer validate"
+
+
- name: "Install dependencies"
+
run: "${{ env.composer-install }}"
+
+
- name: "Coding Standard"
+
run: "make cs"
+
+
static-analysis:
+
name: "Static analysis"
+
runs-on: "${{ matrix.operating-system }}"
+
+
strategy:
+
matrix:
+
php-version: [ "7.4" ]
+
operating-system: [ "ubuntu-latest" ]
+
fail-fast: false
+
+
steps:
+
- name: "Checkout"
+
uses: "actions/checkout@v2"
+
+
- name: "Setup PHP cache environment"
+
id: "extcache"
+
uses: "shivammathur/cache-extensions@v1"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
key: "${{ env.cache-version }}"
+
+
- name: "Cache PHP extensions"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.extcache.outputs.dir }}"
+
key: "${{ steps.extcache.outputs.key }}"
+
restore-keys: "${{ steps.extcache.outputs.key }}"
+
+
- name: "Install PHP"
+
uses: "shivammathur/setup-php@v2"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
tools: "composer:${{ env.composer-version }} "
+
+
- name: "Setup problem matchers for PHP"
+
run: 'echo "::add-matcher::${{ runner.tool_cache }}/php.json"'
+
+
- name: "Get Composer cache directory"
+
id: "composercache"
+
run: 'echo "::set-output name=dir::$(composer config cache-files-dir)"'
+
+
- name: "Cache PHP dependencies"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.composercache.outputs.dir }}"
+
key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}"
+
restore-keys: "${{ runner.os }}-composer-"
+
+
- name: "Install dependencies"
+
run: "${{ env.composer-install }}"
+
+
- name: "PHPStan"
+
run: "make phpstan"
+
+
tests:
+
name: "Tests"
+
runs-on: "${{ matrix.operating-system }}"
+
+
strategy:
+
matrix:
+
php-version: [ "7.2", "7.3", "7.4" ]
+
operating-system: [ "ubuntu-latest" ]
+
composer-args: [ "" ]
+
include:
+
- php-version: "7.2"
+
operating-system: "ubuntu-latest"
+
composer-args: "--prefer-lowest"
+
- php-version: "8.0"
+
operating-system: "ubuntu-latest"
+
composer-args: ""
+
fail-fast: false
+
+
continue-on-error: "${{ matrix.php-version == '8.0' }}"
+
+
steps:
+
- name: "Checkout"
+
uses: "actions/checkout@v2"
+
+
- name: "Setup PHP cache environment"
+
id: "extcache"
+
uses: "shivammathur/cache-extensions@v1"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
key: "${{ env.cache-version }}"
+
+
- name: "Cache PHP extensions"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.extcache.outputs.dir }}"
+
key: "${{ steps.extcache.outputs.key }}"
+
restore-keys: "${{ steps.extcache.outputs.key }}"
+
+
- name: "Install PHP"
+
uses: "shivammathur/setup-php@v2"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
tools: "composer:${{ env.composer-version }} "
+
+
- name: "Setup problem matchers for PHP"
+
run: 'echo "::add-matcher::${{ runner.tool_cache }}/php.json"'
+
+
- name: "Get Composer cache directory"
+
id: "composercache"
+
run: 'echo "::set-output name=dir::$(composer config cache-files-dir)"'
+
+
- name: "Cache PHP dependencies"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.composercache.outputs.dir }}"
+
key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}"
+
restore-keys: "${{ runner.os }}-composer-"
+
+
- name: "Install dependencies"
+
run: "${{ env.composer-install }} ${{ matrix.composer-args }}"
+
+
- name: "Setup problem matchers for PHPUnit"
+
run: 'echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"'
+
+
- name: "Tests"
+
run: "make tests"
+
+
tests-code-coverage:
+
name: "Tests with code coverage"
+
runs-on: "${{ matrix.operating-system }}"
+
+
strategy:
+
matrix:
+
php-version: [ "7.4" ]
+
operating-system: [ "ubuntu-latest" ]
+
fail-fast: false
+
+
if: "github.event_name == 'push'"
+
+
steps:
+
- name: "Checkout"
+
uses: "actions/checkout@v2"
+
+
- name: "Setup PHP cache environment"
+
id: "extcache"
+
uses: "shivammathur/cache-extensions@v1"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
key: "${{ env.cache-version }}"
+
+
- name: "Cache PHP extensions"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.extcache.outputs.dir }}"
+
key: "${{ steps.extcache.outputs.key }}"
+
restore-keys: "${{ steps.extcache.outputs.key }}"
+
+
- name: "Install PHP"
+
uses: "shivammathur/setup-php@v2"
+
with:
+
php-version: "${{ matrix.php-version }}"
+
extensions: "${{ env.extensions }}"
+
tools: "composer:${{ env.composer-version }} "
+
+
- name: "Setup problem matchers for PHP"
+
run: 'echo "::add-matcher::${{ runner.tool_cache }}/php.json"'
+
+
- name: "Get Composer cache directory"
+
id: "composercache"
+
run: 'echo "::set-output name=dir::$(composer config cache-files-dir)"'
+
+
- name: "Cache PHP dependencies"
+
uses: "actions/cache@v2"
+
with:
+
path: "${{ steps.composercache.outputs.dir }}"
+
key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}"
+
restore-keys: "${{ runner.os }}-composer-"
+
+
- name: "Install dependencies"
+
run: "${{ env.composer-install }}"
+
+
- name: "Tests"
+
run: "make coverage-clover"
+
+
- name: "Coveralls.io"
+
env:
+
CI_NAME: github
+
CI: true
+
COVERALLS_REPO_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+
run: |
+
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.1.0/php-coveralls.phar
+
php php-coveralls.phar --verbose --config tests/.coveralls.yml
+21
vendor/contributte/logging/LICENSE
···
···
+
MIT License
+
+
Copyright (c) 2016 Contributte
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in all
+
copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+
SOFTWARE.
+24
vendor/contributte/logging/Makefile
···
···
+
.PHONY: install qa cs csf phpstan tests coverage-clover coverage-html
+
+
install:
+
composer update
+
+
qa: phpstan cs
+
+
cs:
+
vendor/bin/codesniffer src tests
+
+
csf:
+
vendor/bin/codefixer src tests
+
+
phpstan:
+
vendor/bin/phpstan analyse -l 8 -c phpstan.neon src
+
+
tests:
+
vendor/bin/tester -s -p php --colors 1 -C tests/cases
+
+
coverage-clover:
+
vendor/bin/tester -s -p phpdbg --colors 1 -C --coverage ./coverage.xml --coverage-src ./src tests/cases
+
+
coverage-html:
+
vendor/bin/tester -s -p phpdbg --colors 1 -C --coverage ./coverage.html --coverage-src ./src tests/cases
+64
vendor/contributte/logging/composer.json
···
···
+
{
+
"name": "contributte/logging",
+
"description": "Plug-in support logging for Tracy / Nette Framework",
+
"keywords": [
+
"nette",
+
"logging",
+
"tracy",
+
"monolog",
+
"plugins"
+
],
+
"type": "library",
+
"license": "MIT",
+
"homepage": "https://github.com/contributte/logging",
+
"authors": [
+
{
+
"name": "Milan Felix Šulc",
+
"homepage": "https://f3l1x.io"
+
}
+
],
+
"require": {
+
"php": ">=7.2",
+
"tracy/tracy": "~2.5.5|~2.6.2|~2.7.0|~2.8.0|~2.9.0|~2.10.0"
+
},
+
"require-dev": {
+
"ext-json": "*",
+
"ninjify/qa": "^0.12",
+
"ninjify/nunjuck": "^0.4",
+
"nette/di": "^3.0.0",
+
"sentry/sdk": "^3.0.0",
+
"phpstan/phpstan": "^1.0",
+
"phpstan/phpstan-deprecation-rules": "^1.0",
+
"phpstan/phpstan-nette": "^1.0",
+
"phpstan/phpstan-strict-rules": "^1.0"
+
},
+
"conflict": {
+
"nette/di": "<3.0"
+
},
+
"suggest": {
+
"nette/di": "to use TracyLoggingExtension",
+
"sentry/sdk": "to use SentryLoggingExtension"
+
},
+
"autoload": {
+
"psr-4": {
+
"Contributte\\Logging\\": "src"
+
}
+
},
+
"autoload-dev": {
+
"psr-4": {
+
"Tests\\Helpers\\": "tests/helpers"
+
}
+
},
+
"minimum-stability": "dev",
+
"prefer-stable": true,
+
"extra": {
+
"branch-alias": {
+
"dev-master": "0.6.x-dev"
+
}
+
},
+
"config": {
+
"allow-plugins": {
+
"dealerdirect/phpcodesniffer-composer-installer": true
+
}
+
}
+
}
+61
vendor/contributte/logging/src/AbstractLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use DirectoryIterator;
+
use Throwable;
+
+
/**
+
* AbstractTracyLogger based on official Tracy\Logger (@copyright David Grudl)
+
*/
+
abstract class AbstractLogger implements ILogger
+
{
+
+
/** @var string */
+
protected $directory;
+
+
public function __construct(string $directory)
+
{
+
$this->directory = $directory;
+
}
+
+
public function setDirectory(string $directory): void
+
{
+
$this->directory = $directory;
+
}
+
+
protected function getExceptionFile(Throwable $exception): string
+
{
+
$data = [];
+
+
while ($exception) {
+
$data[] = [
+
$exception->getMessage(),
+
$exception->getCode(),
+
$exception->getFile(),
+
$exception->getLine(),
+
array_map(function ($item): array {
+
unset($item['args']);
+
+
return $item;
+
}, $exception->getTrace()),
+
];
+
$exception = $exception->getPrevious();
+
}
+
+
$hash = substr(md5(serialize($data)), 0, 10);
+
+
foreach (new DirectoryIterator($this->directory) as $file) {
+
if ($file->isDot()) {
+
continue;
+
}
+
+
if ((bool) strpos($file->getBasename(), $hash)) {
+
return $file->getPathname();
+
}
+
}
+
+
return $this->directory . '/exception--' . @date('Y-m-d--H-i') . '--' . $hash . '.html'; // @ timezone may not be set
+
}
+
+
}
+41
vendor/contributte/logging/src/BlueScreenFileLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Contributte\Logging\Exceptions\Logical\InvalidStateException;
+
use Contributte\Logging\Utils\Utils;
+
use Throwable;
+
use Tracy\BlueScreen;
+
+
/**
+
* BlueScreenFileLogger based on official Tracy\Logger (@copyright David Grudl)
+
*
+
* Log every exception as single html file
+
*/
+
class BlueScreenFileLogger extends AbstractLogger implements ILogger
+
{
+
+
/** @var BlueScreen|null */
+
private $blueScreen;
+
+
public function __construct(string $directory, ?BlueScreen $blueScreen = null)
+
{
+
parent::__construct($directory);
+
$this->blueScreen = $blueScreen;
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void
+
{
+
if (!is_dir($this->directory)) {
+
throw new InvalidStateException('Directory ' . $this->directory . ' is not found or is not directory.');
+
}
+
+
if ($message instanceof Throwable) {
+
Utils::dumpException($message, $this->getExceptionFile($message), $this->blueScreen);
+
}
+
}
+
+
}
+74
vendor/contributte/logging/src/DI/SentryLoggingExtension.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\DI;
+
+
use Contributte\Logging\Sentry\SentryLogger;
+
use Contributte\Logging\UniversalLogger;
+
use Nette\DI\CompilerExtension;
+
use Nette\DI\Definitions\ServiceDefinition;
+
use Nette\DI\ServiceCreationException;
+
use Nette\Schema\Expect;
+
use Nette\Schema\Schema;
+
use stdClass;
+
+
/**
+
* @property-read stdClass $config
+
*/
+
final class SentryLoggingExtension extends CompilerExtension
+
{
+
+
public function getConfigSchema(): Schema
+
{
+
return Expect::structure([
+
'url' => Expect::string()->required(),
+
'enabled' => Expect::bool(true),
+
'options' => Expect::array(),
+
]);
+
}
+
+
/**
+
* Register services
+
*/
+
public function loadConfiguration(): void
+
{
+
$builder = $this->getContainerBuilder();
+
$config = $this->config;
+
+
if ($config->enabled === false) {
+
return;
+
}
+
+
$builder->addDefinition($this->prefix('logger'))
+
->setFactory(SentryLogger::class, [(array) $config]);
+
}
+
+
/**
+
* Decorate services
+
*/
+
public function beforeCompile(): void
+
{
+
$builder = $this->getContainerBuilder();
+
$config = $this->config;
+
+
if ($config->enabled === false) {
+
return;
+
}
+
+
$logger = $builder->getByType(UniversalLogger::class);
+
+
if ($logger === null) {
+
throw new ServiceCreationException(
+
sprintf(
+
'Service "%s" is required. Did you register %s extension as well?',
+
UniversalLogger::class,
+
TracyLoggingExtension::class
+
)
+
);
+
}
+
+
$def = $builder->getDefinition($logger);
+
assert($def instanceof ServiceDefinition);
+
$def->addSetup('addLogger', ['@' . $this->prefix('logger')]);
+
}
+
+
}
+86
vendor/contributte/logging/src/DI/SlackLoggingExtension.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\DI;
+
+
use Contributte\Logging\Slack\Formatter\ColorFormatter;
+
use Contributte\Logging\Slack\Formatter\ContextFormatter;
+
use Contributte\Logging\Slack\Formatter\ExceptionFormatter;
+
use Contributte\Logging\Slack\Formatter\ExceptionPreviousExceptionsFormatter;
+
use Contributte\Logging\Slack\Formatter\ExceptionStackTraceFormatter;
+
use Contributte\Logging\Slack\SlackLogger;
+
use Contributte\Logging\UniversalLogger;
+
use Nette\DI\CompilerExtension;
+
use Nette\DI\Definitions\ServiceDefinition;
+
use Nette\DI\ServiceCreationException;
+
use Nette\Schema\Expect;
+
use Nette\Schema\Schema;
+
use stdClass;
+
+
/**
+
* @property-read stdClass $config
+
*/
+
final class SlackLoggingExtension extends CompilerExtension
+
{
+
+
public function getConfigSchema(): Schema
+
{
+
return Expect::structure([
+
'url' => Expect::string()->required(),
+
'channel' => Expect::string()->required(),
+
'username' => Expect::string('Tracy'),
+
'icon_emoji' => Expect::string(':rocket:'),
+
'icon_url' => Expect::string()->nullable(),
+
'formatters' => Expect::listOf('array|string|Nette\DI\Definitions\Statement')->default([
+
ContextFormatter::class,
+
ColorFormatter::class,
+
ExceptionFormatter::class,
+
ExceptionStackTraceFormatter::class,
+
ExceptionPreviousExceptionsFormatter::class,
+
]),
+
]);
+
}
+
+
/**
+
* Register services
+
*/
+
public function loadConfiguration(): void
+
{
+
$builder = $this->getContainerBuilder();
+
$config = $this->config;
+
+
$loggerSlack = $builder->addDefinition($this->prefix('logger'))
+
->setFactory(SlackLogger::class, [(array) $config]);
+
+
foreach ($config->formatters as $n => $formatter) {
+
$def = $builder->addDefinition($this->prefix('formatter.' . ($n + 1)))
+
->setType($formatter);
+
+
$loggerSlack->addSetup('addFormatter', [$def]);
+
}
+
}
+
+
/**
+
* Decorate services
+
*/
+
public function beforeCompile(): void
+
{
+
$builder = $this->getContainerBuilder();
+
+
$logger = $builder->getByType(UniversalLogger::class);
+
+
if ($logger === null) {
+
throw new ServiceCreationException(
+
sprintf(
+
'Service "%s" is required. Did you register %s extension as well?',
+
UniversalLogger::class,
+
TracyLoggingExtension::class
+
)
+
);
+
}
+
+
$def = $builder->getDefinition($logger);
+
assert($def instanceof ServiceDefinition);
+
$def->addSetup('addLogger', ['@' . $this->prefix('logger')]);
+
}
+
+
}
+85
vendor/contributte/logging/src/DI/TracyLoggingExtension.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\DI;
+
+
use Contributte\Logging\BlueScreenFileLogger;
+
use Contributte\Logging\FileLogger;
+
use Contributte\Logging\UniversalLogger;
+
use Nette\DI\CompilerExtension;
+
use Nette\Schema\Expect;
+
use Nette\Schema\Schema;
+
use stdClass;
+
+
/**
+
* @property-read stdClass $config
+
*/
+
final class TracyLoggingExtension extends CompilerExtension
+
{
+
+
public function getConfigSchema(): Schema
+
{
+
return Expect::structure([
+
'logDir' => Expect::string()->required(),
+
'loggers' => Expect::listOf('array|string|Nette\DI\Definitions\Statement'),
+
]);
+
}
+
+
/**
+
* Register services
+
*/
+
public function loadConfiguration(): void
+
{
+
$builder = $this->getContainerBuilder();
+
$config = $this->config;
+
+
$logger = $builder->addDefinition($this->prefix('logger'))
+
->setType(UniversalLogger::class);
+
+
// Register defined loggers
+
if (count($config->loggers) !== 0) {
+
$loggers = [];
+
+
foreach ($config->loggers as $k => $v) {
+
$loggers[$this->prefix('logger.' . $k)] = $v;
+
}
+
+
$this->compiler->loadDefinitionsFromConfig($loggers);
+
+
foreach (array_keys($loggers) as $name) {
+
$logger->addSetup('addLogger', [$builder->getDefinition($name)]);
+
}
+
+
return;
+
}
+
+
// Register default loggers
+
$fileLogger = $builder->addDefinition($this->prefix('logger.filelogger'))
+
->setFactory(FileLogger::class, [$config->logDir])
+
->setAutowired('self');
+
+
$blueScreenFileLogger = $builder->addDefinition($this->prefix('logger.bluescreenfilelogger'))
+
->setFactory(BlueScreenFileLogger::class, [$config->logDir])
+
->setAutowired('self');
+
+
$logger->addSetup('addLogger', [$fileLogger]);
+
$logger->addSetup('addLogger', [$blueScreenFileLogger]);
+
}
+
+
/**
+
* Decorate services
+
*/
+
public function beforeCompile(): void
+
{
+
$builder = $this->getContainerBuilder();
+
+
// Replace tracy default logger for ours
+
if ($builder->hasDefinition('tracy.logger')) {
+
$builder->addDefinition($this->prefix('originalLogger'), clone $builder->getDefinition('tracy.logger'))
+
->setAutowired(false);
+
+
$builder->removeDefinition('tracy.logger');
+
$builder->addAlias('tracy.logger', $this->prefix('logger'));
+
}
+
}
+
+
}
+10
vendor/contributte/logging/src/Exceptions/Logical/InvalidStateException.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Exceptions\Logical;
+
+
use Contributte\Logging\Exceptions\LogicalException;
+
+
final class InvalidStateException extends LogicalException
+
{
+
+
}
+10
vendor/contributte/logging/src/Exceptions/LogicalException.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Exceptions;
+
+
use LogicException;
+
+
abstract class LogicalException extends LogicException
+
{
+
+
}
+30
vendor/contributte/logging/src/Exceptions/Runtime/Logger/SlackBadRequestException.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Exceptions\Runtime\Logger;
+
+
use Contributte\Logging\Exceptions\RuntimeException;
+
+
final class SlackBadRequestException extends RuntimeException
+
{
+
+
/** @var mixed[] */
+
private $request;
+
+
/**
+
* @param mixed[] $request
+
*/
+
public function __construct(array $request)
+
{
+
parent::__construct();
+
$this->request = $request;
+
}
+
+
/**
+
* @return mixed[]
+
*/
+
public function getRequest(): array
+
{
+
return $this->request;
+
}
+
+
}
+8
vendor/contributte/logging/src/Exceptions/RuntimeException.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Exceptions;
+
+
abstract class RuntimeException extends \RuntimeException
+
{
+
+
}
+38
vendor/contributte/logging/src/FileLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Contributte\Logging\Exceptions\Logical\InvalidStateException;
+
use Throwable;
+
use Tracy\Logger;
+
+
/**
+
* FileLogger based on official Tracy\Logger (@copyright David Grudl)
+
*
+
* Log all messages to <priority>.log
+
*/
+
class FileLogger extends AbstractLogger implements ILogger
+
{
+
+
/**
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void
+
{
+
if (!is_dir($this->directory)) {
+
throw new InvalidStateException('Directory "' . $this->directory . '" is not found or is not directory.');
+
}
+
+
$exceptionFile = ($message instanceof Throwable)
+
? $this->getExceptionFile($message)
+
: null;
+
+
$line = Logger::formatLogLine($message, $exceptionFile);
+
$file = $this->directory . '/' . strtolower($priority) . '.log';
+
+
if (!(bool) @file_put_contents($file, $line . PHP_EOL, FILE_APPEND | LOCK_EX)) {
+
throw new InvalidStateException('Unable to write to log file "' . $file . '". Is directory writable?');
+
}
+
}
+
+
}
+22
vendor/contributte/logging/src/ILogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Tracy\ILogger as TracyLogger;
+
+
interface ILogger
+
{
+
+
public const DEBUG = TracyLogger::DEBUG;
+
public const INFO = TracyLogger::INFO;
+
public const WARNING = TracyLogger::WARNING;
+
public const ERROR = TracyLogger::ERROR;
+
public const EXCEPTION = TracyLogger::EXCEPTION;
+
public const CRITICAL = TracyLogger::CRITICAL;
+
+
/**
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void;
+
+
}
+44
vendor/contributte/logging/src/Mailer/FileMailer.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Mailer;
+
+
use Tracy\Helpers;
+
use Tracy\Logger;
+
+
class FileMailer implements IMailer
+
{
+
+
/** @var string */
+
private $directory;
+
+
public function __construct(string $directory)
+
{
+
$this->directory = $directory;
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
public function send($message): void
+
{
+
/** @var string $host */
+
$host = preg_replace('#[^\w.-]+#', '', $_SERVER['HTTP_HOST'] ?? php_uname('n'));
+
$parts = str_replace(
+
["\r\n", "\n"],
+
["\n", PHP_EOL],
+
[
+
'headers' => implode("\n", [
+
'From: file@mailer',
+
'X-Mailer: Tracy',
+
'Content-Type: text/plain; charset=UTF-8',
+
'Content-Transfer-Encoding: 8bit',
+
]) . "\n",
+
'subject' => 'PHP: An error occurred on the server ' . $host,
+
'body' => Logger::formatMessage($message) . "\n\nsource: " . Helpers::getSource(),
+
]
+
);
+
+
@file_put_contents($this->directory . '/tracy-mail-' . time() . '.txt', implode("\n\n", $parts));
+
}
+
+
}
+13
vendor/contributte/logging/src/Mailer/IMailer.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Mailer;
+
+
interface IMailer
+
{
+
+
/**
+
* @param mixed $message
+
*/
+
public function send($message): void;
+
+
}
+55
vendor/contributte/logging/src/Mailer/TracyMailer.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Mailer;
+
+
use Tracy\Helpers;
+
use Tracy\Logger;
+
+
/**
+
* TracyMailer based on official Tracy\Logger[default mailer] (@copyright David Grudl)
+
*/
+
class TracyMailer implements IMailer
+
{
+
+
/** @var string|null */
+
private $from;
+
+
/** @var mixed[] */
+
private $to = [];
+
+
/**
+
* @param mixed[] $to
+
*/
+
public function __construct(?string $from, array $to)
+
{
+
$this->from = $from;
+
$this->to = $to;
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
public function send($message): void
+
{
+
/** @var string $host */
+
$host = preg_replace('#[^\w.-]+#', '', $_SERVER['HTTP_HOST'] ?? php_uname('n'));
+
$parts = str_replace(
+
["\r\n", "\n"],
+
["\n", PHP_EOL],
+
[
+
'headers' => implode("\n", [
+
'From: ' . ($this->from ?? 'noreply@' . $host),
+
'X-Mailer: Tracy',
+
'Content-Type: text/plain; charset=UTF-8',
+
'Content-Transfer-Encoding: 8bit',
+
]) . "\n",
+
'subject' => 'PHP: An error occurred on the server ' . $host,
+
'body' => Logger::formatMessage($message) . "\n\nsource: " . Helpers::getSource(),
+
]
+
);
+
+
$email = implode(',', $this->to);
+
mail($email, $parts['subject'], $parts['body'], $parts['headers']);
+
}
+
+
}
+18
vendor/contributte/logging/src/NullLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Tracy\ILogger as TracyLogger;
+
+
class NullLogger implements TracyLogger
+
{
+
+
/**
+
* @param mixed $message
+
* @param string $priority
+
*/
+
public function log($message, $priority = self::INFO): void // phpcs:ignore
+
{
+
}
+
+
}
+77
vendor/contributte/logging/src/SendMailLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Contributte\Logging\Mailer\IMailer;
+
use Nette\InvalidArgumentException;
+
+
class SendMailLogger extends AbstractLogger
+
{
+
+
/** @var string */
+
private $emailSnooze = '2 days';
+
+
/** @var IMailer */
+
private $mailer;
+
+
/** @var string[] */
+
private $allowedPriority = [ILogger::ERROR, ILogger::EXCEPTION, ILogger::CRITICAL];
+
+
public function __construct(IMailer $mailer, string $directory)
+
{
+
parent::__construct($directory);
+
$this->mailer = $mailer;
+
}
+
+
/**
+
* @param string[] $allowedPriority
+
*/
+
public function setAllowedPriority(array $allowedPriority): void
+
{
+
$this->allowedPriority = $allowedPriority;
+
}
+
+
public function setEmailSnooze(string $emailSnooze): void
+
{
+
$this->emailSnooze = $emailSnooze;
+
}
+
+
public function setMailer(IMailer $mailer): void
+
{
+
$this->mailer = $mailer;
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void
+
{
+
if (!in_array($priority, $this->allowedPriority, true)) {
+
return;
+
}
+
+
if (is_numeric($this->emailSnooze)) {
+
$snooze = (int) $this->emailSnooze;
+
} else {
+
$strtotime = @strtotime($this->emailSnooze);
+
+
if ($strtotime === false) {
+
throw new InvalidArgumentException('Email snooze was not parsed');
+
}
+
+
$snooze = $strtotime - time();
+
}
+
+
$filemtime = @filemtime($this->directory . '/email-sent');
+
+
if ($filemtime === false) {
+
$filemtime = 0;
+
}
+
+
if ($filemtime + $snooze < time() && (bool) @file_put_contents($this->directory . '/email-sent', 'sent')
+
) {
+
$this->mailer->send($message);
+
}
+
}
+
+
}
+101
vendor/contributte/logging/src/Sentry/SentryLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Sentry;
+
+
use Contributte\Logging\Exceptions\Logical\InvalidStateException;
+
use Contributte\Logging\ILogger;
+
use Sentry\ClientBuilder;
+
use Sentry\SentrySdk;
+
use Sentry\Severity;
+
use Sentry\State\Scope;
+
use Throwable;
+
+
class SentryLogger implements ILogger
+
{
+
+
public const LEVEL_PRIORITY_MAP = [
+
self::DEBUG => Severity::DEBUG,
+
self::INFO => Severity::INFO,
+
self::WARNING => Severity::WARNING,
+
self::ERROR => Severity::ERROR,
+
self::EXCEPTION => Severity::FATAL,
+
self::CRITICAL => Severity::FATAL,
+
];
+
+
public const CONFIG_URL = 'url';
+
public const CONFIG_OPTIONS = 'options';
+
+
/** @var mixed[] */
+
protected $configuration;
+
+
/** @var string[] */
+
private $allowedPriority = [ILogger::ERROR, ILogger::EXCEPTION, ILogger::CRITICAL];
+
+
/**
+
* @param mixed[] $configuration
+
*/
+
public function __construct(array $configuration)
+
{
+
if (!isset($configuration[self::CONFIG_URL])) {
+
throw new InvalidStateException('Missing url in SentryLogger configuration');
+
}
+
+
if (!isset($configuration[self::CONFIG_OPTIONS])) {
+
$configuration[self::CONFIG_OPTIONS] = [];
+
}
+
+
$this->configuration = $configuration;
+
}
+
+
/**
+
* @param string[] $allowedPriority
+
*/
+
public function setAllowedPriority(array $allowedPriority): void
+
{
+
$this->allowedPriority = $allowedPriority;
+
}
+
+
/**
+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void
+
{
+
if (!in_array($priority, $this->allowedPriority, true)) {
+
return;
+
}
+
+
$level = $this->getLevel($priority);
+
+
if ($level === null) {
+
return;
+
}
+
+
$scope = (new Scope())->setLevel(new Severity($level));
+
+
$this->makeRequest($message, $scope);
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
protected function makeRequest($message, Scope $scope): void
+
{
+
$client = ClientBuilder::create($this->configuration[self::CONFIG_OPTIONS] + ['dsn' => $this->configuration[self::CONFIG_URL]])
+
->getClient();
+
SentrySdk::init()->bindClient($client);
+
+
if ($message instanceof Throwable) {
+
$client->captureException($message, $scope);
+
} else {
+
$client->captureMessage($message, null, $scope);
+
}
+
}
+
+
protected function getLevel(string $priority): ?string
+
{
+
return self::LEVEL_PRIORITY_MAP[$priority] ?? null;
+
}
+
+
}
+37
vendor/contributte/logging/src/Slack/Formatter/ColorFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Contributte\Logging\Exceptions\Logical\InvalidStateException;
+
use Contributte\Logging\ILogger;
+
use Throwable;
+
+
final class ColorFormatter implements IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $message, string $priority): SlackContext
+
{
+
switch ($priority) {
+
case ILogger::ERROR:
+
$color = 'warning';
+
+
break;
+
case ILogger::EXCEPTION:
+
$color = '#ff0000';
+
+
break;
+
case ILogger::CRITICAL:
+
$color = 'danger';
+
+
break;
+
default:
+
throw new InvalidStateException(sprintf('Unsupported priority "%s".', $priority));
+
}
+
+
$context = clone $context;
+
$context->setColor($color);
+
+
return $context;
+
}
+
+
}
+24
vendor/contributte/logging/src/Slack/Formatter/ContextFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Throwable;
+
+
final class ContextFormatter implements IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $message, string $priority): SlackContext
+
{
+
$context = clone $context;
+
+
$context->setChannel($context->getConfig('channel'));
+
$context->setUsername($context->getConfig('username', 'Tracy'));
+
$context->setIconEmoji($context->getConfig('icon_emoji', 'rocket'));
+
$context->setIconUrl($context->getConfig('icon_emoji', null));
+
$context->setText(':bangbang::bangbang::bangbang: Exception occured :bangbang::bangbang::bangbang:');
+
$context->setMarkdown();
+
+
return $context;
+
}
+
+
}
+54
vendor/contributte/logging/src/Slack/Formatter/ExceptionFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Throwable;
+
use Tracy\Helpers;
+
+
final class ExceptionFormatter implements IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $exception, string $priority): SlackContext
+
{
+
$context = clone $context;
+
+
$attachment = $context->createAttachment();
+
$attachment->setColor('danger');
+
$attachment->setMarkdown();
+
+
$message = $attachment->createField();
+
$message->setTitle(':date: Date');
+
$message->setValue(@date('[d.m.Y]'));
+
$message->setShort();
+
+
$message = $attachment->createField();
+
$message->setTitle(':timer_clock: Time');
+
$message->setValue(@date('[H:i:s]'));
+
$message->setShort();
+
+
$message = $attachment->createField();
+
$message->setTitle(':computer: Source');
+
$message->setValue(Helpers::getSource());
+
+
$message = $attachment->createField();
+
$message->setTitle(':mag_right: Exception');
+
$message->setValue(get_class($exception));
+
+
$message = $attachment->createField();
+
$message->setTitle(':envelope: Message');
+
$message->setValue($exception->getMessage());
+
$message->setShort();
+
+
$code = $attachment->createField();
+
$code->setTitle(':1234: Code');
+
$code->setValue((string) $exception->getCode());
+
$code->setShort();
+
+
$file = $attachment->createField();
+
$file->setTitle(':open_file_folder: File');
+
$file->setValue('```' . $exception->getFile() . ':' . $exception->getLine() . '```');
+
+
return $context;
+
}
+
+
}
+45
vendor/contributte/logging/src/Slack/Formatter/ExceptionPreviousExceptionsFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Throwable;
+
+
final class ExceptionPreviousExceptionsFormatter implements IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $exception, string $priority): SlackContext
+
{
+
$context = clone $context;
+
+
while (($previous = $exception->getPrevious()) !== null) {
+
$attachment = $context->createAttachment();
+
$attachment->setFallback('Required plain-text summary of the attachment.');
+
$attachment->setText(sprintf('*Previous exception* (_%s_)', $previous->getMessage()));
+
$attachment->setMarkdown();
+
+
$message = $attachment->createField();
+
$message->setTitle(':mag_right: Exception');
+
$message->setValue(get_class($previous));
+
+
$message = $attachment->createField();
+
$message->setTitle(':envelope: Message');
+
$message->setValue($previous->getMessage());
+
$message->setShort();
+
+
$code = $attachment->createField();
+
$code->setTitle(':1234: Code');
+
$code->setValue((string) $previous->getCode());
+
$code->setShort();
+
+
$file = $attachment->createField();
+
$file->setTitle(':open_file_folder: File');
+
$file->setValue('```' . $previous->getFile() . ':' . $previous->getLine() . '```');
+
+
// Change pointer
+
$exception = $previous;
+
}
+
+
return $context;
+
}
+
+
}
+33
vendor/contributte/logging/src/Slack/Formatter/ExceptionStackTraceFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Throwable;
+
+
final class ExceptionStackTraceFormatter implements IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $exception, string $priority): SlackContext
+
{
+
// Skip empty trace
+
if (count($exception->getTrace()) < 1) {
+
return $context;
+
}
+
+
$context = clone $context;
+
$attachment = $context->createAttachment();
+
$attachment->setText(sprintf('*Stack trace* (_%s_)', get_class($exception)));
+
$attachment->setMarkdown();
+
+
foreach ($exception->getTrace() as $id => $trace) {
+
$func = $attachment->createField();
+
$func->setTitle(sprintf(':fireworks: Trace #%s', $id + 1));
+
$file = $attachment->createField();
+
$file->setTitle(':open_file_folder: File');
+
$file->setValue('```Function: ' . $trace['function'] . "\nFile: " . $trace['file'] . ':' . $trace['line'] . '```');
+
}
+
+
return $context;
+
}
+
+
}
+12
vendor/contributte/logging/src/Slack/Formatter/IFormatter.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Throwable;
+
+
interface IFormatter
+
{
+
+
public function format(SlackContext $context, Throwable $message, string $priority): SlackContext;
+
+
}
+112
vendor/contributte/logging/src/Slack/Formatter/SlackContext.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
use Nette\Utils\Arrays;
+
+
final class SlackContext
+
{
+
+
/** @var mixed[] */
+
private $config = [];
+
+
/** @var mixed[] */
+
private $data = [];
+
+
/** @var SlackContextField[] */
+
private $fields = [];
+
+
/** @var SlackContextAttachment[] */
+
private $attachments = [];
+
+
/**
+
* @param mixed[] $config
+
*/
+
public function __construct(array $config)
+
{
+
$this->config = $config;
+
}
+
+
/**
+
* @param mixed $default
+
* @return mixed
+
*/
+
public function getConfig(string $key, $default = null)
+
{
+
return func_num_args() > 1
+
? Arrays::get($this->config, explode('.', $key), $default)
+
: Arrays::get($this->config, explode('.', $key));
+
}
+
+
public function setChannel(string $channel): void
+
{
+
$this->data['channel'] = $channel;
+
}
+
+
public function setUsername(string $username): void
+
{
+
$this->data['username'] = $username;
+
}
+
+
public function setIconEmoji(string $icon): void
+
{
+
$this->data['icon_emoji'] = sprintf(':%s:', trim($icon, ':'));
+
}
+
+
public function setIconUrl(string $icon): void
+
{
+
$this->data['icon_url'] = $icon;
+
}
+
+
public function setText(string $text): void
+
{
+
$this->data['text'] = $text;
+
}
+
+
public function setColor(string $color): void
+
{
+
$this->data['color'] = $color;
+
}
+
+
public function setMarkdown(bool $markdown = true): void
+
{
+
$this->data['mrkdwn'] = $markdown;
+
}
+
+
public function createField(): SlackContextField
+
{
+
return $this->fields[] = new SlackContextField();
+
}
+
+
public function createAttachment(): SlackContextAttachment
+
{
+
return $this->attachments[] = new SlackContextAttachment();
+
}
+
+
/**
+
* @return mixed[]
+
*/
+
public function toArray(): array
+
{
+
$data = $this->data;
+
+
if (count($this->fields) > 0) {
+
$data['fields'] = [];
+
+
foreach ($this->fields as $attachment) {
+
$data['fields'][] = $attachment->toArray();
+
}
+
}
+
+
if (count($this->attachments) > 0) {
+
$data['attachments'] = [];
+
+
foreach ($this->attachments as $attachment) {
+
$data['attachments'][] = $attachment->toArray();
+
}
+
}
+
+
return $data;
+
}
+
+
}
+114
vendor/contributte/logging/src/Slack/Formatter/SlackContextAttachment.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
final class SlackContextAttachment
+
{
+
+
/** @var mixed[] */
+
private $data = [];
+
+
/** @var SlackContextField[] */
+
private $fields = [];
+
+
public function setFallback(string $fallback): void
+
{
+
$this->data['fallback'] = $fallback;
+
}
+
+
public function setColor(string $color): void
+
{
+
$this->data['color'] = $color;
+
}
+
+
public function setPretext(string $pretext): void
+
{
+
$this->data['pretext'] = $pretext;
+
}
+
+
public function setText(string $text): void
+
{
+
$this->data['text'] = $text;
+
}
+
+
public function setTitle(string $title): void
+
{
+
$this->data['title'] = $title;
+
}
+
+
public function setTitleLink(string $link): void
+
{
+
$this->data['title_link'] = $link;
+
}
+
+
public function setAuthorName(string $name): void
+
{
+
$this->data['author_name'] = $name;
+
}
+
+
public function setAuthorLink(string $link): void
+
{
+
$this->data['author_link'] = $link;
+
}
+
+
public function setAuthorIcon(string $icon): void
+
{
+
$this->data['author_icon'] = $icon;
+
}
+
+
public function setImageUrl(string $url): void
+
{
+
$this->data['image_url'] = $url;
+
}
+
+
public function setThumbUrl(string $url): void
+
{
+
$this->data['thumb_url'] = $url;
+
}
+
+
public function setFooter(string $footer): void
+
{
+
$this->data['footer'] = $footer;
+
}
+
+
public function setFooterIcon(string $icon): void
+
{
+
$this->data['footer_icon'] = $icon;
+
}
+
+
public function setTimestamp(string $timestamp): void
+
{
+
$this->data['ts'] = $timestamp;
+
}
+
+
public function setMarkdown(bool $markdown = true): void
+
{
+
if ($markdown) {
+
$this->data['mrkdwn_in'] = ['pretext', 'text', 'fields'];
+
}
+
}
+
+
public function createField(): SlackContextField
+
{
+
return $this->fields[] = new SlackContextField();
+
}
+
+
/**
+
* @return mixed[]
+
*/
+
public function toArray(): array
+
{
+
$data = $this->data;
+
+
if (count($this->fields) > 0) {
+
$data['fields'] = [];
+
+
foreach ($this->fields as $attachment) {
+
$data['fields'][] = $attachment->toArray();
+
}
+
}
+
+
return $data;
+
}
+
+
}
+34
vendor/contributte/logging/src/Slack/Formatter/SlackContextField.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack\Formatter;
+
+
final class SlackContextField
+
{
+
+
/** @var mixed[] */
+
private $data = [];
+
+
public function setTitle(string $title): void
+
{
+
$this->data['title'] = $title;
+
}
+
+
public function setValue(string $value): void
+
{
+
$this->data['value'] = $value;
+
}
+
+
public function setShort(bool $short = true): void
+
{
+
$this->data['short'] = $short;
+
}
+
+
/**
+
* @return mixed[]
+
*/
+
public function toArray(): array
+
{
+
return $this->data;
+
}
+
+
}
+97
vendor/contributte/logging/src/Slack/SlackLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Slack;
+
+
use Contributte\Logging\Exceptions\Runtime\Logger\SlackBadRequestException;
+
use Contributte\Logging\ILogger;
+
use Contributte\Logging\Slack\Formatter\IFormatter;
+
use Contributte\Logging\Slack\Formatter\SlackContext;
+
use Nette\Utils\Arrays;
+
use Throwable;
+
+
final class SlackLogger implements ILogger
+
{
+
+
/** @var mixed[] */
+
private $config;
+
+
/** @var IFormatter[] */
+
private $formatters = [];
+
+
/**
+
* @param mixed[] $config
+
*/
+
public function __construct(array $config)
+
{
+
$this->config = $config;
+
}
+
+
public function addFormatter(IFormatter $formatter): void
+
{
+
$this->formatters[] = $formatter;
+
}
+
+
/**
+
* @param mixed $message
+
*/
+
public function log($message, string $priority = ILogger::INFO): void
+
{
+
if (!in_array($priority, [ILogger::ERROR, ILogger::EXCEPTION, ILogger::CRITICAL], true)) {
+
return;
+
}
+
+
if (!($message instanceof Throwable)) {
+
return;
+
}
+
+
$context = new SlackContext($this->config);
+
+
// Apply all formatters
+
foreach ($this->formatters as $formatter) {
+
$context = $formatter->format($context, $message, $priority);
+
}
+
+
// Send to channel
+
$this->makeRequest($context);
+
}
+
+
protected function makeRequest(SlackContext $context): void
+
{
+
$url = $this->get('url');
+
+
$streamcontext = [
+
'http' => [
+
'method' => 'POST',
+
'header' => 'Content-type: application/x-www-form-urlencoded',
+
'timeout' => $this->get('timeout', 30),
+
'content' => http_build_query([
+
'payload' => json_encode(array_filter($context->toArray())),
+
]),
+
],
+
];
+
+
$response = @file_get_contents($url, false, stream_context_create($streamcontext));
+
+
if ($response !== 'ok') {
+
throw new SlackBadRequestException([
+
'url' => $url,
+
'context' => $streamcontext,
+
'response' => [
+
'headers' => $http_response_header,
+
],
+
]);
+
}
+
}
+
+
/**
+
* @param mixed $default
+
* @return mixed
+
*/
+
protected function get(string $key, $default = null)
+
{
+
return func_num_args() > 1
+
? Arrays::get($this->config, explode('.', $key), $default)
+
: Arrays::get($this->config, explode('.', $key));
+
}
+
+
}
+35
vendor/contributte/logging/src/UniversalLogger.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging;
+
+
use Tracy\ILogger as TracyLogger;
+
+
class UniversalLogger implements TracyLogger
+
{
+
+
/** @var ILogger[] */
+
private $loggers = [];
+
+
public function addLogger(ILogger $logger): void
+
{
+
$this->loggers[] = $logger;
+
}
+
+
+
/**
+
* LOGGER ******************************************************************
+
*/
+
+
/**
+
* @param mixed $message
+
* @param string $priority
+
*/
+
public function log($message, $priority = self::INFO): void // phpcs:ignore
+
{
+
// Composite logger
+
foreach ($this->loggers as $logger) {
+
$logger->log($message, $priority);
+
}
+
}
+
+
}
+33
vendor/contributte/logging/src/Utils/Utils.php
···
···
+
<?php declare(strict_types = 1);
+
+
namespace Contributte\Logging\Utils;
+
+
use Throwable;
+
use Tracy\BlueScreen;
+
+
/**
+
* Based on official Tracy\Logger (@copyright David Grudl)
+
*/
+
final class Utils
+
{
+
+
public static function dumpException(Throwable $exception, string $file, ?BlueScreen $blueScreen = null): string
+
{
+
$bs = $blueScreen ?? new BlueScreen();
+
$bs->renderToFile($exception, $file);
+
+
return $file;
+
}
+
+
public static function captureException(Throwable $exception, string $file, ?BlueScreen $blueScreen = null): string
+
{
+
$bs = $blueScreen ?? new BlueScreen();
+
+
ob_start();
+
$bs->renderToFile($exception, $file);
+
$contents = ob_get_contents();
+
+
return (string) $contents;
+
}
+
+
}