diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2e7acaf5d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 798753326..41f9ae41c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,12 +1,5 @@ # These are supported funding model platforms github: barryvdh -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +custom: ['https://fruitcake.nl/'] + diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 000000000..27bcee3fb --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,4 @@ +template: | + ## What’s Changed + + $CHANGES diff --git a/.github/workflows/fix-cs.yml b/.github/workflows/fix-cs.yml deleted file mode 100644 index c92491dd9..000000000 --- a/.github/workflows/fix-cs.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Fix Codestyle - -on: - push: - branches: - - master - -jobs: - fix-style: - name: Fix Code Style - timeout-minutes: 15 - runs-on: ubuntu-latest - env: - COMPOSER_NO_INTERACTION: 1 - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none - tools: composer:v2 - - - name: Install dependencies - run: | - composer update --prefer-dist --no-suggest --no-progress - - - run: composer fix-style - continue-on-error: true - - # Revert modifications so they don't get commited 💥 - - run: git checkout -- composer.json - - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: composer fix-style - commit_author: laravel-debugbar diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 000000000..84492e314 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,41 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + # types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v6 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml index 6e626fb24..c3376d46a 100644 --- a/.github/workflows/run-integration-tests.yml +++ b/.github/workflows/run-integration-tests.yml @@ -11,65 +11,29 @@ on: - cron: '0 0 * * *' jobs: - php-lumen-integration-tests: - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - COMPOSER_NO_INTERACTION: 1 - strategy: - matrix: - php: [7.4, 7.3, 7.2] - lumen: [7.*, 6.*] - name: P${{ matrix.php }} - Lumen${{ matrix.lumen }} - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - path: src - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: none - tools: composer:v2 - - - name: Install dependencies - run: | - composer create-project --prefer-dist laravel/lumen:${{ matrix.lumen }} --no-progress sample - cd sample - composer update --prefer-stable --prefer-dist --no-progress - - name: Add package from source - run: | - cd sample - sed -e 's|"type": "project",|&\n"repositories": [ { "type": "path", "url": "../src" } ],|' -i composer.json - composer require --dev "barryvdh/laravel-debugbar:*" - - name: Insert service provider - run: sed -e 's|// \$app->register(App\\\Providers\\\EventServiceProvider::class);|&\n$app->register(\\Barryvdh\\Debugbar\\LumenServiceProvider::class);|' -i sample/bootstrap/app.php - - - name: Execute clear run - run: | - cd sample - php artisan debugbar:clear - - name: Check file count in logs - run: | - if [ `ls -1q "sample/storage/logs/" | wc -l` -gt 0 ];then exit 1;fi php-laravel-integration-tests: runs-on: ubuntu-latest timeout-minutes: 15 env: COMPOSER_NO_INTERACTION: 1 strategy: + fail-fast: false matrix: - php: [7.4, 7.3, 7.2] - laravel: [8.*, 7.*, 6.*] + php: [8.5, 8.4, 8.3, 8.2, 8.1] + laravel: [12.*, 11.*, 10.*, 9.*] exclude: - - laravel: 8.* - php: 7.2 + - laravel: 12.* + php: 8.1 + - laravel: 11.* + php: 8.1 + - laravel: 9.* + php: 8.4 + - laravel: 9.* + php: 8.5 name: P${{ matrix.php }} - Laravel${{ matrix.laravel }} steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: src @@ -82,18 +46,20 @@ jobs: - name: Install dependencies run: | - composer create-project --prefer-dist laravel/laravel:${{ matrix.laravel }} --no-progress sample + composer create-project --prefer-dist --ignore-platform-req=php+ laravel/laravel:${{ matrix.laravel }} --stability=dev --no-progress sample cd sample - composer update --prefer-stable --prefer-dist --no-progress + composer config minimum-stability dev + composer update --prefer-stable --prefer-dist --no-progress --ignore-platform-req=php+ - name: Add package from source run: | cd sample sed -e 's|"type": "project",|&\n"repositories": [ { "type": "path", "url": "../src" } ],|' -i composer.json - composer require --dev "barryvdh/laravel-debugbar:*" + composer require --dev --ignore-platform-req=php+ "barryvdh/laravel-debugbar:*" - name: Execute generate run run: | cd sample + mkdir -p "storage/debugbar/" && touch "storage/debugbar/foo.json" php artisan debugbar:clear - name: Check file count in logs run: | - if [ `ls -1q "sample/storage/logs/" | wc -l` -gt 0 ];then exit 1;fi + if [ `ls -1q "sample/storage/debugbar/" | wc -l` -gt 0 ];then exit 1;fi diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a6787b5f5..40e72492a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -18,19 +18,26 @@ jobs: COMPOSER_NO_INTERACTION: 1 strategy: + fail-fast: false matrix: - php: [7.4, 7.3, 7.2] - laravel: [8.*, 6.*, 7.*] - dependency-version: [prefer-lowest, prefer-stable] + php: [8.5, 8.4, 8.3, 8.2, 8.1] + laravel: [^12, ^11, ^10, ^9] + dependency-version: [prefer-stable] exclude: - - laravel: 8.* - php: 7.2 + - laravel: ^12 + php: 8.1 + - laravel: ^11 + php: 8.1 + - laravel: ^9 + php: 8.4 + - laravel: ^9 + php: 8.5 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -38,7 +45,8 @@ jobs: php-version: ${{ matrix.php }} coverage: none tools: composer:v2 - + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif + - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update @@ -51,7 +59,7 @@ jobs: run: composer test - name: Upload Failed Screenshots - uses: actions/upload-artifact@v2-preview + uses: actions/upload-artifact@v4 if: failure() with: name: screenshots diff --git a/.github/workflows/update-changelog.yaml b/.github/workflows/update-changelog.yaml new file mode 100644 index 000000000..c7832e5ee --- /dev/null +++ b/.github/workflows/update-changelog.yaml @@ -0,0 +1,34 @@ +name: "Update Changelog" + +on: + release: + types: [released] + +jobs: + update: + runs-on: ubuntu-latest + + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # updated CHANGELOG back to the repository. + # https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/ + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.target_commitish }} + + - name: Update Changelog + uses: stefanzweifel/changelog-updater-action@v1 + with: + latest-version: ${{ github.event.release.tag_name }} + release-notes: ${{ github.event.release.body }} + + - name: Commit updated CHANGELOG + uses: stefanzweifel/git-auto-commit-action@v5 + with: + branch: ${{ github.event.release.target_commitish }} + commit_message: Update CHANGELOG + file_pattern: CHANGELOG.md diff --git a/.gitignore b/.gitignore index 7639d5a9a..0f88edc4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ +/.idea /build /vendor composer.phar composer.lock .DS_Store -.phpunit.result.cache -/tests/Browser \ No newline at end of file +.phpunit* +/tests/Browser diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..7a278ac2d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,172 @@ +# Changelog + +## v3.16.0 - 2025-07-21 + +### What's Changed + +* Make all scalar config values configurable through environment variables by @wimski in https://github.com/barryvdh/laravel-debugbar/pull/1784 +* Check if file exists on FilesystemStorage by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1790 +* Bump php-debugbar by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1791 +* Fix counter tests by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1792 +* `$group` arg support on TimelineCollectors methods by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1789 +* Collect other eloquent model events by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1781 +* Add new cache events on CacheCollector by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1773 +* Exclude events on EventCollector by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1786 +* Use `addWarning` on warnings, silenced errors, notices by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1767 +* Do not rely on DB::connection() to get information in query collector by @cweiske in https://github.com/barryvdh/laravel-debugbar/pull/1779 +* Trace file for Gate checks(GateCollector) by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1770 +* Fix support for PDOExceptions by @LukeTowers in https://github.com/barryvdh/laravel-debugbar/pull/1752 +* Time measure on cache events by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1794 +* fix debugbar for Lumen usage by @flibidi67 in https://github.com/barryvdh/laravel-debugbar/pull/1796 +* Custom path for Inertia views by @joaopms in https://github.com/barryvdh/laravel-debugbar/pull/1797 +* Better contrast in dark theme titles. by @angeljqv in https://github.com/barryvdh/laravel-debugbar/pull/1798 + +### New Contributors + +* @wimski made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1784 +* @cweiske made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1779 +* @flibidi67 made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1796 +* @joaopms made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1797 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.15.4...v3.16.0 + +## v3.15.4 - 2025-04-16 + +### What's Changed + +* Remove html `` tag from route on clockwork by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1777 +* Fix default for capturing dd/dump by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1783 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.15.3...v3.15.4 + +## v3.15.3 - 2025-04-08 + +### What's Changed + +* Add condition for implemented query grammar by @rikwillems in https://github.com/barryvdh/laravel-debugbar/pull/1757 +* Collect dumps on message collector by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1759 +* Fix `capture_dumps` option on laravel `dd();` by @parallels999 in https://github.com/barryvdh/laravel-debugbar/pull/1762 +* Preserve laravel error handler by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1760 +* Fix `Trying to access array offset on false on LogsCollector.php` by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1763 +* Update css theme for views widget by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1768 +* Fix laravel-debugbar.css on query widget by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1765 +* Use htmlvardumper if available on CacheCollector by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1766 +* Update QueryCollector.php fix issue #1775 by @Mathias-DS in https://github.com/barryvdh/laravel-debugbar/pull/1776 +* Better grouping the events count by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1774 + +### New Contributors + +* @rikwillems made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1757 +* @Mathias-DS made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1776 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.15.2...v3.15.3 + +## v3.15.2 - 2025-02-25 + +### What's Changed + +* Fix empty tabs on clockwork by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1750 +* fix: Ignore info query statements in Clockwork converter by @boserup in https://github.com/barryvdh/laravel-debugbar/pull/1749 +* Check if request controller is string by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1751 + +### New Contributors + +* @boserup made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1749 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.15.1...v3.15.2 + +## v3.15.1 - 2025-02-24 + +### What's Changed + +* Hide more empty tabs by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1742 +* Always show application by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1745 +* Add conflict with old debugbar by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1746 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.15.0...v3.15.1 + +## v3.15.0 - 2025-02-21 + +### What's Changed + +* Add middleware to web to save session by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1710 +* Check web middleware by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1712 +* Add special `dev` to composer keywords by @jnoordsij in https://github.com/barryvdh/laravel-debugbar/pull/1713 +* Removed extra sentence by @cheack in https://github.com/barryvdh/laravel-debugbar/pull/1714 +* Hide empty tabs by default by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1711 +* Combine route info with Request by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1720 +* fix: The log is not processed correctly when it consists of multiple lines. by @uniho in https://github.com/barryvdh/laravel-debugbar/pull/1721 +* [WIP] Use php-debugbar dark theme, move to variables by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1717 +* Remove openhandler overrides by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1723 +* Drop Lumen And Laravel 9 by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1725 +* Use tooltip for Laravel collector by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1724 +* Add more data to timeline by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1726 +* Laravel version preview as repo branch name by @angeljqv in https://github.com/barryvdh/laravel-debugbar/pull/1727 +* Laravel 12 support by @jonnott in https://github.com/barryvdh/laravel-debugbar/pull/1730 +* Preview action_name on request tooltip by @angeljqv in https://github.com/barryvdh/laravel-debugbar/pull/1728 +* Map tooltips by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1732 +* Add back L9 by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1734 +* Fix tooltip url by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1735 +* Show request status as badge by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1736 +* Fix request badge by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1737 +* Use Laravel ULID for key by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1738 +* defer datasets by config option by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1739 +* Reorder request tab by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1740 +* Defer config by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1741 + +### New Contributors + +* @cheack made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1714 +* @angeljqv made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1727 +* @jonnott made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1730 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.14.10...v3.15.0 + +## v3.14.10 - 2024-12-23 + +### What's Changed + +* Fix Debugbar spelling inconsistencies by @ralphjsmit in https://github.com/barryvdh/laravel-debugbar/pull/1626 +* Fix Visual Explain confirm message by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1709 + +### New Contributors + +* @ralphjsmit made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1626 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.14.9...v3.14.10 + +## v3.14.9 - 2024-11-25 + +### What's Changed + +* Fix custom prototype array by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1706 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.14.8...v3.14.9 + +## v3.14.8 - 2024-11-25 + +### What's Changed + +* Add fix + failing test for custom array prototype by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1705 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.14.7...v3.14.8 + +## v3.14.7 - 2024-11-14 + +### What's Changed + +* Make better use of query tab space by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1694 +* Do not open query details on text selecting by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1693 +* Add (initial) support for PHP 8.4 by @jnoordsij in https://github.com/barryvdh/laravel-debugbar/pull/1631 +* More warnings by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1696 +* Fix sql-duplicate highlight by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1699 +* ci: Use GitHub Actions V4 by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1700 +* Fix "Uncaught TypeError: is not iterable" by @erikn69 in https://github.com/barryvdh/laravel-debugbar/pull/1701 +* Fix Exception when QueryCollector softLimit exceeded by @johnkary in https://github.com/barryvdh/laravel-debugbar/pull/1702 +* Test soft/hard limit queries by @barryvdh in https://github.com/barryvdh/laravel-debugbar/pull/1703 + +### New Contributors + +* @johnkary made their first contribution in https://github.com/barryvdh/laravel-debugbar/pull/1702 + +**Full Changelog**: https://github.com/barryvdh/laravel-debugbar/compare/v3.14.6...v3.14.7 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..c6138bc29 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to `barryvdh@gmail.com` diff --git a/changelog.md b/changelog.md deleted file mode 100644 index 072b56141..000000000 --- a/changelog.md +++ /dev/null @@ -1,86 +0,0 @@ -# Changelog for Laravel Debugbar - -## 1.8.4 (2014-10-31) - -- Add Redis/PDO storage options - -## 1.8.3 (2014-11-23) - -- Base EventCollector on TimeData Collector - -## 1.8.2 (2014-11-18) - -- Use XHR handler instead of jQuery handler - -## 1.8.1 (2014-11-14) - -- Fix compatability with Symfony 2.3 (Laravel 4.) - -## 1.8.0 (2014-10-31) - -- Fix L5 compatability -- add hints + explain options to QueryLogger -- update to Debugbar 1.10.x -- new ViewCollector layout with more information - -## 1.7.7 (2014-09-15) - -- Make it compatible with Laravel 5.0-dev -- Allow anonymous function as `enabled` setting (for IP checks etc) -- Escape query bindings, to prevent executing of scripts/html - -## 1.7.6 (2014-09-12) - -- Fix reflash bug -- Fix caching of debugbar assets - -## 1.7.5 (2014-09-12) - -- Reflash data for all debugbar requests - -## 1.7.4 (2014-09-08) - -- Rename assets routes to prevent Nginx conflicts - -## 1.7.3 (2014-09-05) - -- Add helper functions (debug(), add/start/stop_measure() and measure() -- Collect data on responses that are not redirect/ajax/html also. - -## 1.7.2 (2014-09-04) - -- Fix 4.0 compatibility (problem with Controller namespace) -- Give deprecation notice instead of publishing assets. - -## 1.7.1 (2014-09-03) - -- Deprecated `debugbar:publish` command in favor of AssetController -- Fixed issue with detecting absolute paths in Windows - -## 1.7.0 (2014-09-03) - -- Use AssetController instead of publishing assets to the public folder. -- Inline fonts + images to base64 Data-URI -- Use PSR-4 file structure - -## 1.6.8 (2014-08-27) - -- Change OpenHandler layout -- Add backtrace option for query origin - -## 1.6.7 (2014-08-09) - -- Add Twig extensions for better integration with rcrowe/TwigBridge - -## 1.6.6 (2014-07-08) - -- Check if Requests wantsJSON instead of only isXmlHttpRequest -- Make sure closure for timing is run, even when disabled - -## 1.6.5 (2014-06-24) - -- Add Laravel style - -## 1.6.4 (2014-06-15) - -- Work on non-UTF-8 handling \ No newline at end of file diff --git a/composer.json b/composer.json index bb508768e..622cea34b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,14 @@ { "name": "barryvdh/laravel-debugbar", "description": "PHP Debugbar integration for Laravel", - "keywords": ["laravel", "debugbar", "profiler", "debug", "webprofiler"], + "keywords": [ + "laravel", + "debugbar", + "profiler", + "debug", + "webprofiler", + "dev" + ], "license": "MIT", "authors": [ { @@ -10,17 +17,17 @@ } ], "require": { - "php": ">=7.2", - "maximebf/debugbar": "^1.16.3", - "illuminate/routing": "^6|^7|^8", - "illuminate/session": "^6|^7|^8", - "illuminate/support": "^6|^7|^8", - "symfony/debug": "^4.3|^5", - "symfony/finder": "^4.3|^5" + "php": "^8.1", + "php-debugbar/php-debugbar": "^2.2.4", + "illuminate/routing": "^9|^10|^11|^12", + "illuminate/session": "^9|^10|^11|^12", + "illuminate/support": "^9|^10|^11|^12", + "symfony/finder": "^6|^7" }, "require-dev": { - "orchestra/testbench-dusk": "^4|^5|^6", - "phpunit/phpunit": "^8.5|^9.0", + "mockery/mockery": "^1.3.3", + "orchestra/testbench-dusk": "^7|^8|^9|^10", + "phpunit/phpunit": "^9.5.10|^10|^11", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { @@ -40,20 +47,20 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.16-dev" }, "laravel": { "providers": [ "Barryvdh\\Debugbar\\ServiceProvider" ], "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facade" + "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" } } }, "scripts": { - "check-style": "phpcs -p --standard=PSR12 config/ src/ tests/", - "fix-style": "phpcbf -p --standard=PSR12 config/ src/ tests/", + "check-style": "phpcs", + "fix-style": "phpcbf", "test": "phpunit" } } diff --git a/config/debugbar.php b/config/debugbar.php index ada83c6d3..2d86208c3 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -15,6 +15,7 @@ */ 'enabled' => env('DEBUGBAR_ENABLED', null), + 'hide_empty_tabs' => env('DEBUGBAR_HIDE_EMPTY_TABS', true), // Hide tabs until they have content 'except' => [ 'telescope*', 'horizon*', @@ -25,21 +26,70 @@ | Storage settings |-------------------------------------------------------------------------- | - | DebugBar stores data for session/ajax requests. + | Debugbar stores data for session/ajax requests. | You can disable this, so the debugbar stores data in headers/session, | but this can cause problems with large data collectors. | By default, file storage (in the storage folder) is used. Redis and PDO | can also be used. For PDO, run the package migrations first. | + | Warning: Enabling storage.open will allow everyone to access previous + | request, do not enable open storage in publicly available environments! + | Specify a callback if you want to limit based on IP or authentication. + | Leaving it to null will allow localhost only. */ 'storage' => [ - 'enabled' => true, - 'driver' => 'file', // redis, file, pdo, custom - 'path' => storage_path('debugbar'), // For file driver - 'connection' => null, // Leave null for default connection (Redis/PDO) - 'provider' => '', // Instance of StorageInterface for custom driver + 'enabled' => env('DEBUGBAR_STORAGE_ENABLED', true), + 'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback. + 'driver' => env('DEBUGBAR_STORAGE_DRIVER', 'file'), // redis, file, pdo, socket, custom + 'path' => env('DEBUGBAR_STORAGE_PATH', storage_path('debugbar')), // For file driver + 'connection' => env('DEBUGBAR_STORAGE_CONNECTION', null), // Leave null for default connection (Redis/PDO) + 'provider' => env('DEBUGBAR_STORAGE_PROVIDER', ''), // Instance of StorageInterface for custom driver + 'hostname' => env('DEBUGBAR_STORAGE_HOSTNAME', '127.0.0.1'), // Hostname to use with the "socket" driver + 'port' => env('DEBUGBAR_STORAGE_PORT', 2304), // Port to use with the "socket" driver ], + /* + |-------------------------------------------------------------------------- + | Editor + |-------------------------------------------------------------------------- + | + | Choose your preferred editor to use when clicking file name. + | + | Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote", + | "vscode-insiders-remote", "vscodium", "textmate", "emacs", + | "sublime", "atom", "nova", "macvim", "idea", "netbeans", + | "xdebug", "espresso" + | + */ + + 'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'), + + /* + |-------------------------------------------------------------------------- + | Remote Path Mapping + |-------------------------------------------------------------------------- + | + | If you are using a remote dev server, like Laravel Homestead, Docker, or + | even a remote VPS, it will be necessary to specify your path mapping. + | + | Leaving one, or both of these, empty or null will not trigger the remote + | URL changes and Debugbar will treat your editor links as local files. + | + | "remote_sites_path" is an absolute base path for your sites or projects + | in Homestead, Vagrant, Docker, or another remote development server. + | + | Example value: "/home/vagrant/Code" + | + | "local_sites_path" is an absolute base path for your sites or projects + | on your local computer where your IDE or code editor is running on. + | + | Example values: "/Users//Code", "C:\Users\\Documents\Code" + | + */ + + 'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'), + 'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')), + /* |-------------------------------------------------------------------------- | Vendors @@ -48,13 +98,13 @@ | Vendor files are included by default, but can be set to false. | This can also be set to 'js' or 'css', to only include javascript or css vendor files. | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) - | and for js: jquery and and highlight.js + | and for js: jquery and highlight.js | So if you want syntax highlighting, set it to true. | jQuery is set to not conflict with existing jQuery scripts. | */ - 'include_vendors' => true, + 'include_vendors' => env('DEBUGBAR_INCLUDE_VENDORS', true), /* |-------------------------------------------------------------------------- @@ -65,11 +115,21 @@ | you can use this option to disable sending the data through the headers. | | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. + | + | Note for your request to be identified as ajax requests they must either send the header + | X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header. + | + | By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar. + | Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading. + | + | You can defer loading the dataset, so it will be loaded with ajax after the request is done. (Experimental) */ - 'capture_ajax' => true, - 'add_ajax_timing' => false, - + 'capture_ajax' => env('DEBUGBAR_CAPTURE_AJAX', true), + 'add_ajax_timing' => env('DEBUGBAR_ADD_AJAX_TIMING', false), + 'ajax_handler_auto_show' => env('DEBUGBAR_AJAX_HANDLER_AUTO_SHOW', true), + 'ajax_handler_enable_tab' => env('DEBUGBAR_AJAX_HANDLER_ENABLE_TAB', true), + 'defer_datasets' => env('DEBUGBAR_DEFER_DATASETS', false), /* |-------------------------------------------------------------------------- | Custom Error Handler for Deprecated warnings @@ -79,7 +139,7 @@ | in the Messages tab. | */ - 'error_handler' => false, + 'error_handler' => env('DEBUGBAR_ERROR_HANDLER', false), /* |-------------------------------------------------------------------------- @@ -90,7 +150,7 @@ | Extension, without the server-side code. It uses Debugbar collectors instead. | */ - 'clockwork' => false, + 'clockwork' => env('DEBUGBAR_CLOCKWORK', false), /* |-------------------------------------------------------------------------- @@ -102,29 +162,31 @@ */ 'collectors' => [ - 'phpinfo' => true, // Php version - 'messages' => true, // Messages - 'time' => true, // Time Datalogger - 'memory' => true, // Memory usage - 'exceptions' => true, // Exception displayer - 'log' => true, // Logs from Monolog (merged in messages if enabled) - 'db' => true, // Show database (PDO) queries and bindings - 'views' => true, // Views with their data - 'route' => true, // Current route information - 'auth' => false, // Display Laravel authentication status - 'gate' => true, // Display Laravel Gate checks - 'session' => true, // Display session data - 'symfony_request' => true, // Only one can be enabled.. - 'mail' => true, // Catch mail messages - 'laravel' => false, // Laravel version and environment - 'events' => false, // All events fired - 'default_request' => false, // Regular or special Symfony request logger - 'logs' => false, // Add the latest log messages - 'files' => false, // Show the included files - 'config' => false, // Display config settings - 'cache' => false, // Display cache events - 'models' => true, // Display models - 'livewire' => true, // Display Livewire (when available) + 'phpinfo' => env('DEBUGBAR_COLLECTORS_PHPINFO', false), // Php version + 'messages' => env('DEBUGBAR_COLLECTORS_MESSAGES', true), // Messages + 'time' => env('DEBUGBAR_COLLECTORS_TIME', true), // Time Datalogger + 'memory' => env('DEBUGBAR_COLLECTORS_MEMORY', true), // Memory usage + 'exceptions' => env('DEBUGBAR_COLLECTORS_EXCEPTIONS', true), // Exception displayer + 'log' => env('DEBUGBAR_COLLECTORS_LOG', true), // Logs from Monolog (merged in messages if enabled) + 'db' => env('DEBUGBAR_COLLECTORS_DB', true), // Show database (PDO) queries and bindings + 'views' => env('DEBUGBAR_COLLECTORS_VIEWS', true), // Views with their data + 'route' => env('DEBUGBAR_COLLECTORS_ROUTE', false), // Current route information + 'auth' => env('DEBUGBAR_COLLECTORS_AUTH', false), // Display Laravel authentication status + 'gate' => env('DEBUGBAR_COLLECTORS_GATE', true), // Display Laravel Gate checks + 'session' => env('DEBUGBAR_COLLECTORS_SESSION', false), // Display session data + 'symfony_request' => env('DEBUGBAR_COLLECTORS_SYMFONY_REQUEST', true), // Only one can be enabled.. + 'mail' => env('DEBUGBAR_COLLECTORS_MAIL', true), // Catch mail messages + 'laravel' => env('DEBUGBAR_COLLECTORS_LARAVEL', true), // Laravel version and environment + 'events' => env('DEBUGBAR_COLLECTORS_EVENTS', false), // All events fired + 'default_request' => env('DEBUGBAR_COLLECTORS_DEFAULT_REQUEST', false), // Regular or special Symfony request logger + 'logs' => env('DEBUGBAR_COLLECTORS_LOGS', false), // Add the latest log messages + 'files' => env('DEBUGBAR_COLLECTORS_FILES', false), // Show the included files + 'config' => env('DEBUGBAR_COLLECTORS_CONFIG', false), // Display config settings + 'cache' => env('DEBUGBAR_COLLECTORS_CACHE', false), // Display cache events + 'models' => env('DEBUGBAR_COLLECTORS_MODELS', true), // Display models + 'livewire' => env('DEBUGBAR_COLLECTORS_LIVEWIRE', true), // Display Livewire (when available) + 'jobs' => env('DEBUGBAR_COLLECTORS_JOBS', false), // Display dispatched jobs + 'pennant' => env('DEBUGBAR_COLLECTORS_PENNANT', false), // Display Pennant feature flags ], /* @@ -137,35 +199,77 @@ */ 'options' => [ + 'time' => [ + 'memory_usage' => env('DEBUGBAR_OPTIONS_TIME_MEMORY_USAGE', false), // Calculated by subtracting memory start and end, it may be inaccurate + ], + 'messages' => [ + 'trace' => env('DEBUGBAR_OPTIONS_MESSAGES_TRACE', true), // Trace the origin of the debug message + 'capture_dumps' => env('DEBUGBAR_OPTIONS_MESSAGES_CAPTURE_DUMPS', false), // Capture laravel `dump();` as message + ], + 'memory' => [ + 'reset_peak' => env('DEBUGBAR_OPTIONS_MEMORY_RESET_PEAK', false), // run memory_reset_peak_usage before collecting + 'with_baseline' => env('DEBUGBAR_OPTIONS_MEMORY_WITH_BASELINE', false), // Set boot memory usage as memory peak baseline + 'precision' => (int) env('DEBUGBAR_OPTIONS_MEMORY_PRECISION', 0), // Memory rounding precision + ], 'auth' => [ - 'show_name' => true, // Also show the users name/email in the debugbar + 'show_name' => env('DEBUGBAR_OPTIONS_AUTH_SHOW_NAME', true), // Also show the users name/email in the debugbar + 'show_guards' => env('DEBUGBAR_OPTIONS_AUTH_SHOW_GUARDS', true), // Show the guards that are used + ], + 'gate' => [ + 'trace' => false, // Trace the origin of the Gate checks ], 'db' => [ - 'with_params' => true, // Render SQL with the parameters substituted - 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. + 'with_params' => env('DEBUGBAR_OPTIONS_WITH_PARAMS', true), // Render SQL with the parameters substituted + 'exclude_paths' => [ // Paths to exclude entirely from the collector + //'vendor/laravel/framework/src/Illuminate/Session', // Exclude sessions queries + ], + 'backtrace' => env('DEBUGBAR_OPTIONS_DB_BACKTRACE', true), // Use a backtrace to find the origin of the query in your files. 'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults) - 'timeline' => false, // Add the queries to the timeline + 'timeline' => env('DEBUGBAR_OPTIONS_DB_TIMELINE', false), // Add the queries to the timeline + 'duration_background' => env('DEBUGBAR_OPTIONS_DB_DURATION_BACKGROUND', true), // Show shaded background on each query relative to how long it took to execute. 'explain' => [ // Show EXPLAIN output on queries - 'enabled' => false, - 'types' => ['SELECT'], // Deprecated setting, is always only SELECT + 'enabled' => env('DEBUGBAR_OPTIONS_DB_EXPLAIN_ENABLED', false), ], - 'hints' => false, // Show hints for common mistakes - 'show_copy' => false, // Show copy button next to the query + 'hints' => env('DEBUGBAR_OPTIONS_DB_HINTS', false), // Show hints for common mistakes + 'show_copy' => env('DEBUGBAR_OPTIONS_DB_SHOW_COPY', true), // Show copy button next to the query, + 'only_slow_queries' => env('DEBUGBAR_OPTIONS_DB_ONLY_SLOW_QUERIES', true), // Only track queries that last longer than `slow_threshold` + 'slow_threshold' => env('DEBUGBAR_OPTIONS_DB_SLOW_THRESHOLD', false), // Max query execution time (ms). Exceeding queries will be highlighted + 'memory_usage' => env('DEBUGBAR_OPTIONS_DB_MEMORY_USAGE', false), // Show queries memory usage + 'soft_limit' => (int) env('DEBUGBAR_OPTIONS_DB_SOFT_LIMIT', 100), // After the soft limit, no parameters/backtrace are captured + 'hard_limit' => (int) env('DEBUGBAR_OPTIONS_DB_HARD_LIMIT', 500), // After the hard limit, queries are ignored ], 'mail' => [ - 'full_log' => false, + 'timeline' => env('DEBUGBAR_OPTIONS_MAIL_TIMELINE', true), // Add mails to the timeline + 'show_body' => env('DEBUGBAR_OPTIONS_MAIL_SHOW_BODY', true), ], 'views' => [ - 'data' => false, //Note: Can slow down the application, because the data can be quite large.. + 'timeline' => env('DEBUGBAR_OPTIONS_VIEWS_TIMELINE', true), // Add the views to the timeline + 'data' => env('DEBUGBAR_OPTIONS_VIEWS_DATA', false), // True for all data, 'keys' for only names, false for no parameters. + 'group' => (int) env('DEBUGBAR_OPTIONS_VIEWS_GROUP', 50), // Group duplicate views. Pass value to auto-group, or true/false to force + 'inertia_pages' => env('DEBUGBAR_OPTIONS_VIEWS_INERTIA_PAGES', 'js/Pages'), // Path for Inertia views + 'exclude_paths' => [ // Add the paths which you don't want to appear in the views + 'vendor/filament' // Exclude Filament components by default + ], ], 'route' => [ - 'label' => true, // show complete route on bar + 'label' => env('DEBUGBAR_OPTIONS_ROUTE_LABEL', true), // Show complete route on bar + ], + 'session' => [ + 'hiddens' => [], // Hides sensitive values using array paths + ], + 'symfony_request' => [ + 'label' => env('DEBUGBAR_OPTIONS_SYMFONY_REQUEST_LABEL', true), // Show route on bar + 'hiddens' => [], // Hides sensitive values using array paths, example: request_request.password + ], + 'events' => [ + 'data' => env('DEBUGBAR_OPTIONS_EVENTS_DATA', false), // Collect events data, listeners + 'excluded' => [], // Example: ['eloquent.*', 'composing', Illuminate\Cache\Events\CacheHit::class] ], 'logs' => [ - 'file' => null, + 'file' => env('DEBUGBAR_OPTIONS_LOGS_FILE', null), ], 'cache' => [ - 'values' => true, // collect cache values + 'values' => env('DEBUGBAR_OPTIONS_CACHE_VALUES', true), // Collect cache values ], ], @@ -180,37 +284,56 @@ | */ - 'inject' => true, + 'inject' => env('DEBUGBAR_INJECT', true), /* |-------------------------------------------------------------------------- - | DebugBar route prefix + | Debugbar route prefix |-------------------------------------------------------------------------- | - | Sometimes you want to set route prefix to be used by DebugBar to load + | Sometimes you want to set route prefix to be used by Debugbar to load | its resources from. Usually the need comes from misconfigured web server or | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 | */ - 'route_prefix' => '_debugbar', + 'route_prefix' => env('DEBUGBAR_ROUTE_PREFIX', '_debugbar'), /* |-------------------------------------------------------------------------- - | DebugBar route domain + | Debugbar route middleware |-------------------------------------------------------------------------- | - | By default DebugBar route served from the same domain that request served. + | Additional middleware to run on the Debugbar routes + */ + 'route_middleware' => [], + + /* + |-------------------------------------------------------------------------- + | Debugbar route domain + |-------------------------------------------------------------------------- + | + | By default Debugbar route served from the same domain that request served. | To override default domain, specify it as a non-empty value. */ - 'route_domain' => null, + 'route_domain' => env('DEBUGBAR_ROUTE_DOMAIN', null), /* |-------------------------------------------------------------------------- - | DebugBar theme + | Debugbar theme |-------------------------------------------------------------------------- | | Switches between light and dark theme. If set to auto it will respect system preferences | Possible values: auto, light, dark */ - 'theme' => 'auto', + 'theme' => env('DEBUGBAR_THEME', 'auto'), + + /* + |-------------------------------------------------------------------------- + | Backtrace stack limit + |-------------------------------------------------------------------------- + | + | By default, the Debugbar limits the number of frames returned by the 'debug_backtrace()' function. + | If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit. + */ + 'debug_backtrace_limit' => (int) env('DEBUGBAR_DEBUG_BACKTRACE_LIMIT', 50), ]; diff --git a/database/migrations/2014_12_01_120000_create_phpdebugbar_storage_table.php b/database/migrations/2014_12_01_120000_create_phpdebugbar_storage_table.php index 7e2637cee..90daa081e 100644 --- a/database/migrations/2014_12_01_120000_create_phpdebugbar_storage_table.php +++ b/database/migrations/2014_12_01_120000_create_phpdebugbar_storage_table.php @@ -4,14 +4,12 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreatePhpdebugbarStorageTable extends Migration +return new class extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('phpdebugbar', function (Blueprint $table) { $table->string('id'); @@ -30,13 +28,12 @@ public function up() $table->index('meta_method'); }); } + /** * Reverse the migrations. - * - * @return void */ public function down() { Schema::drop('phpdebugbar'); } -} +}; diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 000000000..2f3b8fe03 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,16 @@ + + + config + src + tests + + src/Resources/* + *.blade.php + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 11da2e94f..182cfd815 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -28,5 +28,7 @@ + + diff --git a/readme.md b/readme.md index ed4291a9e..edb7d523f 100644 --- a/readme.md +++ b/readme.md @@ -1,19 +1,23 @@ -## Laravel Debugbar +## Debugbar for Laravel ![Unit Tests](https://github.com/barryvdh/laravel-debugbar/workflows/Unit%20Tests/badge.svg) -[![Packagist License](https://poser.pugx.org/barryvdh/laravel-debugbar/license.png)](http://choosealicense.com/licenses/mit/) -[![Latest Stable Version](https://poser.pugx.org/barryvdh/laravel-debugbar/version.png)](https://packagist.org/packages/barryvdh/laravel-debugbar) -[![Total Downloads](https://poser.pugx.org/barryvdh/laravel-debugbar/d/total.png)](https://packagist.org/packages/barryvdh/laravel-debugbar) +[![Packagist License](https://img.shields.io/badge/Licence-MIT-blue)](http://choosealicense.com/licenses/mit/) +[![Latest Stable Version](https://img.shields.io/packagist/v/barryvdh/laravel-debugbar?label=Stable)](https://packagist.org/packages/barryvdh/laravel-debugbar) +[![Total Downloads](https://img.shields.io/packagist/dt/barryvdh/laravel-debugbar?label=Downloads)](https://packagist.org/packages/barryvdh/laravel-debugbar) +[![Fruitcake](https://img.shields.io/badge/Powered%20By-Fruitcake-b2bc35.svg)](https://fruitcake.nl/) -This is a package to integrate [PHP Debug Bar](http://phpdebugbar.com/) with Laravel. +This is a package to integrate [PHP Debug Bar](https://github.com/php-debugbar/php-debugbar) with Laravel. It includes a ServiceProvider to register the debugbar and attach it to the output. You can publish assets and configure it through Laravel. It bootstraps some Collectors to work with Laravel and implements a couple custom DataCollectors, specific for Laravel. -It is configured to display Redirects and (jQuery) Ajax Requests. (Shown in a dropdown) +It is configured to display Redirects and Ajax/Livewire Requests. (Shown in a dropdown) Read [the documentation](http://phpdebugbar.com/docs/) for more configuration options. -![Debugbar 3.3 Screenshot](https://user-images.githubusercontent.com/973269/79428890-196cc680-7fc7-11ea-8229-189f5eac9009.png) +![Debugbar Dark Mode screenshot](https://github.com/barryvdh/laravel-debugbar/assets/973269/6600837a-8b2d-4acb-ab0c-158c9ca5439c) +> [!CAUTION] +> Use the DebugBar only in development. Do not use Debugbar on publicly accessible websites, as it will leak information from stored requests (by design). -Note: Use the DebugBar only in development. It can slow the application down (because it has to gather data). So when experiencing slowness, try disabling some of the collectors. +> [!WARNING] +> It can also slow the application down (because it has to gather and render data). So when experiencing slowness, try disabling some of the collectors. This package includes some custom collectors: - QueryCollector: Show all queries, including binding + timing @@ -29,7 +33,7 @@ This package includes some custom collectors: Bootstraps the following collectors for Laravel: - LogCollector: Show all Log messages - - SwiftMailCollector and SwiftLogCollector for Mail + - SymfonyMailCollector for Mail And the default collectors: - PhpInfoCollector @@ -38,7 +42,7 @@ And the default collectors: - MemoryCollector - ExceptionsCollector -It also provides a Facade interface for easy logging Messages, Exceptions and Time +It also provides a facade interface (`Debugbar`) for easy logging Messages, Exceptions and Time ## Installation @@ -56,16 +60,20 @@ The Debugbar will be enabled when `APP_DEBUG` is `true`. ### Laravel without auto-discovery: -If you don't use auto-discovery, add the ServiceProvider to the providers array in config/app.php +If you don't use auto-discovery, add the ServiceProvider to the providers list. For Laravel 11 or newer, add the ServiceProvider in bootstrap/providers.php. For Laravel 10 or older, add the ServiceProvider in config/app.php. ```php Barryvdh\Debugbar\ServiceProvider::class, ``` -If you want to use the facade to log messages, add this to your facades in app.php: +If you want to use the facade to log messages, add this within the `register` method of `app/Providers/AppServiceProvider.php` class: ```php -'Debugbar' => Barryvdh\Debugbar\Facade::class, +public function register(): void +{ + $loader = \Illuminate\Foundation\AliasLoader::getInstance(); + $loader->alias('Debugbar', \Barryvdh\Debugbar\Facades\Debugbar::class); +} ``` The profiler is enabled by default, if you have APP_DEBUG=true. You can override that in the config (`debugbar.enabled`) or by setting `DEBUGBAR_ENABLED` in your `.env`. See more options in `config/debugbar.php` @@ -78,6 +86,16 @@ You can also only display the js or css vendors, by setting it to 'js' or 'css'. php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider" ``` +### Laravel with Octane: + +Make sure to add LaravelDebugbar to your flush list in `config/octane.php`. + +```php + 'flush' => [ + \Barryvdh\Debugbar\LaravelDebugbar::class, + ], +``` + ### Lumen: For Lumen, register a different Provider in `bootstrap/app.php`: @@ -172,6 +190,12 @@ You can enable or disable the debugbar during run time. NB. Once enabled, the collectors are added (and could produce extra overhead), so if you want to use the debugbar in production, disable in the config and only enable when needed. +## Storage + +Debugbar remembers previous requests, which you can view using the Browse button on the right. This will only work if you enable `debugbar.storage.open` in the config. +Make sure you only do this on local development, because otherwise other people will be able to view previous requests. +In general, Debugbar should only be used locally or at least restricted by IP. +It's possible to pass a callback, which will receive the Request object, so you can determine access to the OpenHandler storage. ## Twig Integration @@ -200,3 +224,7 @@ The Stopwatch extension adds a [stopwatch tag](http://symfony.com/blog/new-in-sy …some things that gets timed {% endstopwatch %} ``` + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=barryvdh/laravel-debugbar&type=Date)](https://www.star-history.com/#barryvdh/laravel-debugbar&Date) diff --git a/src/Controllers/OpenHandlerController.php b/src/Controllers/OpenHandlerController.php index c2e95b300..4b7449e0c 100644 --- a/src/Controllers/OpenHandlerController.php +++ b/src/Controllers/OpenHandlerController.php @@ -3,16 +3,60 @@ namespace Barryvdh\Debugbar\Controllers; use Barryvdh\Debugbar\Support\Clockwork\Converter; +use DebugBar\DebugBarException; use DebugBar\OpenHandler; +use Illuminate\Http\Request; use Illuminate\Http\Response; class OpenHandlerController extends BaseController { + /** + * Check if the storage is open for inspecting. + * + * @param Request $request + * @return bool + */ + protected function isStorageOpen(Request $request) + { + $open = config('debugbar.storage.open'); + + if (is_callable($open)) { + return call_user_func($open, [$request]); + } + + if (is_string($open) && class_exists($open)) { + return method_exists($open, 'resolve') ? $open::resolve($request) : false; + } - public function handle() + if (is_bool($open)) { + return $open; + } + + // Allow localhost request when not explicitly allowed/disallowed + if (in_array($request->ip(), ['127.0.0.1', '::1'], true)) { + return true; + } + + return false; + } + + public function handle(Request $request) { - $openHandler = new OpenHandler($this->debugbar); - $data = $openHandler->handle(null, false, false); + if ($request->input('op') === 'get' || $this->isStorageOpen($request)) { + $openHandler = new OpenHandler($this->debugbar); + $data = $openHandler->handle($request->input(), false, false); + } else { + $data = [ + [ + 'datetime' => date("Y-m-d H:i:s"), + 'id' => null, + 'ip' => $request->getClientIp(), + 'method' => 'ERROR', + 'uri' => '!! To enable public access to previous requests, set debugbar.storage.open to true in your config, or enable DEBUGBAR_OPEN_STORAGE if you did not publish the config. !!', + 'utime' => microtime(true), + ] + ]; + } return new Response( $data, @@ -30,7 +74,7 @@ public function handle() * @return mixed * @throws \DebugBar\DebugBarException */ - public function clockwork($id) + public function clockwork(Request $request, $id) { $request = [ 'op' => 'get', diff --git a/src/Controllers/QueriesController.php b/src/Controllers/QueriesController.php new file mode 100644 index 000000000..314a9b1c2 --- /dev/null +++ b/src/Controllers/QueriesController.php @@ -0,0 +1,47 @@ +json([ + 'success' => false, + 'message' => 'EXPLAIN is currently disabled in the Debugbar.', + ], 400); + } + + try { + $explain = new Explain(); + + if ($request->json('mode') === 'visual') { + return response()->json([ + 'success' => true, + 'data' => $explain->generateVisualExplain($request->json('connection'), $request->json('query'), $request->json('bindings'), $request->json('hash')), + ]); + } + + return response()->json([ + 'success' => true, + 'data' => $explain->generateRawExplain($request->json('connection'), $request->json('query'), $request->json('bindings'), $request->json('hash')), + 'visual' => $explain->isVisualExplainSupported($request->json('connection')) ? [ + 'confirm' => $explain->confirmVisualExplain($request->json('connection')), + ] : null, + ]); + } catch (Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } +} diff --git a/src/Controllers/TelescopeController.php b/src/Controllers/TelescopeController.php index dcc4d2f6c..5fb9943bb 100644 --- a/src/Controllers/TelescopeController.php +++ b/src/Controllers/TelescopeController.php @@ -12,13 +12,12 @@ class TelescopeController extends BaseController { - public function show(EntriesRepository $storage, $uuid) { $entry = $storage->find($uuid); $result = $storage->get('request', (new EntryQueryOptions())->batchId($entry->batchId))->first(); - return redirect(config('telescope.path') . '/requests/' . $result->id); + return redirect(config('telescope.domain') . '/' . config('telescope.path') . '/requests/' . $result->id); } } diff --git a/src/DataCollector/CacheCollector.php b/src/DataCollector/CacheCollector.php index 19d0a3a12..059514725 100644 --- a/src/DataCollector/CacheCollector.php +++ b/src/DataCollector/CacheCollector.php @@ -3,50 +3,71 @@ namespace Barryvdh\Debugbar\DataCollector; use DebugBar\DataCollector\TimeDataCollector; -use Illuminate\Cache\Events\CacheEvent; -use Illuminate\Cache\Events\CacheHit; -use Illuminate\Cache\Events\CacheMissed; -use Illuminate\Cache\Events\KeyForgotten; -use Illuminate\Cache\Events\KeyWritten; +use DebugBar\DataFormatter\HasDataFormatter; +use Illuminate\Cache\Events\{ + CacheFlushed, + CacheFlushFailed, + CacheFlushing, + CacheHit, + CacheMissed, + ForgettingKey, + KeyForgetFailed, + KeyForgotten, + KeyWriteFailed, + KeyWritten, + RetrievingKey, + WritingKey, +}; use Illuminate\Events\Dispatcher; class CacheCollector extends TimeDataCollector { + use HasDataFormatter; + /** @var bool */ protected $collectValues; + /** @var array */ + protected $eventStarts = []; + /** @var array */ protected $classMap = [ - CacheHit::class => 'hit', - CacheMissed::class => 'missed', - KeyWritten::class => 'written', - KeyForgotten::class => 'forgotten', + CacheHit::class => ['hit', RetrievingKey::class], + CacheMissed::class => ['missed', RetrievingKey::class], + CacheFlushed::class => ['flushed', CacheFlushing::class], + CacheFlushFailed::class => ['flush_failed', CacheFlushing::class], + KeyWritten::class => ['written', WritingKey::class], + KeyWriteFailed::class => ['write_failed', WritingKey::class], + KeyForgotten::class => ['forgotten', ForgettingKey::class], + KeyForgetFailed::class => ['forget_failed', ForgettingKey::class], ]; public function __construct($requestStartTime, $collectValues) { - parent::__construct(); + parent::__construct($requestStartTime); $this->collectValues = $collectValues; } - public function onCacheEvent(CacheEvent $event) + public function onCacheEvent($event) { $class = get_class($event); $params = get_object_vars($event); - - $label = $this->classMap[$class]; + $label = $this->classMap[$class][0]; if (isset($params['value'])) { if ($this->collectValues) { - $params['value'] = htmlspecialchars($this->getDataFormatter()->formatVar($event->value)); + if ($this->isHtmlVarDumperUsed()) { + $params['value'] = $this->getVarDumper()->renderVar($params['value']); + } else { + $params['value'] = htmlspecialchars($this->getDataFormatter()->formatVar($params['value'])); + } } else { unset($params['value']); } } - - if (!empty($params['key']) && in_array($label, ['hit', 'written'])) { + if (!empty($params['key'] ?? null) && in_array($label, ['hit', 'written'])) { $params['delete'] = route('debugbar.cache.delete', [ 'key' => urlencode($params['key']), 'tags' => !empty($params['tags']) ? json_encode($params['tags']) : '', @@ -54,21 +75,44 @@ public function onCacheEvent(CacheEvent $event) } $time = microtime(true); - $this->addMeasure($label . "\t" . $event->key, $time, $time, $params); + $startHashKey = $this->getEventHash($this->classMap[$class][1] ?? '', $params); + $startTime = $this->eventStarts[$startHashKey] ?? $time; + $this->addMeasure($label . "\t" . ($params['key'] ?? ''), $startTime, $time, $params); + } + + public function onStartCacheEvent($event) + { + $startHashKey = $this->getEventHash(get_class($event), get_object_vars($event)); + $this->eventStarts[$startHashKey] = microtime(true); } + private function getEventHash(string $class, array $params): string + { + unset($params['value']); + + return $class . ':' . substr(hash('sha256', json_encode($params)), 0, 12); + } public function subscribe(Dispatcher $dispatcher) { - foreach ($this->classMap as $eventClass => $type) { + foreach (array_keys($this->classMap) as $eventClass) { $dispatcher->listen($eventClass, [$this, 'onCacheEvent']); } + + $startEvents = array_unique(array_filter(array_map( + fn ($values) => $values[1] ?? null, + array_values($this->classMap) + ))); + + foreach ($startEvents as $eventClass) { + $dispatcher->listen($eventClass, [$this, 'onStartCacheEvent']); + } } public function collect() { $data = parent::collect(); - $data['nb_measures'] = count($data['measures']); + $data['nb_measures'] = $data['count'] = count($data['measures']); return $data; } diff --git a/src/DataCollector/EventCollector.php b/src/DataCollector/EventCollector.php index 07b67af2e..5dfff86ba 100644 --- a/src/DataCollector/EventCollector.php +++ b/src/DataCollector/EventCollector.php @@ -13,20 +13,41 @@ class EventCollector extends TimeDataCollector /** @var Dispatcher */ protected $events; + /** @var Dispatcher */ + protected $excludedEvents; + /** @var integer */ protected $previousTime; - public function __construct($requestStartTime = null) + /** @var bool */ + protected $collectValues; + + public function __construct($requestStartTime = null, $collectValues = false, $excludedEvents = []) { parent::__construct($requestStartTime); - $this->previousTime = microtime(true); + $this->collectValues = $collectValues; + $this->excludedEvents = $excludedEvents; $this->setDataFormatter(new SimpleFormatter()); } public function onWildcardEvent($name = null, $data = []) { - $params = $this->prepareParams($data); $currentTime = microtime(true); + $eventClass = explode(':', $name)[0]; + + foreach ($this->excludedEvents as $excludedEvent) { + if (Str::is($excludedEvent, $eventClass)) { + return; + } + } + + if (! $this->collectValues) { + $this->addMeasure($name, $currentTime, $currentTime, [], null, $eventClass); + + return; + } + + $params = $this->prepareParams($data); // Find all listeners for the current event foreach ($this->events->getListeners($name) as $i => $listener) { @@ -62,8 +83,7 @@ public function onWildcardEvent($name = null, $data = []) $params['listeners.' . $i] = $listener; } - $this->addMeasure($name, $this->previousTime, $currentTime, $params); - $this->previousTime = $currentTime; + $this->addMeasure($name, $currentTime, $currentTime, $params, null, $eventClass); } public function subscribe(Dispatcher $events) @@ -88,7 +108,7 @@ protected function prepareParams($params) public function collect() { $data = parent::collect(); - $data['nb_measures'] = count($data['measures']); + $data['nb_measures'] = $data['count'] = count($data['measures']); return $data; } diff --git a/src/DataCollector/FilesCollector.php b/src/DataCollector/FilesCollector.php index 2371b95e6..f6180f2bf 100644 --- a/src/DataCollector/FilesCollector.php +++ b/src/DataCollector/FilesCollector.php @@ -15,7 +15,7 @@ class FilesCollector extends DataCollector implements Renderable /** * @param \Illuminate\Container\Container $app */ - public function __construct(Container $app = null) + public function __construct(?Container $app = null) { $this->app = $app; $this->basePath = base_path(); @@ -50,7 +50,7 @@ public function collect() } else { $alreadyCompiled[] = [ 'message' => "* '" . $this->stripBasePath($file) . "',", - // Mark with *, so know they are compiled anyways. + // Mark with *, so know they are compiled anyway. 'is_string' => true, ]; } diff --git a/src/DataCollector/GateCollector.php b/src/DataCollector/GateCollector.php index e08235332..ebb5a781e 100644 --- a/src/DataCollector/GateCollector.php +++ b/src/DataCollector/GateCollector.php @@ -7,26 +7,54 @@ use Illuminate\Auth\Access\Response; use Illuminate\Contracts\Auth\Access\Gate; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Routing\Router; use Symfony\Component\VarDumper\Cloner\VarCloner; use Illuminate\Support\Str; /** - * Collector for Laravel's Auth provider + * Collector for Laravel's gate checks */ class GateCollector extends MessagesCollector { + /** @var int */ + protected $backtraceLimit = 15; + + /** @var array */ + protected $reflection = []; + + /** @var \Illuminate\Routing\Router */ + protected $router; + /** * @param Gate $gate */ - public function __construct(Gate $gate) + public function __construct(Gate $gate, Router $router) { parent::__construct('gate'); + $this->router = $router; $this->setDataFormatter(new SimpleFormatter()); $gate->after(function ($user, $ability, $result, $arguments = []) { $this->addCheck($user, $ability, $result, $arguments); }); } + /** + * {@inheritDoc} + */ + protected function customizeMessageHtml($messageHtml, $message) + { + $pos = strpos((string) $messageHtml, 'array:5'); + if ($pos !== false) { + + $name = $message['ability'] .' ' . $message['target'] ?? ''; + + $messageHtml = substr_replace($messageHtml, $name, $pos, 7); + } + + return parent::customizeMessageHtml($messageHtml, $message); + } + public function addCheck($user, $ability, $result, $arguments = []) { $userKey = 'user'; @@ -34,21 +62,121 @@ public function addCheck($user, $ability, $result, $arguments = []) if ($user) { $userKey = Str::snake(class_basename($user)); - $userId = $user instanceof Authenticatable ? $user->getAuthIdentifier() : $user->id; + $userId = $user instanceof Authenticatable ? $user->getAuthIdentifier() : $user->getKey(); } $label = $result ? 'success' : 'error'; - // Response::allowed() was added in Laravel 6.x - if ($result instanceof Response && method_exists($result, 'allowed')) { + if ($result instanceof Response) { $label = $result->allowed() ? 'success' : 'error'; } + $target = null; + if (isset($arguments[0])) { + if ($arguments[0] instanceof Model) { + $model = $arguments[0]; + if ($model->getKeyName() && isset($model[$model->getKeyName()])) { + $target = get_class($model) . '(' . $model->getKeyName() . '=' . $model->getKey() . ')'; + } else { + $target = get_class($model); + } + } else if (is_string($arguments[0])) { + $target = $arguments[0]; + } + } + $this->addMessage([ 'ability' => $ability, + 'target' => $target, 'result' => $result, $userKey => $userId, 'arguments' => $this->getDataFormatter()->formatVar($arguments), ], $label, false); } + + /** + * @param array $stacktrace + * + * @return array + */ + protected function getStackTraceItem($stacktrace) + { + foreach ($stacktrace as $i => $trace) { + if (!isset($trace['file'])) { + continue; + } + + if (str_ends_with($trace['file'], 'Illuminate/Routing/ControllerDispatcher.php')) { + $trace = $this->findControllerFromDispatcher($trace); + } elseif (str_starts_with($trace['file'], storage_path())) { + $hash = pathinfo($trace['file'], PATHINFO_FILENAME); + + if ($file = $this->findViewFromHash($hash)) { + $trace['file'] = $file; + } + } + + if ($this->fileIsInExcludedPath($trace['file'])) { + continue; + } + + return $trace; + } + + return $stacktrace[0]; + } + + /** + * Find the route action file + * + * @param array $trace + * @return array + */ + protected function findControllerFromDispatcher($trace) + { + /** @var \Closure|string|array $action */ + $action = $this->router->current()->getAction('uses'); + + if (is_string($action)) { + [$controller, $method] = explode('@', $action); + + $reflection = new \ReflectionMethod($controller, $method); + $trace['file'] = $reflection->getFileName(); + $trace['line'] = $reflection->getStartLine(); + } elseif ($action instanceof \Closure) { + $reflection = new \ReflectionFunction($action); + $trace['file'] = $reflection->getFileName(); + $trace['line'] = $reflection->getStartLine(); + } + + return $trace; + } + + /** + * Find the template name from the hash. + * + * @param string $hash + * @return null|array + */ + protected function findViewFromHash($hash) + { + $finder = app('view')->getFinder(); + + if (isset($this->reflection['viewfinderViews'])) { + $property = $this->reflection['viewfinderViews']; + } else { + $reflection = new \ReflectionClass($finder); + $property = $reflection->getProperty('views'); + $property->setAccessible(true); + $this->reflection['viewfinderViews'] = $property; + } + + $xxh128Exists = in_array('xxh128', hash_algos()); + + foreach ($property->getValue($finder) as $name => $path) { + if (($xxh128Exists && hash('xxh128', 'v2' . $path) == $hash) || sha1('v2' . $path) == $hash) { + return $path; + } + } + } } diff --git a/src/DataCollector/JobsCollector.php b/src/DataCollector/JobsCollector.php new file mode 100644 index 000000000..10de9f85e --- /dev/null +++ b/src/DataCollector/JobsCollector.php @@ -0,0 +1,63 @@ +listen(\Illuminate\Queue\Events\JobQueued::class, function ($event) { + $class = get_class($event->job); + $this->jobs[$class] = ($this->jobs[$class] ?? 0) + 1; + $this->count++; + }); + } + + public function collect() + { + ksort($this->jobs, SORT_NUMERIC); + + return ['data' => array_reverse($this->jobs), 'count' => $this->count]; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'jobs'; + } + + /** + * {@inheritDoc} + */ + public function getWidgets() + { + return [ + "jobs" => [ + "icon" => "briefcase", + "widget" => "PhpDebugBar.Widgets.HtmlVariableListWidget", + "map" => "jobs.data", + "default" => "{}" + ], + 'jobs:badge' => [ + 'map' => 'jobs.count', + 'default' => 0 + ] + ]; + } +} diff --git a/src/DataCollector/LaravelCollector.php b/src/DataCollector/LaravelCollector.php index 4201058e2..7c9a53a04 100644 --- a/src/DataCollector/LaravelCollector.php +++ b/src/DataCollector/LaravelCollector.php @@ -4,19 +4,17 @@ use DebugBar\DataCollector\DataCollector; use DebugBar\DataCollector\Renderable; +use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Foundation\Application; +use Illuminate\Support\Str; class LaravelCollector extends DataCollector implements Renderable { - /** @var \Illuminate\Foundation\Application $app */ - protected $app; - /** * @param Application $app */ - public function __construct(Application $app = null) + public function __construct(protected ApplicationContract $laravel) { - $this->app = $app; } /** @@ -24,16 +22,21 @@ public function __construct(Application $app = null) */ public function collect() { - // Fallback if not injected - $app = $this->app ?: app(); - return [ - "version" => $app::VERSION, - "environment" => $app->environment(), - "locale" => $app->getLocale(), + "version" => Str::of($this->laravel->version())->explode('.')->first() . '.x', + 'tooltip' => [ + 'Laravel Version' => $this->laravel->version(), + 'PHP Version' => phpversion(), + 'Environment' => $this->laravel->environment(), + 'Debug Mode' => config('app.debug') ? 'Enabled' : 'Disabled', + 'URL' => Str::of(config('app.url'))->replace(['http://', 'https://'], ''), + 'Timezone' => config('app.timezone'), + 'Locale' => config('app.locale'), + ] ]; } + /** * {@inheritDoc} */ @@ -49,22 +52,13 @@ public function getWidgets() { return [ "version" => [ - "icon" => "github", - "tooltip" => "Laravel Version", + "icon" => "laravel phpdebugbar-fab", "map" => "laravel.version", "default" => "" ], - "environment" => [ - "icon" => "desktop", - "tooltip" => "Environment", - "map" => "laravel.environment", - "default" => "" - ], - "locale" => [ - "icon" => "flag", - "tooltip" => "Current locale", - "map" => "laravel.locale", - "default" => "", + "version:tooltip" => [ + "map" => "laravel.tooltip", + "default" => "{}" ], ]; } diff --git a/src/DataCollector/LivewireCollector.php b/src/DataCollector/LivewireCollector.php index fe8d29d86..c70a4201b 100644 --- a/src/DataCollector/LivewireCollector.php +++ b/src/DataCollector/LivewireCollector.php @@ -12,6 +12,7 @@ use Illuminate\Support\Fluent; use Illuminate\Support\Str; use Livewire\Livewire; +use Livewire\Component; /** * Collector for Models. @@ -27,7 +28,7 @@ public function __construct(Request $request) /** @var \Livewire\Component $component */ $component = $view->getData()['_instance']; - // Create an unique name for each compoent + // Create a unique name for each component $key = $component->getName() . ' #' . $component->id; $data = [ @@ -46,6 +47,26 @@ public function __construct(Request $request) $this->data[$key] = $this->formatVar($data); }); + + Livewire::listen('render', function (Component $component) use ($request) { + // Create an unique name for each compoent + $key = $component->getName() . ' #' . $component->getId(); + + $data = [ + 'data' => $component->all(), + ]; + + if ($request->request->get('id') == $component->getId()) { + $data['oldData'] = $request->request->get('data'); + $data['actionQueue'] = $request->request->get('actionQueue'); + } + + $data['name'] = $component->getName(); + $data['component'] = get_class($component); + $data['id'] = $component->getId(); + + $this->data[$key] = $this->formatVar($data); + }); } public function collect() diff --git a/src/DataCollector/LogsCollector.php b/src/DataCollector/LogsCollector.php index 28ed6e14f..1872f767e 100644 --- a/src/DataCollector/LogsCollector.php +++ b/src/DataCollector/LogsCollector.php @@ -3,6 +3,7 @@ namespace Barryvdh\Debugbar\DataCollector; use DebugBar\DataCollector\MessagesCollector; +use Illuminate\Support\Arr; use Psr\Log\LogLevel; use ReflectionClass; @@ -14,26 +15,14 @@ public function __construct($path = null, $name = 'logs') { parent::__construct($name); - $path = $path ?: $this->getLogsFile(); - $this->getStorageLogs($path); - } + $paths = Arr::wrap($path ?: [ + storage_path('logs/laravel.log'), + storage_path('logs/laravel-' . date('Y-m-d') . '.log'), // for daily driver + ]); - /** - * Get the path to the logs file - * - * @return string - */ - public function getLogsFile() - { - // default daily rotating logs (Laravel 5.0) - $path = storage_path() . '/logs/laravel-' . date('Y-m-d') . '.log'; - - // single file logs - if (!file_exists($path)) { - $path = storage_path() . '/logs/laravel.log'; + foreach ($paths as $path) { + $this->getStorageLogs($path); } - - return $path; } /** @@ -52,9 +41,16 @@ public function getStorageLogs($path) //Load the latest lines, guessing about 15x the number of log entries (for stack traces etc) $file = implode("", $this->tailFile($path, $this->lines)); + $basename = basename($path); foreach ($this->getLogs($file) as $log) { - $this->addMessage($log['header'] . $log['stack'], $log['level'], false); + $this->messages[] = [ + 'message' => trim($log['header'] . $log['stack']), + 'label' => $log['level'], + 'time' => substr($log['header'], 1, 19), + 'collector' => $basename, + 'is_string' => false, + ]; } } @@ -104,30 +100,36 @@ protected function tailFile($file, $lines) */ public function getLogs($file) { - $pattern = "/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\].*/"; + $pattern = "/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\](?:(?!\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\])[\s\S])*/"; $log_levels = $this->getLevels(); // There has GOT to be a better way of doing this... preg_match_all($pattern, $file, $headings); - $log_data = preg_split($pattern, $file); + $log_data = preg_split($pattern, $file) ?: []; $log = []; foreach ($headings as $h) { for ($i = 0, $j = count($h); $i < $j; $i++) { foreach ($log_levels as $ll) { if (strpos(strtolower($h[$i]), strtolower('.' . $ll))) { - $log[] = ['level' => $ll, 'header' => $h[$i], 'stack' => $log_data[$i]]; + $log[] = ['level' => $ll, 'header' => $h[$i], 'stack' => $log_data[$i] ?? '']; } } } } - $log = array_reverse($log); - return $log; } + /** + * @return array + */ + public function getMessages() + { + return array_reverse(parent::getMessages()); + } + /** * Get the log levels from psr/log. * Based on https://github.com/mikemand/logviewer/blob/master/src/Kmd/Logviewer/Logviewer.php by mikemand diff --git a/src/DataCollector/ModelsCollector.php b/src/DataCollector/ModelsCollector.php index 0ac99cf09..913877548 100644 --- a/src/DataCollector/ModelsCollector.php +++ b/src/DataCollector/ModelsCollector.php @@ -9,6 +9,7 @@ /** * Collector for Models. + * @deprecated in favor of \DebugBar\DataCollector\ObjectCountCollector */ class ModelsCollector extends DataCollector implements DataCollectorInterface, Renderable { diff --git a/src/DataCollector/MultiAuthCollector.php b/src/DataCollector/MultiAuthCollector.php index c5c0aa720..32f4b416e 100644 --- a/src/DataCollector/MultiAuthCollector.php +++ b/src/DataCollector/MultiAuthCollector.php @@ -25,6 +25,9 @@ class MultiAuthCollector extends DataCollector implements Renderable /** @var bool */ protected $showName = false; + /** @var bool */ + protected $showGuardsData = true; + /** * @param \Illuminate\Auth\AuthManager $auth * @param array $guards @@ -44,6 +47,15 @@ public function setShowName($showName) $this->showName = (bool) $showName; } + /** + * Set to hide the guards tab, and show only name + * @param bool $showGuardsData + */ + public function setShowGuardsData($showGuardsData) + { + $this->showGuardsData = (bool) $showGuardsData; + } + /** * @{inheritDoc} */ @@ -79,6 +91,9 @@ public function collect() } $data['names'] = rtrim($names, ', '); + if (!$this->showGuardsData) { + unset($data['guards']); + } return $data; } @@ -89,11 +104,6 @@ private function hasUser(Guard $guard) return $guard->hasUser(); } - // For Laravel 5.5 - if (method_exists($guard, 'alreadyAuthenticated')) { - return $guard->alreadyAuthenticated(); - } - return false; } @@ -113,14 +123,16 @@ protected function getUserInformation($user = null) } // The default auth identifer is the ID number, which isn't all that - // useful. Try username and email. - $identifier = $user instanceof Authenticatable ? $user->getAuthIdentifier() : $user->id; - if (is_numeric($identifier)) { + // useful. Try username, email and name. + $identifier = $user instanceof Authenticatable ? $user->getAuthIdentifier() : $user->getKey(); + if (is_numeric($identifier) || Str::isUuid($identifier) || Str::isUlid($identifier)) { try { if (isset($user->username)) { $identifier = $user->username; } elseif (isset($user->email)) { $identifier = $user->email; + } elseif (isset($user->name)) { + $identifier = Str::limit($user->name, 24); } } catch (\Throwable $e) { } @@ -145,14 +157,16 @@ public function getName() */ public function getWidgets() { - $widgets = [ - "auth" => [ + $widgets = []; + + if ($this->showGuardsData) { + $widgets["auth"] = [ "icon" => "lock", "widget" => "PhpDebugBar.Widgets.VariableListWidget", "map" => "auth.guards", - "default" => "{}" - ] - ]; + "default" => "{}", + ]; + } if ($this->showName) { $widgets['auth.name'] = [ diff --git a/src/DataCollector/PennantCollector.php b/src/DataCollector/PennantCollector.php new file mode 100644 index 000000000..ecc58eb4c --- /dev/null +++ b/src/DataCollector/PennantCollector.php @@ -0,0 +1,57 @@ +manager = $manager; + } + + /** + * {@inheritdoc} + */ + public function collect() + { + $store = $this->manager->store(Config::get('pennant.default')); + + return $store->values($store->stored()); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'pennant'; + } + + /** + * {@inheritDoc} + */ + public function getWidgets() + { + return [ + "pennant" => [ + "icon" => "flag", + "widget" => "PhpDebugBar.Widgets.VariableListWidget", + "map" => "pennant", + "default" => "{}" + ] + ]; + } +} diff --git a/src/DataCollector/PhpInfoCollector.php b/src/DataCollector/PhpInfoCollector.php deleted file mode 100644 index 0c955e900..000000000 --- a/src/DataCollector/PhpInfoCollector.php +++ /dev/null @@ -1,18 +0,0 @@ -timeCollector = $timeCollector; } + /** + * @param int|null $softLimit After the soft limit, no parameters/backtrace are captured + * @param int|null $hardLimit After the hard limit, queries are ignored + * @return void + */ + public function setLimits(?int $softLimit, ?int $hardLimit): void + { + $this->softLimit = $softLimit; + $this->hardLimit = $hardLimit; + } + /** * Renders the SQL of traced statements with params embedded * @@ -68,15 +93,20 @@ public function setShowCopyButton($enabled = true) /** * Enable/disable finding the source * - * @param bool $value + * @param bool|int $value * @param array $middleware */ public function setFindSource($value, array $middleware) { - $this->findSource = (bool) $value; + $this->findSource = $value; $this->middleware = $middleware; } + public function mergeExcludePaths(array $excludePaths) + { + $this->excludePaths = array_merge($this->excludePaths, $excludePaths); + } + /** * Set additional paths to exclude from the backtrace * @@ -87,6 +117,16 @@ public function mergeBacktraceExcludePaths(array $excludePaths) $this->backtraceExcludePaths = array_merge($this->backtraceExcludePaths, $excludePaths); } + /** + * Enable/disable the shaded duration background on queries + * + * @param bool $enabled + */ + public function setDurationBackground($enabled = true) + { + $this->durationBackground = $enabled; + } + /** * Enable/disable the EXPLAIN queries * @@ -96,88 +136,74 @@ public function mergeBacktraceExcludePaths(array $excludePaths) public function setExplainSource($enabled, $types) { $this->explainQuery = $enabled; - // workaround ['SELECT'] only. https://github.com/barryvdh/laravel-debugbar/issues/888 -// if($types){ -// $this->explainTypes = $types; -// } + } + + public function startMemoryUsage() + { + $this->lastMemoryUsage = memory_get_usage(false); } /** * - * @param string $query - * @param array $bindings - * @param float $time - * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Events\QueryExecuted $query */ - public function addQuery($query, $bindings, $time, $connection) + public function addQuery($query) { - $explainResults = []; - $time = $time / 1000; + $this->queryCount++; + + if ($this->hardLimit && $this->queryCount > $this->hardLimit) { + return; + } + + $limited = $this->softLimit && $this->queryCount > $this->softLimit; + + $sql = (string) $query->sql; + $time = $query->time / 1000; $endTime = microtime(true); $startTime = $endTime - $time; - $hints = $this->performQueryAnalysis($query); + $hints = $this->performQueryAnalysis($sql); $pdo = null; try { - $pdo = $connection->getPdo(); - } catch (\Exception $e) { - // ignore error for non-pdo laravel drivers - } - $bindings = $connection->prepareBindings($bindings); - - // Run EXPLAIN on this query (if needed) - if ($this->explainQuery && $pdo && preg_match('/^\s*(' . implode('|', $this->explainTypes) . ') /i', $query)) { - $statement = $pdo->prepare('EXPLAIN ' . $query); - $statement->execute($bindings); - $explainResults = $statement->fetchAll(\PDO::FETCH_CLASS); - } - - $bindings = $this->getDataFormatter()->checkBindings($bindings); - if (!empty($bindings) && $this->renderSqlWithParams) { - foreach ($bindings as $key => $binding) { - // This regex matches placeholders only, not the question marks, - // nested in quotes, while we iterate through the bindings - // and substitute placeholders by suitable values. - $regex = is_numeric($key) - ? "/(?quote($binding); - } else { - $binding = $this->emulateQuote($binding); - } - } + $pdo = $query->connection->getPdo(); - $query = preg_replace($regex, $binding, $query, 1); + if(! ($pdo instanceof \PDO)) { + $pdo = null; } + } catch (\Throwable $e) { + // ignore error for non-pdo laravel drivers } $source = []; - if ($this->findSource) { + if (!$limited && $this->findSource) { try { - $source = array_slice($this->findSource(), 0, 5); + $source = $this->findSource(); } catch (\Exception $e) { } } + $bindings = match (true) { + $limited && filled($query->bindings) => [], + default => $query->connection->prepareBindings($query->bindings), + }; + $this->queries[] = [ - 'query' => $query, + 'query' => $sql, 'type' => 'query', - 'bindings' => $this->getDataFormatter()->escapeBindings($bindings), + 'bindings' => $bindings, + 'start' => $startTime, 'time' => $time, + 'memory' => $this->lastMemoryUsage ? memory_get_usage(false) - $this->lastMemoryUsage : 0, 'source' => $source, - 'explain' => $explainResults, - 'connection' => $connection->getDatabaseName(), - 'hints' => $this->showHints ? $hints : null, + 'connection' => $query->connection, + 'driver' => $query->connection->getConfig('driver'), + 'hints' => ($this->showHints && !$limited) ? $hints : null, 'show_copy' => $this->showCopyButton, ]; if ($this->timeCollector !== null) { - $this->timeCollector->addMeasure($query, $startTime, $endTime); + $this->timeCollector->addMeasure(Str::limit($sql, 100), $startTime, $endTime, [], 'db', 'Database Query'); } } @@ -192,7 +218,7 @@ protected function emulateQuote($value) $search = ["\\", "\x00", "\n", "\r", "'", '"', "\x1a"]; $replace = ["\\\\","\\0","\\n", "\\r", "\'", '\"', "\\Z"]; - return "'" . str_replace($search, $replace, $value) . "'"; + return "'" . str_replace($search, $replace, (string) $value) . "'"; } /** @@ -206,7 +232,7 @@ protected function emulateQuote($value) * @version $Id$ * @access public * @param string $query - * @return string + * @return string[] */ protected function performQueryAnalysis($query) { @@ -217,8 +243,8 @@ protected function performQueryAnalysis($query) } if (preg_match('/ORDER BY RAND()/i', $query)) { $hints[] = 'ORDER BY RAND() is slow, try to avoid if you can. - You can read this - or this'; + You can read this + or this'; } if (strpos($query, '!=') !== false) { $hints[] = 'The != operator is not standard. Use the <> operator to test for inequality instead.'; @@ -245,7 +271,7 @@ protected function performQueryAnalysis($query) */ protected function findSource() { - $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, 50); + $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, app('config')->get('debugbar.debug_backtrace_limit', 50)); $sources = []; @@ -253,7 +279,7 @@ protected function findSource() $sources[] = $this->parseTrace($index, $trace); } - return array_filter($sources); + return array_slice(array_filter($sources), 0, is_int($this->findSource) ? $this->findSource : 5); } /** @@ -269,7 +295,8 @@ protected function parseTrace($index, array $trace) 'index' => $index, 'namespace' => null, 'name' => null, - 'line' => isset($trace['line']) ? $trace['line'] : '?', + 'file' => null, + 'line' => $trace['line'] ?? '1', ]; if (isset($trace['function']) && $trace['function'] == 'substituteBindings') { @@ -283,33 +310,36 @@ protected function parseTrace($index, array $trace) isset($trace['file']) && !$this->fileIsInExcludedPath($trace['file']) ) { - $file = $trace['file']; + $frame->file = $trace['file']; - if (isset($trace['object']) && is_a($trace['object'], 'Twig_Template')) { - list($file, $frame->line) = $this->getTwigInfo($trace); - } elseif (strpos($file, storage_path()) !== false) { - $hash = pathinfo($file, PATHINFO_FILENAME); + if (isset($trace['object']) && is_a($trace['object'], '\Twig\Template')) { + list($frame->file, $frame->line) = $this->getTwigInfo($trace); + } elseif (strpos($frame->file, storage_path()) !== false) { + $hash = pathinfo($frame->file, PATHINFO_FILENAME); - if (! $frame->name = $this->findViewFromHash($hash)) { + if ($frame->name = $this->findViewFromHash($hash)) { + $frame->file = $frame->name[1]; + $frame->name = $frame->name[0]; + } else { $frame->name = $hash; } $frame->namespace = 'view'; return $frame; - } elseif (strpos($file, 'Middleware') !== false) { - $frame->name = $this->findMiddlewareFromFile($file); + } elseif (strpos($frame->file, 'Middleware') !== false) { + $frame->name = $this->findMiddlewareFromFile($frame->file); if ($frame->name) { $frame->namespace = 'middleware'; } else { - $frame->name = $this->normalizeFilename($file); + $frame->name = $this->normalizeFilePath($frame->file); } return $frame; } - $frame->name = $this->normalizeFilename($file); + $frame->name = $this->normalizeFilePath($frame->file); return $frame; } @@ -348,7 +378,7 @@ protected function findMiddlewareFromFile($file) $filename = pathinfo($file, PATHINFO_FILENAME); foreach ($this->middleware as $alias => $class) { - if (strpos($class, $filename) !== false) { + if (!is_null($class) && !is_null($filename) && strpos($class, $filename) !== false) { return $alias; } } @@ -358,7 +388,7 @@ protected function findMiddlewareFromFile($file) * Find the template name from the hash. * * @param string $hash - * @return null|string + * @return null|array */ protected function findViewFromHash($hash) { @@ -373,9 +403,11 @@ protected function findViewFromHash($hash) $this->reflection['viewfinderViews'] = $property; } + $xxh128Exists = in_array('xxh128', hash_algos()); + foreach ($property->getValue($finder) as $name => $path) { - if (sha1($path) == $hash || md5($path) == $hash) { - return $name; + if (($xxh128Exists && hash('xxh128', 'v2' . $path) == $hash) || sha1('v2' . $path) == $hash) { + return [$name, $path]; } } } @@ -401,20 +433,6 @@ protected function getTwigInfo($trace) return [$file, -1]; } - /** - * Shorten the path by removing the relative links and base dir - * - * @param string $path - * @return string - */ - protected function normalizeFilename($path) - { - if (file_exists($path)) { - $path = realpath($path); - } - return str_replace(base_path(), '', $path); - } - /** * Collect a database transaction event. * @param string $event @@ -423,6 +441,7 @@ protected function normalizeFilename($path) */ public function collectTransactionEvent($event, $connection) { + $this->transactionEventsCount++; $source = []; if ($this->findSource) { @@ -436,10 +455,12 @@ public function collectTransactionEvent($event, $connection) 'query' => $event, 'type' => 'transaction', 'bindings' => [], + 'start' => microtime(true), 'time' => 0, + 'memory' => 0, 'source' => $source, - 'explain' => [], - 'connection' => $connection->getDatabaseName(), + 'connection' => $connection, + 'driver' => $connection->getConfig('driver'), 'hints' => null, 'show_copy' => false, ]; @@ -451,6 +472,8 @@ public function collectTransactionEvent($event, $connection) public function reset() { $this->queries = []; + $this->queryCount = 0; + $this->infoStatements = 0 ; } /** @@ -459,47 +482,120 @@ public function reset() public function collect() { $totalTime = 0; + $totalMemory = 0; $queries = $this->queries; $statements = []; foreach ($queries as $query) { + $source = reset($query['source']); + $normalizedPath = is_object($source) ? $this->normalizeFilePath($source->file ?: '') : ''; + if ($query['type'] != 'transaction' && Str::startsWith($normalizedPath, $this->excludePaths)) { + continue; + } + $totalTime += $query['time']; + $totalMemory += $query['memory']; + + $connectionName = $query['connection']->getDatabaseName(); + if (str_ends_with($connectionName, '.sqlite')) { + $connectionName = $this->normalizeFilePath($connectionName); + } + + $canExplainQuery = match (true) { + in_array($query['driver'], ['mariadb', 'mysql', 'pgsql']) => $query['bindings'] !== null && preg_match('/^\s*(' . implode('|', $this->explainTypes) . ') /i', $query['query']), + default => false, + }; $statements[] = [ - 'sql' => $this->getDataFormatter()->formatSql($query['query']), + 'sql' => $this->getSqlQueryToDisplay($query), 'type' => $query['type'], 'params' => [], - 'bindings' => $query['bindings'], + 'bindings' => $query['bindings'] ?? [], 'hints' => $query['hints'], 'show_copy' => $query['show_copy'], 'backtrace' => array_values($query['source']), + 'start' => $query['start'] ?? null, 'duration' => $query['time'], 'duration_str' => ($query['type'] == 'transaction') ? '' : $this->formatDuration($query['time']), - 'stmt_id' => $this->getDataFormatter()->formatSource(reset($query['source'])), - 'connection' => $query['connection'], + 'slow' => $this->slowThreshold && $this->slowThreshold <= $query['time'], + 'memory' => $query['memory'], + 'memory_str' => $query['memory'] ? $this->getDataFormatter()->formatBytes($query['memory']) : null, + 'filename' => $this->getDataFormatter()->formatSource($source, true), + 'source' => $source, + 'xdebug_link' => is_object($source) ? $this->getXdebugLink($source->file ?: '', $source->line) : null, + 'connection' => $connectionName, + 'explain' => $this->explainQuery && $canExplainQuery ? [ + 'url' => route('debugbar.queries.explain'), + 'driver' => $query['driver'], + 'connection' => $query['connection']->getName(), + 'query' => $query['query'], + 'hash' => (new Explain())->hash($query['connection']->getName(), $query['query'], $query['bindings']), + ] : null, ]; + } + + if ($this->durationBackground) { + if ($totalTime > 0) { + // For showing background measure on Queries tab + $start_percent = 0; + + foreach ($statements as $i => $statement) { + if (!isset($statement['duration'])) { + continue; + } - //Add the results from the explain as new rows - foreach ($query['explain'] as $explain) { - $statements[] = [ - 'sql' => " - EXPLAIN # {$explain->id}: `{$explain->table}` ({$explain->select_type})", - 'type' => 'explain', - 'params' => $explain, - 'row_count' => $explain->rows, - 'stmt_id' => $explain->id, - ]; + $width_percent = $statement['duration'] / $totalTime * 100; + + $statements[$i] = array_merge($statement, [ + 'start_percent' => round($start_percent, 3), + 'width_percent' => round($width_percent, 3), + ]); + + $start_percent += $width_percent; + } } } - $nb_statements = array_filter($queries, function ($query) { - return $query['type'] == 'query'; - }); + if ($this->softLimit && $this->hardLimit && ($this->queryCount > $this->softLimit && $this->queryCount > $this->hardLimit)) { + array_unshift($statements, [ + 'sql' => '# Query soft and hard limit for Debugbar are reached. Only the first ' . $this->softLimit . ' queries show details. Queries after the first ' . $this->hardLimit . ' are ignored. Limits can be raised in the config (debugbar.options.db.soft/hard_limit).', + 'type' => 'info', + ]); + $statements[] = [ + 'sql' => '... ' . ($this->queryCount - $this->hardLimit) . ' additional queries are executed but now shown because of Debugbar query limits. Limits can be raised in the config (debugbar.options.db.soft/hard_limit)', + 'type' => 'info', + ]; + $this->infoStatements+= 2; + } elseif ($this->hardLimit && $this->queryCount > $this->hardLimit) { + array_unshift($statements, [ + 'sql' => '# Query hard limit for Debugbar is reached after ' . $this->hardLimit . ' queries, additional ' . ($this->queryCount - $this->hardLimit) . ' queries are not shown.. Limits can be raised in the config (debugbar.options.db.hard_limit)', + 'type' => 'info', + ]); + $statements[] = [ + 'sql' => '... ' . ($this->queryCount - $this->hardLimit) . ' additional queries are executed but now shown because of Debugbar query limits. Limits can be raised in the config (debugbar.options.db.hard_limit)', + 'type' => 'info', + ]; + $this->infoStatements+= 2; + } elseif ($this->softLimit && $this->queryCount > $this->softLimit) { + array_unshift($statements, [ + 'sql' => '# Query soft limit for Debugbar is reached after ' . $this->softLimit . ' queries, additional ' . ($this->queryCount - $this->softLimit) . ' queries only show the query. Limits can be raised in the config (debugbar.options.db.soft_limit)', + 'type' => 'info', + ]); + $this->infoStatements++; + } + + $visibleStatements = count($statements) - $this->infoStatements; $data = [ - 'nb_statements' => count($nb_statements), + 'count' => $visibleStatements, + 'nb_statements' => $this->queryCount, + 'nb_visible_statements' => $visibleStatements, + 'nb_excluded_statements' => $this->queryCount + $this->transactionEventsCount - $visibleStatements, 'nb_failed_statements' => 0, 'accumulated_duration' => $totalTime, 'accumulated_duration_str' => $this->formatDuration($totalTime), + 'memory_usage' => $totalMemory, + 'memory_usage_str' => $totalMemory ? $this->getDataFormatter()->formatBytes($totalMemory) : null, 'statements' => $statements ]; return $data; @@ -521,7 +617,7 @@ public function getWidgets() return [ "queries" => [ "icon" => "database", - "widget" => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget", + "widget" => "PhpDebugBar.Widgets.LaravelQueriesWidget", "map" => "queries", "default" => "[]" ], @@ -531,4 +627,55 @@ public function getWidgets() ] ]; } + + private function getSqlQueryToDisplay(array $query): string + { + $sql = $query['query']; + if ($query['type'] === 'query' && $this->renderSqlWithParams && $query['connection']->getQueryGrammar() instanceof \Illuminate\Database\Query\Grammars\Grammar && method_exists($query['connection']->getQueryGrammar(), 'substituteBindingsIntoRawSql')) { + try { + $sql = $query['connection']->getQueryGrammar()->substituteBindingsIntoRawSql($sql, $query['bindings'] ?? []); + return $this->getDataFormatter()->formatSql($sql); + } catch (\Throwable $e) { + // Continue using the old substitute + } + } + + if ($query['type'] === 'query' && $this->renderSqlWithParams) { + $bindings = $this->getDataFormatter()->checkBindings($query['bindings']); + if (!empty($bindings)) { + $pdo = null; + try { + $pdo = $query->connection->getPdo(); + } catch (\Throwable) { + // ignore error for non-pdo laravel drivers + } + + foreach ($bindings as $key => $binding) { + // This regex matches placeholders only, not the question marks, + // nested in quotes, while we iterate through the bindings + // and substitute placeholders by suitable values. + $regex = is_numeric($key) + ? "/(?quote((string) $binding); + } catch (\Exception $e) { + $binding = $this->emulateQuote($binding); + } + } else { + $binding = $this->emulateQuote($binding); + } + } + + $sql = preg_replace($regex, addcslashes($binding, '$'), $sql, 1); + } + } + } + + return $this->getDataFormatter()->formatSql($sql); + } } diff --git a/src/DataCollector/RequestCollector.php b/src/DataCollector/RequestCollector.php index 25760a4e2..f3ae8b513 100644 --- a/src/DataCollector/RequestCollector.php +++ b/src/DataCollector/RequestCollector.php @@ -5,9 +5,13 @@ use DebugBar\DataCollector\DataCollector; use DebugBar\DataCollector\DataCollectorInterface; use DebugBar\DataCollector\Renderable; +use Illuminate\Http\Request; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Str; use Laravel\Telescope\IncomingEntry; use Laravel\Telescope\Telescope; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; /** @@ -19,12 +23,14 @@ class RequestCollector extends DataCollector implements DataCollectorInterface, { /** @var \Symfony\Component\HttpFoundation\Request $request */ protected $request; - /** @var \Symfony\Component\HttpFoundation\Request $response */ + /** @var \Symfony\Component\HttpFoundation\Response $response */ protected $response; /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */ protected $session; /** @var string|null */ protected $currentRequestId; + /** @var array */ + protected $hiddens; /** * Create a new SymfonyRequestCollector @@ -32,13 +38,19 @@ class RequestCollector extends DataCollector implements DataCollectorInterface, * @param \Symfony\Component\HttpFoundation\Request $request * @param \Symfony\Component\HttpFoundation\Response $response * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session + * @param string|null $currentRequestId + * @param array $hiddens */ - public function __construct($request, $response, $session = null, $currentRequestId = null) + public function __construct($request, $response, $session = null, $currentRequestId = null, $hiddens = []) { $this->request = $request; $this->response = $response; $this->session = $session; $this->currentRequestId = $currentRequestId; + $this->hiddens = array_merge($hiddens, [ + 'request_request.password', + 'request_headers.php-auth-pw.0', + ]); } /** @@ -54,14 +66,34 @@ public function getName() */ public function getWidgets() { - return [ + $widgets = [ "request" => [ "icon" => "tags", "widget" => "PhpDebugBar.Widgets.HtmlVariableListWidget", - "map" => "request", + "map" => "request.data", + "order" => -100, "default" => "{}" + ], + 'request:badge' => [ + "map" => "request.badge", + "default" => "null" ] ]; + + if (Config::get('debugbar.options.request.label', true)) { + $widgets['currentrequest'] = [ + "icon" => "share", + "map" => "request.data.uri", + "link" => "request", + "default" => "" + ]; + $widgets['currentrequest:tooltip'] = [ + "map" => "request.tooltip", + "default" => "{}" + ]; + } + + return $widgets; } /** @@ -90,48 +122,57 @@ public function collect() } $statusCode = $response->getStatusCode(); + $startTime = defined('LARAVEL_START') ? LARAVEL_START : $request->server->get('REQUEST_TIME_FLOAT'); + $query = $request->getQueryString(); + $htmlData = []; $data = [ - 'path_info' => $request->getPathInfo(), - 'status_code' => $statusCode, - 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', - 'format' => $request->getRequestFormat(), - 'content_type' => $response->headers->get('Content-Type') ? $response->headers->get( + 'status' => $statusCode . ' ' . (isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : ''), + 'duration' => $startTime ? $this->formatDuration(microtime(true) - $startTime) : null, + 'peak_memory' => $this->formatBytes(memory_get_peak_usage(true), 1), + ]; + + if ($request instanceof Request) { + + if ($route = $request->route()) { + $htmlData += $this->getRouteInformation($route); + } + + $fullUrl = $request->fullUrl(); + $data += [ + 'full_url' => strlen($fullUrl) > 100 ? [$fullUrl] : $fullUrl, + ]; + } + + if ($response instanceof RedirectResponse) { + $data['response'] = 'Redirect to ' . $response->getTargetUrl(); + } + + $data += [ + 'response' => $response->headers->get('Content-Type') ? $response->headers->get( 'Content-Type' ) : 'text/html', + 'request_format' => $request->getRequestFormat(), 'request_query' => $request->query->all(), 'request_request' => $request->request->all(), 'request_headers' => $request->headers->all(), - 'request_server' => $request->server->all(), 'request_cookies' => $request->cookies->all(), 'response_headers' => $responseHeaders, ]; if ($this->session) { - $sessionAttributes = []; - foreach ($this->session->all() as $key => $value) { - $sessionAttributes[$key] = $value; - } - $data['session_attributes'] = $sessionAttributes; + $data['session_attributes'] = $this->session->all(); } - foreach ($data['request_server'] as $key => $value) { - if ( - Str::is('*_KEY', $key) || Str::is('*_PASSWORD', $key) - || Str::is('*_SECRET', $key) || Str::is('*_PW', $key) - ) { - $data['request_server'][$key] = '******'; - } + if (isset($data['request_headers']['authorization'][0])) { + $data['request_headers']['authorization'][0] = substr($data['request_headers']['authorization'][0], 0, 12) . '******'; } - if (isset($data['request_headers']['php-auth-pw'])) { - $data['request_headers']['php-auth-pw'] = '******'; - } - - if (isset($data['request_server']['PHP_AUTH_PW'])) { - $data['request_server']['PHP_AUTH_PW'] = '******'; + foreach ($this->hiddens as $key) { + if (Arr::has($data, $key)) { + Arr::set($data, $key, '******'); + } } - ; foreach ($data as $key => $var) { if (!is_string($data[$key])) { @@ -141,7 +182,6 @@ public function collect() } } - $htmlData = []; if (class_exists(Telescope::class)) { $entry = IncomingEntry::make([ 'requestId' => $this->currentRequestId, @@ -151,12 +191,107 @@ public function collect() $htmlData['telescope'] = 'View in Telescope'; } - return $htmlData + $data; + $tooltip = [ + 'status' => $data['status'], + 'full_url' => Str::limit($request->fullUrl(), 100), + ]; + + if ($this->request instanceof Request) { + $tooltip += [ + 'action_name' => optional($this->request->route())->getName(), + 'controller_action' => optional($this->request->route())->getActionName(), + ]; + } + + unset($htmlData['as'], $htmlData['uses']); + + return [ + 'data' => $tooltip + $htmlData + $data, + 'tooltip' => array_filter($tooltip), + 'badge' => $statusCode >= 300 ? $data['status'] : null, + ]; + } + + protected function getRouteInformation($route) + { + if (!is_a($route, 'Illuminate\Routing\Route')) { + return []; + } + $uri = head($route->methods()) . ' ' . $route->uri(); + $action = $route->getAction(); + + $result = [ + 'uri' => $uri ?: '-', + ]; + + $result = array_merge($result, $action); + $uses = $action['uses'] ?? null; + $controller = is_string($action['controller'] ?? null) ? $action['controller'] : ''; + + if (request()->hasHeader('X-Livewire')) { + try { + $component = request('components')[0]; + $name = json_decode($component['snapshot'], true)['memo']['name']; + $method = $component['calls'][0]['method']; + $class = app(\Livewire\Mechanisms\ComponentRegistry::class)->getClass($name); + if (class_exists($class) && method_exists($class, $method)) { + $controller = $class . '@' . $method; + $result['controller'] = ltrim($controller, '\\'); + } + } catch (\Throwable $e) { + // + } + } + + if (str_contains($controller, '@')) { + list($controller, $method) = explode('@', $controller); + if (class_exists($controller) && method_exists($controller, $method)) { + $reflector = new \ReflectionMethod($controller, $method); + } + unset($result['uses']); + } elseif ($uses instanceof \Closure) { + $reflector = new \ReflectionFunction($uses); + $result['uses'] = $this->formatVar($uses); + } elseif (is_string($uses) && str_contains($uses, '@__invoke')) { + if (class_exists($controller) && method_exists($controller, 'render')) { + $reflector = new \ReflectionMethod($controller, 'render'); + $result['controller'] = $controller . '@render'; + } + } + + if (isset($reflector)) { + $filename = $this->normalizeFilePath($reflector->getFileName()); + + if ($link = $this->getXdebugLink($reflector->getFileName(), $reflector->getStartLine())) { + $result['file'] = sprintf( + '%s:%s-%s', + $link['url'], + $link['ajax'] ? 'event.preventDefault();$.ajax(this.href);' : '', + $filename, + $reflector->getStartLine(), + $reflector->getEndLine() + ); + + if (isset($result['controller']) && is_string($result['controller'])) { + $result['controller'] .= ''; + } + } else { + $result['file'] = sprintf('%s:%s-%s', $filename, $reflector->getStartLine(), $reflector->getEndLine()); + } + } + + if (isset($result['middleware']) && is_array($result['middleware'])) { + $middleware = implode(', ', $result['middleware']); + unset($result['middleware']); + $result['middleware'] = $middleware; + } + + return array_filter($result); } private function getCookieHeader($name, $value, $expires, $path, $domain, $secure, $httponly) { - $cookie = sprintf('%s=%s', $name, urlencode($value)); + $cookie = sprintf('%s=%s', $name, urlencode($value ?? '')); if (0 !== $expires) { if (is_numeric($expires)) { @@ -173,10 +308,10 @@ private function getCookieHeader($name, $value, $expires, $path, $domain, $secur } $cookie .= '; expires=' . substr( - \DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), - 0, - -5 - ); + \DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), + 0, + -5 + ); } if ($domain) { diff --git a/src/DataCollector/RouteCollector.php b/src/DataCollector/RouteCollector.php index 53c9b673b..f548a795e 100644 --- a/src/DataCollector/RouteCollector.php +++ b/src/DataCollector/RouteCollector.php @@ -2,10 +2,9 @@ namespace Barryvdh\Debugbar\DataCollector; +use Closure; use DebugBar\DataCollector\DataCollector; use DebugBar\DataCollector\Renderable; -use Illuminate\Http\Request; -use Illuminate\Routing\Route; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Config; @@ -56,35 +55,66 @@ protected function getRouteInformation($route) ]; $result = array_merge($result, $action); + $uses = $action['uses'] ?? null; + $controller = is_string($action['controller'] ?? null) ? $action['controller'] : ''; + + if (request()->hasHeader('X-Livewire')) { + try { + $component = request('components')[0]; + $name = json_decode($component['snapshot'], true)['memo']['name']; + $method = $component['calls'][0]['method']; + $class = app(\Livewire\Mechanisms\ComponentRegistry::class)->getClass($name); + if (class_exists($class) && method_exists($class, $method)) { + $controller = $class . '@' . $method; + $result['controller'] = ltrim($controller, '\\'); + } + } catch (\Throwable $e) { + // + } + } - - if ( - isset($action['controller']) - && is_string($action['controller']) - && strpos($action['controller'], '@') !== false - ) { - list($controller, $method) = explode('@', $action['controller']); + if (str_contains($controller, '@')) { + list($controller, $method) = explode('@', $controller); if (class_exists($controller) && method_exists($controller, $method)) { $reflector = new \ReflectionMethod($controller, $method); } unset($result['uses']); - } elseif (isset($action['uses']) && $action['uses'] instanceof \Closure) { - $reflector = new \ReflectionFunction($action['uses']); - $result['uses'] = $this->formatVar($result['uses']); + } elseif ($uses instanceof \Closure) { + $reflector = new \ReflectionFunction($uses); + $result['uses'] = $this->formatVar($uses); + } elseif (is_string($uses) && str_contains($uses, '@__invoke')) { + if (class_exists($controller) && method_exists($controller, 'render')) { + $reflector = new \ReflectionMethod($controller, 'render'); + $result['controller'] = $controller . '@render'; + } } if (isset($reflector)) { - $filename = ltrim(str_replace(base_path(), '', $reflector->getFileName()), '/'); - $result['file'] = $filename . ':' . $reflector->getStartLine() . '-' . $reflector->getEndLine(); + $filename = $this->normalizeFilePath($reflector->getFileName()); + + if ($link = $this->getXdebugLink($reflector->getFileName(), $reflector->getStartLine())) { + $result['file'] = sprintf( + '%s:%s-%s', + $link['url'], + $link['ajax'] ? 'event.preventDefault();$.ajax(this.href);' : '', + $filename, + $reflector->getStartLine(), + $reflector->getEndLine() + ); + + if (isset($result['controller'])) { + $result['controller'] .= ''; + } + } else { + $result['file'] = sprintf('%s:%s-%s', $filename, $reflector->getStartLine(), $reflector->getEndLine()); + } } if ($middleware = $this->getMiddleware($route)) { $result['middleware'] = $middleware; } - - - return $result; + return array_filter($result); } /** @@ -95,7 +125,9 @@ protected function getRouteInformation($route) */ protected function getMiddleware($route) { - return implode(', ', $route->middleware()); + return implode(', ', array_map(function ($middleware) { + return $middleware instanceof Closure ? 'Closure' : $middleware; + }, $route->gatherMiddleware())); } /** @@ -114,19 +146,11 @@ public function getWidgets() $widgets = [ "route" => [ "icon" => "share", - "widget" => "PhpDebugBar.Widgets.VariableListWidget", + "widget" => "PhpDebugBar.Widgets.HtmlVariableListWidget", "map" => "route", "default" => "{}" ] ]; - if (Config::get('debugbar.options.route.label', true)) { - $widgets['currentroute'] = [ - "icon" => "share", - "tooltip" => "Route", - "map" => "route.uri", - "default" => "" - ]; - } return $widgets; } diff --git a/src/DataCollector/SessionCollector.php b/src/DataCollector/SessionCollector.php index 8501646d6..e8855efac 100644 --- a/src/DataCollector/SessionCollector.php +++ b/src/DataCollector/SessionCollector.php @@ -5,20 +5,25 @@ use DebugBar\DataCollector\DataCollector; use DebugBar\DataCollector\DataCollectorInterface; use DebugBar\DataCollector\Renderable; +use Illuminate\Support\Arr; class SessionCollector extends DataCollector implements DataCollectorInterface, Renderable { /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface|\Illuminate\Contracts\Session\Session $session */ protected $session; + /** @var array */ + protected $hiddens; /** * Create a new SessionCollector * * @param \Symfony\Component\HttpFoundation\Session\SessionInterface|\Illuminate\Contracts\Session\Session $session + * @param array $hiddens */ - public function __construct($session) + public function __construct($session, $hiddens = []) { $this->session = $session; + $this->hiddens = $hiddens; } /** @@ -26,10 +31,18 @@ public function __construct($session) */ public function collect() { - $data = []; - foreach ($this->session->all() as $key => $value) { + $data = $this->session->all(); + + foreach ($this->hiddens as $key) { + if (Arr::has($data, $key)) { + Arr::set($data, $key, '******'); + } + } + + foreach ($data as $key => $value) { $data[$key] = is_string($value) ? $value : $this->formatVar($value); } + return $data; } diff --git a/src/DataCollector/ViewCollector.php b/src/DataCollector/ViewCollector.php index 46a33166b..70041f50d 100644 --- a/src/DataCollector/ViewCollector.php +++ b/src/DataCollector/ViewCollector.php @@ -3,26 +3,38 @@ namespace Barryvdh\Debugbar\DataCollector; use Barryvdh\Debugbar\DataFormatter\SimpleFormatter; -use DebugBar\Bridge\Twig\TwigCollector; +use DebugBar\DataCollector\AssetProvider; +use DebugBar\DataCollector\DataCollector; +use DebugBar\DataCollector\Renderable; +use DebugBar\DataCollector\TimeDataCollector; +use Illuminate\Support\Str; use Illuminate\View\View; -use Symfony\Component\VarDumper\Cloner\VarCloner; -class ViewCollector extends TwigCollector +class ViewCollector extends DataCollector implements Renderable, AssetProvider { + protected $name; protected $templates = []; protected $collect_data; + protected $exclude_paths; + protected $group; + protected $timeCollector; /** * Create a ViewCollector * - * @param bool $collectData Collects view data when tru - */ - public function __construct($collectData = true) + * @param bool|string $collectData Collects view data when true + * @param string[] $excludePaths Paths to exclude from collection + * @param int|bool $group Group the same templates together + * @param TimeDataCollector|null TimeCollector + * */ + public function __construct($collectData = true, $excludePaths = [], $group = true, ?TimeDataCollector $timeCollector = null) { $this->setDataFormatter(new SimpleFormatter()); $this->collect_data = $collectData; - $this->name = 'views'; $this->templates = []; + $this->exclude_paths = $excludePaths; + $this->group = $group; + $this->timeCollector = $timeCollector; } public function getName() @@ -46,6 +58,17 @@ public function getWidgets() ]; } + /** + * @return array + */ + public function getAssets() + { + return [ + 'css' => 'widgets/templates/widget.css', + 'js' => 'widgets/templates/widget.js', + ]; + } + /** * Add a View instance to the Collector * @@ -54,41 +77,104 @@ public function getWidgets() public function addView(View $view) { $name = $view->getName(); + $type = null; + $data = $view->getData(); $path = $view->getPath(); - - if (!is_object($path)) { - if ($path) { - $path = ltrim(str_replace(base_path(), '', realpath($path)), '/'); + + if (class_exists('\Inertia\Inertia')) { + list($name, $type, $data, $path) = $this->getInertiaView($name, $data, $path); + } + + if (is_object($path)) { + $type = get_class($view); + $path = null; + } + + if ($path) { + if (!$type) { + if (substr($path, -10) == '.blade.php') { + $type = 'blade'; + } else { + $type = pathinfo($path, PATHINFO_EXTENSION); + } } - if (substr($path, -10) == '.blade.php') { - $type = 'blade'; - } else { + $shortPath = $this->normalizeFilePath($path); + foreach ($this->exclude_paths as $excludePath) { + if (str_starts_with($shortPath, $excludePath)) { + return; + } + } + } + + $this->addTemplate($name, $data, $type, $path); + + if ($this->timeCollector !== null) { + $time = microtime(true); + $this->timeCollector->addMeasure('View: ' . $name, $time, $time, [], 'views', 'View'); + } + } + + private function getInertiaView(string $name, array $data, ?string $path) + { + if (isset($data['page']) && is_array($data['page'])) { + $data = $data['page']; + } + + if (isset($data['props'], $data['component'])) { + $name = $data['component']; + $data = $data['props']; + + if ($files = glob(resource_path(config('debugbar.options.views.inertia_pages') .'/'. $name . '.*'))) { + $path = $files[0]; $type = pathinfo($path, PATHINFO_EXTENSION); + + if (in_array($type, ['js', 'jsx'])) { + $type = 'react'; + } } - } else { - $type = get_class($view); - $path = ''; } - if (!$this->collect_data) { - $params = array_keys($view->getData()); + return [$name, $type ?? '', $data, $path]; + } + + public function addInertiaAjaxView(array $data) + { + list($name, $type, $data, $path) = $this->getInertiaView('', $data, ''); + + if (! $name) { + return; + } + + $this->addTemplate($name, $data, $type, $path); + } + + private function addTemplate(string $name, array $data, ?string $type, ?string $path) + { + // Prevent duplicates + $hash = $type . $path . $name . ($this->collect_data ? implode(array_keys($data)) : ''); + + if ($this->collect_data === 'keys') { + $params = array_keys($data); + } elseif ($this->collect_data) { + $params = array_map( + fn ($value) => $this->getDataFormatter()->formatVar($value), + $data + ); } else { - $data = []; - foreach ($view->getData() as $key => $value) { - $data[$key] = $this->getDataFormatter()->formatVar($value); - } - $params = $data; + $params = []; } $template = [ - 'name' => $path ? sprintf('%s (%s)', $name, $path) : $name, - 'param_count' => count($params), + 'name' => $name, + 'param_count' => $this->collect_data ? count($params) : null, 'params' => $params, + 'start' => microtime(true), 'type' => $type, + 'hash' => $hash, ]; - if ($this->getXdebugLink($path)) { + if ($path && $this->getXdebugLinkTemplate()) { $template['xdebug_link'] = $this->getXdebugLink($path); } @@ -97,10 +183,27 @@ public function addView(View $view) public function collect() { - $templates = $this->templates; + if ($this->group === true || count($this->templates) > $this->group) { + $templates = []; + foreach ($this->templates as $template) { + $hash = $template['hash']; + if (!isset($templates[$hash])) { + $template['render_count'] = 0; + $template['name_original'] = $template['name']; + $templates[$hash] = $template; + } + + $templates[$hash]['render_count']++; + $templates[$hash]['name'] = $templates[$hash]['render_count'] . 'x ' . $templates[$hash]['name_original']; + } + $templates = array_values($templates); + } else { + $templates = $this->templates; + } return [ - 'nb_templates' => count($templates), + 'count' => count($this->templates), + 'nb_templates' => count($this->templates), 'templates' => $templates, ]; } diff --git a/src/DataFormatter/QueryFormatter.php b/src/DataFormatter/QueryFormatter.php index 8b77c6144..645b6ff13 100644 --- a/src/DataFormatter/QueryFormatter.php +++ b/src/DataFormatter/QueryFormatter.php @@ -4,9 +4,9 @@ use DebugBar\DataFormatter\DataFormatter; +#[\AllowDynamicProperties] class QueryFormatter extends DataFormatter { - /** * Removes extra spaces at the beginning and end of the SQL query and its lines. * @@ -33,21 +33,15 @@ public function checkBindings($bindings) if (is_string($binding) && !mb_check_encoding($binding, 'UTF-8')) { $binding = '[BINARY DATA]'; } - } - return $bindings; - } + if (is_array($binding)) { + $binding = $this->checkBindings($binding); + $binding = '[' . implode(',', $binding) . ']'; + } - /** - * Make the bindings safe for outputting. - * - * @param array $bindings - * @return array - */ - public function escapeBindings($bindings) - { - foreach ($bindings as &$binding) { - $binding = htmlentities($binding, ENT_QUOTES, 'UTF-8', false); + if (is_object($binding)) { + $binding = json_encode($binding); + } } return $bindings; @@ -59,7 +53,7 @@ public function escapeBindings($bindings) * @param object|null $source If the backtrace is disabled, the $source will be null. * @return string */ - public function formatSource($source) + public function formatSource($source, $short = false) { if (! is_object($source)) { return ''; @@ -67,11 +61,11 @@ public function formatSource($source) $parts = []; - if ($source->namespace) { + if (!$short && $source->namespace) { $parts['namespace'] = $source->namespace . '::'; } - $parts['name'] = $source->name; + $parts['name'] = $short ? basename($source->name) : $source->name; $parts['line'] = ':' . $source->line; return implode($parts); diff --git a/src/DataFormatter/SimpleFormatter.php b/src/DataFormatter/SimpleFormatter.php index 9825e9133..674a52813 100644 --- a/src/DataFormatter/SimpleFormatter.php +++ b/src/DataFormatter/SimpleFormatter.php @@ -9,6 +9,7 @@ * * @see https://github.com/symfony/symfony/blob/v3.4.4/src/Symfony/Component/HttpKernel/DataCollector/Util/ValueExporter.php */ +#[\AllowDynamicProperties] class SimpleFormatter extends DataFormatter { /** @@ -51,7 +52,7 @@ private function exportValue($value, $depth = 1, $deep = false) $indent = str_repeat(' ', $depth); - $a = array(); + $a = []; foreach ($value as $k => $v) { if (is_array($v)) { $deep = true; diff --git a/src/DebugbarViewEngine.php b/src/DebugbarViewEngine.php new file mode 100644 index 000000000..ebae06624 --- /dev/null +++ b/src/DebugbarViewEngine.php @@ -0,0 +1,75 @@ +engine = $engine; + $this->laravelDebugbar = $laravelDebugbar; + $this->exclude_paths = app('config')->get('debugbar.options.views.exclude_paths', []); + } + + /** + * @param string $path + * @param array $data + * @return string + */ + public function get($path, array $data = []) + { + $basePath = base_path(); + $shortPath = @file_exists((string) $path) ? realpath($path) : $path; + + if (str_starts_with($shortPath, $basePath)) { + $shortPath = ltrim( + str_replace('\\', '/', substr($shortPath, strlen($basePath))), + '/' + ); + } + + foreach ($this->exclude_paths as $excludePath) { + if (str_starts_with($shortPath, $excludePath)) { + return $this->engine->get($path, $data); + } + } + + return $this->laravelDebugbar->measure($shortPath, function () use ($path, $data) { + return $this->engine->get($path, $data); + }, 'views'); + } + + /** + * NOTE: This is done to support other Engine swap (example: Livewire). + * @param $name + * @param $arguments + * @return mixed + */ + public function __call($name, $arguments) + { + return $this->engine->$name(...$arguments); + } +} diff --git a/src/Facade.php b/src/Facade.php index a4e15b4b1..933040d63 100644 --- a/src/Facade.php +++ b/src/Facade.php @@ -18,16 +18,13 @@ * @method static void log(mixed $message) * @method static void notice(mixed $message) * @method static void warning(mixed $message) + * @method static mixed measure(string $label, \Closure $closure) + * + * @deprecated Renamed to \Barryvdh\Debugbar\Facades\Debugbar + * @see \Barryvdh\Debugbar\Facades\Debugbar * * @see \Barryvdh\Debugbar\LaravelDebugbar */ -class Facade extends \Illuminate\Support\Facades\Facade +class Facade extends \Barryvdh\Debugbar\Facades\Debugbar { - /** - * {@inheritDoc} - */ - protected static function getFacadeAccessor() - { - return LaravelDebugbar::class; - } } diff --git a/src/Facades/Debugbar.php b/src/Facades/Debugbar.php new file mode 100644 index 000000000..b4003579e --- /dev/null +++ b/src/Facades/Debugbar.php @@ -0,0 +1,33 @@ +cssFiles['laravel'] = __DIR__ . '/Resources/laravel-debugbar.css'; - $this->cssVendors['fontawesome'] = __DIR__ . '/Resources/vendor/font-awesome/style.css'; - $this->jsFiles['laravel-sql'] = __DIR__ . '/Resources/sqlqueries/widget.js'; $this->jsFiles['laravel-cache'] = __DIR__ . '/Resources/cache/widget.js'; + $this->jsFiles['laravel-queries'] = __DIR__ . '/Resources/queries/widget.js'; - $theme = config('debugbar.theme', 'auto'); - switch ($theme) { - case 'dark': - $this->cssFiles['laravel-dark'] = __DIR__ . '/Resources/laravel-debugbar-dark-mode.css'; - break; - case 'auto': - $this->cssFiles['laravel-dark-0'] = __DIR__ . '/Resources/laravel-debugbar-dark-mode-media-start.css'; - $this->cssFiles['laravel-dark-1'] = __DIR__ . '/Resources/laravel-debugbar-dark-mode.css'; - $this->cssFiles['laravel-dark-2'] = __DIR__ . '/Resources/laravel-debugbar-dark-mode-media-end.css'; - } + $this->setTheme(config('debugbar.theme', 'auto')); } /** @@ -51,26 +41,28 @@ public function setUrlGenerator($url) */ public function renderHead() { - $cssRoute = route('debugbar.assets.css', [ + $cssRoute = preg_replace('/\Ahttps?:\/\/[^\/]+/', '', route('debugbar.assets.css', [ 'v' => $this->getModifiedTime('css'), - 'theme' => config('debugbar.theme', 'auto'), - ]); + ])); - $jsRoute = route('debugbar.assets.js', [ + $jsRoute = preg_replace('/\Ahttps?:\/\/[^\/]+/', '', route('debugbar.assets.js', [ 'v' => $this->getModifiedTime('js') - ]); + ])); - $cssRoute = preg_replace('/\Ahttps?:/', '', $cssRoute); - $jsRoute = preg_replace('/\Ahttps?:/', '', $jsRoute); + $nonce = $this->getNonceAttribute(); - $html = ""; - $html .= ""; + $html = ""; + $html .= ""; if ($this->isJqueryNoConflictEnabled()) { - $html .= '' . "\n"; + $html .= "jQuery.noConflict(true);" . "\n"; } - $html .= $this->getInlineHtml(); + $inlineHtml = $this->getInlineHtml(); + if ($nonce != '') { + $inlineHtml = preg_replace("/<(script|style)>/", "<$1{$nonce}>", $inlineHtml); + } + $html .= $inlineHtml; return $html; @@ -147,7 +139,7 @@ protected function makeUriRelativeTo($uri, $root) return $uris; } - if (substr($uri, 0, 1) === '/' || preg_match('/^([a-zA-Z]+:\/\/|[a-zA-Z]:\/|[a-zA-Z]:\\\)/', $uri)) { + if (substr($uri ?? '', 0, 1) === '/' || preg_match('/^([a-zA-Z]+:\/\/|[a-zA-Z]:\/|[a-zA-Z]:\\\)/', $uri ?? '')) { return $uri; } return rtrim($root, '/') . "/$uri"; diff --git a/src/LaravelDebugbar.php b/src/LaravelDebugbar.php index 79b849431..6ef7d8eae 100644 --- a/src/LaravelDebugbar.php +++ b/src/LaravelDebugbar.php @@ -2,42 +2,53 @@ namespace Barryvdh\Debugbar; -use Barryvdh\Debugbar\DataCollector\AuthCollector; use Barryvdh\Debugbar\DataCollector\CacheCollector; use Barryvdh\Debugbar\DataCollector\EventCollector; use Barryvdh\Debugbar\DataCollector\FilesCollector; use Barryvdh\Debugbar\DataCollector\GateCollector; use Barryvdh\Debugbar\DataCollector\LaravelCollector; +use Barryvdh\Debugbar\DataCollector\LivewireCollector; use Barryvdh\Debugbar\DataCollector\LogsCollector; -use Barryvdh\Debugbar\DataCollector\ModelsCollector; use Barryvdh\Debugbar\DataCollector\MultiAuthCollector; +use Barryvdh\Debugbar\DataCollector\PennantCollector; use Barryvdh\Debugbar\DataCollector\QueryCollector; use Barryvdh\Debugbar\DataCollector\SessionCollector; use Barryvdh\Debugbar\DataCollector\RequestCollector; +use Barryvdh\Debugbar\DataCollector\RouteCollector; use Barryvdh\Debugbar\DataCollector\ViewCollector; +use Barryvdh\Debugbar\DataFormatter\QueryFormatter; +use Barryvdh\Debugbar\Storage\SocketStorage; use Barryvdh\Debugbar\Storage\FilesystemStorage; +use Barryvdh\Debugbar\Support\Clockwork\ClockworkCollector; +use Barryvdh\Debugbar\Support\RequestIdGenerator; use DebugBar\Bridge\MonologCollector; -use DebugBar\Bridge\SwiftMailer\SwiftLogCollector; -use DebugBar\Bridge\SwiftMailer\SwiftMailCollector; +use DebugBar\Bridge\Symfony\SymfonyMailCollector; use DebugBar\DataCollector\ConfigCollector; use DebugBar\DataCollector\DataCollectorInterface; use DebugBar\DataCollector\ExceptionsCollector; use DebugBar\DataCollector\MemoryCollector; use DebugBar\DataCollector\MessagesCollector; -use Barryvdh\Debugbar\DataCollector\PhpInfoCollector; +use DebugBar\DataCollector\ObjectCountCollector; +use DebugBar\DataCollector\PhpInfoCollector; use DebugBar\DataCollector\RequestDataCollector; use DebugBar\DataCollector\TimeDataCollector; -use Barryvdh\Debugbar\DataFormatter\QueryFormatter; -use Barryvdh\Debugbar\Support\Clockwork\ClockworkCollector; use DebugBar\DebugBar; +use DebugBar\HttpDriverInterface; +use DebugBar\PhpHttpDriver; use DebugBar\Storage\PdoStorage; use DebugBar\Storage\RedisStorage; use Exception; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Mail\Events\MessageSent; use Illuminate\Session\SessionManager; use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractTransport; +use Symfony\Component\Mime\RawMessage; +use Throwable; /** * Debug bar subclass which adds all without Request and with LaravelCollector. @@ -77,7 +88,7 @@ class LaravelDebugbar extends DebugBar protected $booted = false; /** - * True when enabled, false disabled an null for still unknown + * True when enabled, false disabled on null for still unknown * * @var bool */ @@ -90,6 +101,17 @@ class LaravelDebugbar extends DebugBar */ protected $is_lumen = false; + /** + * Laravel default error handler + * + * @var callable|null + */ + protected $prevErrorHandler = null; + + protected ?string $editorTemplateLink = null; + protected array $remoteServerReplacements = []; + protected bool $responseIsModified = false; + protected array $stackedData = []; /** * @param Application $app */ @@ -101,6 +123,27 @@ public function __construct($app = null) $this->app = $app; $this->version = $app->version(); $this->is_lumen = Str::contains($this->version, 'Lumen'); + if ($this->is_lumen) { + $this->version = Str::betweenFirst($app->version(), '(', ')'); + } else { + $this->setRequestIdGenerator(new RequestIdGenerator()); + } + } + + /** + * Returns the HTTP driver + * + * If no http driver where defined, a PhpHttpDriver is automatically created + * + * @return HttpDriverInterface + */ + public function getHttpDriver() + { + if ($this->httpDriver === null) { + $this->httpDriver = $this->app->make(SymfonyHttpDriver::class); + } + + return $this->httpDriver; } /** @@ -124,18 +167,24 @@ public function boot() return; } - /** @var \Barryvdh\Debugbar\LaravelDebugbar $debugbar */ - $debugbar = $this; - /** @var Application $app */ $app = $this->app; + /** @var \Illuminate\Config\Repository $config */ + $config = $app['config']; + + /** @var \Illuminate\Events\Dispatcher|null $events */ + $events = isset($app['events']) ? $app['events'] : null; + + $this->editorTemplateLink = $config->get('debugbar.editor') ?: null; + $this->remoteServerReplacements = $this->getRemoteServerReplacements(); + // Set custom error handler - if ($app['config']->get('debugbar.error_handler', false)) { - set_error_handler([$this, 'handleError']); + if ($config->get('debugbar.error_handler', false)) { + $this->prevErrorHandler = set_error_handler([$this, 'handleError']); } - $this->selectStorage($debugbar); + $this->selectStorage($this); if ($this->shouldCollect('phpinfo', true)) { $this->addCollector(new PhpInfoCollector()); @@ -143,100 +192,129 @@ public function boot() if ($this->shouldCollect('messages', true)) { $this->addCollector(new MessagesCollector()); + + if ($config->get('debugbar.options.messages.trace', true)) { + $this['messages']->collectFileTrace(true); + } + + if ($config->get('debugbar.options.messages.capture_dumps', false)) { + $originalHandler = \Symfony\Component\VarDumper\VarDumper::setHandler(function ($var) use (&$originalHandler) { + if ($originalHandler) { + $originalHandler($var); + } + + self::addMessage($var); + }); + } } if ($this->shouldCollect('time', true)) { - $this->addCollector(new TimeDataCollector()); - - if (! $this->isLumen()) { - $this->app->booted( - function () use ($debugbar) { - $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); - if ($startTime) { - $debugbar['time']->addMeasure('Booting', $startTime, microtime(true)); - } + $startTime = $app['request']->server('REQUEST_TIME_FLOAT'); + + if (!$this->hasCollector('time')) { + $this->addCollector(new TimeDataCollector($startTime)); + } + + if ($config->get('debugbar.options.time.memory_usage')) { + $this['time']->showMemoryUsage(); + } + + if ($startTime && !$this->isLumen()) { + $app->booted( + function () use ($startTime) { + $this->addMeasure('Booting', $startTime, microtime(true), [], 'time'); } ); } - $debugbar->startMeasure('application', 'Application'); + $this->startMeasure('application', 'Application', 'time'); + + if ($events) { + $events->listen(\Illuminate\Routing\Events\Routing::class, function() { + $this->startMeasure('Routing'); + }); + $events->listen(\Illuminate\Routing\Events\RouteMatched::class, function() { + $this->stopMeasure('Routing'); + }); + + $events->listen(\Illuminate\Routing\Events\PreparingResponse::class, function() { + $this->startMeasure('Preparing Response'); + }); + $events->listen(\Illuminate\Routing\Events\ResponsePrepared::class, function() { + $this->stopMeasure('Preparing Response'); + }); + } } if ($this->shouldCollect('memory', true)) { $this->addCollector(new MemoryCollector()); + $this['memory']->setPrecision($config->get('debugbar.options.memory.precision', 0)); + + if (function_exists('memory_reset_peak_usage') && $config->get('debugbar.options.memory.reset_peak')) { + memory_reset_peak_usage(); + } + if ($config->get('debugbar.options.memory.with_baseline')) { + $this['memory']->resetMemoryBaseline(); + } } if ($this->shouldCollect('exceptions', true)) { try { - $exceptionCollector = new ExceptionsCollector(); - $exceptionCollector->setChainExceptions( - $this->app['config']->get('debugbar.options.exceptions.chain', true) + $this->addCollector(new ExceptionsCollector()); + $this['exceptions']->setChainExceptions( + $config->get('debugbar.options.exceptions.chain', true) ); - $this->addCollector($exceptionCollector); - } catch (\Exception $e) { + } catch (Exception $e) { } } if ($this->shouldCollect('laravel', false)) { - $this->addCollector(new LaravelCollector($this->app)); + $this->addCollector(new LaravelCollector($app)); } if ($this->shouldCollect('default_request', false)) { $this->addCollector(new RequestDataCollector()); } - if ($this->shouldCollect('events', false) && isset($this->app['events'])) { + if ($this->shouldCollect('events', false) && $events) { try { - $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); - $eventCollector = new EventCollector($startTime); - $this->addCollector($eventCollector); - $this->app['events']->subscribe($eventCollector); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add EventCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $startTime = $app['request']->server('REQUEST_TIME_FLOAT'); + $collectData = $config->get('debugbar.options.events.data', false); + $excludedEvents = $config->get('debugbar.options.events.excluded', []); + $this->addCollector(new EventCollector($startTime, $collectData, $excludedEvents)); + $events->subscribe($this['event']); + } catch (Exception $e) { + $this->addCollectorException('Cannot add EventCollector', $e); } } - if ($this->shouldCollect('views', true) && isset($this->app['events'])) { + if ($this->shouldCollect('views', true) && $events) { try { - $collectData = $this->app['config']->get('debugbar.options.views.data', true); - $this->addCollector(new ViewCollector($collectData)); - $this->app['events']->listen( + $collectData = $config->get('debugbar.options.views.data', true); + $excludePaths = $config->get('debugbar.options.views.exclude_paths', []); + $group = $config->get('debugbar.options.views.group', true); + if ($this->hasCollector('time') && $config->get('debugbar.options.views.timeline', false)) { + $timeCollector = $this['time']; + } else { + $timeCollector = null; + } + $this->addCollector(new ViewCollector($collectData, $excludePaths, $group, $timeCollector)); + $events->listen( 'composing:*', - function ($view, $data = []) use ($debugbar) { - if ($data) { - $view = $data[0]; // For Laravel >= 5.4 - } - $debugbar['views']->addView($view); + function ($event, $params) { + $this['views']->addView($params[0]); } ); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add ViewCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + } catch (Exception $e) { + $this->addCollectorException('Cannot add ViewCollector', $e); } } if (!$this->isLumen() && $this->shouldCollect('route')) { try { - $this->addCollector($this->app->make('Barryvdh\Debugbar\DataCollector\RouteCollector')); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add RouteCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $this->addCollector($app->make(RouteCollector::class)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add RouteCollector', $e); } } @@ -245,30 +323,22 @@ function ($view, $data = []) use ($debugbar) { if ($this->hasCollector('messages')) { $logger = new MessagesCollector('log'); $this['messages']->aggregate($logger); - $this->app['log']->listen( - function ($level, $message = null, $context = null) use ($logger) { - // Laravel 5.4 changed how the global log listeners are called. We must account for - // the first argument being an "event object", where arguments are passed - // via object properties, instead of individual arguments. - if ($level instanceof \Illuminate\Log\Events\MessageLogged) { - $message = $level->message; - $context = $level->context; - $level = $level->level; - } - + $app['log']->listen( + function (\Illuminate\Log\Events\MessageLogged $log) use ($logger) { try { - $logMessage = (string) $message; + $logMessage = (string) $log->message; if (mb_check_encoding($logMessage, 'UTF-8')) { - $logMessage .= (!empty($context) ? ' ' . json_encode($context) : ''); + $context = $log->context; + $logMessage .= (!empty($context) ? ' ' . json_encode($context, JSON_PRETTY_PRINT) : ''); } else { $logMessage = "[INVALID UTF-8 DATA]"; } - } catch (\Exception $e) { + } catch (Exception $e) { $logMessage = "[Exception: " . $e->getMessage() . "]"; } $logger->addMessage( - '[' . date('H:i:s') . '] ' . "LOG.$level: " . $logMessage, - $level, + '[' . date('H:i:s') . '] ' . "LOG.{$log->level}: " . $logMessage, + $log->level, false ); } @@ -276,213 +346,217 @@ function ($level, $message = null, $context = null) use ($logger) { } else { $this->addCollector(new MonologCollector($this->getMonologLogger())); } - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add LogsCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + } catch (Exception $e) { + $this->addCollectorException('Cannot add LogsCollector', $e); } } - if ($this->shouldCollect('db', true) && isset($this->app['db'])) { - $db = $this->app['db']; - if ( - $debugbar->hasCollector('time') && $this->app['config']->get( - 'debugbar.options.db.timeline', - false - ) - ) { - $timeCollector = $debugbar->getCollector('time'); + if ($this->shouldCollect('db', true) && isset($app['db']) && $events) { + if ($this->hasCollector('time') && $config->get('debugbar.options.db.timeline', false)) { + $timeCollector = $this['time']; } else { $timeCollector = null; } $queryCollector = new QueryCollector($timeCollector); $queryCollector->setDataFormatter(new QueryFormatter()); + $queryCollector->setLimits($config->get('debugbar.options.db.soft_limit'), $config->get('debugbar.options.db.hard_limit')); + $queryCollector->setDurationBackground($config->get('debugbar.options.db.duration_background')); - if ($this->app['config']->get('debugbar.options.db.with_params')) { + $threshold = $config->get('debugbar.options.db.slow_threshold', false); + if ($threshold && !$config->get('debugbar.options.db.only_slow_queries', true)) { + $queryCollector->setSlowThreshold($threshold); + } + + if ($config->get('debugbar.options.db.with_params')) { $queryCollector->setRenderSqlWithParams(true); } - if ($this->app['config']->get('debugbar.options.db.backtrace')) { - $middleware = ! $this->is_lumen ? $this->app['router']->getMiddleware() : []; - $queryCollector->setFindSource(true, $middleware); + if ($dbBacktrace = $config->get('debugbar.options.db.backtrace')) { + $middleware = ! $this->is_lumen ? $app['router']->getMiddleware() : []; + $queryCollector->setFindSource($dbBacktrace, $middleware); } - if ($this->app['config']->get('debugbar.options.db.backtrace_exclude_paths')) { - $excludePaths = $this->app['config']->get('debugbar.options.db.backtrace_exclude_paths'); - $queryCollector->mergeBacktraceExcludePaths($excludePaths); + if ($excludePaths = $config->get('debugbar.options.db.exclude_paths')) { + $queryCollector->mergeExcludePaths($excludePaths); } - if ($this->app['config']->get('debugbar.options.db.explain.enabled')) { - $types = $this->app['config']->get('debugbar.options.db.explain.types'); + if ($excludeBacktracePaths = $config->get('debugbar.options.db.backtrace_exclude_paths')) { + $queryCollector->mergeBacktraceExcludePaths($excludeBacktracePaths); + } + + if ($config->get('debugbar.options.db.explain.enabled')) { + $types = $config->get('debugbar.options.db.explain.types'); $queryCollector->setExplainSource(true, $types); } - if ($this->app['config']->get('debugbar.options.db.hints', true)) { + if ($config->get('debugbar.options.db.hints', true)) { $queryCollector->setShowHints(true); } - if ($this->app['config']->get('debugbar.options.db.show_copy', false)) { + if ($config->get('debugbar.options.db.show_copy', false)) { $queryCollector->setShowCopyButton(true); } $this->addCollector($queryCollector); try { - $db->listen( - function ( - $query, - $bindings = null, - $time = null, - $connectionName = null - ) use ( - $db, - $queryCollector - ) { - if (!$this->shouldCollect('db', true)) { + $events->listen( + function (\Illuminate\Database\Events\QueryExecuted $query) { + if (!app(static::class)->shouldCollect('db', true)) { return; // Issue 776 : We've turned off collecting after the listener was attached } - // Laravel 5.2 changed the way some core events worked. We must account for - // the first argument being an "event object", where arguments are passed - // via object properties, instead of individual arguments. - if ($query instanceof \Illuminate\Database\Events\QueryExecuted) { - $bindings = $query->bindings; - $time = $query->time; - $connection = $query->connection; - - $query = $query->sql; - } else { - $connection = $db->connection($connectionName); - } + $threshold = app('config')->get('debugbar.options.db.slow_threshold', false); + $onlyThreshold = app('config')->get('debugbar.options.db.only_slow_queries', true); //allow collecting only queries slower than a specified amount of milliseconds - $threshold = $this->app['config']->get('debugbar.options.db.slow_threshold', false); - if (!$threshold || $time > $threshold) { - $queryCollector->addQuery((string)$query, $bindings, $time, $connection); + if (!$onlyThreshold || !$threshold || $query->time > $threshold) { + $this['queries']->addQuery($query); } } ); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add listen to Queries for Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + } catch (Exception $e) { + $this->addCollectorException('Cannot listen to Queries', $e); } try { - $db->getEventDispatcher()->listen( + $events->listen( \Illuminate\Database\Events\TransactionBeginning::class, - function ($transaction) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Begin Transaction', $transaction->connection); + function ($transaction) { + $this['queries']->collectTransactionEvent('Begin Transaction', $transaction->connection); } ); - $db->getEventDispatcher()->listen( + $events->listen( \Illuminate\Database\Events\TransactionCommitted::class, - function ($transaction) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Commit Transaction', $transaction->connection); + function ($transaction) { + $this['queries']->collectTransactionEvent('Commit Transaction', $transaction->connection); } ); - $db->getEventDispatcher()->listen( + $events->listen( \Illuminate\Database\Events\TransactionRolledBack::class, - function ($transaction) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Rollback Transaction', $transaction->connection); + function ($transaction) { + $this['queries']->collectTransactionEvent('Rollback Transaction', $transaction->connection); } ); - $db->getEventDispatcher()->listen( + $events->listen( 'connection.*.beganTransaction', - function ($event, $params) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Begin Transaction', $params[0]); + function ($event, $params) { + $this['queries']->collectTransactionEvent('Begin Transaction', $params[0]); } ); - $db->getEventDispatcher()->listen( + $events->listen( 'connection.*.committed', - function ($event, $params) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Commit Transaction', $params[0]); + function ($event, $params) { + $this['queries']->collectTransactionEvent('Commit Transaction', $params[0]); } ); - $db->getEventDispatcher()->listen( + $events->listen( 'connection.*.rollingBack', - function ($event, $params) use ($queryCollector) { - $queryCollector->collectTransactionEvent('Rollback Transaction', $params[0]); + function ($event, $params) { + $this['queries']->collectTransactionEvent('Rollback Transaction', $params[0]); } ); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add listen transactions to Queries for Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) + + $events->listen( + function (\Illuminate\Database\Events\ConnectionEstablished $event) { + $this['queries']->collectTransactionEvent('Connection Established', $event->connection); + + if (app('config')->get('debugbar.options.db.memory_usage')) { + $event->connection->beforeExecuting(function () { + $this['queries']->startMemoryUsage(); + }); + } + } ); + } catch (Exception $e) { + $this->addCollectorException('Cannot listen transactions to Queries', $e); } } - if ($this->shouldCollect('models', true)) { + if ($this->shouldCollect('models', true) && $events) { try { - $modelsCollector = $this->app->make('Barryvdh\Debugbar\DataCollector\ModelsCollector'); - $this->addCollector($modelsCollector); - } catch (\Exception $e) { - // No Models collector + $this->addCollector(new ObjectCountCollector('models')); + $eventList = ['retrieved', 'created', 'updated', 'deleted']; + $this['models']->setKeyMap(array_combine($eventList, array_map('ucfirst', $eventList))); + $this['models']->collectCountSummary(true); + foreach ($eventList as $event) { + $events->listen("eloquent.{$event}: *", function ($event, $models) { + $event = explode(': ', $event); + $count = count(array_filter($models)); + $this['models']->countClass($event[1], $count, explode('.', $event[0])[1]); + }); + } + } catch (Exception $e) { + $this->addCollectorException('Cannot add Models Collector', $e); } } - if ($this->shouldCollect('livewire', true) && $this->app->bound('livewire')) { + if ($this->shouldCollect('livewire', true) && $app->bound('livewire')) { try { - $livewireCollector = $this->app->make('Barryvdh\Debugbar\DataCollector\LivewireCollector'); - $this->addCollector($livewireCollector); - } catch (\Exception $e) { - $this->addThrowable( - new Exception('Cannot add Livewire Collector: ' . $e->getMessage(), $e->getCode(), $e) - ); + $this->addCollector($app->make(LivewireCollector::class)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add Livewire Collector', $e); } } - if ($this->shouldCollect('mail', true) && class_exists('Illuminate\Mail\MailServiceProvider')) { + if ($this->shouldCollect('mail', true) && class_exists('Illuminate\Mail\MailServiceProvider') && $events) { try { - $mailer = $this->app['mailer']->getSwiftMailer(); - $this->addCollector(new SwiftMailCollector($mailer)); - if ( - $this->app['config']->get('debugbar.options.mail.full_log') && $this->hasCollector( - 'messages' - ) - ) { - $this['messages']->aggregate(new SwiftLogCollector($mailer)); + $mailCollector = new SymfonyMailCollector(); + $this->addCollector($mailCollector); + $events->listen(function (MessageSent $event) use ($mailCollector) { + $mailCollector->addSymfonyMessage($event->sent->getSymfonySentMessage()); + }); + + if ($config->get('debugbar.options.mail.show_body') || $config->get('debugbar.options.mail.full_log')) { + $mailCollector->showMessageBody(); } - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add MailCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + + if ($this->hasCollector('time') && $config->get('debugbar.options.mail.timeline')) { + $transport = $app['mailer']->getSymfonyTransport(); + $app['mailer']->setSymfonyTransport(new class ($transport, $this) extends AbstractTransport{ + private $originalTransport; + private $laravelDebugbar; + + public function __construct($transport, $laravelDebugbar) + { + $this->originalTransport = $transport; + $this->laravelDebugbar = $laravelDebugbar; + } + public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage + { + return $this->laravelDebugbar['time']->measure( + 'mail: ' . Str::limit($message->getSubject(), 100), + function () use ($message, $envelope) { + return $this->originalTransport->send($message, $envelope); + }, + 'mail' + ); + } + protected function doSend(SentMessage $message): void + { + } + public function __toString(): string + { + return $this->originalTransport->__toString(); + } + }); + } + } catch (Exception $e) { + $this->addCollectorException('Cannot add SymfonyMailCollector', $e); } } if ($this->shouldCollect('logs', false)) { try { - $file = $this->app['config']->get('debugbar.options.logs.file'); + $file = $config->get('debugbar.options.logs.file'); $this->addCollector(new LogsCollector($file)); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add LogsCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + } catch (Exception $e) { + $this->addCollectorException('Cannot add LogsCollector', $e); } } if ($this->shouldCollect('files', false)) { @@ -491,55 +565,72 @@ function ($event, $params) use ($queryCollector) { if ($this->shouldCollect('auth', false)) { try { - $guards = $this->app['config']->get('auth.guards', []); - $authCollector = new MultiAuthCollector($app['auth'], $guards); + $guards = $config->get('auth.guards', []); + $this->addCollector(new MultiAuthCollector($app['auth'], $guards)); - $authCollector->setShowName( - $this->app['config']->get('debugbar.options.auth.show_name') + $this['auth']->setShowName( + $config->get('debugbar.options.auth.show_name') ); - $this->addCollector($authCollector); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add AuthCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) + $this['auth']->setShowGuardsData( + $config->get('debugbar.options.auth.show_guards', true) ); + } catch (Exception $e) { + $this->addCollectorException('Cannot add AuthCollector', $e); } } if ($this->shouldCollect('gate', false)) { try { - $gateCollector = $this->app->make('Barryvdh\Debugbar\DataCollector\GateCollector'); - $this->addCollector($gateCollector); - } catch (\Exception $e) { - // No Gate collector + $this->addCollector($app->make(GateCollector::class)); + + if ($config->get('debugbar.options.gate.trace', false)) { + $this['gate']->collectFileTrace(true); + $this['gate']->addBacktraceExcludePaths($config->get('debugbar.options.gate.exclude_paths',[])); + } + } catch (Exception $e) { + $this->addCollectorException('Cannot add GateCollector', $e); } } - if ($this->shouldCollect('cache', false) && isset($this->app['events'])) { + if ($this->shouldCollect('cache', false) && $events) { try { - $collectValues = $this->app['config']->get('debugbar.options.cache.values', true); - $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); - $cacheCollector = new CacheCollector($startTime, $collectValues); - $this->addCollector($cacheCollector); - $this->app['events']->subscribe($cacheCollector); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add CacheCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $collectValues = $config->get('debugbar.options.cache.values', true); + $startTime = $app['request']->server('REQUEST_TIME_FLOAT'); + $this->addCollector(new CacheCollector($startTime, $collectValues)); + $events->subscribe($this['cache']); + } catch (Exception $e) { + $this->addCollectorException('Cannot add CacheCollector', $e); + } + } + + if ($this->shouldCollect('jobs', false) && $events) { + try { + $this->addCollector(new ObjectCountCollector('jobs', 'briefcase')); + $events->listen(\Illuminate\Queue\Events\JobQueued::class, function ($event) { + $this['jobs']->countClass($event->job); + }); + } catch (Exception $e) { + $this->addCollectorException('Cannot add Jobs Collector', $e); + } + } + + if ($this->shouldCollect('pennant', false)) { + if (class_exists('Laravel\Pennant\FeatureManager') && $app->bound(\Laravel\Pennant\FeatureManager::class)) { + $featureManager = $app->make(\Laravel\Pennant\FeatureManager::class); + try { + $this->addCollector(new PennantCollector($featureManager)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add PennantCollector', $e); + } } } $renderer = $this->getJavascriptRenderer(); - $renderer->setIncludeVendors($this->app['config']->get('debugbar.include_vendors', true)); - $renderer->setBindAjaxHandlerToFetch($app['config']->get('debugbar.capture_ajax', true)); - $renderer->setBindAjaxHandlerToXHR($app['config']->get('debugbar.capture_ajax', true)); + $renderer->setHideEmptyTabs($config->get('debugbar.hide_empty_tabs', false)); + $renderer->setIncludeVendors($config->get('debugbar.include_vendors', true)); + $renderer->setBindAjaxHandlerToFetch($config->get('debugbar.capture_ajax', true)); + $renderer->setBindAjaxHandlerToXHR($config->get('debugbar.capture_ajax', true)); + $renderer->setDeferDatasets($config->get('debugbar.defer_datasets', false)); $this->booted = true; } @@ -564,6 +655,12 @@ public function addCollector(DataCollectorInterface $collector) if (method_exists($collector, 'useHtmlVarDumper')) { $collector->useHtmlVarDumper(); } + if (method_exists($collector, 'setEditorLinkTemplate') && $this->editorTemplateLink) { + $collector->setEditorLinkTemplate($this->editorTemplateLink); + } + if (method_exists($collector, 'addXdebugReplacements') && $this->remoteServerReplacements) { + $collector->addXdebugReplacements($this->remoteServerReplacements); + } return $this; } @@ -580,11 +677,20 @@ public function addCollector(DataCollectorInterface $collector) */ public function handleError($level, $message, $file = '', $line = 0, $context = []) { - if (error_reporting() & $level) { - throw new \ErrorException($message, 0, $level, $file, $line); - } else { - $this->addMessage($message, 'deprecation'); + if ($this->hasCollector('exceptions')) { + $this['exceptions']->addWarning($level, $message, $file, $line); + } + + if ($this->hasCollector('messages')) { + $file = $file ? ' on ' . $this['messages']->normalizeFilePath($file) . ":{$line}" : ''; + $this['messages']->addMessage($message . $file, 'deprecation'); } + + if (! $this->prevErrorHandler) { + return; + } + + return call_user_func($this->prevErrorHandler, $level, $message, $file, $line, $context); } /** @@ -592,13 +698,15 @@ public function handleError($level, $message, $file = '', $line = 0, $context = * * @param string $name Internal name, used to stop the measure * @param string $label Public name + * @param string|null $collector + * @param string|null $group */ - public function startMeasure($name, $label = null) + public function startMeasure($name, $label = null, $collector = null, $group = null) { if ($this->hasCollector('time')) { - /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ - $collector = $this->getCollector('time'); - $collector->startMeasure($name, $label); + /** @var \DebugBar\DataCollector\TimeDataCollector */ + $time = $this->getCollector('time'); + $time->startMeasure($name, $label, $collector, $group); } } @@ -614,7 +722,7 @@ public function stopMeasure($name) $collector = $this->getCollector('time'); try { $collector->stopMeasure($name); - } catch (\Exception $e) { + } catch (Exception $e) { // $this->addThrowable($e); } } @@ -634,7 +742,7 @@ public function addException(Exception $e) /** * Adds an exception to be profiled in the debug bar * - * @param Exception $e + * @param Throwable $e */ public function addThrowable($e) { @@ -645,11 +753,28 @@ public function addThrowable($e) } } + /** + * Register collector exceptions + * + * @param string $message + * @param Exception $exception + */ + protected function addCollectorException(string $message, Exception $exception) + { + $this->addThrowable( + new Exception( + $message . ' on Laravel Debugbar: ' . $exception->getMessage(), + (int) $exception->getCode(), + $exception + ) + ); + } + /** * Returns a JavascriptRenderer for this instance * * @param string $baseUrl - * @param string $basePathng + * @param string $basePath * @return JavascriptRenderer */ public function getJavascriptRenderer($baseUrl = null, $basePath = null) @@ -669,11 +794,21 @@ public function getJavascriptRenderer($baseUrl = null, $basePath = null) */ public function modifyResponse(Request $request, Response $response) { + /** @var Application $app */ $app = $this->app; - if (!$this->isEnabled() || $this->isDebugbarRequest()) { + if (!$this->isEnabled() || !$this->booted || $this->isDebugbarRequest() || $this->responseIsModified) { return $response; } + // Prevent duplicate modification + $this->responseIsModified = true; + + // Set the Response if required + $httpDriver = $this->getHttpDriver(); + if ($httpDriver instanceof SymfonyHttpDriver) { + $httpDriver->setResponse($response); + } + // Show the Http Response Exception in the Debugbar, when available if (isset($response->exception)) { $this->addThrowable($response->exception); @@ -684,114 +819,101 @@ public function modifyResponse(Request $request, Response $response) $configCollector = new ConfigCollector(); $configCollector->setData($app['config']->all()); $this->addCollector($configCollector); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add ConfigCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + } catch (Exception $e) { + $this->addCollectorException('Cannot add ConfigCollector', $e); } } - if ($this->app->bound(SessionManager::class)) { - + $sessionHiddens = $app['config']->get('debugbar.options.session.hiddens', []); + if ($app->bound(SessionManager::class)) { /** @var \Illuminate\Session\SessionManager $sessionManager */ $sessionManager = $app->make(SessionManager::class); - $httpDriver = new SymfonyHttpDriver($sessionManager, $response); - $this->setHttpDriver($httpDriver); if ($this->shouldCollect('session') && ! $this->hasCollector('session')) { try { - $this->addCollector(new SessionCollector($sessionManager)); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add SessionCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $this->addCollector(new SessionCollector($sessionManager, $sessionHiddens)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add SessionCollector', $e); } } } else { $sessionManager = null; } + $requestHiddens = array_merge( + $app['config']->get('debugbar.options.symfony_request.hiddens', []), + array_map(fn ($key) => 'session_attributes.' . $key, $sessionHiddens) + ); if ($this->shouldCollect('symfony_request', true) && !$this->hasCollector('request')) { try { $reqId = $this->getCurrentRequestId(); - $this->addCollector(new RequestCollector($request, $response, $sessionManager, $reqId)); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add SymfonyRequestCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $this->addCollector(new RequestCollector($request, $response, $sessionManager, $reqId, $requestHiddens)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add SymfonyRequestCollector', $e); } } if ($app['config']->get('debugbar.clockwork') && ! $this->hasCollector('clockwork')) { try { - $this->addCollector(new ClockworkCollector($request, $response, $sessionManager)); - } catch (\Exception $e) { - $this->addThrowable( - new Exception( - 'Cannot add ClockworkCollector to Laravel Debugbar: ' . $e->getMessage(), - $e->getCode(), - $e - ) - ); + $this->addCollector(new ClockworkCollector($request, $response, $sessionManager, $requestHiddens)); + } catch (Exception $e) { + $this->addCollectorException('Cannot add ClockworkCollector', $e); } $this->addClockworkHeaders($response); } + try { + if ($this->hasCollector('views') && $response->headers->has('X-Inertia')) { + $content = $response->getContent(); + + if (is_string($content)) { + $content = json_decode($content, true); + } + + if (is_array($content)) { + $this['views']->addInertiaAjaxView($content); + } + } + } catch (Exception $e) { + } + + if ($app['config']->get('debugbar.add_ajax_timing', false)) { + $this->addServerTimingHeaders($response); + } + if ($response->isRedirection()) { try { $this->stackData(); - } catch (\Exception $e) { + } catch (Exception $e) { $app['log']->error('Debugbar exception: ' . $e->getMessage()); } - } elseif ( - $this->isJsonRequest($request) && - $app['config']->get('debugbar.capture_ajax', true) - ) { - try { - $this->sendDataInHeaders(true); - if ($app['config']->get('debugbar.add_ajax_timing', false)) { - $this->addServerTimingHeaders($response); - } - } catch (\Exception $e) { - $app['log']->error('Debugbar exception: ' . $e->getMessage()); - } - } elseif ( - ($response->headers->has('Content-Type') && - strpos($response->headers->get('Content-Type'), 'html') === false) || - $request->getRequestFormat() !== 'html' || - $response->getContent() === false || - $this->isJsonRequest($request) + return $response; + } + + try { + // Collect + store data, only inject the ID in theheaders + $this->sendDataInHeaders(true); + } catch (Exception $e) { + $app['log']->error('Debugbar exception: ' . $e->getMessage()); + } + + // Check if it's safe to inject the Debugbar + if ( + $app['config']->get('debugbar.inject', true) + && str_contains($response->headers->get('Content-Type', 'text/html'), 'html') + && !$this->isJsonRequest($request, $response) + && $response->getContent() !== false + && in_array($request->getRequestFormat(), [null, 'html'], true) ) { - try { - // Just collect + store data, don't inject it. - $this->collect(); - } catch (\Exception $e) { - $app['log']->error('Debugbar exception: ' . $e->getMessage()); - } - } elseif ($app['config']->get('debugbar.inject', true)) { try { $this->injectDebugbar($response); - } catch (\Exception $e) { + } catch (Exception $e) { $app['log']->error('Debugbar exception: ' . $e->getMessage()); } } - - return $response; } @@ -802,6 +924,7 @@ public function modifyResponse(Request $request, Response $response) public function isEnabled() { if ($this->enabled === null) { + /** @var \Illuminate\Config\Repository $config */ $config = $this->app['config']; $configEnabled = value($config->get('debugbar.enabled')); @@ -822,23 +945,38 @@ public function isEnabled() */ protected function isDebugbarRequest() { - return $this->app['request']->segment(1) == $this->app['config']->get('debugbar.route_prefix'); + return $this->app['request']->is($this->app['config']->get('debugbar.route_prefix') . '*'); } /** * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response * @return bool */ - protected function isJsonRequest(Request $request) + protected function isJsonRequest(Request $request, Response $response) { - // If XmlHttpRequest or Live, return true - if ($request->isXmlHttpRequest() || $request->headers->get('X-Livewire')) { + // If XmlHttpRequest, Live or HTMX, return true + if ( + $request->isXmlHttpRequest() || + $request->headers->has('X-Livewire') || + ($request->headers->has('Hx-Request') && $request->headers->has('Hx-Target')) + ) { return true; } // Check if the request wants Json $acceptable = $request->getAcceptableContentTypes(); - return (isset($acceptable[0]) && $acceptable[0] == 'application/json'); + if (isset($acceptable[0]) && in_array($acceptable[0], ['application/json', 'application/javascript'], true)) { + return true; + } + + // Check if content looks like JSON without actually validating + $content = $response->getContent(); + if (is_string($content) && strlen($content) > 0 && in_array($content[0], ['{', '['], true)) { + return true; + } + + return false; } /** @@ -891,9 +1029,17 @@ function (&$item) { */ public function injectDebugbar(Response $response) { + /** @var \Illuminate\Config\Repository $config */ + $config = $this->app['config']; $content = $response->getContent(); $renderer = $this->getJavascriptRenderer(); + $autoShow = $config->get('debugbar.ajax_handler_auto_show', true); + $renderer->setAjaxHandlerAutoShow($autoShow); + + $enableTab = $config->get('debugbar.ajax_handler_enable_tab', true); + $renderer->setAjaxHandlerEnableTab($enableTab); + if ($this->getStorage()) { $openHandlerUrl = route('debugbar.openhandler'); $renderer->setOpenHandlerUrl($openHandlerUrl); @@ -903,7 +1049,7 @@ public function injectDebugbar(Response $response) $widget = $renderer->render(); // Try to put the js/css directly before the - $pos = strripos($content, ''); + $pos = stripos($content, ''); if (false !== $pos) { $content = substr($content, 0, $pos) . $head . substr($content, $pos); } else { @@ -928,12 +1074,35 @@ public function injectDebugbar(Response $response) $response->setContent($content); $response->headers->remove('Content-Length'); - // Restore original response (eg. the View or Ajax data) + // Restore original response (e.g. the View or Ajax data) if ($original) { $response->original = $original; } } + /** + * Checks if there is stacked data in the session + * + * @return boolean + */ + public function hasStackedData() + { + return count($this->getStackedData(false)) > 0; + } + + /** + * Returns the data stacked in the session + * + * @param boolean $delete Whether to delete the data in the session + * @return array + */ + public function getStackedData($delete = true): array + { + $this->stackedData = array_merge($this->stackedData, parent::getStackedData($delete)); + + return $this->stackedData; + } + /** * Disable the Debugbar */ @@ -948,13 +1117,16 @@ public function disable() * @param string $label * @param float $start * @param float $end + * @param array|null $params + * @param string|null $collector + * @param string|null $group */ - public function addMeasure($label, $start, $end) + public function addMeasure($label, $start, $end, $params = [], $collector = null, $group = null) { if ($this->hasCollector('time')) { - /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ - $collector = $this->getCollector('time'); - $collector->addMeasure($label, $start, $end); + /** @var \DebugBar\DataCollector\TimeDataCollector */ + $time = $this->getCollector('time'); + $time->addMeasure($label, $start, $end, $params, $collector, $group); } } @@ -963,14 +1135,16 @@ public function addMeasure($label, $start, $end) * * @param string $label * @param \Closure $closure + * @param string|null $collector + * @param string|null $group * @return mixed */ - public function measure($label, \Closure $closure) + public function measure($label, \Closure $closure, $collector = null, $group = null) { if ($this->hasCollector('time')) { - /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ - $collector = $this->getCollector('time'); - $result = $collector->measure($label, $closure); + /** @var \DebugBar\DataCollector\TimeDataCollector */ + $time = $this->getCollector('time'); + $result = $time->measure($label, $closure, $collector, $group); } else { $result = $closure(); } @@ -1076,6 +1250,7 @@ protected function isLumen() */ protected function selectStorage(DebugBar $debugbar) { + /** @var \Illuminate\Config\Repository $config */ $config = $this->app['config']; if ($config->get('debugbar.storage.enabled')) { $driver = $config->get('debugbar.storage.driver', 'file'); @@ -1099,6 +1274,11 @@ protected function selectStorage(DebugBar $debugbar) $class = $config->get('debugbar.storage.provider'); $storage = $this->app->make($class); break; + case 'socket': + $hostname = $config->get('debugbar.storage.hostname', '127.0.0.1'); + $port = $config->get('debugbar.storage.port', 2304); + $storage = new SocketStorage($hostname, $port); + break; case 'file': default: $path = $config->get('debugbar.storage.path'); @@ -1114,7 +1294,7 @@ protected function addClockworkHeaders(Response $response) { $prefix = $this->app['config']->get('debugbar.route_prefix'); $response->headers->set('X-Clockwork-Id', $this->getCurrentRequestId(), true); - $response->headers->set('X-Clockwork-Version', 1, true); + $response->headers->set('X-Clockwork-Version', 9, true); $response->headers->set('X-Clockwork-Path', $prefix . '/clockwork/', true); } @@ -1130,26 +1310,32 @@ protected function addServerTimingHeaders(Response $response) $collector = $this->getCollector('time'); $headers = []; - foreach ($collector->collect()['measures'] as $k => $m) { - $headers[] = sprintf('app;desc="%s";dur=%F', str_replace('"', "'", $m['label']), $m['duration'] * 1000); + foreach ($collector->collect()['measures'] as $m) { + $headers[] = sprintf('app;desc="%s";dur=%F', str_replace(array("\n", "\r"), ' ', str_replace('"', "'", $m['label'])), $m['duration'] * 1000); } $response->headers->set('Server-Timing', $headers, false); } } + /** + * @return array + */ + private function getRemoteServerReplacements() + { + $localPath = $this->app['config']->get('debugbar.local_sites_path') ?: base_path(); + $remotePaths = array_filter(explode(',', $this->app['config']->get('debugbar.remote_sites_path') ?: '')) ?: [base_path()]; + + return array_fill_keys($remotePaths, $localPath); + } + /** * @return \Monolog\Logger * @throws Exception */ private function getMonologLogger() { - // The logging was refactored in Laravel 5.6 - if ($this->checkVersion('5.6')) { - $logger = $this->app['log']->getLogger(); - } else { - $logger = $this->app['log']->getMonolog(); - } + $logger = $this->app['log']->getLogger(); if (get_class($logger) !== 'Monolog\Logger') { throw new Exception('Logger is not a Monolog\Logger instance'); diff --git a/src/LumenServiceProvider.php b/src/LumenServiceProvider.php index ed6297115..0afa1294d 100644 --- a/src/LumenServiceProvider.php +++ b/src/LumenServiceProvider.php @@ -3,12 +3,39 @@ namespace Barryvdh\Debugbar; use Laravel\Lumen\Application; +use DebugBar\DataCollector\TimeDataCollector; class LumenServiceProvider extends ServiceProvider { /** @var Application */ protected $app; + public function boot() + { + parent::boot(); + + $this->app->call( + function () { + $debugBar = $this->app->get(LaravelDebugbar::class); + if ($debugBar->shouldCollect('time', true)) { + $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); + + if (!$debugBar->hasCollector('time')) { + $debugBar->addCollector(new TimeDataCollector($startTime)); + } + + if ($this->app['config']->get('debugbar.options.time.memory_usage')) { + $debugBar['time']->showMemoryUsage(); + } + + if ($startTime) { + $debugBar->addMeasure('Booting', $startTime, microtime(true), [], 'time'); + } + } + } + ); + } + /** * Get the active router. * diff --git a/src/Middleware/InjectDebugbar.php b/src/Middleware/InjectDebugbar.php index 2b4e3e158..48be02d8a 100644 --- a/src/Middleware/InjectDebugbar.php +++ b/src/Middleware/InjectDebugbar.php @@ -2,14 +2,13 @@ namespace Barryvdh\Debugbar\Middleware; -use Error; use Closure; use Exception; use Illuminate\Http\Request; use Barryvdh\Debugbar\LaravelDebugbar; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Debug\ExceptionHandler; -use Symfony\Component\Debug\Exception\FatalThrowableError; +use Throwable; class InjectDebugbar { @@ -65,10 +64,7 @@ public function handle($request, Closure $next) try { /** @var \Illuminate\Http\Response $response */ $response = $next($request); - } catch (Exception $e) { - $response = $this->handleException($request, $e); - } catch (Error $error) { - $e = new FatalThrowableError($error); + } catch (Throwable $e) { $response = $this->handleException($request, $e); } @@ -84,11 +80,11 @@ public function handle($request, Closure $next) * (Copy from Illuminate\Routing\Pipeline by Taylor Otwell) * * @param $passable - * @param Exception $e + * @param Throwable $e * @return mixed * @throws Exception */ - protected function handleException($passable, Exception $e) + protected function handleException($passable, $e) { if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) { throw $e; diff --git a/src/Resources/cache/widget.js b/src/Resources/cache/widget.js index 5314eacec..5635363e6 100644 --- a/src/Resources/cache/widget.js +++ b/src/Resources/cache/widget.js @@ -40,6 +40,9 @@ var m = lines[i]; if (measure.params && !$.isEmptyObject(measure.params)) { + if (measure.params.delete) { + $(m).next().find('td.phpdebugbar-widgets-name:contains(delete)').closest('tr').remove(); + } if (measure.params.delete && measure.params.key) { $('') .addClass(csscls('forget')) diff --git a/src/Resources/laravel-debugbar-dark-mode-media-end.css b/src/Resources/laravel-debugbar-dark-mode-media-end.css deleted file mode 100644 index ff30235f0..000000000 --- a/src/Resources/laravel-debugbar-dark-mode-media-end.css +++ /dev/null @@ -1 +0,0 @@ -} \ No newline at end of file diff --git a/src/Resources/laravel-debugbar-dark-mode-media-start.css b/src/Resources/laravel-debugbar-dark-mode-media-start.css deleted file mode 100644 index 5b1a64706..000000000 --- a/src/Resources/laravel-debugbar-dark-mode-media-start.css +++ /dev/null @@ -1 +0,0 @@ -@media (prefers-color-scheme: dark) { \ No newline at end of file diff --git a/src/Resources/laravel-debugbar-dark-mode.css b/src/Resources/laravel-debugbar-dark-mode.css deleted file mode 100644 index 4c55466cd..000000000 --- a/src/Resources/laravel-debugbar-dark-mode.css +++ /dev/null @@ -1,289 +0,0 @@ -/* Dark mode */ - -div.phpdebugbar, -div.phpdebugbar-openhandler { - --color-gray-100: #F7FAFC; - --color-gray-200: #EDF2F7; - --color-gray-300: #E2E8F0; - --color-gray-400: #CBD5E0; - --color-gray-500: #A0AEC0; - --color-gray-600: #718096; - --color-gray-700: #4A5568; - --color-gray-800: #2D3748; - --color-gray-900: #1A202C; - --color-red-vivid: #FF0040; -} - -div.phpdebugbar, -div.phpdebugbar-openhandler { - background: var(--color-gray-800); -} - -div.phpdebugbar, -div.phpdebugbar-openhandler, -div.phpdebugbar div.phpdebugbar-header > div > *, -div.phpdebugbar ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label, -div.phpdebugbar ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector, -div.phpdebugbar code.phpdebugbar-widgets-sql span.hljs-keyword, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-header, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-header a { - color: var(--color-gray-200); -} - -div.phpdebugbar-openhandler, -div.phpdebugbar div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar, -div.phpdebugbar div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file, -div.phpdebugbar ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select { - border-color: var(--color-gray-600); -} - -div.phpdebugbar div.phpdebugbar-header, -div.phpdebugbar div.phpdebugbar-panel div.phpdebugbar-widgets-status > span:first-child:before, -div.phpdebugbar-openhandler table th, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select { - text-shadow: 1px 1px var(--color-gray-700); -} - -div.phpdebugbar div.phpdebugbar-header > div > select, -div.phpdebugbar ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select, -div.phpdebugbar input[type='text'], -div.phpdebugbar input[type='password'] { - background-color: var(--color-gray-800); -} - -div.phpdebugbar div.phpdebugbar-header, -div.phpdebugbar a.phpdebugbar-restore-btn, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-header, -div.phpdebugbar dl.phpdebugbar-widgets-kvlist > :nth-child(4n-1), -div.phpdebugbar dl.phpdebugbar-widgets-kvlist > :nth-child(4n), -div.phpdebugbar ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even), -div.phpdebugbar .hljs, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params th, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button, -div.phpdebugbar div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params th { - background-color: var(--color-gray-900); -} - -div.phpdebugbar .phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-headers, -div.phpdebugbar ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params { - border-left-color: var(--color-gray-600); -} - -div.phpdebugbar a.phpdebugbar-tab:hover, -div.phpdebugbar span.phpdebugbar-indicator:hover, -div.phpdebugbar a.phpdebugbar-indicator:hover, -div.phpdebugbar a.phpdebugbar-close-btn:hover, -div.phpdebugbar a.phpdebugbar-minimize-btn:hover, -div.phpdebugbar a.phpdebugbar-maximize-btn:hover, -div.phpdebugbar a.phpdebugbar-open-btn:hover, -div.phpdebugbar-openhandler table th, -div.phpdebugbar-openhandler table tr:nth-child(2n), -div.phpdebugbar div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar { - background-color: var(--color-gray-700); -} - -div.phpdebugbar .phpdebugbar-indicator span.phpdebugbar-tooltip, -div.phpdebugbar div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text, -div.phpdebugbar pre.sf-dump, -div.phpdebugbar .hljs, -div.phpdebugbar code.phpdebugbar-widgets-sql span.hljs-operator { - color: var(--color-gray-100); -} - -div.phpdebugbar pre.sf-dump .sf-dump-public, -div.phpdebugbar pre.sf-dump .sf-dump-protected, -div.phpdebugbar pre.sf-dump .sf-dump-private { - color: var(--color-gray-100) !important; -} - -div.phpdebugbar div.phpdebugbar-panel div.phpdebugbar-widgets-status > span:first-child:before, -div.phpdebugbar-openhandler a { - color: var(--color-gray-500); -} - -div.phpdebugbar .phpdebugbar-indicator span.phpdebugbar-tooltip, -div.phpdebugbar div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text { - background: var(--color-gray-900); -} - -div.phpdebugbar .hljs-tag .hljs-value, -div.phpdebugbar .hljs-phpdoc, -div.phpdebugbar .tex .hljs-formula, -div.phpdebugbar div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-message { - color: var(--color-red-vivid); -} - -div.phpdebugbar div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-filename, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-database, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-duration, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-memory, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-row-count, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-copy-clipboard, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-stmt-id, -div.phpdebugbar .phpdebugbar-text-muted, -div.phpdebugbar-openhandler .phpdebugbar-text-muted -{ - color: var(--color-gray-600); -} - -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate { - background-color: #6f6200; -} - -div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value:before { - color: #7B7B7B; -} - -div.phpdebugbar-openhandler { - border-top-color: #fa5661; -} - -div.phpdebugbar div.phpdebugbar-header .phpdebugbar-tab { - border-left-color: var(--color-gray-800); -} - -div.phpdebugbar div.phpdebugbar-body { - border-top-color: var(--color-gray-800); -} - -div.phpdebugbar a.phpdebugbar-restore-btn { - border-right-color: var(--color-gray-800) !important; -} - -div.phpdebugbar span.phpdebugbar-indicator, -div.phpdebugbar a.phpdebugbar-indicator, -div.phpdebugbar a.phpdebugbar-close-btn { - border-right-color: var(--color-gray-800); -} - -div.phpdebugbar div.phpdebugbar-panel div.phpdebugbar-widgets-status { - background-color: var(--color-gray-900) !important; - border-bottom-color: var(--color-gray-800) !important; -} - -div.phpdebugbar div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status { - background: var(--color-gray-900) !important; -} - -div.phpdebugbar div.phpdebugbar-panel div.phpdebugbar-widgets-status > * { - color: var(--color-gray-200) !important; -} - -div.phpdebugbar div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-render-time, -div.phpdebugbar div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-memory, -div.phpdebugbar div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count, -div.phpdebugbar div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type { - color: var(--color-gray-600) !important; -} - -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td { - border-color: var(--color-gray-600) !important; -} - -div.phpdebugbar code, -div.phpdebugbar pre { - color: #FFF; -} - -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select, -div.phpdebugbar input[type='text'], -div.phpdebugbar input[type='password'] { - color: var(--color-gray-300); -} - -div.phpdebugbar div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-filename, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-database, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-duration, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-memory, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-row-count, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-copy-clipboard, -div.phpdebugbar div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate span.phpdebugbar-widgets-stmt-id { - color: var(--color-gray-500); -} - -div.phpdebugbar a.phpdebugbar-minimize-btn { - background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22chevron-down%22%3E%3Cpath%20d%3D%22M1683%20808l-742%20741q-19%2019-45%2019t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19%2045-19t45%2019l531%20531%20531-531q19-19%2045-19t45%2019l166%20165q19%2019%2019%2045.5t-19%2045.5z%22%20style%3D%22fill%3A%20%23EDF2F7%22%2F%3E%3C%2Fsvg%3E) no-repeat 6px 6px / 14px 14px; -} - -div.phpdebugbar a.phpdebugbar-maximize-btn { - background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201792%201792%22%20id%3D%22chevron-up%22%3E%3Cpath%20d%3D%22M1683%201331l-166%20165q-19%2019-45%2019t-45-19l-531-531-531%20531q-19%2019-45%2019t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19%2045-19t45%2019l742%20741q19%2019%2019%2045.5t-19%2045.5z%22%20style%3D%22fill%3A%20%23EDF2F7%22%2F%3E%3C%2Fsvg%3E) no-repeat 6px 6px / 14px 14px; -} - -div.phpdebugbar a.phpdebugbar-open-btn { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOCAYAAADJ7fe0AAABL0lEQVQ4T63RvWqEQBAA4LkjdX46WQh5hoDpFnyAgGzKWKQJdgFfIT8vIKSwCSEQHyJN4AptomAlZMXAVRuxWAwibqMGBUVygdtAplmWmf1mmVnAP8RiNDjne0KINQDsz9wCIXSwrc+AMMa6tm0hz3PozzGWyyUoirJhIISm5n1yuIRh2G3rNs+rqvo7IoTIMMbnkthqXjf9JAiCO0LIR9d1T5JQXzbMbEA8z/vEGB9TSt/KsjyURZIkeTQM43JAHMe5JYS8Zlm2appG1gDXdbFt2/44IIVS+lCW5amswBh70XX9DADEuOIjzvlaCCFrgO/7F5ZlPU8rTtP0viiKK1mhqqpU07STfrATEsfxV13Xu7JIFEU3pmlej/XjTDAA7MgiAPAOANlP5A/vN0u/AXUIgA+u4l6FAAAAAElFTkSuQmCC) no-repeat 8px 6px; -} - -div.phpdebugbar a.phpdebugbar-close-btn { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABUElEQVQ4T52SMUsDMRTH3xUPChHCxeko3YrUqeFGEexym3boJ9DBufhJpLODfoIbTrcuFcSxxMki3Uq5yZRAAweRVp70HdfWQZolb3i/l/x/iQd7Lm9PDjbA2Wz24HmerFarbSGEwaFaa57n+XC1WqlarXZNBxUgQsaYK+ccCCHeGWPn2GStfdFat3zfB875I8EFOBqNPpbLZRObsQnh9YktHIarUqmMoyg6wboApZTtfr9/zxhrEIw7QdbaSa/Xu1FKDTdAADiQUp6VYcpTgl4B4HsbhCRJjsIwfPN9/7hs2zn3mWXZabfb/dqRg/ZIBF2PmigzCiPbRcbpdKrQXjkTguXMKKxer8uNqw4Gg6cgCC7WT/ArAuty5vl8/hzH8eV2xmaapnec88baHooAEmaMmXQ6nVsAGO/IAQB8x0MAUGQPbSMPAAuC/gL//XV/ALsvnQ+MsaHgAAAAAElFTkSuQmCC) no-repeat 9px 6px; -} - - -/* Dracula Theme v1.2.5 - * - * https://github.com/dracula/highlightjs - * - * Copyright 2016-present, All rights reserved - * - * Code licensed under the MIT license - * - * @author Denis Ciccale - * @author Zeno Rocha - */ - -div.phpdebugbar .hljs-built_in, -div.phpdebugbar .hljs-selector-tag, -div.phpdebugbar .hljs-section, -div.phpdebugbar .hljs-link { - color: #8be9fd; -} - -div.phpdebugbar .hljs-keyword { - color: #ff79c6; -} - -div.phpdebugbar .hljs, -div.phpdebugbar .hljs-subst { - color: #f8f8f2; -} - -div.phpdebugbar .hljs-title { - color: #50fa7b; -} - -div.phpdebugbar .hljs-string, -div.phpdebugbar .hljs-meta, -div.phpdebugbar .hljs-name, -div.phpdebugbar .hljs-type, -div.phpdebugbar .hljs-attr, -div.phpdebugbar .hljs-symbol, -div.phpdebugbar .hljs-bullet, -div.phpdebugbar .hljs-addition, -div.phpdebugbar .hljs-variable, -div.phpdebugbar .hljs-template-tag, -div.phpdebugbar .hljs-template-variable { - color: #f1fa8c; -} - -div.phpdebugbar .hljs-comment, -div.phpdebugbar .hljs-quote, -div.phpdebugbar .hljs-deletion { - color: #6272a4; -} - -div.phpdebugbar .hljs-literal, -div.phpdebugbar .hljs-number { - color: #bd93f9; -} diff --git a/src/Resources/laravel-debugbar.css b/src/Resources/laravel-debugbar.css index db2cc92c2..4a33ba8a8 100644 --- a/src/Resources/laravel-debugbar.css +++ b/src/Resources/laravel-debugbar.css @@ -1,161 +1,128 @@ -/* Force Laravel Whoops exception handler to be displayed under the debug bar */ -.Whoops.container { - z-index: 5999999; -} +div.phpdebugbar, +div.phpdebugbar-openhandler { + --debugbar-red-vivid: #eb4432; -div.phpdebugbar { - font-size: 13px; - font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; - direction: ltr; - text-align: left; - z-index: 6000000; -} + /*--debugbar-background: #fff;*/ + --debugbar-background-alt: #f5f5f5; + --debugbar-text: #222; + /*--debugbar-text-muted: #888;*/ + --debugbar-border: #bbb; -div.phpdebugbar-openhandler-overlay { - z-index: 6000001; - cursor: pointer; -} + --debugbar-header: #fff; + --debugbar-header-text: #555; + /*--debugbar-header-border: #ddd;*/ -div.phpdebugbar-openhandler { - border: 1px solid #aaa; - border-top: 3px solid #fa5661; - width: 80%; - height: 70%; - padding: 10px; - border-radius: 5px; - overflow-y: scroll; - z-index: 6000002; - cursor: default; -} + /*--debugbar-active: #ccc;*/ + /*--debugbar-active-text: #666;*/ -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button { - display: inline-block; - cursor: pointer; - margin: 5px; - padding: 0px 12px 2px; - border-radius: 3px; - border: 1px solid #ddd; - background-color: #f5f5f5; - color: #000; - text-shadow: 1px 1px #fff; - font-size: 13px; -} + --debugbar-icons: var(--debugbar-header-text); + --debugbar-badge: #fff; + --debugbar-badge-text: var(--debugbar-red-vivid); -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form { - margin: 15px 0px 5px; - text-transform: uppercase; - font-size: 13px; - font-weight: bold; -} + --debugbar-badge-active: var(--debugbar-red-vivid); + --debugbar-badge-active-text: #fff; -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form br { - display: none; -} + --debugbar-link: #777; + --debugbar-hover: #666; -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form > b { - display: none; + --debugbar-header-hover: #ebebeb; } -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select { - margin: 0px 10px 10px 2px; - border: 1px solid #aaa; - border-radius: 3px; - padding: 3px 6px 2px; - line-height: 16px; -} +/* Dark mode */ +div.phpdebugbar[data-theme='dark'], +div.phpdebugbar-openhandler[data-theme='dark'] { + --debugbar-white: #FFFFFF; + --debugbar-gray-100: #F7FAFC; + --debugbar-gray-200: #EDF2F7; + --debugbar-gray-300: #E2E8F0; + --debugbar-gray-400: #CBD5E0; + --debugbar-gray-500: #A0AEC0; + --debugbar-gray-600: #718096; + --debugbar-gray-700: #4A5568; + --debugbar-gray-800: #252a37; + --debugbar-gray-900: #18181b; + --debugbar-red-vivid: #eb4432; -@media (max-width: 720px) { - div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select + br, - div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input[name="uri"] + br { - display: block; - } -} + --debugbar-background: var(--debugbar-gray-800); + --debugbar-background-alt: var(--debugbar-gray-900); + --debugbar-text: var(--debugbar-gray-100); + --debugbar-text-muted: var(--debugbar-gray-600); + --debugbar-border: var(--debugbar-gray-600); -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select { - padding: 2px 5px 1px; -} + --debugbar-header:var(--debugbar-gray-900); + --debugbar-header-text: var(--debugbar-gray-200); + --debugbar-header-border: var(--debugbar-gray-800); + --debugbar-header-hover: var(--debugbar-gray-700); -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions input[name="uri"] { - width: 200px; -} + --debugbar-active: var(--debugbar-gray-800); + --debugbar-active-text: var(--debugbar-gray-100); -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions input[name="ip"] { - width: 90px; -} + --debugbar-icons: var(--debugbar-header-text); + --debugbar-badge: var(--debugbar-white); + --debugbar-badge-text: var(--debugbar-red-vivid); -div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button { - outline: none; - margin: 0px 0px 10px 2px; - padding: 5px 15px 4px; - line-height: 12px; -} + --debugbar-badge-active: var(--debugbar-red-vivid); + --debugbar-badge-active-text: var(--debugbar-white); -div.phpdebugbar-openhandler table { - margin: 15px 0px 10px; - table-layout: auto; - border-collapse: collapse; - width: 100%; + --debugbar-link: var(--debugbar-gray-300); + --debugbar-hover: var(--debugbar-gray-100); + --debugbar-hover-bg: var(--debugbar-gray-700); } -div.phpdebugbar-openhandler table td, -div.phpdebugbar-openhandler table th { - width: auto!important; - text-align: left; - border: 0px solid #bbb; - padding: 2px 8px; - font-size: 14px; +div.phpdebugbar[data-theme='dark'] code.phpdebugbar-widgets-sql, +div.phpdebugbar[data-theme='dark'] .phpdebugbar-widgets-name, +div.phpdebugbar[data-theme='dark'] .phpdebugbar-widgets-key, +div.phpdebugbar[data-theme='dark'] .phpdebugbar-widgets-success > pre.sf-dump > .sf-dump-note { + color: #fdfd96; } -div.phpdebugbar-openhandler table th { - text-shadow: 1px 1px #fff; - font-size: 12px; - text-transform: uppercase; - padding: 5px 8px; +/* Force Laravel Whoops exception handler to be displayed under the debug bar */ +.Whoops.container { + z-index: 5999999; } -div.phpdebugbar-openhandler table th, -div.phpdebugbar-openhandler table tr:nth-child(2n) { - background-color: #efefef; +div.phpdebugbar { + font-size: 13px; + z-index: 6000000; } -div.phpdebugbar-openhandler table th:nth-child(1), div.phpdebugbar-openhandler table td:nth-child(1), /* Date */ -div.phpdebugbar-openhandler table th:nth-child(2), div.phpdebugbar-openhandler table td:nth-child(2), /* Method */ -div.phpdebugbar-openhandler table th:nth-child(4), div.phpdebugbar-openhandler table td:nth-child(4), /* IP */ -div.phpdebugbar-openhandler table th:nth-child(5), div.phpdebugbar-openhandler table td:nth-child(5) { /* Filter */ - width: 5%!important; - white-space: nowrap; +div.phpdebugbar-openhandler-overlay { + z-index: 6000001; + cursor: pointer; } -div.phpdebugbar-openhandler table th:nth-child(2), div.phpdebugbar-openhandler table td:nth-child(2), /* Method */ -div.phpdebugbar-openhandler table th:nth-child(4), div.phpdebugbar-openhandler table td:nth-child(4), /* IP */ -div.phpdebugbar-openhandler table th:nth-child(5), div.phpdebugbar-openhandler table td:nth-child(5) { /* Filter */ - text-align: center; +div.phpdebugbar-openhandler { + border: 1px solid var(--debugbar-border); + border-top: 3px solid var(--debugbar-red-vivid); + border-radius: 5px; + overflow-y: scroll; + z-index: 6000002; + cursor: default; } -div.phpdebugbar-openhandler table th:nth-child(3) { /* URL */ - width: calc(100vw - 100% - 196px)!important; + +div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form { + margin: 15px 0px 5px; + font-size: 13px; + font-weight: bold; } -div.phpdebugbar-openhandler table td a { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; +div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button, +div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions select, +div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions input { + height: 24px; } div.phpdebugbar-resize-handle { display: block!important; height: 3px; - margin-top: -4px; + margin-top: -3px; width: 100%; background: none; cursor: ns-resize; border-top: none; border-bottom: 0px; - background-color: #fa5661; + background-color: var(--debugbar-red-vivid); } .phpdebugbar.phpdebugbar-minimized div.phpdebugbar-resize-handle { @@ -164,15 +131,7 @@ div.phpdebugbar-resize-handle { div.phpdebugbar-closed, div.phpdebugbar-minimized { - border-top-color: #ddd; -} - -div.phpdebugbar code, div.phpdebugbar pre, div.phpdebugbar samp { - background: none; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 1em; - border: 0; - padding: 0; + border-top-color: var(--debugbar-border); } div.phpdebugbar .hljs { @@ -184,67 +143,46 @@ div.phpdebugbar .phpdebugbar-widgets-messages .hljs > code { } div.phpdebugbar code, div.phpdebugbar pre { - color: #000; + color: var(--debugbar-text); } div.phpdebugbar-widgets-exceptions .phpdebugbar-widgets-filename { margin-top: 4px; } -div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file { - border: 1px solid #d2d2d2; - border-left: 2px solid #d2d2d2; -} - div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file[style="display: block;"] ~ div { display: block; } div.phpdebugbar pre.sf-dump { - color: #000; + color: var(--debugbar-text); outline: none; - padding: 0px!important; + padding-left: 0px; } div.phpdebugbar-body { - border-top: 1px solid #ddd; + border-top: 1px solid var(--debugbar-header-border); } -div.phpdebugbar-header { - min-height: 30px; - line-height: 20px; - padding-left: 39px; - text-shadow: 1px 1px #FFF; +div.phpdebugbar-header span.phpdebugbar-text, div.phpdebugbar-header > div > span > span, div.phpdebugbar-header > div > span > i{ + display: inline-block; } -div.phpdebugbar-header, -a.phpdebugbar-restore-btn, -div.phpdebugbar-openhandler .phpdebugbar-openhandler-header { - background: #f5f5f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIcAAACHCAYAAAA850oKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAmVAAAJlQBMwDy4QAAABJ0RVh0U29mdHdhcmUAZXpnaWYuY29toMOzWAAAGE5JREFUeJztnXt8VOWZx7/PyQURrK3YukUkZwJycXIGMF6KWqVbrNsLdq3A9qLW3W2hVatowUQEQUFNBNvS7dpCrb34sVtFe9N22zWraNeiSETmZOSazJmIvWxFViEqw+Q8+8dMQiDnTCbJzGQS5vtn5jlnnsz85pznvO9zEfKAVleXcfCNryB6DXAiog+hepfYrfvy8f5F+obk+g3UqpwJ7reA4FEv7QPq2c8acZx3c+1Hkd6TM3FosPJsDHc1cGEPpq2ILCUcfVBAc+VPkd6TdXFosHIsJe0rUbmil+d/EWSh2NFns+1Tkb6RNXFocMxJGGW3gl4HlPuYbUH0NVQ+lcajn2MYtfJyy65s+Vakbxj9PYFWV5epFZiHUbod9Ca8hfEayHwmOWdLODYLYTrwR+8T8hna3Yha5loNjftAf/0r0nf6deXQUGAWqt8ExvmYtCGsZkSiXjbueeeIY0GwArNB64BKn3c4gMi9vEVdMWjNP30Sh4bMc3F1NSIX+FgkQB7AZZlEnL+kPVcwWE5J21dRlgPv9bHaA8YK7Oj9Am5ffC7Se3olDp1aeTrt7p3AbP9jtQEpvVHCzU29OndwzEkYpTcDC4BhPu42orpQmpwNvTl3kb6RkTgy++J0MyqL+vvF6eRABWXuivRPO9oAJTeJ3WL3572KpCetODK75PMqyMpsX/I1ZJ6Lsgr4sI9FxreuIn3DUxxdgsV6IOB9aH6CxYyCXvgO7oiVEokcyJUfxyLdxKFWxXSQ1cB5PsccAn6IlCyVcPP/5tS7Dp+qq8uIv/HPoCuB9/uYvQZyB5OiP5D1tOfDr6FOpzg0FJiI6gpgjr+1PkF76Y0Sad6dD+eORq2x7wOjBrgBOM7H7BVUb5am2G/y6NqQRLS68kQOuXWofgmk1MduY/IpIea9cJVnNFQRALkbZS7+cdN/4pZcP1BCzgY6hxK2VVwNXI1IHOH7hJ2H87UHJVpl/g7hEp/Xm0FuwY4+WoibYj2vt/BnyuPjpfFPb+fVsSygVsXHgXtAqo58gU0YslDC0T/k2gdRy0wAJR7u7UaNC6Qp+tdcO9EfNBgsx2j7BfAJTwPXOEciLS/m16u+o0FzKoauApnZg+kvMIxa2dqyM1e+GHgKA0DGI7pNLbNGTdPv/j6gaCgwC+NABD9hABjtfrfKgkKnjDlVLXMtBpszEAbAZbhuk1rmWq0KnJILn0QtM5PbRQsqt9AUXV8It5cMnqi6Wp8ndmxjzp3qIzpx4gmUxWsRXQAc72P2CjAc32UF3gTqGJlYc/QeVn/IdFe2EtGHscwX1Ar0lLyTMzRYOVZDFT8BeY6MhFG46AxK1QrMo/zgTkQX4yUM1dcRFjDKmUL5qIkg84G/eZzuROBuDpTsVCswT+f43Q16R/crh+itqJwLXOp7lMpjlMot+cq5SC3fLwWuIV2uiMo3EH3wqKML7sqhocClqNYDk3xM3gb9Bu7I+qMX9lKfxRLgWvw/i5dwdaFEYk/3x0+vK8cOsZ1PI0xH5DnPo0Qvz0fOxZG5Iiygh1wRyhJP5cqXbKDW2Gq1zKdR/RXewnCB9STkDLFjS71WfCWy5w2xnZtwjdNTPwSv2/yZGPKUWhVPalUg1Fd/fW8rEnaeJxz9MMhcoMXDpAyYhyaaNWQuz2bQqiBqBeYQ37sNdC2eq6J6AOF2RiZOFzu6rpBXRTU0foxa5lowNgEzfKwacKkW25kr26Kxns4pkZZWCceuwjXOBXxSK2Umols0VPETDZp/11u/08YcAip2dD3uiMkg81F93cNqJMoyTtBdagXmaT+zyzRknkso8AfQR/DcT9EEsA41xkvYWZ7NACzbaDA4UkPmcjSxC5iH92ezDWSu2LGLJeK83Nv3kEjLi2I7FyFyKajXgp+BypUY7FbLrNOJE0/I9NwZfZESicTFjq5D2ycC9cBBD6sxoGuxApu0ypyRqQMd6JTKCWqZj6BsRPV8H6sGpHSa2M78Ql5/OXw7bGtGWYb3Uv+fUrdDS+zo+v6+p4Sjj1N+8hmpoNVrz2sEUEP5we2ZBq1eAelsCcceS3eQVp02DjHqQC7Hf/n610CN2M72tOeqnnAy8YPLUv9UmY9ZRpnpOu200SRKXjvqr3kNSDVUcTmu3I1wuo/FAZBVyDv3SvivbTnxYar5XhK6GJGv4b8HFU7m30T/y+88fRJHpxNTAufg6mr6kHORi1yRgRRHBnU6LqIPcYga2Rb7c679AdCqcach7UuAL+F7l9AGXFnkdUvrV3wgW6ObxHYu9L/fSSkwr/N+FwyO7Aw2jbbtKN/CUxipYHM/E8SOrivkvNHOtRfDfQFfYWgDKtMkHLsqX8IAkKbmV8V25qPGucAGH6uZGDSqZT6ikwMVR7zSnytHV1JXgmtQlgIn+VjtAfkbMM3nNHGU75I4tEK2v7a31z7k8cqh1ZUnEneXkX69YQviLpJw639n+/37glrmp0nGjBN9TNqAexmZqJONe97pd91KBxKJxCXsfAvc8Yjci2/Q6ikMBX0UtyQoTc6CvggjnygI8fYG4Ea8hfEqIl/Eds4qFGEAiO38ilFOFaLX4h+03saB0scgC0VN3R1o3Sfh6EJEJyM8TM97MRtRvUDs2JxBk3thjQ2AnOXz6rOUxydJOPqTQrwdygYSEo7dB+4kIOxj9nENnTIi6+LodCIci0rY+WxqkeYZD5PW1K/r/EJJIsocw+82AnAh8bJn1DIvyps7vSTZ+cDYAPivnuqw8pyJowOJtLzIO6Xd92lUvpz6dQ34Lm/2kbOADWpVPKmhcVU9mucJDY49Qy3zN+A+STphpMi5OI5tZCba/lIucy4y4XCuiBEmXe7LURTFkT2WAVGPvyf3oER3qGXW6vQxw/PlkI4f/x6tCtyJW9qxfO+1KvoKUOd1fFEc2cJwHxuInAsvOnNFhid2pHJFuguya66I6JNe5ymKI4tIY+MhsaPrwO3Yg/Io9krtQW03N2mw4iPZ9kGtypnsNV9K7WZ77cS+DdTzbtk4CTtrZAMJv3MVxZEDxG7dJ7ZTi2tMzHXORQeduSLJYNPyMOmSK+LUyu7db/V0zqI4ckg+ci5ykSvSQVEceeBwzoXOTp9zobvUCizR6tF+icad6FTzvWqZ9T3kioRRuaSvuSJFceQRCcceS59zISNBVxAv3+UXtHbmirSzA7iZ9LkiZ6bbku+JojjyTGfQ6o4Yh3A7nkEro1NBq61VlZ3N9TQUmMXBvZFUsOmVu9sG1BMfNikbqZODouBnKJJKHl6uU80f0c6dwOfonjg1GXEf1yrz9xgyEtXzfZpm5KRXSfHKMcDIy44jtvMF1DgHv5wL4ZK0qZNqVIvtzM92E5uiOAoEaWrZLLbzETAuBs2kn1oE9JNixy6Wpqjf7mq/KIqjwBC7pYFRsWmpoNUribqjTmeK2LHf5tKXYsxRgCRXLaPr1KqwQY5KZ3AvFLu1hTy0yiteOQoZt6T70rZb6rvcnW2K4ijiS1EcRXwpiqOIL0VxFPGlKI4ivhTFUcSXojiK+FIURxFfiuIo4ktRHH3C6N5HJOHxt8GCiKfvRXH0AgVDQ4GrwO2eXWXQoJZZo8FgulLJgkKDwXINmTfg8jOv14viyBANBWZhmU2o/hjvlP9RQB1G29ZUq4OCJdkjpeILlLTt9OmR0kb5+w8Ud2V7QK2x1YixCtVMa0wmAb9Uy3wG11hUaH3XtcqcgegqkLN8qpTjoPOlsfFQ8crhgwYrx6pV8SAYm1D8hPEkyu99XrsIw31BLfMhnWqaOXIzY9QyJ2mV+WuEp33aRxzukWLHHoLibaUbne0hDXc7yBV4f0bbk+0hnY9Jk/MPYFyM4JX6L8DnaWeHhsw1OtX0632WM3TSqaPUMuuArQizvI3YhMhFR/dIKYojRZf2kLtT7SHT1Zce0R5S7JYGwk41yFwEr6KhcpTraadZLbNGx4/3mbCZPXT6mOFqmTWUlTUDNXh3IEr2SGlyPuQ1v6UoDkCrKi4jvrcplfLv1SqhDWU5xrumX32pgCt2dD1l8TNAbku2lOzGSUAdww81aSgwW/s5EdzzfwFDLfNqDpTsJFk9f6KH2RsIN+KOOD1dj5RjOiDVqsqzEHc14NeFp9ftIVNToVZo9YTvcii+FNVruo9Hk/GorqfK3KRZnLqkobEfRY1VwDQf3SWHN5aWLpEtu706ARzBMSmOw/053R76cxoLJeJs7ct7SOPO14EbNBS4z3ewonAOqs9qqKJfgxXVMicBd6C+wxsVeBRtv0WaXm3O9LzHlDgOT5dsz9t0SQlHdwBzNVTx96isAs7s7ph8CqP9ErXMjH/VkOr+7Ht16uR5DGORbG35n976fkyIo8tc2hV4lxFCjufSSjj2lMJZaYY5JzsAJRJz1DLr2c8av3Np9ejjiZd/jXh8MfAen1vITpAl/RneeAwEpHJpl9EcXsLYj+gSyuMTcj2ao3MKxchEEGUx4NUj431AHSfwCiXuZd1eNfSLxMt3kww23+Nx/N9Ar6N8VJXY/Ru7dixcOWp9/p4MNtvlZonEslpG2BOpMSB366RT11FWtgjvZrcBlFu6H613+Jw2jvA9yozbpLHlTTyfqHvHsSAOD7QBSm6ScEseSoP8SXVqrtUplQ/guiuB2fT+8TYZbIrWSDjm1bCuzwwZcWj1hJM5GF+S/qOVxtRk7Q15cisjUrNh5yaHK+pq4OzMDuRp0IUSjr2UC78GvTjUNI/jBK4nHr8F8R3N0Qq6GNv5aSE3xRU7+qxONT9Guz6Zpn02wCuoUSN2yxO59GfQikNBsCo+h3AXSoWP2ZvA3exnjTgxryYpBYMGg+VI21dI6FJETk5j+XVGxb6drgtgthiU4kj2FdfVabadD4F+j/Jhd6QWowqWpMgDs6HtbmBcjyGHW/JoPoQBg0wcGgpMPLza6PchagOGLpCtrZF8+tYXNGR+CFdXgV4w0L54MSjEoZNOHUVZ2SJU/eabALwILBI75jWhoaDoFLkyGxEflWsDys8QuT+/3h2moMWh08cM50Dp9cAteO8uQnLwzRLC0QcLOdiErsvdbprlbt2My80SiT2twcqzkYEb2VKQ4lAwCJlXckBXAmN8zPaBruSdsn+X3bs9pkIVDho6ZQQ6/OvEDy5KtpP0vFg4CIsJx35WKCIvOHF0bjtrdradB5KkyANXoFoHfNDn/9kH1CefqJyCeqIqGHFksO0Mok/gugt6s+08UCSnIbn3ourX1zwO/KiQRT7g4tDJFR+klNuBf8F7HgiIPJdaCXw+r871gdR82XvAneFnAvwHJdwqLztO/jzrPQMoDneEWuZtoKn7sAfKLtBasZ2f59m5XqOhigCu3IW4/4Tfc7bwNOouEru1Mb/e9Y2BE4fIT4HjPD9H1ddBVjBs1HelsfFQ3n3rBRoccxJG2a2oXovglzgcAb1ZwrltDZltBvK24pWJldx2LpFl8rLzf+Dk26eM0WCwHOPtq0FXgr7fx+xPILczKvpAvlY1s8mAxxwpktvOuLUSbm0ZaGfS0WW5uw6o9DFrA75DfNidsmPH/jy6l1XyI46SEsX/h7MDNa6QppbNefGlH6Qes+8B7Z4HmuQQKt/HMG6XcLPXxOdBRV7SBGXHjv0+xT4AE0BrdUrlhHz40hc0NK5KLfO3qNGAV4Jwkl8gYklT9NqhIAzIZw6p6GXg2ZRZEL0c123SKvO+gZy/ejQ6ZcypGgrcj7a/DHzcx2wjrnuB2M5nUpnmQ4a8iUO2xrZgO1NB5uI3f1X4KqLNapl1On68V/JsXtDQKSPUMmtwS7eh+q94rb8Isc5x65HW5/LvZe7Ja/Z5Z8mgO2ISwgLg/zzMRgA1HHeoOd/NUDrrZXV4R3b3CR5me4Fa3i6dOHTHrScZkNIEiUTiEnbW4CbG4Td/NZkNVYfRZqsVmJOLutKuqFU5k/jr6eaxvgPUU26ME9upL/TNvmwwoHUrEtnzhthOLVoyAVgHnjUjE0Afocp8PpkBll10SuActcxnkvNYpcrDJDmPtYTkPNbGljez7UOhUhBFTdLU/KrYznwMdwqi3kmzwjnAhqwN6Z1aebpa5iO4+jxwoY/V4XmsBb4PkgsKQhwdyNbWiIRjs5KjrNjiY3V4SO+000b39j06m5m0uzbJ4mav21UE1U/1dR7rUKGgxNGB2C0N2KlmKOC1Ypoc0pso2aWWWafVlX5ZYp1o9ejjj2pm4rEPons6R2RlqZB6MFOQ4oAudaXuiMmpeWdeOQ/HAzXEXd+OOclmJoE5xMsj+DYz0QMItzOyPef1soOJghVHBxKJxMWOrgN3Isknm3c8zJJtHo9P7NBQ4KqOJxu1KmcSMhtBHwFMj+MOAeuQ0nESdpanaliLpCiUjbceEbt1H1Crwcr7EHcFQvdmbkoF6I+pMq9V0bfAnemzCqEIj6DuYrELe6NvIOl+5XDFb/u5IJBIS6s0OV9EZRrwO28jzgGZ6XOKZzHkQxJ2PlvwwjDaB/S7ELXMo39bivIwhi7OdtV2LkjulJbUg1b3YLoNkVoJR3+dF8f6gYbGfQC3fTmiX+5WwuAaFRJpac2HH6KW2QQEPV47iOi/oXpX6pJesCgIIfOzKHfSvWPOX4DljHJ+UOgJN1o9+ngOld2ISg3eS/f72c8H8pWlLmqNrQb5JYhffcgbCCtoH3GfRCLxfDjVVzRYORbDPTI1QPV8aYr90eeQgkDBwDKvSralSvM9GHxetjp+HZOzjiF2ayMj2yeQ7IDjtTR8Eso3Mdp2dX0SKEhK490fQY0BLBnLAA2N/SiWuRn4oY8wkk9UpaWT8ikMOOqL7qxJ9W5DlDJiE1nsnZlNdMqYU3FL9xzxR2G6hJ2CK2norNPxakGZMqEP7SGzyRFPK7L9tb1iO7WIhID1nkcc7p35uAbHjc+Hk0MJrZ5wsobMNaAdy/devIBhXCi2M3cgC7jS3iLUqpgOsho4z8ckWZooJUsLITWukK8cne0hIdUe0suIXYjc2p/2kNkk7Qqp2LGN2M4FabO3YB6aaNaQuVxN06/x6zFL53Sn9O0h3wBqebfU6m97yGzS4/J5lz2ONNlbMhJlGSewU63APB0Ey/L5QK3KmVjmS6npTh/0MIkjfJsSCjKBqNdPHskKr9KbgQV47mwC6GaQhWI7eW2kUii3FQ2OPQOj5B7QT/qZkKP2kNmk17/wzuytEsMiGbR6XALlLDoSc0LjvLKrhiQ6ZcypaplrMYxwGmFsBD1fbGduIQsD+nH5l5dbdontzEWYjqpP03WZiSa2qGWuLaSSg2xzRLY6zMO7W8CO1HSn88SObcyzi32i37GBhJ0XpCn2YUQuBTweu6QUmIe4uzVkLtfpY7pPQBqk6AxKe8xWPzzdqarrdKfBQNYCRwlHH6d8VJrEnFTQeqB0l1qBeTrHpxfHIEGtypnsNdNlq78N1PNu2Ti/6U6FTk6Wwg/PNSHdXJNkWwI7e20J8hGQHh4l6jsx0gUeIyGLZFu0/93pB5CcPHKK3bpPbKcW15iI6IN4P7cHQX6TrWzyXKOh8WPUMtemHyXaJVt9kAsDcrweIZGWVgnHrsI1zgWe9bE6nE0+ucJrLWBA6RwlqomdJINNr89sWzLYHFrZ6vmpso+0vCi2cxEilyZbOXn4oXIlpZLMJp840SuXIa9kNEo02ZxlPpOOHCU6VMhvrWw4+jjDRgVTQavXXkyyTrb84PaBDFqTpZF7t5BulCjUEx82aShnqw9YboYGgyMpaVuIUoN/0LoNZFmmv8r+BqSpToCr8a2A6/0o0cHMgCfuaGj8GDSxFOhhjKcs6ul+3ldxaLByLCXtK1G5At/PRBtQ4+vSFA2nO9dQYsDF0UEyXdFYDczwMenxEbG34hioR+7BQsHsnord2ii285FUnaxXByADmEOpvtLf5i6dwSbGDvC9rb3WWRp5DAoDCkgcHYjd0sAo58xU0Oo1tTFZAnncoWYNmTfojN4VZmkoMKvLKFGvupC2ZGlk4vShHGxmQsHcVrxITRu4DnSJb5dj2A5ym9jR9eluKxoyz8XV1Yj4DL7RBMgDuCyTiJPXUaKFSkGLo4NU0LoSuBL/q91TqPFNxH38yIPlKgz9R5TPpHmLXyFSM9QavvWXQSGODpJJNMYq4BNZOuNmVBZJk7MhO+cbWgwqcXTQOa4C+ron8yrISuzo/ZJ8CiriwaAUB3gNusnoqAOI3Mtb1BXa4JtCZNCKo4PDQSu34l1fCgVWQjFYGPTi6ECnnTaaRMky4MimsqJP0F56o0Sadw+Yc4OUISOODtSqtMC9DihH9fuFXkRdpEiRIkOL/wek5t5E7P42/AAAADV0RVh0Q29tbWVudABDb252ZXJ0ZWQgd2l0aCBlemdpZi5jb20gU1ZHIHRvIFBORyBjb252ZXJ0ZXIsKeMjAAAAAElFTkSuQmCC) no-repeat 5px 3px; +a.phpdebugbar-restore-btn:after { + background: url(data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2048%2048%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M47.973%2010.859a.74.74%200%200%201%20.027.196v10.303a.735.735%200%200%201-.104.377.763.763%200%200%201-.285.275l-8.902%204.979v9.868a.75.75%200%200%201-.387.652L19.74%2047.9c-.043.023-.09.038-.135.054-.018.006-.034.016-.053.021a.801.801%200%200%201-.396%200c-.021-.006-.04-.017-.061-.024-.043-.015-.087-.029-.128-.051L.39%2037.509a.763.763%200%200%201-.285-.276.736.736%200%200%201-.104-.376V5.947c0-.067.01-.133.027-.196.006-.022.02-.042.027-.063.015-.04.028-.08.05-.117.014-.024.035-.044.053-.067.022-.03.042-.06.068-.087.022-.021.051-.037.077-.056.028-.023.053-.047.085-.065L9.677.1a.793.793%200%200%201%20.774%200l9.29%205.196h.002c.03.019.057.042.085.064.025.019.053.036.075.056.027.027.047.058.07.088.016.023.038.043.052.067.022.038.035.077.05.117.008.021.021.04.027.063a.74.74%200%200%201%20.027.197v19.305l7.742-4.33v-9.869c0-.066.01-.132.027-.195.006-.023.019-.042.027-.064.015-.04.029-.08.05-.116.014-.025.036-.045.052-.067.023-.03.043-.061.07-.087.022-.022.05-.038.075-.057.03-.022.054-.047.085-.065l9.292-5.195a.792.792%200%200%201%20.773%200l9.29%205.195c.033.02.058.043.087.064.025.02.053.036.075.057.027.027.046.058.07.088.017.022.038.042.052.067.022.036.034.077.05.116.009.022.021.041.027.064ZM46.45%2020.923v-8.567l-3.25%201.818-4.492%202.512v8.567l7.743-4.33Zm-9.29%2015.5v-8.574l-4.417%202.45-12.616%206.995v8.654l17.033-9.526ZM1.55%207.247v29.174l17.03%209.525v-8.653l-8.897-4.89-.003-.003-.003-.002c-.03-.017-.056-.041-.084-.062-.024-.018-.052-.033-.073-.054l-.002-.003c-.025-.023-.042-.053-.064-.079-.02-.025-.042-.047-.058-.073v-.003c-.018-.028-.029-.062-.041-.094-.013-.028-.03-.054-.037-.084-.01-.036-.012-.075-.016-.111-.003-.028-.011-.056-.011-.085V11.58L4.8%209.064%201.549%207.248Zm8.516-5.628-7.74%204.328%207.738%204.328%207.74-4.33-7.74-4.326h.002Zm4.026%2027.01%204.49-2.51V7.247L15.33%209.066l-4.492%202.512V30.45l3.253-1.819ZM37.935%206.727l-7.74%204.328%207.74%204.328%207.738-4.329-7.738-4.327Zm-.775%209.959-4.49-2.512-3.252-1.818v8.567l4.49%202.511%203.252%201.82v-8.568ZM19.353%2035.993l11.352-6.295%205.674-3.146-7.733-4.325-8.904%204.98-8.116%204.538%207.727%204.248Z%22%20fill%3D%22%23FF2D20%22/%3E%3C/svg%3E) no-repeat 11px center / 20px 20px !important; } div.phpdebugbar-openhandler .phpdebugbar-openhandler-header { - background-size: 20px; - padding: 2px 0px 3px 36px; - background-position: 9px 5px; - border-radius: 3px; + background: url(data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2048%2048%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M47.973%2010.859a.74.74%200%200%201%20.027.196v10.303a.735.735%200%200%201-.104.377.763.763%200%200%201-.285.275l-8.902%204.979v9.868a.75.75%200%200%201-.387.652L19.74%2047.9c-.043.023-.09.038-.135.054-.018.006-.034.016-.053.021a.801.801%200%200%201-.396%200c-.021-.006-.04-.017-.061-.024-.043-.015-.087-.029-.128-.051L.39%2037.509a.763.763%200%200%201-.285-.276.736.736%200%200%201-.104-.376V5.947c0-.067.01-.133.027-.196.006-.022.02-.042.027-.063.015-.04.028-.08.05-.117.014-.024.035-.044.053-.067.022-.03.042-.06.068-.087.022-.021.051-.037.077-.056.028-.023.053-.047.085-.065L9.677.1a.793.793%200%200%201%20.774%200l9.29%205.196h.002c.03.019.057.042.085.064.025.019.053.036.075.056.027.027.047.058.07.088.016.023.038.043.052.067.022.038.035.077.05.117.008.021.021.04.027.063a.74.74%200%200%201%20.027.197v19.305l7.742-4.33v-9.869c0-.066.01-.132.027-.195.006-.023.019-.042.027-.064.015-.04.029-.08.05-.116.014-.025.036-.045.052-.067.023-.03.043-.061.07-.087.022-.022.05-.038.075-.057.03-.022.054-.047.085-.065l9.292-5.195a.792.792%200%200%201%20.773%200l9.29%205.195c.033.02.058.043.087.064.025.02.053.036.075.057.027.027.046.058.07.088.017.022.038.042.052.067.022.036.034.077.05.116.009.022.021.041.027.064ZM46.45%2020.923v-8.567l-3.25%201.818-4.492%202.512v8.567l7.743-4.33Zm-9.29%2015.5v-8.574l-4.417%202.45-12.616%206.995v8.654l17.033-9.526ZM1.55%207.247v29.174l17.03%209.525v-8.653l-8.897-4.89-.003-.003-.003-.002c-.03-.017-.056-.041-.084-.062-.024-.018-.052-.033-.073-.054l-.002-.003c-.025-.023-.042-.053-.064-.079-.02-.025-.042-.047-.058-.073v-.003c-.018-.028-.029-.062-.041-.094-.013-.028-.03-.054-.037-.084-.01-.036-.012-.075-.016-.111-.003-.028-.011-.056-.011-.085V11.58L4.8%209.064%201.549%207.248Zm8.516-5.628-7.74%204.328%207.738%204.328%207.74-4.33-7.74-4.326h.002Zm4.026%2027.01%204.49-2.51V7.247L15.33%209.066l-4.492%202.512V30.45l3.253-1.819ZM37.935%206.727l-7.74%204.328%207.74%204.328%207.738-4.329-7.738-4.327Zm-.775%209.959-4.49-2.512-3.252-1.818v8.567l4.49%202.511%203.252%201.82v-8.568ZM19.353%2035.993l11.352-6.295%205.674-3.146-7.733-4.325-8.904%204.98-8.116%204.538%207.727%204.248Z%22%20fill%3D%22%23FF2D20%22/%3E%3C/svg%3E) no-repeat 11px center / 20px 20px !important; + padding: 4px 4px 6px 38px; + margin: 0px !important; } div.phpdebugbar-openhandler .phpdebugbar-openhandler-header a { + display: flex; cursor: pointer; } -a.phpdebugbar-close-btn { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuM4zml1AAAADDSURBVDhPxZCxCsIwFEUzuGdwCvQD7BIIcehUXDqVfGM/wsG/iG4ifkzMlRuSPLo4eeFBue8c6Iv6b4wxW557Hs0KnWa3seqDxTiOyVqbhmF4UND4Rofdruyce3rvE6bIRSo9GOI1McbLPM/vVm4l7MAQr0kpHaQsJTDE+6zrepym6SVFdNgR69M+hBTLzWCI10gJvydvBkO8ZlmWayvhJnkzGOI1+fBTCOHWPkT7YNiBId4HizxnCKy+r81uX/otSn0A7dioI/vYX+8AAAAASUVORK5CYII=) no-repeat 9px 6px; - color : #555; - border-right: none; -} - -a.phpdebugbar-open-btn { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOCAYAAADJ7fe0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfdCgYULwwNKp3GAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAA1UlEQVQ4T2OgKpCUlOQH4vdA/B8Jv4dKEwYgDdLS0v8NDQ3/GxsbwzGIj2YoGEO1oQJkjcRgqDZUAJKwsrJ6/v//fwdiMFQbKgAZkpGR0QR0ajy60wlgRJhBXSGhpqb2CNnZhHBkZORcqBEMDFBX2BsYGGBVjAv39vZaQ41gYIC6Ygs2hbiwr6/vdqA+DqgR4CiW19bWxqoYF87Ly4uFaocAZWXlydgU4sJ2dna3ga4QgGqHAC0trY/YFOPCKSkpDVCtCAA01QaIsaYJHFgCqpVagIEBACGlF2c3r4ViAAAAAElFTkSuQmCC) no-repeat 8px 6px; -} - - div.phpdebugbar-header, div.phpdebugbar-openhandler-header { background-size: 21px auto; @@ -252,21 +190,30 @@ div.phpdebugbar-openhandler-header { } a.phpdebugbar-restore-btn { - border-right-color: #ddd!important; + border-right-color: var(--debugbar-border) !important; height: 20px; - width: 23px; + width: 24px; background-position: center; background-size: 21px; } -div.phpdebugbar-header > div > * { - font-size: 13px; - padding: 5px; +.phpdebugbar:not(.phpdebugbar-closed) a.phpdebugbar-restore-btn { + border-right: none; } + div.phpdebugbar-header .phpdebugbar-tab { - padding: 5px 8px; - border-left: 1px solid #ddd; + border-left: 1px solid var(--debugbar-header-border); + display: flex; + align-items: center; + justify-content: center; + min-width: 16px; +} + +a.phpdebugbar-tab.phpdebugbar-tab-history { + display: flex; + justify-content: center; + align-items: center; } div.phpdebugbar-header .phpdebugbar-header-left { @@ -279,7 +226,7 @@ div.phpdebugbar-header .phpdebugbar-header-left { } div.phpdebugbar .phpdebugbar-header select { - margin: 4px 0px 0px 8px; + margin: 0 4px; padding: 2px 3px 3px 3px; border-radius: 3px; width: auto; @@ -287,7 +234,8 @@ div.phpdebugbar .phpdebugbar-header select { } dl.phpdebugbar-widgets-kvlist dt, -dl.phpdebugbar-widgets-kvlist dd { +dl.phpdebugbar-widgets-kvlist dd, +table.phpdebugbar-widgets-tablevar td { min-height: 20px; line-height: 20px; padding: 4px 5px 5px; @@ -299,67 +247,25 @@ dl.phpdebugbar-widgets-kvlist dd.phpdebugbar-widgets-value.phpdebugbar-widgets-p background: transparent; } -dl.phpdebugbar-widgets-kvlist dt { - width: 25%; +dl.phpdebugbar-widgets-kvlist dt, +table.phpdebugbar-widgets-tablevar td:first-child { + width: calc(25% - 10px); } dl.phpdebugbar-widgets-kvlist dd { margin-left: 25%; } -ul.phpdebugbar-widgets-timeline .phpdebugbar-widgets-measure { - height: 28px; - line-height: 28px; - border: none; -} - -ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-value { - height: 16px; - background-color: #63abca; - border-bottom: 2px solid #477e96; -} - -ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label, -ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector { - top: 0px; - color: #000; - font-size: 11px; -} - -ul.phpdebugbar-widgets-timeline li .phpdebugbar-widgets-value span.phpdebugbar-widgets-label { - color: #fff; - text-shadow: 1px 1px #000; -} - -ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params { - font-size: 11px; -} - -div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar { - width: calc(100% - 20px); - padding: 4px 0px 4px; - height: 20px; - border: 1px solid #ddd; - border-bottom: 0px; - background-color: #e8e8e8; - border-radius: 5px 5px 0px 0px; -} - -div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar input { - width: calc(100% - 48px); - margin-left: 0px; - border-radius: 3px; - padding: 2px 6px; - height: 15px; -} div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-label, div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector { - padding: 1px 0px 0px 10px; - margin: 0px; + text-transform: uppercase; font-style: normal; - color: #333; +} + +div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector { + text-transform: none; } .phpdebugbar-widgets-toolbar i.phpdebugbar-fa.phpdebugbar-fa-search { @@ -370,16 +276,13 @@ div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugb div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter, div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded { - position: relative; - top: -48px; + display: inline-block; background-color: #6d6d6d; margin-left: 3px; border-radius: 3px; - padding: 5px 8px 4px; text-transform: uppercase; font-size: 10px; - text-shadow: 1px 1px #585858; transition: background-color .25s linear 0s, color .25s linear 0s; color: #FFF; @@ -391,16 +294,29 @@ div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-w user-select: none; } +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="alert"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="alert"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="info"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="info"] { background-color: #5896e2; } +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="debug"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="debug"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="success"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="success"] { + background-color: #45ab45; +} + +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="critical"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="critical"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="error"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="error"] { - background-color: #fa5661; + background-color: var(--debugbar-red-vivid); } +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="notice"], +div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="notice"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="warning"], div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="warning"] { background-color: #f99400; @@ -415,46 +331,27 @@ div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-w opacity: 0.45; } -a.phpdebugbar-tab:hover, -span.phpdebugbar-indicator:hover, -a.phpdebugbar-indicator:hover, -a.phpdebugbar-close-btn:hover, -a.phpdebugbar-minimize-btn:hover, -a.phpdebugbar-maximize-btn:hover, -a.phpdebugbar-open-btn:hover { - background-color: #ebebeb; - /* transition: background-color .25s linear 0s, color .25s linear 0s; */ -} - -a.phpdebugbar-minimize-btn, -a.phpdebugbar-maximize-btn { - width: 28px!important; -} a.phpdebugbar-tab.phpdebugbar-active { - background: #fa5661; + background: var(--debugbar-red-vivid); background-image: none; color: #fff !important; - text-shadow: 1px 1px #bf3039; } a.phpdebugbar-tab.phpdebugbar-active span.phpdebugbar-badge { - background-color: white; - color: #fa5661; - text-shadow: 1px 1px #ebebeb; + background-color: var(--debugbar-badge); + color: var(--debugbar-badge-text); } a.phpdebugbar-tab span.phpdebugbar-badge { vertical-align: 0px; - padding: 2px 8px 3px 8px; + padding: 2px 8px; text-align: center; - background: #fa5661; + background: var(--debugbar-red-vivid); font-size: 11px; - font-family: monospace; - color: #fff; - text-shadow: 1px 1px #bf3039; + font-family: var(--font-mono); + color: var(--debugbar-badge-active-text); border-radius: 10px; - top: -1px; position: relative; } @@ -462,25 +359,6 @@ a.phpdebugbar-tab span.phpdebugbar-badge { cursor: text; } -.phpdebugbar-indicator span.phpdebugbar-tooltip, -div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text { - border: none; - border-radius: 5px; - background: #f5f5f5; - font-size: 12px; - width: auto; - white-space: nowrap; - padding: 2px 18px; - text-shadow: none; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text { left: 0px; right: auto; @@ -489,7 +367,6 @@ div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text { .phpdebugbar-widgets-toolbar > .fa { width: 25px; font-size: 15px; - color: #555; text-align: center; } @@ -500,15 +377,10 @@ ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item { overflow: visible; } -ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:hover, -ul.phpdebugbar-widgets-timeline li:hover { - background-color: initial; +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-sql { + line-height: 20px; } -.phpdebugbar-widgets-sqlqueries ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item { - display: flex; - flex-wrap: wrap; -} .phpdebugbar-widgets-templates ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item { display: block; @@ -532,14 +404,10 @@ ul.phpdebugbar-widgets-timeline li:hover { .phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-headers { margin: 10px 0px; padding: 7px 10px; - border-left: 2px solid #ddd; + border-left: 2px solid var(--debugbar-header); line-height: 17px; } -.phpdebugbar-widgets-list .phpdebugbar-widgets-list-item .phpdebugbar-widgets-name { - height: 15px; -} - .phpdebugbar-widgets-sql.phpdebugbar-widgets-name { font-weight: bold; } @@ -547,9 +415,13 @@ ul.phpdebugbar-widgets-timeline li:hover { ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-sql { flex: 1; margin-right: 5px; + max-width: 100%; } -ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-duration { +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-copy-clipboard, +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-duration, +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id, +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-memory { margin-left: auto; margin-right: 5px; } @@ -558,16 +430,18 @@ ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widget margin-left: auto; } -ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id { - margin-left: auto; - margin-right: 5px; +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id a { + color: #888; +} + +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id a:hover { + color: #aaa; } ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params { - background-color: #fdfdfd; margin: 10px 0px; font-size: 12px; - border-left: 2px solid #cecece; + border-left: 2px solid var(--debugbar-border); } div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params th, @@ -577,7 +451,11 @@ div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params td { div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params th { padding: 2px 10px!important; - background-color: #efefef; + background-color: var(--debugbar-background); +} + +div.phpdebugbar-widgets-templates ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(odd) table.phpdebugbar-widgets-params th { + background-color: var(--debugbar-background-alt); } div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name { @@ -590,8 +468,9 @@ div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugb margin-left: 3px; } -ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even) { - background-color: #f5f5f5; +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even), +table.phpdebugbar-widgets-tablevar tr:nth-child(even) { + background-color: var(--debugbar-background-alt); } div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value { @@ -602,20 +481,27 @@ div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugb font-family: PhpDebugbarFontAwesome; content: "\f005"; color: #333; - font-size: 13px; + font-size: 15px !important; margin-right: 8px; float: left; } +table.phpdebugbar-widgets-tablevar td { + border: 0; +} + div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-info { color: #1299DA; } div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-info:before { - font-family: PhpDebugbarFontAwesome; content: "\f05a"; color: #5896e2; - font-size: 15px; +} + +div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-success:before { + content: "\f058"; + color: #45ab45; } div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error { @@ -623,50 +509,43 @@ div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugb } div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error:before { - color: #fa5661; - font-size: 15px; + color: var(--debugbar-red-vivid); } -div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before { +div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before, +div.phpdebugbar-widgets-messages .phpdebugbar-widgets-value.phpdebugbar-widgets-warning { color: #FF9800; - font-size: 13px; } -div.phpdebugbar-widgets-messages .phpdebugbar-widgets-value.phpdebugbar-widgets-warning { +div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-deprecation:before { + content: "\f1f6"; color: #FF9800; } div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item pre.sf-dump { - display: inline-block; + display: inline-block !important; position: relative; top: -1px; } -div.phpdebugbar-widgets-sqlqueries { - line-height: 20px; -} - - div.phpdebugbar-panel div.phpdebugbar-widgets-status { - padding: 9px 20px!important; + padding: 9px 10px !important; width: calc(100% - 20px); - margin-left: -10px; - margin-top: -10px; + margin-top: 0px !important; line-height: 11px!important; font-weight: bold!important; - background: #f5f5f5!important; - border-bottom: 1px solid #cecece!important; + background: var(--debugbar-background-alt) !important; + border-bottom: 1px solid var(--debugbar-border) !important; } div.phpdebugbar-panel div.phpdebugbar-widgets-status > * { - color: #383838!important; + color: var(--debugbar-header-text)!important; } div.phpdebugbar-panel div.phpdebugbar-widgets-status > span:first-child:before { font-family: PhpDebugbarFontAwesome; content: "\f05a"; - color: #737373; - text-shadow: 1px 1px #fff; + color: var(--debugbar-icons); font-size: 14px; position: relative; top: 1px; @@ -679,7 +558,11 @@ div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td { } div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params th { - background-color: #efefef; + background-color: var(--debugbar-background-alt); +} + +div.phpdebugbar-widgets-sqlqueries ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even) table.phpdebugbar-widgets-params th { + background-color: var(--debugbar-background); } div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name { @@ -698,47 +581,47 @@ div.phpdebugbar-widgets-templates .phpdebugbar-widgets-list-item table.phpdebugb ul.phpdebugbar-widgets-list ul.phpdebugbar-widgets-table-list { text-align: left; + line-height: 150%; } -.phpdebugbar-text-muted { - color: #888; -} ul.phpdebugbar-widgets-cache a.phpdebugbar-widgets-forget { float: right; font-size: 12px; padding: 0 4px; - background: #fa5661; + background: var(--debugbar-red-vivid); margin: 0 2px; border-radius: 4px; color: #fff; text-decoration: none; - line-height: 1.5rem; + line-height: 1.25rem; } -a.phpdebugbar-tab i { - line-height: 20px; +div.phpdebugbar-mini-design div.phpdebugbar-header-left a.phpdebugbar-tab { + border-right: none; } -div.phpdebugbar-mini-design a.phpdebugbar-tab { - border-right: none; +div.phpdebugbar-header-right { + display:flex; + flex-direction: row-reverse; + align-items: center; + flex-wrap: wrap; } -div.phpdebugbar-header-right > a { - height: 20px; - width: 20px; - background-position: center; +div.phpdebugbar-header-right > * { + border-right: 1px solid var(--debugbar-header); } -div.phpdebugbar-header-right .phpdebugbar-indicator > i.phpdebugbar-fa { - vertical-align: baseline; - margin-top: 2px; +div.phpdebugbar-header-right > *:first-child { + border-right: 0; } -div.phpdebugbar-panel { - width: calc(100% - 20px); - height: calc(100% - 20px); - padding: 10px; +div.phpdebugbar-header-right a.phpdebugbar-tab.phpdebugbar-tab-settings { + border-left: 0; +} + +div.phpdebugbar-panel[data-collector="__datasets"] { + padding: 0 10px; } div.phpdebugbar-panel table { @@ -752,17 +635,13 @@ div.phpdebugbar-panel table .phpdebugbar-widgets-name { dl.phpdebugbar-widgets-kvlist > :nth-child(4n-1), dl.phpdebugbar-widgets-kvlist > :nth-child(4n) { - background-color: #f5f5f5; + background-color: var(--debugbar-background-alt); } .phpdebugbar pre.sf-dump:after { clear: none!important; } -div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-message { - color: #dd1044; -} - div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item > div { display: none; } @@ -777,3 +656,110 @@ div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before, div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before { margin-right: 6px!important; } + +div.phpdebugbar dl.phpdebugbar-widgets-kvlist > :nth-child(4n)::before { +background-color: var(--background-color-alt); +} + +dt.phpdebugbar-widgets-key { + padding-left: 10px !important; +} + +dt.phpdebugbar-widgets-key { + position: relative; + /*background: white;*/ + z-index: 1; +} + +dd.phpdebugbar-widgets-value { + position: relative; +} + +dd.phpdebugbar-widgets-value::before { + content: " "; + position: absolute; + height: 100%; + left: 0; + top: 0; + width: 33.33%; + margin-left: -33.33%; +} + +dd.phpdebugbar-widgets-value pre.sf-dump { + padding-top: 0; + padding-bottom: 0; +} + +ul.phpdebugbar-widgets-table-list { + padding: 4px 0; +} + +ul.phpdebugbar-widgets-table-list li { + margin-bottom: 4px; +} + +ul.phpdebugbar-widgets-table-list li:last-child { + margin-bottom: 0; +} + +div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard { + margin-left: 8px !important; +} + +div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name { + width: 150px; +} + +div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-connection { + font-size: 12px; + padding: 2px 4px; + background: #737373; + margin-left: 6px; + border-radius: 4px; + color: #fff !important; +} + +div.phpdebugbar-widgets-sqlqueries button.phpdebugbar-widgets-explain-btn { + cursor: pointer; + background: #383838; + color: #fff; + font-size: 13px; + padding: 0 8px; + border-radius: 4px; + line-height: 1.25rem; +} + +div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-explain { + margin: 0 !important; +} + +div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-explain th { + border: 1px solid var(--debugbar-border); + text-align: center; +} + +div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-visual-explain { + display: inline-block; + font-weight: bold; + text-decoration: underline; + margin-top: 6px; +} + +div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-visual-link { + margin-left: 6px; +} + +div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-visual-explain:after { + content: "\f08e"; + font-family: PhpDebugbarFontAwesome; + margin-left: 4px; + font-size: 12px; +} + +div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-expandable { + cursor: pointer; +} + +div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-params { + cursor: default; +} diff --git a/src/Resources/queries/widget.js b/src/Resources/queries/widget.js new file mode 100644 index 000000000..b9de555a2 --- /dev/null +++ b/src/Resources/queries/widget.js @@ -0,0 +1,413 @@ +(function($) { + + let css = PhpDebugBar.utils.makecsscls('phpdebugbar-'); + let csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-'); + + /** + * Widget for displaying sql queries. + * + * Options: + * - data + */ + const QueriesWidget = PhpDebugBar.Widgets.LaravelQueriesWidget = PhpDebugBar.Widget.extend({ + + className: csscls('sqlqueries'), + + duplicateQueries: new Set(), + + hiddenConnections: new Set(), + + copyToClipboard: function (code) { + if (document.selection) { + const range = document.body.createTextRange(); + range.moveToElementText(code); + range.select(); + } else if (window.getSelection) { + const range = document.createRange(); + range.selectNodeContents(code); + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + } + + var isCopied = false; + try { + isCopied = document.execCommand('copy'); + console.log('Query copied to the clipboard'); + } catch (err) { + alert('Oops, unable to copy'); + } + + window.getSelection().removeAllRanges(); + + return isCopied; + }, + + explainMysql: function ($element, statement, rows, visual) { + const headings = []; + for (const key in rows[0]) { + headings.push($('').text(key)); + } + + const values = []; + for (const row of rows) { + const $tr = $(''); + for (const key in row) { + $tr.append($('').text(row[key])); + } + values.push($tr); + } + + const $table = $('
').addClass(csscls('explain')); + $table.find('thead').append($('').append(headings)); + $table.find('tbody').append(values); + + $element.append($table); + if (visual) { + $element.append(this.explainVisual(statement, visual.confirm)); + } + }, + + explainPgsql: function ($element, statement, rows, visual) { + const $ul = $('