120 Commits
Author SHA1 Message Date
sandros 6864095646 Adjust requirements to WordPress 6.2+ / ClassicPress 2.x
Tests / tests (push) Successful in 7s
Release / upload-asset (push) Successful in 4s
2026-09-27 22:49:26 +02:00
sandros 35572de44c Update README: add Hungarian section, remove obsolete badges, add English at bottom
Release / upload-asset (push) Successful in 30s
Tests / tests (push) Successful in 7s
2026-09-26 23:16:48 +02:00
sandros 916cad2d8d Use native git checkout and streamline tests in Gitea workflows
Tests / tests (push) Successful in 6s
Release / upload-asset (push) Successful in 4s
2026-09-26 23:13:08 +02:00
sandros 2bdb8c67da Use composer:2 container in Gitea workflows
Tests / tests (push) Failing after 13s
Release / upload-asset (push) Failing after 1s
2026-09-26 23:10:38 +02:00
sandros d035dbe215 Released version 3.4.0
Tests / tests (push) Failing after 8s
Release / upload-asset (push) Failing after 5s
2026-09-26 22:59:55 +02:00
sandros d70baec3ec Add Gitea release asset workflow
Tests / tests (push) Failing after 6s
2026-09-26 22:58:43 +02:00
sandros a993080fe6 Add Gitea Actions test workflow
Tests / tests (push) Failing after 25s
2026-09-26 22:55:17 +02:00
sandros 36dbd2aaf0 repo switch 2026-09-26 22:49:30 +02:00
sandros 99646663d6 Wordpress 7+
Tests / tests (push) Canceled after 0s
2026-09-26 22:32:01 +02:00
Andrey Savchenko 17eaad7a92 Fixed deprecated set-output. 2022-12-28 15:07:02 +02:00
Andrey Savchenko 08a24caf8c Bumped Ubuntu version in actions.
18.04 is deprecated, 20.04 has PHP 7.4+ pre-installed which is higher than current 7.2 but probably next bump.
2022-12-28 14:59:38 +02:00
Andrey Savchenko 5b31f29ffd Added GitHub token to setup-php step.
Fixes GitHub API rate limit errors.
2022-12-28 14:54:58 +02:00
Andrey Savchenko add15ab277 Refreshed Psalm annotations. 2022-12-28 14:46:50 +02:00
Dave Gaeddert 03d1720e84 Update SAVEQUERIES docs link (#60) 2022-12-21 21:30:59 +02:00
Andrey Savchenko 103525ac92 Fixed Psalm 5 crash on PHP 7.2 (#61) 2022-12-21 21:30:41 +02:00
Andrey Savchenko d4127c7a91 Fixed creating GitHub release with the file 2022-06-16 11:26:38 +03:00
Andrey Savchenko 65fd804bd5 Released version 3.3.7 2022-06-16 11:07:34 +03:00
Andrey Savchenko e6c172f762 Additionally silenced opcache status call 2022-06-16 11:06:04 +03:00
Andrey Savchenko cf1b0157fa Fixed PHP warning, when accessing OPcache status with opcache.restrict_api
See #58
2022-06-14 18:38:50 +03:00
Andrey Savchenko 752271b258 Adjusted down to PHPUnit 8 for PHP 7.2 2022-06-09 15:03:29 +03:00
Andrey Savchenko 7399146214 Adjusted dependencies for minimum PHP version 2022-06-09 14:58:58 +03:00
Andrey Savchenko beeff70d98 Set up GitHub Actions 2022-06-09 14:56:21 +03:00
Andrey Savchenko 430919bcdf Adjusted release-related commands 2022-06-09 14:46:37 +03:00
Andrey Savchenko 9f3f2483f5 Fixed current version in header
Fixes #57
2022-06-09 14:30:51 +03:00
Andrey Savchenko 638bf00b89 Released version 3.3.6 2022-02-08 14:51:40 +02:00
Andrey Savchenko 09b7a76bd6 Adjusted inline docs for Psalm to pass 2022-02-08 14:38:17 +02:00
Andrey Savchenko 321cdce5e2 Fixed deprecation notices on PHP 8.1 2022-02-08 14:37:04 +02:00
Andrey Savchenko 15a9081f3b Updated dependencies and required PHP version
Fixes #55
Closes #56
2022-02-08 14:35:58 +02:00
Andrey Savchenko 5481353386 Fixed wrong release date in change log 2021-09-28 11:27:07 +03:00
Andrey Savchenko 9c7bd34d54 Released version 3.3.5 2021-09-28 11:19:21 +03:00
Andrey Savchenko a797c4f406 Adjusted tests for newer Brain Monkey compatibility 2021-09-28 11:16:09 +03:00
Andrey Savchenko a815ba515c Allowed PHP 8, composer/installers 2, and refreshed lock
Fixes #54
2021-09-28 11:14:51 +03:00
Andrey Savchenko 595b696a03 Reduced maximum Server Timing headers size for nginx
See #52
2021-09-28 11:05:51 +03:00
Andrey Savchenko de563bb5d4 Updated plugin header
Fixes #45
2021-09-28 11:01:39 +03:00
Andrey Savchenko 8875774ddb Fixed non-ASCII characters in Server Timing headers
Fixes #48
2020-11-16 16:17:03 +02:00
Andrey Savchenko ccf3ff3738 Released version 3.3.3 (forgot to update plugin version in header as usual) 2020-03-11 13:23:43 +02:00
Andrey Savchenko cc7670f3c3 Released version 3.3.2 2020-03-11 13:19:54 +02:00
Andrey Savchenko f62eff890a Fixed large server timing headers breaking proxies.
Fixes #40
2020-03-11 13:18:41 +02:00
Andrey Savchenko b0584dea0a Fixed type crash when URL passed to network request filters is invalid.
Fixes #42
2020-03-11 12:58:12 +02:00
Andrey Savchenko dc88dcce1e Adjusted bunch of inline doc to make Psalm pass again. 2020-02-21 18:22:53 +02:00
Andrey Savchenko f4f659c95a Changed bootable interface to inherit so mock can work on PHPUnit 9. 2020-02-21 17:19:02 +02:00
Andrey Savchenko 6be80cc832 Updated dependencies (Mustache.php 2.13) 2020-02-21 17:15:00 +02:00
Andrey Savchenko 4930979989 Dropped no longer used version in file name from readme badges. 2020-02-21 17:11:24 +02:00
Andrey Savchenko 168a226859 Changed download links to native github redirect 2019-12-31 16:14:23 +02:00
Andrey Savchenko 588b597f99 Released version 3.3.1 2019-07-02 14:46:13 +03:00
Andrey Savchenko 54ba716ba8 Fixed crash from broken plugin loaded hook input from global
Fixes #35
2019-07-02 14:46:13 +03:00
Ramon Fincken e2f85f44d2 [TASK] Update link to wp-config define (#38) 2019-07-01 08:03:32 +03:00
Andrey Savchenko a2ae665dd2 Disabled Mixed Psalm errors because I quit. 2019-06-16 00:24:03 +03:00
Andrey Savchenko f8f5e436e5 Fixed global and data Psalm errors. 2019-06-16 00:23:12 +03:00
Andrey Savchenko 6cab9bbd6b Changed Psalm template attempt to type. 2019-06-16 00:22:45 +03:00
Andrey Savchenko 1e9a600096 Psalm cleanup pass. 2019-06-15 10:11:50 +03:00
Andrey Savchenko cc1cdc577a Disabled Psalm error for known deprecated class use. 2019-06-14 20:15:39 +03:00
Andrey Savchenko d3e352d9f3 Adjusted PHPDoc on event classes. 2019-06-14 20:09:54 +03:00
Andrey Savchenko e6bb7fadaa Released version 3.3 2019-03-27 21:42:16 +02:00
Andrey Savchenko bd42c7b6a1 Added tracking load of individual plugin load (WP 5.1+)
Fixes #24
2019-03-27 21:40:52 +02:00
Andrey Savchenko bbc7ff1ada Dropped queries with unknown start time (that never worked). 2019-03-25 19:19:18 +02:00
Andrey Savchenko 7bffa1f49e Added plotting of early SQL queries (WP 5.1+)
Fixes #19
2019-03-24 21:20:55 +02:00
Andrey Savchenko ba72763486 Fixed error when formatting empty hook.
Prop @kellymears Closes #30
2019-03-24 19:52:42 +02:00
Andrey Savchenko 739ed7dd90 Updated unit tests for PHPUnit 8. 2019-03-24 19:32:33 +02:00
Andrey Savchenko 59a17105ae Released version 3.2. 2019-01-30 19:06:55 +02:00
Andrey Savchenko e5079ce186 Added error message on unsupported PHP version. 2019-01-30 19:06:40 +02:00
Andrey Savchenko 657fccb2de Deprecated dedicated classes for extension events. 2019-01-30 19:01:15 +02:00
Andrey Savchenko cc8c8a4756 Added Beaver Builder events. 2019-01-30 18:57:49 +02:00
Andrey Savchenko 2700d83208 Fixed tracking of load events when network–activated. 2019-01-29 17:42:10 +02:00
Andrey Savchenko 8be22411f7 Added WooCommerce loop item event. 2019-01-29 16:34:08 +02:00
Andrey Savchenko a678997c44 Fixed handling of reoccurring hook events. 2019-01-29 16:23:49 +02:00
Andrey Savchenko b4c49f3c67 Released version 3.1 2019-01-22 16:39:05 +02:00
Andrey Savchenko 4d93d2542d Corrected class description. 2019-01-22 16:35:15 +02:00
Andrey Savchenko e1176b7f67 Added error message on incomplete install. 2019-01-22 16:34:25 +02:00
Andrey Savchenko 828d70cdd1 Added PHP version and OPcache status information. 2019-01-22 16:27:38 +02:00
Andrey Savchenko 15ff72afb0 Added WooCommerce events.
Fixes #29
2019-01-22 16:14:14 +02:00
Andrey Savchenko ad246ad1bc Fixed headers error from new line in Server Timing.
Fixes #28
2019-01-22 15:12:21 +02:00
Andrey Savchenko c1a389dc17 Fixed fatal error when HTTP request is pre–filtered. 2019-01-09 19:04:15 +02:00
Andrey Savchenko d724958dec Rounded memory value, saw a screenshot with it blowing up (hadn't seen on my systems). 2019-01-02 23:22:31 +02:00
Andrey Savchenko 950031bbaf Dropped Bootstrap bits from ignore to stop production build complaining. 2019-01-01 22:06:43 +02:00
Andrey Savchenko b173364ce2 Released version 3.0 2018-12-28 15:07:51 +02:00
Andrey Savchenko df8c9b1678 Added closure and anonymous class to hook formatter test. 2018-12-28 15:02:52 +02:00
Andrey Savchenko 5356df262f Added hook formatter test. 2018-12-28 14:55:47 +02:00
Andrey Savchenko df0504f176 Added lazy records collector to pass around. 2018-12-27 16:58:50 +02:00
Andrey Savchenko 960efd86ba Improved info about API authentication in readme. 2018-12-26 17:57:56 +02:00
Andrey Savchenko eeb8de17d0 Moved psalm config to root for simplicity. 2018-12-25 15:17:30 +02:00
Andrey Savchenko 6c9042e253 Updated built script version. 2018-12-24 22:21:48 +02:00
Andrey Savchenko d7af14e504 Increased precision of reported server timing numbers. 2018-12-21 20:44:04 +02:00
Andrey Savchenko 68f2813e9c Refactored hook formatter. 2018-12-21 17:37:55 +02:00
Andrey Savchenko 2f332c51cc Reorganized iterator classes. 2018-12-21 15:52:04 +02:00
Andrey Savchenko 961019ef74 Reorganized collector classes. 2018-12-21 15:45:47 +02:00
Andrey Savchenko fb144ebe01 Fixed typo in readme and added punctuation. 2018-12-21 15:22:47 +02:00
Andrey Savchenko e1b2bd3d40 Updated readme with new screenshots and API profiling info. 2018-12-21 15:18:02 +02:00
Andrey Savchenko 2ded7a3412 Added Server Timing output for Ajax and REST APIs.
Fixes #13
2018-12-21 15:07:04 +02:00
Andrey Savchenko 7d64b920eb Fixed error when no SQL queries logged by core. 2018-12-21 14:11:27 +02:00
Andrey Savchenko fcf17e44d4 Adjusted core events for better calblacks coverage. 2018-12-20 15:17:48 +02:00
Andrey Savchenko 355532fa72 Fixed line break styling in tooltips. 2018-12-20 15:05:32 +02:00
Andrey Savchenko ed2a53a33d Recompiled assets from Bootstrap 3.4 2018-12-20 14:58:57 +02:00
Andrey Savchenko b940fd522f Updated dependencies (Bootstrap 3.4, Stopwatch 4) 2018-12-20 14:57:29 +02:00
Andrey Savchenko aca045b5aa Added callbacks list to hook events.
See #17
2018-12-19 21:41:36 +02:00
Andrey Savchenko 3eb2a91940 Added backtraces to HTTP calls.
Fixes #16
2018-12-17 18:16:35 +02:00
Andrey Savchenko eb573e143e Added optional description to Stopwatch records. 2018-12-17 18:15:59 +02:00
Andrey Savchenko 63f1b9e216 Fixed order of backtrace items if provided as array. 2018-12-17 18:15:12 +02:00
Andrey Savchenko 7412da0741 Simplified closure arguments. 2018-12-14 21:40:46 +02:00
Andrey Savchenko bf4ca812aa Improved sorting of records by origin. 2018-12-14 21:08:16 +02:00
Andrey Savchenko 9301c8256d Fixed changelog typo. 2018-12-14 17:29:46 +02:00
Andrey Savchenko 2cb708f70d Added core events and tracking of distinct sidebars. 2018-12-14 17:08:26 +02:00
Andrey Savchenko 4166802c89 Added head hook event. 2018-12-14 16:04:46 +02:00
Andrey Savchenko 78cbb85de8 Adjusted ignoring edge of hook event to empty string. 2018-12-14 16:04:34 +02:00
Andrey Savchenko c23531e890 Added new format of hook events.
Fixes #27
2018-12-13 17:24:40 +02:00
Andrey Savchenko 70de78b276 Added psalm configuration and refined source. 2018-11-30 18:15:38 +02:00
Andrey Savchenko 4013046e4f Added return types to closures. 2018-11-30 15:05:54 +02:00
Andrey Savchenko 204c1df755 Raised PHP requirement to 7.1 and updated types.
Fixes #25
2018-11-30 13:58:53 +02:00
Andrey Savchenko cd55061e69 Added ignore plugin configuration to drop unneeded dependency bits. 2018-11-15 11:32:37 +02:00
Andrey Savchenko 9fee649a64 Adjusted how drive letter is trimmed and improved test. 2018-11-13 17:49:29 +02:00
Andrey Savchenko a2d92585ac Changed separator to horizontal line for legibility. 2018-11-13 16:38:15 +02:00
Andrey Savchenko e4af00b43e Adjusted paths to truncate for better order and drive–relative paths. 2018-11-13 16:37:33 +02:00
Andrey Savchenko 20b2a767f3 Added direct download links to readme. 2018-11-12 10:40:41 +02:00
Andrey Savchenko 4dbcddad8d Added backtrace formatter test. 2018-03-26 19:43:49 +03:00
Andrey Savchenko 5ee99cddd4 Fixed tests adding wp_normalize_path() mocks. 2018-03-19 22:44:36 +02:00
Andrey Savchenko 1f85d65b9b Added backtraces to SQL record descriptions. 2018-03-13 18:46:04 +02:00
Andrey Savchenko 1e3195ffc8 Implemented includes shortening in backtrace. 2018-03-12 22:44:01 +02:00
Andrey Savchenko 30db67c055 Added backtrace formatter class. 2018-03-09 22:07:06 +02:00
Andrey Savchenko 79ed1ffcfd Moved SQL data processing to a separate method. 2018-03-08 18:28:17 +02:00
Andrey Savchenko 6818236f83 Moved toolbar hook to later priority to capture more of other actions. 2018-03-07 18:41:24 +02:00
72 changed files with 2029 additions and 1414 deletions

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
name: Release
on:
release:
types: [ published ]
push:
tags:
- '*'
permissions:
contents: write
jobs:
upload-asset:
runs-on: ubuntu-latest
container:
image: composer:2
steps:
- name: Checkout
run: |
git init .
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@$(echo "${{ github.server_url }}" | sed -E 's|https?://||')/${{ github.repository }}.git"
git fetch --depth 1 origin "${{ github.ref }}"
git checkout FETCH_HEAD
- name: Build release zip
run: |
mkdir -p release/laps
git archive HEAD | tar -x -C release/laps
cd release/laps
composer config platform.php 7.4
composer remove composer/installers --no-update
composer update --no-dev --optimize-autoloader
cd ..
zip -q -r laps.zip laps
rm -rf laps
- name: Upload zip to Gitea Release
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
API_URL: ${{ github.api_url }}
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
if [ -z "$API_URL" ]; then
API_URL="${SERVER_URL}/api/v1"
fi
echo "Target API: $API_URL/repos/$REPO"
RELEASE_JSON=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API_URL/repos/$REPO/releases/tags/$TAG")
RELEASE_ID=$(echo "$RELEASE_JSON" | php -r '$d = json_decode(file_get_contents("php://stdin")); echo $d->id ?? "";')
if [ -z "$RELEASE_ID" ]; then
echo "Creating release for tag $TAG..."
CREATE_RES=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Release $TAG\"}" \
"$API_URL/repos/$REPO/releases")
RELEASE_ID=$(echo "$CREATE_RES" | php -r '$d = json_decode(file_get_contents("php://stdin")); echo $d->id ?? "";')
fi
echo "Uploading laps.zip to release ID $RELEASE_ID..."
curl -s -f -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "attachment=@release/laps.zip" \
"$API_URL/repos/$REPO/releases/$RELEASE_ID/assets?name=laps.zip"
+27
View File
@@ -0,0 +1,27 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
container:
image: composer:2
steps:
- name: Checkout
run: |
git init .
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@$(echo "${{ github.server_url }}" | sed -E 's|https?://||')/${{ github.repository }}.git"
git fetch --depth 1 origin "${{ github.ref }}"
git checkout FETCH_HEAD
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist
- name: Run PHPUnit
run: vendor/bin/phpunit
+2 -1
View File
@@ -1,2 +1,3 @@
/vendor /vendor
/release /release
.phpunit.result.cache
+115 -1
View File
@@ -6,6 +6,120 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
## 3.4.0 - 2026-09-26
### Changed
- set minimum required PHP version to 7.4
- added `Requires at least: 6.2` to plugin header (supports ClassicPress 2.x and WordPress 6.2+)
- checked user permissions (`laps_can_see`) before enqueueing assets in `Asset_Manager`
## 3.3.7 - 2022-06-16
### Fixed
- PHP warning, when accessing OPcache status with `opcache.restrict_api`
## 3.3.6 - 2022-02-08
### Changed
- updated dependencies (Mustache.php, Pimple)
- minimum required PHP version 7.2.5 (was de-facto required by dependencies before)
### Fixed
- deprecation notices on PHP 8.1
## 3.3.5 - 2021-09-28
### Changed
- reduced maximum Server Timing headers size to 2K (half of possible nginx defaults)
### Fixed
- allowed Composer install on PHP 8
## 3.3.4 - 2020-11-16
### Fixed
- non-ASCII characters in Server Timing headers
## 3.3.3 - 2020-03-11
### Fixed
- wrong plugin version in header
## 3.3.2 - 2020-03-11
### Changed
- `Bootable_Provider_Interface` to extend `ServiceProviderInterface`, so that it can be mocked in PHPUnit 9
### Fixed
- Mustache.php version to resolve issues on PHP 7.4
- type crash when URL passed to network request filters is invalid
- large server timing headers breaking proxies
## 3.3.1 - 2019-07-02
### Fixed
- crash from broken plugin loaded hook input from global
## 3.3 - 2019-03-27
### Added
- plotting of early SQL queries (WP 5.1+)
- tracking load of individual plugin load (WP 5.1+)
### Changed
- unit tests for PHPUnit 8
### Fixed
- error when formatting empty hook.
## 3.2 - 2019-01-30
### Added
- Beaver Builder events
- error message on unsupported PHP version
### Fixed
- handling of reoccurring hook events
- tracking of load events when network–activated
### Deprecated
- dedicated classes for extension events
## 3.1 - 2019-01-22
### Added
- WooCommerce events
- PHP version and OPcache status information
- error message on incomplete install
### Fixed
- headers error from new line in Server Timing
## 3.0.1 - 2019-01-09
### Fixed
- fatal error when HTTP request is pre–filtered
## 3.0 - 2018-12-28
### Added
- call backtraces to SQL records and HTTP calls
- new format of hook events
- sidebar tracking
- list of callbacks to hook events
- Server Timing output for Ajax and REST APIs
- lazy records collector to pass around instead of whole container
### Changed
- required PHP version to 7.1
- organization and names for Record classes
### Deprecated
- old format of hook events
### Fixed
- error when no SQL queries logged by core
## 2.0 - 2018-02-26 ## 2.0 - 2018-02-26
### Added ### Added
@@ -118,4 +232,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## 1.0 - 2013-04-22 ## 1.0 - 2013-04-22
### Added ### Added
- Initial public release - Initial public release
+51 -31
View File
@@ -1,42 +1,62 @@
# Laps — light WordPress profiler ![Laps v3 toolbar screenshot](https://i.imgur.com/NtgAxhp.png)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Rarst/laps/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Rarst/laps/?branch=master)
[![Build Status](https://scrutinizer-ci.com/g/Rarst/laps/badges/build.png?b=master)](https://scrutinizer-ci.com/g/Rarst/laps/build-status/master)
[![Code Coverage](https://scrutinizer-ci.com/g/Rarst/laps/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Rarst/laps/?branch=master)
[![Total Downloads](https://img.shields.io/packagist/dt/rarst/laps.svg)](https://packagist.org/packages/rarst/laps)
[![Latest Stable Version](https://img.shields.io/packagist/v/rarst/laps.svg?label=version)](https://packagist.org/packages/rarst/laps)
[![PHP from Packagist](https://img.shields.io/packagist/php-v/rarst/laps.svg)](https://packagist.org/packages/rarst/laps)
[![PDS Skeleton](https://img.shields.io/badge/pds-skeleton-blue.svg)](https://github.com/php-pds/skeleton)
_Make a site, make it fast._ # Laps — könnyűsúlyú WordPress profilozó
![Laps v2 screenshot](https://i.imgur.com/6n36KPE.png) > *Készíts egy weboldalt, és tedd gyorssá.*
Laps is a plugin that **shows performance information** about WordPress page load. A Laps egy gyors és könnyű WordPress bővítmény, amely valós idejű betöltési és teljesítményadatokat jelenít meg közvetlenül az admin eszköztáron (toolbar).
It provides a visual summary in toolbar that is quick and easy to inspect. ## Főbb funkciók
Laps tracks many events, such as: ### Oldalprofilozás
- PHP, core, plugins, and themes load A Laps automatikusan méri és megjeleníti a kulcsfontosságú eseményeket:
- database queries (with [`SAVEQUERIES` constant](http://codex.wordpress.org/Editing_wp-config.php#Save_queries_for_analysis) enabled) - PHP, WordPress mag, bővítmények és sablonok betöltési ideje, fő ciklus (loop), oldalsávok (sidebars)
- network requests to other sites - Adatbázis-lekérdezések és hívási láncok részletes listázása (ha a `SAVEQUERIES` konstans értéke `true`)
- main posts loop - Külső hálózati kérések (HTTP API hívások más oldalakra)
- events for supported theme frameworks:
- [Theme Hook Alliance](http://zamoose.github.io/themehookalliance/)
- [Hybrid](http://themehybrid.com/)
- [Genesis](http://my.studiopress.com/themes/genesis/)
- events for supported plugins:
- [Yoast SEO](https://wordpress.org/plugins/wordpress-seo/)
## Installation ### API mérés (Ajax & REST API)
Ajax és REST API kérések esetén a Laps a **Server Timing API** segítségével küldi el a mérési adatokat, amelyek közvetlenül megtekinthetők a böngésző fejlesztői eszköztárában (pl. Chrome DevTools Network fülön).
Require with [Composer](https://getcomposer.org/): ![Laps v3 dev tools screenshot](https://i.imgur.com/hkl1Qk9.png)
```bash ### Jogosultságok
composer require rarst/laps A teljesítményadatok és a kapcsolódó stílusok/scriptek alapértelmezetten csak a megfelelő adminisztrátori jogosultsággal rendelkező felhasználóknak jelennek meg. A megjelenítés testreszabható a `laps_can_see` szűrővel (filter).
```
Or download plugin archive from [releases section](https://github.com/Rarst/laps/releases). ## Telepítés
1. Töltsd le a legfrissebb `laps.zip` archívumot a **Releases** oldalról.
2. A WordPress admin felületén válaszd a **Bővítmények → Új hozzáadása → Bővítmény feltöltése** menüpontot.
3. Töltsd fel a `laps.zip` fájlt, majd aktiváld.
## Követelmények
- **WordPress / ClassicPress:** WordPress 6.2+ vagy ClassicPress 2.0+ (pl. 2.7.3)
- **PHP:** 7.4 vagy újabb
---
## English
# Laps — Light WordPress Profiler
> *Make a site, make it fast.*
Laps is a lightweight plugin that displays real-time page load and performance information directly in the admin toolbar.
### Features
- **Page profiling:** Automatically tracks PHP baseline, core initialization, plugin and theme load times, the main query loop, and sidebars.
- **Database queries:** Detailed query logs and execution times (when `SAVEQUERIES` is defined and set to `true`).
- **HTTP requests:** Tracks external network requests made via the WordPress HTTP API.
- **API profiling:** Emits Server Timing API headers for Ajax and REST API requests, inspectable directly in browser DevTools.
- **Access control:** Metrics and frontend/admin assets are only exposed to users with administrative capabilities (customizable via the `laps_can_see` filter).
### Installation
1. Download the latest `laps.zip` from the **Releases** page.
2. In the WordPress Admin, go to **Plugins → Add New → Upload Plugin**.
3. Upload `laps.zip` and activate.
### Requirements
- **WordPress / ClassicPress:** WordPress 6.2+ or ClassicPress 2.0+ (e.g. 2.7.3)
- **PHP:** 7.4 or higher
## License ## License
MIT
MIT
+89 -16
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
@@ -15,13 +16,13 @@ class RoboFile extends \Robo\Tasks {
public function watch() { public function watch() {
$this->taskWatch() $this->taskWatch()
->monitor( [ 'public/css/variables.less', 'public/css/laps.less' ], function () { ->monitor( [ 'public/css/variables.less', 'public/css/laps.less' ], function () {
$this->makeCss(); $this->makeCss();
} ) } )
->monitor( 'public/js/source.js', function () { ->monitor( 'public/js/source.js', function () {
$this->makeJs(); $this->makeJs();
} ) } )
->run(); ->run();
} }
/** /**
@@ -42,7 +43,7 @@ class RoboFile extends \Robo\Tasks {
$this->taskExec( 'lessc public/css/laps.less public/css/laps.css --source-map=public/css/laps.css.map' )->run(); $this->taskExec( 'lessc public/css/laps.less public/css/laps.css --source-map=public/css/laps.css.map' )->run();
$this->taskMinify( 'public/css/laps.css' ) $this->taskMinify( 'public/css/laps.css' )
->run(); ->run();
} }
/** /**
@@ -54,11 +55,11 @@ class RoboFile extends \Robo\Tasks {
'vendor/twbs/bootstrap/js/tooltip.js', 'vendor/twbs/bootstrap/js/tooltip.js',
'public/js/source.js', 'public/js/source.js',
] ) ] )
->to( 'public/js/laps.js' ) ->to( 'public/js/laps.js' )
->run(); ->run();
$this->taskMinify( 'public/js/laps.js' ) $this->taskMinify( 'public/js/laps.js' )
->run(); ->run();
} }
/** /**
@@ -68,14 +69,65 @@ class RoboFile extends \Robo\Tasks {
$dir = __DIR__ . '/src/mustache/cache'; $dir = __DIR__ . '/src/mustache/cache';
$this->_cleanDir( $dir ); $this->_cleanDir( $dir );
$mustache = new \Mustache_Engine( $mustache = new \Mustache_Engine(
array( [
'loader' => new \Mustache_Loader_FilesystemLoader( __DIR__ . '/src/mustache' ), 'loader' => new \Mustache_Loader_FilesystemLoader( __DIR__ . '/src/mustache' ),
'cache' => $dir, 'cache' => $dir,
) ]
); );
$mustache->loadTemplate( 'laps' ); $mustache->loadTemplate( 'laps' );
} }
/**
* Creates release zip
*
* @param string $php PHP version to target.
*/
public function makeArchive( $php = '7.4' ): void {
$composer = json_decode( file_get_contents( __DIR__ . '/composer.json' ) );
$package = $composer->name;
[ $vendor, $name ] = explode( '/', $package );
if ( empty( $vendor ) || empty( $name ) ) {
return;
}
$this->_mkdir( 'release' );
$version = $this->latestTag();
$this->taskExec( "composer create-project {$package} {$name} {$version}" )
->dir( __DIR__ . '/release' )
->arg( '--prefer-dist' )
->arg( '--no-install' )
->run();
if ( $php ) {
$this->taskExec( "composer config platform.php {$php}" )
->dir( __DIR__ . "/release/{$name}" )
->run();
}
$this->taskExec( 'composer remove composer/installers --no-update' )
->dir( __DIR__ . "/release/{$name}" )
->run();
$this->taskExec( 'composer update --no-dev --optimize-autoloader' )
->dir( __DIR__ . "/release/{$name}" )
->run();
$zipFile = "release/{$name}.zip";
$this->_remove( $zipFile );
$this->taskPack( $zipFile )
->addDir( $name, "release/{$name}" )
->run();
$this->_deleteDir( "release/{$name}" );
}
/** /**
* Updates version in header and makes git version tag. * Updates version in header and makes git version tag.
* *
@@ -83,12 +135,13 @@ class RoboFile extends \Robo\Tasks {
*/ */
public function tag( $version ) { public function tag( $version ) {
$this->versionSet( $version ); $this->makeVersion( $version );
$this->taskGitStack() $this->taskGitStack()
->stopOnFail() ->stopOnFail()
->add( 'CHANGELOG.md' )
->add( 'laps.php' ) ->add( 'laps.php' )
->commit( "Updated header version to {$version}" ) ->commit( "Released version $version" )
->tag( $version ) ->tag( $version )
->run(); ->run();
} }
@@ -98,11 +151,31 @@ class RoboFile extends \Robo\Tasks {
* *
* @param string $version Version string. * @param string $version Version string.
*/ */
public function versionSet( $version ) { public function makeVersion( $version ) {
$this->taskReplaceInFile( 'CHANGELOG.md' )
->from( '## Unreleased' )
->to( '## Unreleased' . PHP_EOL . PHP_EOL . "## $version - " . date_format( date_create(), 'Y-m-d' ) )
->run();
$this->taskReplaceInFile( 'laps.php' ) $this->taskReplaceInFile( 'laps.php' )
->regex( '|^Version:.*$|m' ) ->regex( '|^Version:.*$|m' )
->to( 'Version: ' . $version ) ->to( 'Version: ' . $version )
->run(); ->run();
} }
public function upload() {
$version = $this->latestTag();
$zipFile = __DIR__ . '/release/laps.zip';
$this->makeArchive();
$this->_exec("gh release create $version $zipFile");
$this->_remove( $zipFile );
}
private function latestTag(): string {
$commit = escapeshellarg( exec( 'git rev-list --tags --max-count=1' ) );
return escapeshellarg( exec( "git describe --tags {$commit}" ) );
}
} }
+31 -8
View File
@@ -8,7 +8,7 @@
"authors" : [ "authors" : [
{ {
"name" : "Andrey Savchenko", "name" : "Andrey Savchenko",
"homepage": "http://www.Rarst.net/" "homepage": "https://www.Rarst.net/"
} }
], ],
"support" : { "support" : {
@@ -25,15 +25,38 @@
} }
}, },
"require" : { "require" : {
"php" : ">=5.6", "php" : "^7.4 || ^8",
"composer/installers": "~1.0", "composer/installers": "^1.6 || ^2",
"symfony/stopwatch" : "^3.4.4", "symfony/stopwatch" : "^4.2.1",
"mustache/mustache" : "~2.4", "mustache/mustache" : "^2.14.1",
"pimple/pimple" : "^3.2.3" "pimple/pimple" : "^3.5"
}, },
"require-dev": { "require-dev": {
"twbs/bootstrap": "~3.2", "twbs/bootstrap": "^3.4",
"brain/monkey" : "^2.2", "brain/monkey" : "^2.2",
"pds/skeleton": "^1.0" "pds/skeleton": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.5"
},
"extra" : {
"light-ignore-plugin": {
"mustache/mustache": [
"test"
],
"pimple/pimple" : [
"ext",
"src/Pimple/Tests"
],
"symfony/stopwatch": [
"Tests"
]
}
},
"config": {
"allow-plugins": {
"composer/installers": true
},
"platform": {
"php": "7.4"
}
} }
} }
Generated
-635
View File
@@ -1,635 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "b88211148a5c0893e190e574ac18bd30",
"packages": [
{
"name": "composer/installers",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "049797d727261bf27f2690430d935067710049c2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2",
"reference": "049797d727261bf27f2690430d935067710049c2",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0"
},
"replace": {
"roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
"composer/composer": "1.0.*@dev",
"phpunit/phpunit": "^4.8.36"
},
"type": "composer-plugin",
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
"homepage": "https://composer.github.io/installers/",
"keywords": [
"Craft",
"Dolibarr",
"Eliasis",
"Hurad",
"ImageCMS",
"Kanboard",
"Lan Management System",
"MODX Evo",
"Mautic",
"Maya",
"OXID",
"Plentymarkets",
"Porto",
"RadPHP",
"SMF",
"Thelia",
"WolfCMS",
"agl",
"aimeos",
"annotatecms",
"attogram",
"bitrix",
"cakephp",
"chef",
"cockpit",
"codeigniter",
"concrete5",
"croogo",
"dokuwiki",
"drupal",
"eZ Platform",
"elgg",
"expressionengine",
"fuelphp",
"grav",
"installer",
"itop",
"joomla",
"kohana",
"laravel",
"lavalite",
"lithium",
"magento",
"majima",
"mako",
"mediawiki",
"modulework",
"modx",
"moodle",
"osclass",
"phpbb",
"piwik",
"ppi",
"puppet",
"pxcms",
"reindex",
"roundcube",
"shopware",
"silverstripe",
"sydes",
"symfony",
"typo3",
"wordpress",
"yawik",
"zend",
"zikula"
],
"time": "2017-12-29T09:13:20+00:00"
},
{
"name": "mustache/mustache",
"version": "v2.12.0",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/mustache.php.git",
"reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e",
"reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~1.11",
"phpunit/phpunit": "~3.7|~4.0|~5.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Mustache": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
}
],
"description": "A Mustache implementation in PHP.",
"homepage": "https://github.com/bobthecow/mustache.php",
"keywords": [
"mustache",
"templating"
],
"time": "2017-07-11T12:54:05+00:00"
},
{
"name": "pimple/pimple",
"version": "v3.2.3",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/container": "^1.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2.x-dev"
}
},
"autoload": {
"psr-0": {
"Pimple": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Pimple, a simple Dependency Injection Container",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
"time": "2018-01-21T07:42:36+00:00"
},
{
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "symfony/stopwatch",
"version": "v3.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "c865551df7c17e63fc1f09f763db04387f91ae4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/c865551df7c17e63fc1f09f763db04387f91ae4d",
"reference": "c865551df7c17e63fc1f09f763db04387f91ae4d",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Stopwatch\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
"time": "2018-01-03T07:37:34+00:00"
}
],
"packages-dev": [
{
"name": "antecedent/patchwork",
"version": "2.1.8",
"source": {
"type": "git",
"url": "https://github.com/antecedent/patchwork.git",
"reference": "3bb81ace3914c220aa273d1c0603d5e1b454c0d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/antecedent/patchwork/zipball/3bb81ace3914c220aa273d1c0603d5e1b454c0d7",
"reference": "3bb81ace3914c220aa273d1c0603d5e1b454c0d7",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignas Rudaitis",
"email": "ignas.rudaitis@gmail.com"
}
],
"description": "Method redefinition (monkey-patching) functionality for PHP.",
"homepage": "http://patchwork2.org/",
"keywords": [
"aop",
"aspect",
"interception",
"monkeypatching",
"redefinition",
"runkit",
"testing"
],
"time": "2018-02-19T18:52:50+00:00"
},
{
"name": "brain/monkey",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/Brain-WP/BrainMonkey.git",
"reference": "ed9e0698bc1292f33698719da8ca1aa2e18acc51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/ed9e0698bc1292f33698719da8ca1aa2e18acc51",
"reference": "ed9e0698bc1292f33698719da8ca1aa2e18acc51",
"shasum": ""
},
"require": {
"antecedent/patchwork": "^2.0",
"mockery/mockery": ">=0.9 <2",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.7.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-version/1": "1.x-dev",
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Brain\\Monkey\\": "src/"
},
"files": [
"inc/api.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Giuseppe Mazzapica",
"email": "giuseppe.mazzapica@gmail.com",
"homepage": "https://gmazzap.me",
"role": "Developer"
}
],
"description": "Mocking utility for PHP functions and WordPress plugin API",
"keywords": [
"Monkey Patching",
"interception",
"mock",
"mock functions",
"mockery",
"patchwork",
"redefinition",
"runkit",
"test",
"testing"
],
"time": "2017-12-01T16:32:09+00:00"
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad",
"shasum": ""
},
"require": {
"php": "^5.3|^7.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
"davedevelopment/hamcrest-php": "*",
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "1.3.3",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"hamcrest"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
],
"description": "This is the PHP port of Hamcrest Matchers",
"keywords": [
"test"
],
"time": "2016-01-20T08:20:44+00:00"
},
{
"name": "mockery/mockery",
"version": "1.0",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38",
"reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "~2.0",
"lib-pcre": ">=7.0",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.7|~6.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Mockery": "library/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Pádraic Brady",
"email": "padraic.brady@gmail.com",
"homepage": "http://blog.astrumfutura.com"
},
{
"name": "Dave Marshall",
"email": "dave.marshall@atstsolutions.co.uk",
"homepage": "http://davedevelopment.co.uk"
}
],
"description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
"homepage": "http://github.com/mockery/mockery",
"keywords": [
"BDD",
"TDD",
"library",
"mock",
"mock objects",
"mockery",
"stub",
"test",
"test double",
"testing"
],
"time": "2017-10-06T16:20:43+00:00"
},
{
"name": "pds/skeleton",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-pds/skeleton.git",
"reference": "95e476e5d629eadacbd721c5a9553e537514a231"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-pds/skeleton/zipball/95e476e5d629eadacbd721c5a9553e537514a231",
"reference": "95e476e5d629eadacbd721c5a9553e537514a231",
"shasum": ""
},
"bin": [
"bin/pds-skeleton"
],
"type": "standard",
"autoload": {
"psr-4": {
"Pds\\Skeleton\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"CC-BY-SA-4.0"
],
"description": "Standard for PHP package skeletons.",
"homepage": "https://github.com/php-pds/skeleton",
"time": "2017-01-25T23:30:41+00:00"
},
{
"name": "twbs/bootstrap",
"version": "v3.3.7",
"source": {
"type": "git",
"url": "https://github.com/twbs/bootstrap.git",
"reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86",
"reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86",
"shasum": ""
},
"replace": {
"twitter/bootstrap": "self.version"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jacob Thornton",
"email": "jacobthornton@gmail.com"
},
{
"name": "Mark Otto",
"email": "markdotto@gmail.com"
}
],
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
"homepage": "http://getbootstrap.com",
"keywords": [
"JS",
"css",
"framework",
"front-end",
"less",
"mobile-first",
"responsive",
"web"
],
"time": "2016-07-25T15:51:55+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.6"
},
"platform-dev": []
}
+12 -3
View File
@@ -5,10 +5,11 @@ Plugin Name: Laps
Plugin URI: https://github.com/Rarst/laps Plugin URI: https://github.com/Rarst/laps
Description: Light WordPress profiler. Description: Light WordPress profiler.
Author: Andrey “Rarst” Savchenko Author: Andrey “Rarst” Savchenko
Version: 2.0 Version: 3.4.0
Author URI: https://www.rarst.net/ Author URI: https://www.rarst.net/
Text Domain: laps Requires at least: 6.2
Domain Path: /lang Requires PHP: 7.4
Update URI: false
License: MIT License: MIT
*/ */
@@ -16,6 +17,14 @@ if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
} }
if ( ! class_exists( '\Rarst\Laps\Plugin' ) ) {
trigger_error( 'Laps not fully installed! Please install with Composer or download full release archive.', E_USER_ERROR );
}
if ( PHP_VERSION_ID < 70400 ) {
trigger_error( 'Laps requires PHP version >=7.4.', E_USER_ERROR );
}
$laps = new \Rarst\Laps\Plugin(); $laps = new \Rarst\Laps\Plugin();
$laps->run(); $laps->run();
+1 -1
View File
@@ -12,4 +12,4 @@
</exclude> </exclude>
</whitelist> </whitelist>
</filter> </filter>
</phpunit> </phpunit>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorLevel="2"
>
<projectFiles>
<directory name="src"/>
<ignoreFiles>
<directory name="vendor"/>
<directory name="src/mustache/cache"/>
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info"/>
<UndefinedFunction errorLevel="suppress"/>
<UndefinedClass errorLevel="suppress"/>
<UndefinedDocblockClass errorLevel="suppress"/>
<UndefinedConstant errorLevel="suppress"/>
</issueHandlers>
</psalm>
+32 -29
View File
@@ -17,9 +17,9 @@
display: none; display: none;
} }
#wpadminbar .laps-timeline { #wpadminbar .laps-timeline {
overflow: hidden;
height: 20px; height: 20px;
margin-bottom: 20px; margin-bottom: 20px;
overflow: hidden;
background-color: #f5f5f5; background-color: #f5f5f5;
border-radius: 4px; border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
@@ -70,8 +70,8 @@
position: absolute; position: absolute;
width: 1px; width: 1px;
height: 1px; height: 1px;
margin: -1px;
padding: 0; padding: 0;
margin: -1px;
overflow: hidden; overflow: hidden;
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
border: 0; border: 0;
@@ -82,57 +82,42 @@
display: block; display: block;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: 400;
letter-spacing: normal;
line-break: auto;
line-height: 1.42857143; line-height: 1.42857143;
line-break: auto;
text-align: left; text-align: left;
text-align: start; text-align: start;
text-decoration: none; text-decoration: none;
text-shadow: none; text-shadow: none;
text-transform: none; text-transform: none;
white-space: normal; letter-spacing: normal;
word-break: normal; word-break: normal;
word-spacing: normal; word-spacing: normal;
word-wrap: normal; word-wrap: normal;
white-space: normal;
font-size: 12px; font-size: 12px;
opacity: 0;
filter: alpha(opacity=0); filter: alpha(opacity=0);
opacity: 0;
} }
#wpadminbar .tooltip.in { #wpadminbar .tooltip.in {
opacity: 0.9;
filter: alpha(opacity=90); filter: alpha(opacity=90);
opacity: 0.9;
} }
#wpadminbar .tooltip.top { #wpadminbar .tooltip.top {
margin-top: -3px;
padding: 5px 0; padding: 5px 0;
margin-top: -3px;
} }
#wpadminbar .tooltip.right { #wpadminbar .tooltip.right {
margin-left: 3px;
padding: 0 5px; padding: 0 5px;
margin-left: 3px;
} }
#wpadminbar .tooltip.bottom { #wpadminbar .tooltip.bottom {
margin-top: 3px;
padding: 5px 0; padding: 5px 0;
margin-top: 3px;
} }
#wpadminbar .tooltip.left { #wpadminbar .tooltip.left {
margin-left: -3px;
padding: 0 5px; padding: 0 5px;
} margin-left: -3px;
#wpadminbar .tooltip-inner {
max-width: 300px;
padding: 3px 8px;
color: #eee;
text-align: center;
background-color: #464646;
border-radius: 4px;
}
#wpadminbar .tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
} }
#wpadminbar .tooltip.top .tooltip-arrow { #wpadminbar .tooltip.top .tooltip-arrow {
bottom: 0; bottom: 0;
@@ -142,8 +127,8 @@
border-top-color: #464646; border-top-color: #464646;
} }
#wpadminbar .tooltip.top-left .tooltip-arrow { #wpadminbar .tooltip.top-left .tooltip-arrow {
bottom: 0;
right: 5px; right: 5px;
bottom: 0;
margin-bottom: -5px; margin-bottom: -5px;
border-width: 5px 5px 0; border-width: 5px 5px 0;
border-top-color: #464646; border-top-color: #464646;
@@ -190,12 +175,30 @@
border-width: 0 5px 5px; border-width: 0 5px 5px;
border-bottom-color: #464646; border-bottom-color: #464646;
} }
#wpadminbar .tooltip-inner {
max-width: 300px;
padding: 3px 8px;
color: #eee;
text-align: center;
background-color: #464646;
border-radius: 4px;
}
#wpadminbar .tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
#wpadminbar .tooltip.in { #wpadminbar .tooltip.in {
opacity: 1;
filter: alpha(opacity=100); filter: alpha(opacity=100);
opacity: 1;
} }
#wpadminbar .tooltip-inner { #wpadminbar .tooltip-inner {
line-height: 20px; line-height: 20px;
text-align: left; text-align: left;
} }
#wpadminbar .tooltip-inner br {
line-height: inherit;
}
/*# sourceMappingURL=laps.css.map */ /*# sourceMappingURL=laps.css.map */
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sources":["laps.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/progress-bars.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/vendor-prefixes.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/scaffolding.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/tooltip.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/reset-text.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/variables.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/opacity.less"],"names":[],"mappings":"AAMA,WAEC,mBAEC;EACC,cAAA;EACA,UAAA;EACA,eAAA;EACA,QAAA;;AARH,WAEC,mBASC;EACC,UAAA;;AAZH,WAgBC;EAEC,eAAA;;AAEA,WAJD,0BAIE;EACA,6BAAA;;AArBH,WAgBC,0BAQC;EACC,aAAA;;AAzBH,WA6BC;ECTC,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,yBAAA;EACA,kBAAA;ECsCA,sDAAA;EACQ,8CAAA;EFhCR,aAAA;EACA,kBAAA;;AAhCF,WA6BC,eAKC;ECJA,WAAA;EACA,SAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;ECyBA,sDAAA;EACQ,8CAAA;EAyHR,mCAAA;EACK,8BAAA;EACG,2BAAA;EFtJP,kBAAA;EACA,8BAAA;EACA,cAAA;;AAtCH,WA6BC,eAYC;EACC,yBAAA;;AA1CH,WA6BC,eAeC;EACC,yBAAA;;AA7CH,WA6BC,eAkBC;EACC,yBAAA;;AAhDH,WA6BC,eAqBC;EACC,yBAAA;;AAnDH,WA6BC,eAwBC;EACC,yBAAA;;AAtDH,WA6BC,eA2BC;EACC,yBAAA;;AAzDH,WA6BC,eA8BC;EACC,yBAAA;;AA5DH,WA6BC,eAkCC;EGwDA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,YAAA;EACA,UAAA;EACA,gBAAA;EACA,MAAM,gBAAN;EACA,SAAA;;AH9HF,WIAA;EACE,kBAAA;EACA,eAAA;EACA,cAAA;ECRA,aC2CwB,8CD3CxB;EAEA,kBAAA;EACA,mBAAA;EACA,sBAAA;EACA,gBAAA;EACA,uBAAA;EACA,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kBAAA;EACA,oBAAA;EACA,iBAAA;EDHA,eAAA;EGVA,UAAA;EAGA,wBAAA;;AHWA,WAXF,SAWG;EGdD,YAAA;EAGA,yBAAA;;AHYA,WAZF,SAYG;EAAU,gBAAA;EAAmB,cAAA;;AAC9B,WAbF,SAaG;EAAU,gBAAA;EAAmB,cAAA;;AAC9B,WAdF,SAcG;EAAU,eAAA;EAAmB,cAAA;;AAC9B,WAfF,SAeG;EAAU,iBAAA;EAAmB,cAAA;;AJfhC,WImBA;EACE,gBAAA;EACA,gBAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,kBAAA;;AJzBF,WI6BA;EACE,kBAAA;EACA,QAAA;EACA,SAAA;EACA,yBAAA;EACA,mBAAA;;AAIA,WADF,SACG,IAAK;EACJ,SAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WARF,SAQG,SAAU;EACT,SAAA;EACA,UAAA;EACA,mBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WAfF,SAeG,UAAW;EACV,SAAA;EACA,SAAA;EACA,mBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WAtBF,SAsBG,MAAO;EACN,QAAA;EACA,OAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;;AAEF,WA7BF,SA6BG,KAAM;EACL,QAAA;EACA,QAAA;EACA,gBAAA;EACA,2BAAA;EACA,0BAAA;;AAEF,WApCF,SAoCG,OAAQ;EACP,MAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,WA3CF,SA2CG,YAAa;EACZ,MAAA;EACA,UAAA;EACA,gBAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,WAlDF,SAkDG,aAAc;EACb,MAAA;EACA,SAAA;EACA,gBAAA;EACA,uBAAA;EACA,4BAAA;;AJ5FJ,WAsEC,SAAQ;EOzEP,UAAA;EAGA,0BAAA;;APAF,WA0EC;EACC,iBAAA;EACA,gBAAA","file":"laps.css"} {"version":3,"sources":["laps.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/progress-bars.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/vendor-prefixes.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/scaffolding.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/tooltip.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/reset-text.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/variables.less","C:/server/www/dev/wp-content/plugins/laps/vendor/twbs/bootstrap/less/mixins/opacity.less"],"names":[],"mappings":"AAMA,WAEC,mBAEC;EACC,cAAA;EACA,UAAA;EACA,eAAA;EACA,QAAA;;AARH,WAEC,mBASC;EACC,UAAA;;AAZH,WAgBC;EAEC,eAAA;;AAEA,WAJD,0BAIE;EACA,6BAAA;;AArBH,WAgBC,0BAQC;EACC,aAAA;;AAzBH,WA6BC;ECPC,YAAA;EACA,mBAAA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;ECsCA,sDAAA;EACQ,8CAAA;EFlCR,aAAA;EACA,kBAAA;;AAhCF,WA6BC,eAKC;ECFA,WAAA;EACA,SAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;ECyBA,sDAAA;EACQ,8CAAA;EAyHR,mCAAA;EACK,8BAAA;EACG,2BAAA;EFxJP,kBAAA;EACA,8BAAA;EACA,cAAA;;AAtCH,WA6BC,eAYC;EACC,yBAAA;;AA1CH,WA6BC,eAeC;EACC,yBAAA;;AA7CH,WA6BC,eAkBC;EACC,yBAAA;;AAhDH,WA6BC,eAqBC;EACC,yBAAA;;AAnDH,WA6BC,eAwBC;EACC,yBAAA;;AAtDH,WA6BC,eA2BC;EACC,yBAAA;;AAzDH,WA6BC,eA8BC;EACC,yBAAA;;AA5DH,WA6BC,eAkCC;EGwDA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;EACA,MAAM,gBAAN;EACA,SAAA;;AH9HF,WIAA;EACE,kBAAA;EACA,eAAA;EACA,cAAA;ECRA,aC6CwB,8CD7CxB;EAEA,kBAAA;EACA,gBAAA;EACA,uBAAA;EACA,gBAAA;EACA,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,iBAAA;EACA,oBAAA;EACA,sBAAA;EACA,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,mBAAA;EDHA,eAAA;EGTA,wBAAA;EACA,UAAA;;AHYA,WAXF,SAWG;EGbD,yBAAA;EACA,YAAA;;AHaA,WAZF,SAYG;EACC,cAAA;EACA,gBAAA;;AAEF,WAhBF,SAgBG;EACC,cAAA;EACA,gBAAA;;AAEF,WApBF,SAoBG;EACC,cAAA;EACA,eAAA;;AAEF,WAxBF,SAwBG;EACC,cAAA;EACA,iBAAA;;AAIF,WA9BF,SA8BG,IAAK;EACJ,SAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WArCF,SAqCG,SAAU;EACT,UAAA;EACA,SAAA;EACA,mBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WA5CF,SA4CG,UAAW;EACV,SAAA;EACA,SAAA;EACA,mBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,WAnDF,SAmDG,MAAO;EACN,QAAA;EACA,OAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;;AAEF,WA1DF,SA0DG,KAAM;EACL,QAAA;EACA,QAAA;EACA,gBAAA;EACA,2BAAA;EACA,0BAAA;;AAEF,WAjEF,SAiEG,OAAQ;EACP,MAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,WAxEF,SAwEG,YAAa;EACZ,MAAA;EACA,UAAA;EACA,gBAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,WA/EF,SA+EG,aAAc;EACb,MAAA;EACA,SAAA;EACA,gBAAA;EACA,uBAAA;EACA,4BAAA;;AJpFJ,WIyFA;EACE,gBAAA;EACA,gBAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,kBAAA;;AJ/FF,WImGA;EACE,kBAAA;EACA,QAAA;EACA,SAAA;EACA,yBAAA;EACA,mBAAA;;AJxGF,WAsEC,SAAQ;EOxEP,0BAAA;EACA,UAAA;;APCF,WA0EC;EACC,iBAAA;EACA,gBAAA;;AA5EF,WA0EC,eAIC;EACC,oBAAA","file":"laps.css"}
+4
View File
@@ -81,6 +81,10 @@
.tooltip-inner { .tooltip-inner {
line-height: 20px; line-height: 20px;
text-align: left; text-align: left;
br {
line-height: inherit;
}
} }
} }
+1 -1
View File
@@ -1 +1 @@
#wpadminbar #wp-admin-bar-laps .ab-sub-wrapper{min-width:90%;width:90%;position:fixed;left:5%}#wpadminbar #wp-admin-bar-laps .ab-submenu{padding:0}#wpadminbar #wp-admin-bar-laps_output{padding:0 10px}#wpadminbar #wp-admin-bar-laps_output:hover{background-color:transparent}#wpadminbar #wp-admin-bar-laps_output .ab-empty-item{display:none}#wpadminbar .laps-timeline{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);margin:6px 0;position:relative}#wpadminbar .laps-timeline .event{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#AAAAAA;-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;position:absolute;border-left:1px solid #f5f5f5;min-width:2px}#wpadminbar .laps-timeline .event-php{background-color:#B10DC9}#wpadminbar .laps-timeline .event-theme{background-color:#2ECC40}#wpadminbar .laps-timeline .event-plugin{background-color:#0074D9}#wpadminbar .laps-timeline .event-core{background-color:#FF851B}#wpadminbar .laps-timeline .event-sql-read{background-color:#7FDBFF}#wpadminbar .laps-timeline .event-sql-write{background-color:#FF4136}#wpadminbar .laps-timeline .event-http{background-color:#FF4136}#wpadminbar .laps-timeline .sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}#wpadminbar .tooltip{position:absolute;z-index:100000;display:block;font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}#wpadminbar .tooltip.in{opacity:0.9;filter:alpha(opacity=90)}#wpadminbar .tooltip.top{margin-top:-3px;padding:5px 0}#wpadminbar .tooltip.right{margin-left:3px;padding:0 5px}#wpadminbar .tooltip.bottom{margin-top:3px;padding:5px 0}#wpadminbar .tooltip.left{margin-left:-3px;padding:0 5px}#wpadminbar .tooltip-inner{max-width:300px;padding:3px 8px;color:#eee;text-align:center;background-color:#464646;border-radius:4px}#wpadminbar .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}#wpadminbar .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#464646}#wpadminbar .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#464646}#wpadminbar .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip.in{opacity:1;filter:alpha(opacity=100)}#wpadminbar .tooltip-inner{line-height:20px;text-align:left} #wpadminbar #wp-admin-bar-laps .ab-sub-wrapper{min-width:90%;width:90%;position:fixed;left:5%}#wpadminbar #wp-admin-bar-laps .ab-submenu{padding:0}#wpadminbar #wp-admin-bar-laps_output{padding:0 10px}#wpadminbar #wp-admin-bar-laps_output:hover{background-color:transparent}#wpadminbar #wp-admin-bar-laps_output .ab-empty-item{display:none}#wpadminbar .laps-timeline{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);margin:6px 0;position:relative}#wpadminbar .laps-timeline .event{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#AAAAAA;-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;position:absolute;border-left:1px solid #f5f5f5;min-width:2px}#wpadminbar .laps-timeline .event-php{background-color:#B10DC9}#wpadminbar .laps-timeline .event-theme{background-color:#2ECC40}#wpadminbar .laps-timeline .event-plugin{background-color:#0074D9}#wpadminbar .laps-timeline .event-core{background-color:#FF851B}#wpadminbar .laps-timeline .event-sql-read{background-color:#7FDBFF}#wpadminbar .laps-timeline .event-sql-write{background-color:#FF4136}#wpadminbar .laps-timeline .event-http{background-color:#FF4136}#wpadminbar .laps-timeline .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}#wpadminbar .tooltip{position:absolute;z-index:100000;display:block;font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}#wpadminbar .tooltip.in{filter:alpha(opacity=90);opacity:0.9}#wpadminbar .tooltip.top{padding:5px 0;margin-top:-3px}#wpadminbar .tooltip.right{padding:0 5px;margin-left:3px}#wpadminbar .tooltip.bottom{padding:5px 0;margin-top:3px}#wpadminbar .tooltip.left{padding:0 5px;margin-left:-3px}#wpadminbar .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#464646}#wpadminbar .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#464646}#wpadminbar .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#464646}#wpadminbar .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#464646}#wpadminbar .tooltip-inner{max-width:300px;padding:3px 8px;color:#eee;text-align:center;background-color:#464646;border-radius:4px}#wpadminbar .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}#wpadminbar .tooltip.in{filter:alpha(opacity=100);opacity:1}#wpadminbar .tooltip-inner{line-height:20px;text-align:left}#wpadminbar .tooltip-inner br{line-height:inherit}
+6 -6
View File
@@ -1,9 +1,9 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: tooltip.js v3.3.7 * Bootstrap: tooltip.js v3.4.0
* http://getbootstrap.com/javascript/#tooltip * https://getbootstrap.com/docs/3.4/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame * Inspired by the original jQuery.tipsy by Jason Frame
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -26,7 +26,7 @@
this.init('tooltip', element, options) this.init('tooltip', element, options)
} }
Tooltip.VERSION = '3.3.7' Tooltip.VERSION = '3.4.0'
Tooltip.TRANSITION_DURATION = 150 Tooltip.TRANSITION_DURATION = 150
@@ -51,7 +51,7 @@
this.type = type this.type = type
this.$element = $(element) this.$element = $(element)
this.options = this.getOptions(options) this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
this.inState = { click: false, hover: false, focus: false } this.inState = { click: false, hover: false, focus: false }
if (this.$element[0] instanceof document.constructor && !this.options.selector) { if (this.$element[0] instanceof document.constructor && !this.options.selector) {
@@ -204,7 +204,7 @@
.addClass(placement) .addClass(placement)
.data('bs.' + this.type, this) .data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
this.$element.trigger('inserted.bs.' + this.type) this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition() var pos = this.getPosition()
+2 -2
View File
@@ -1,5 +1,5 @@
+function(t){'use strict';var e=function(t,e){this.type=null;this.options=null;this.enabled=null;this.timeout=null;this.hoverState=null;this.$element=null;this.inState=null;this.init('tooltip',t,e)};e.VERSION='3.3.7';e.TRANSITION_DURATION=150;e.DEFAULTS={animation:!0,placement:'top',selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:'hover focus',title:'',delay:0,html:!1,container:!1,viewport:{selector:'body',padding:0}};e.prototype.init=function(e,i,o){this.enabled=!0;this.type=e;this.$element=t(i);this.options=this.getOptions(o);this.$viewport=this.options.viewport&&t(t.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):(this.options.viewport.selector||this.options.viewport));this.inState={click:!1,hover:!1,focus:!1};if(this.$element[0]instanceof document.constructor&&!this.options.selector){throw new Error('`selector` option must be specified when initializing '+this.type+' on the window.document object!')};var r=this.options.trigger.split(' ');for(var s=r.length;s--;){var n=r[s];if(n=='click'){this.$element.on('click.'+this.type,this.options.selector,t.proxy(this.toggle,this))} +function(t){'use strict';var e=function(t,e){this.type=null;this.options=null;this.enabled=null;this.timeout=null;this.hoverState=null;this.$element=null;this.inState=null;this.init('tooltip',t,e)};e.VERSION='3.4.0';e.TRANSITION_DURATION=150;e.DEFAULTS={animation:!0,placement:'top',selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:'hover focus',title:'',delay:0,html:!1,container:!1,viewport:{selector:'body',padding:0}};e.prototype.init=function(e,i,o){this.enabled=!0;this.type=e;this.$element=t(i);this.options=this.getOptions(o);this.$viewport=this.options.viewport&&t(document).find(t.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):(this.options.viewport.selector||this.options.viewport));this.inState={click:!1,hover:!1,focus:!1};if(this.$element[0]instanceof document.constructor&&!this.options.selector){throw new Error('`selector` option must be specified when initializing '+this.type+' on the window.document object!')};var r=this.options.trigger.split(' ');for(var s=r.length;s--;){var n=r[s];if(n=='click'){this.$element.on('click.'+this.type,this.options.selector,t.proxy(this.toggle,this))}
else if(n!='manual'){var l=n=='hover'?'mouseenter':'focusin',a=n=='hover'?'mouseleave':'focusout';this.$element.on(l+'.'+this.type,this.options.selector,t.proxy(this.enter,this));this.$element.on(a+'.'+this.type,this.options.selector,t.proxy(this.leave,this))}};this.options.selector?(this._options=t.extend({},this.options,{trigger:'manual',selector:''})):this.fixTitle()};e.prototype.getDefaults=function(){return e.DEFAULTS};e.prototype.getOptions=function(e){e=t.extend({},this.getDefaults(),this.$element.data(),e);if(e.delay&&typeof e.delay=='number'){e.delay={show:e.delay,hide:e.delay}};return e};e.prototype.getDelegateOptions=function(){var e={};var i=this.getDefaults();this._options&&t.each(this._options,function(t,o){if(i[t]!=o)e[t]=o});return e};e.prototype.enter=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)};if(e instanceof t.Event){i.inState[e.type=='focusin'?'focus':'hover']=!0};if(i.tip().hasClass('in')||i.hoverState=='in'){i.hoverState='in';return};clearTimeout(i.timeout);i.hoverState='in';if(!i.options.delay||!i.options.delay.show)return i.show();i.timeout=setTimeout(function(){if(i.hoverState=='in')i.show()},i.options.delay.show)};e.prototype.isInStateTrue=function(){for(var t in this.inState){if(this.inState[t])return!0};return!1};e.prototype.leave=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)};if(e instanceof t.Event){i.inState[e.type=='focusout'?'focus':'hover']=!1};if(i.isInStateTrue())return;clearTimeout(i.timeout);i.hoverState='out';if(!i.options.delay||!i.options.delay.hide)return i.hide();i.timeout=setTimeout(function(){if(i.hoverState=='out')i.hide()},i.options.delay.hide)};e.prototype.show=function(){var c=t.Event('show.bs.'+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(c);var v=t.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(c.isDefaultPrevented()||!v)return;var n=this,o=this.tip(),u=this.getUID(this.type);this.setContent();o.attr('id',u);this.$element.attr('aria-describedby',u);if(this.options.animation)o.addClass('fade');var i=typeof this.options.placement=='function'?this.options.placement.call(this,o[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,f=h.test(i);if(f)i=i.replace(h,'')||'top';o.detach().css({top:0,left:0,display:'block'}).addClass(i).data('bs.'+this.type,this);this.options.container?o.appendTo(this.options.container):o.insertAfter(this.$element);this.$element.trigger('inserted.bs.'+this.type);var s=this.getPosition(),l=o[0].offsetWidth,a=o[0].offsetHeight;if(f){var m=i,r=this.getPosition(this.$viewport);i=i=='bottom'&&s.bottom+a>r.bottom?'top':i=='top'&&s.top-a<r.top?'bottom':i=='right'&&s.right+l>r.width?'left':i=='left'&&s.left-l<r.left?'right':i;o.removeClass(m).addClass(i)};var d=this.getCalculatedOffset(i,s,l,a);this.applyPlacement(d,i);var p=function(){var t=n.hoverState;n.$element.trigger('shown.bs.'+n.type);n.hoverState=null;if(t=='out')n.leave(n)};t.support.transition&&this.$tip.hasClass('fade')?o.one('bsTransitionEnd',p).emulateTransitionEnd(e.TRANSITION_DURATION):p()}};e.prototype.applyPlacement=function(e,o){var i=this.tip(),c=i[0].offsetWidth,l=i[0].offsetHeight,a=parseInt(i.css('margin-top'),10),p=parseInt(i.css('margin-left'),10);if(isNaN(a))a=0;if(isNaN(p))p=0;e.top+=a;e.left+=p;t.offset.setOffset(i[0],t.extend({using:function(t){i.css({top:Math.round(t.top),left:Math.round(t.left)})}},e),0);i.addClass('in');var h=i[0].offsetWidth,s=i[0].offsetHeight;if(o=='top'&&s!=l){e.top=e.top+l-s};var n=this.getViewportAdjustedDelta(o,e,h,s);if(n.left)e.left+=n.left;else e.top+=n.top;var r=/top|bottom/.test(o),f=r?n.left*2-c+h:n.top*2-l+s,u=r?'offsetWidth':'offsetHeight';i.offset(e);this.replaceArrow(f,i[0][u],r)};e.prototype.replaceArrow=function(t,e,i){this.arrow().css(i?'left':'top',50*(1-t/e)+'%').css(i?'top':'left','')};e.prototype.setContent=function(){var t=this.tip(),e=this.getTitle();t.find('.tooltip-inner')[this.options.html?'html':'text'](e);t.removeClass('fade in top bottom left right')};e.prototype.hide=function(i){var o=this,n=t(this.$tip),s=t.Event('hide.bs.'+this.type);function r(){if(o.hoverState!='in')n.detach();if(o.$element){o.$element.removeAttr('aria-describedby').trigger('hidden.bs.'+o.type)};i&&i()};this.$element.trigger(s);if(s.isDefaultPrevented())return;n.removeClass('in');t.support.transition&&n.hasClass('fade')?n.one('bsTransitionEnd',r).emulateTransitionEnd(e.TRANSITION_DURATION):r();this.hoverState=null;return this};e.prototype.fixTitle=function(){var t=this.$element;if(t.attr('title')||typeof t.attr('data-original-title')!='string'){t.attr('data-original-title',t.attr('title')||'').attr('title','')}};e.prototype.hasContent=function(){return this.getTLine truncated else if(n!='manual'){var l=n=='hover'?'mouseenter':'focusin',a=n=='hover'?'mouseleave':'focusout';this.$element.on(l+'.'+this.type,this.options.selector,t.proxy(this.enter,this));this.$element.on(a+'.'+this.type,this.options.selector,t.proxy(this.leave,this))}};this.options.selector?(this._options=t.extend({},this.options,{trigger:'manual',selector:''})):this.fixTitle()};e.prototype.getDefaults=function(){return e.DEFAULTS};e.prototype.getOptions=function(e){e=t.extend({},this.getDefaults(),this.$element.data(),e);if(e.delay&&typeof e.delay=='number'){e.delay={show:e.delay,hide:e.delay}};return e};e.prototype.getDelegateOptions=function(){var e={};var i=this.getDefaults();this._options&&t.each(this._options,function(t,o){if(i[t]!=o)e[t]=o});return e};e.prototype.enter=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)};if(e instanceof t.Event){i.inState[e.type=='focusin'?'focus':'hover']=!0};if(i.tip().hasClass('in')||i.hoverState=='in'){i.hoverState='in';return};clearTimeout(i.timeout);i.hoverState='in';if(!i.options.delay||!i.options.delay.show)return i.show();i.timeout=setTimeout(function(){if(i.hoverState=='in')i.show()},i.options.delay.show)};e.prototype.isInStateTrue=function(){for(var t in this.inState){if(this.inState[t])return!0};return!1};e.prototype.leave=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)};if(e instanceof t.Event){i.inState[e.type=='focusout'?'focus':'hover']=!1};if(i.isInStateTrue())return;clearTimeout(i.timeout);i.hoverState='out';if(!i.options.delay||!i.options.delay.hide)return i.hide();i.timeout=setTimeout(function(){if(i.hoverState=='out')i.hide()},i.options.delay.hide)};e.prototype.show=function(){var c=t.Event('show.bs.'+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(c);var v=t.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(c.isDefaultPrevented()||!v)return;var n=this,o=this.tip(),u=this.getUID(this.type);this.setContent();o.attr('id',u);this.$element.attr('aria-describedby',u);if(this.options.animation)o.addClass('fade');var i=typeof this.options.placement=='function'?this.options.placement.call(this,o[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,f=h.test(i);if(f)i=i.replace(h,'')||'top';o.detach().css({top:0,left:0,display:'block'}).addClass(i).data('bs.'+this.type,this);this.options.container?o.appendTo(t(document).find(this.options.container)):o.insertAfter(this.$element);this.$element.trigger('inserted.bs.'+this.type);var s=this.getPosition(),l=o[0].offsetWidth,a=o[0].offsetHeight;if(f){var m=i,r=this.getPosition(this.$viewport);i=i=='bottom'&&s.bottom+a>r.bottom?'top':i=='top'&&s.top-a<r.top?'bottom':i=='right'&&s.right+l>r.width?'left':i=='left'&&s.left-l<r.left?'right':i;o.removeClass(m).addClass(i)};var d=this.getCalculatedOffset(i,s,l,a);this.applyPlacement(d,i);var p=function(){var t=n.hoverState;n.$element.trigger('shown.bs.'+n.type);n.hoverState=null;if(t=='out')n.leave(n)};t.support.transition&&this.$tip.hasClass('fade')?o.one('bsTransitionEnd',p).emulateTransitionEnd(e.TRANSITION_DURATION):p()}};e.prototype.applyPlacement=function(e,o){var i=this.tip(),c=i[0].offsetWidth,l=i[0].offsetHeight,a=parseInt(i.css('margin-top'),10),p=parseInt(i.css('margin-left'),10);if(isNaN(a))a=0;if(isNaN(p))p=0;e.top+=a;e.left+=p;t.offset.setOffset(i[0],t.extend({using:function(t){i.css({top:Math.round(t.top),left:Math.round(t.left)})}},e),0);i.addClass('in');var h=i[0].offsetWidth,s=i[0].offsetHeight;if(o=='top'&&s!=l){e.top=e.top+l-s};var n=this.getViewportAdjustedDelta(o,e,h,s);if(n.left)e.left+=n.left;else e.top+=n.top;var r=/top|bottom/.test(o),f=r?n.left*2-c+h:n.top*2-l+s,u=r?'offsetWidth':'offsetHeight';i.offset(e);this.replaceArrow(f,i[0][u],r)};e.prototype.replaceArrow=function(t,e,i){this.arrow().css(i?'left':'top',50*(1-t/e)+'%').css(i?'top':'left','')};e.prototype.setContent=function(){var t=this.tip(),e=this.getTitle();t.find('.tooltip-inner')[this.options.html?'html':'text'](e);t.removeClass('fade in top bottom left right')};e.prototype.hide=function(i){var o=this,n=t(this.$tip),s=t.Event('hide.bs.'+this.type);function r(){if(o.hoverState!='in')n.detach();if(o.$element){o.$element.removeAttr('aria-describedby').trigger('hidden.bs.'+o.type)};i&&i()};this.$element.trigger(s);if(s.isDefaultPrevented())return;n.removeClass('in');t.support.transition&&n.hasClass('fade')?n.one('bsTransitionEnd',r).emulateTransitionEnd(e.TRANSITION_DURATION):r();this.hoverState=null;return this};e.prototype.fixTitle=function(){var t=this.$element;if(t.attr('title')||typeof t.attr('data-original-title')!='string'){t.attr('data-original-title',t.attr('title')||'').attr('title','')}};e.prototype.hasContent=function(Line truncated
else if(h>e.top+e.height){o.top=e.top+e.height-h}} else if(h>e.top+e.height){o.top=e.top+e.height-h}}
else{var l=i.left-s,a=i.left+s+n;if(l<e.left){o.left=e.left-l} else{var l=i.left-s,a=i.left+s+n;if(l<e.left){o.left=e.left-l}
else if(a>e.right){o.left=e.left+e.width-a}};return o};e.prototype.getTitle=function(){var e,i=this.$element,t=this.options;e=i.attr('data-original-title')||(typeof t.title=='function'?t.title.call(i[0]):t.title);return e};e.prototype.getUID=function(t){do t+=~~(Math.random()*1000000);while(document.getElementById(t))return t};e.prototype.tip=function(){if(!this.$tip){this.$tip=t(this.options.template);if(this.$tip.length!=1){throw new Error(this.type+' `template` option must consist of exactly 1 top-level element!')}};return this.$tip};e.prototype.arrow=function(){return(this.$arrow=this.$arrow||this.tip().find('.tooltip-arrow'))};e.prototype.enable=function(){this.enabled=!0};e.prototype.disable=function(){this.enabled=!1};e.prototype.toggleEnabled=function(){this.enabled=!this.enabled};e.prototype.toggle=function(e){var i=this;if(e){i=t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)}};if(e){i.inState.click=!i.inState.click;if(i.isInStateTrue())i.enter(i);else i.leave(i)} else if(a>e.right){o.left=e.left+e.width-a}};return o};e.prototype.getTitle=function(){var e,i=this.$element,t=this.options;e=i.attr('data-original-title')||(typeof t.title=='function'?t.title.call(i[0]):t.title);return e};e.prototype.getUID=function(t){do t+=~~(Math.random()*1000000);while(document.getElementById(t))return t};e.prototype.tip=function(){if(!this.$tip){this.$tip=t(this.options.template);if(this.$tip.length!=1){throw new Error(this.type+' `template` option must consist of exactly 1 top-level element!')}};return this.$tip};e.prototype.arrow=function(){return(this.$arrow=this.$arrow||this.tip().find('.tooltip-arrow'))};e.prototype.enable=function(){this.enabled=!0};e.prototype.disable=function(){this.enabled=!1};e.prototype.toggleEnabled=function(){this.enabled=!this.enabled};e.prototype.toggle=function(e){var i=this;if(e){i=t(e.currentTarget).data('bs.'+this.type);if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)}};if(e){i.inState.click=!i.inState.click;if(i.isInStateTrue())i.enter(i);else i.leave(i)}
+19 -92
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
@@ -8,100 +9,26 @@ namespace Rarst\Laps\Event;
class Core_Events implements Hook_Event_Config_Interface { class Core_Events implements Hook_Event_Config_Interface {
/** /**
* @return array * @inheritDoc
*/ */
public function get_events() { public function get_events(): array {
$stops = []; return [
[ 'Plugins Load', 'plugin', '', 'plugins_loaded', - 1, - 2 ],
$stops['plugins_loaded'][-2] = [ [ 'Plugins Loaded Hook', 'plugin', 'plugins_loaded' ],
'action' => 'stop', [ 'Themes Load', 'theme', 'setup_theme', 'after_setup_theme' ],
'event' => 'Plugins Load', [ 'Init Hook', 'core', 'init' ],
'category' => 'plugin', [ 'WP Loaded Hook', 'core', 'wp_loaded' ],
[ 'Query Setup', 'core', 'wp_loaded', 'template_redirect', PHP_INT_MAX, -1 ],
[ 'Template Loader', 'theme', 'template_redirect', 'template_include' ],
[ 'Admin Init Hook', 'core', 'admin_init' ],
[ 'Admin Menu', 'core', '_admin_menu', 'admin_menu' ],
[ 'Admin Print Scripts', 'core', 'admin_enqueue_scripts', 'admin_print_scripts' ],
[ 'Head Hook', 'theme', 'wp_head' ],
[ 'Main Loop', 'theme', 'loop_start', 'loop_end' ],
[ 'Sidebar', 'theme', 'dynamic_sidebar_before', 'dynamic_sidebar_after' ],
[ 'Toolbar', 'core', 'admin_bar_menu', '' ],
[ 'Footer Hook', 'theme', 'wp_footer' ],
]; ];
$stops['plugins_loaded'][-1] = [
'event' => 'Plugins Loaded Hook',
'category' => 'plugin',
];
$stops['plugins_loaded'][20] = [
'action' => 'stop',
'event' => 'Plugins Loaded Hook',
'category' => 'plugin',
];
$stops['setup_theme'][0] = [
'event' => 'Themes Load',
'category' => 'theme',
];
$stops['after_setup_theme'][20] = [
'action' => 'stop',
'event' => 'Themes Load',
'category' => 'theme',
];
$stops['init'][-1] = [
'event' => 'Core Init',
'category' => 'core',
];
$stops['wp_loaded'][20] = [
'action' => 'stop',
'event' => 'Core Init',
'category' => 'core',
];
$stops['admin_init'][-1] = [
'event' => 'Core Admin Init',
'category' => 'core',
];
$stops['admin_init'][ PHP_INT_MAX - 1 ] = [
'action' => 'stop',
'event' => 'Core Admin Init',
'category' => 'core',
];
$stops['_admin_menu'][-1] = [
'event' => 'Admin Menu',
'category' => 'core',
];
$stops['admin_menu'][ PHP_INT_MAX - 1 ] = [
'action' => 'stop',
'event' => 'Admin Menu',
'category' => 'core',
];
$stops['admin_bar_menu'][-1] = [
'event' => 'Toolbar',
'category' => 'core',
];
$stops['loop_start'][10] = [
'event' => 'Main Loop',
'category' => 'theme',
];
$stops['loop_end'][10] = [
'action' => 'stop',
'event' => 'Main Loop',
'category' => 'theme',
];
$stops['admin_enqueue_scripts'][10] = [
'event' => 'Admin Print Scripts',
'category' => 'core',
];
$stops['admin_print_scripts'][10] = [
'action' => 'stop',
'event' => 'Admin Print Scripts',
'category' => 'core',
];
return $stops;
} }
} }
+52
View File
@@ -0,0 +1,52 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Events for third party themes and plugins.
*/
class Extension_Events implements Hook_Event_Config_Interface {
/**
* @inheritDoc
*/
public function get_events(): array {
return $this->beaver_builder();
}
/**
* Beaver Builder events.
*
* @return array[]
*/
private function beaver_builder(): array {
$plugin = class_exists( 'FLBuilderLoader' ) ? [
[
'Beaver Builder Post Content',
'plugin',
'fl_builder_before_render_content',
'fl_builder_after_render_content',
],
[
'Beaver Builder Module',
'plugin',
'fl_builder_before_render_module',
'fl_builder_after_render_module',
],
] : [];
$theme = class_exists( 'FLTheme' ) ? [
[ 'Beaver Builder Top Bar', 'theme', 'fl_before_top_bar', 'fl_after_top_bar' ],
[ 'Beaver Builder Header', 'theme', 'fl_before_header', 'fl_after_header' ],
[ 'Beaver Builder Page Content', 'theme', 'fl_before_content', 'fl_after_content' ],
[ 'Beaver Builder Post', 'theme', 'fl_before_post', 'fl_after_post' ],
[ 'Beaver Builder Footer Widgets', 'theme', 'fl_before_footer_widgets', 'fl_after_footer_widgets' ],
[ 'Beaver Builder Footer', 'theme', 'fl_before_footer', 'fl_after_footer' ],
] : [];
return array_merge( $plugin, $theme );
}
}
+15 -42
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
@@ -6,53 +7,25 @@ namespace Rarst\Laps\Event;
* Events for Genesis Framework based themes * Events for Genesis Framework based themes
* *
* @link http://my.studiopress.com/themes/genesis/ * @link http://my.studiopress.com/themes/genesis/
*
* @deprecated 3.2:4.0 Events going to be moved to Extension_Events class.
*/ */
class Genesis_Events implements Hook_Event_Config_Interface { class Genesis_Events implements Hook_Event_Config_Interface {
/** /**
* @return array * @inheritDoc
*/ */
public function get_events() { public function get_events(): array {
$events = []; return \function_exists( 'genesis' ) ? [
[ 'Header', 'theme', 'genesis_before_header', 'genesis_after_header' ],
if ( ! function_exists( 'genesis' ) ) { [ 'Sidebar', 'theme', 'genesis_before_sidebar_widget_area', 'genesis_after_sidebar_widget_area' ],
return $events; [
} 'Sidebar (alternate)',
'theme',
$events['genesis_before_header'][10] = [ 'genesis_before_sidebar_alt_widget_area',
'event' => 'Header', 'genesis_after_sidebar_alt_widget_area',
'category' => 'theme', ],
]; ] : [];
$events['genesis_after_header'][10] = [
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
];
$events['genesis_before_sidebar_widget_area'][10] = [
'event' => 'Sidebar',
'category' => 'theme',
];
$events['genesis_after_sidebar_widget_area'][10] = [
'action' => 'stop',
'event' => 'Sidebar',
'category' => 'theme',
];
$events['genesis_before_sidebar_alt_widget_area'][10] = [
'event' => 'Sidebar (alternate)',
'category' => 'theme',
];
$events['genesis_after_sidebar_alt_widget_area'][10] = [
'action' => 'stop',
'event' => 'Sidebar (alternate)',
'category' => 'theme',
];
return $events;
} }
} }
+10 -2
View File
@@ -1,11 +1,19 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
use Rarst\Laps\Record\Collector\Hook_Collector;
/**
* Interface to retrieve configuration array of hook events to track.
*/
interface Hook_Event_Config_Interface { interface Hook_Event_Config_Interface {
/** /**
* @return array * @see Hook_Collector::add_event()
*
* @return array[]
*/ */
public function get_events(); public function get_events(): array;
} }
+16 -22
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
@@ -6,47 +7,40 @@ namespace Rarst\Laps\Event;
* Events for Hybrid Core based themes * Events for Hybrid Core based themes
* *
* @link http://themehybrid.com/hybrid-core * @link http://themehybrid.com/hybrid-core
*
* @deprecated 3.2:4.0 Events going to be moved to Extension_Events class.
*/ */
class Hybrid_Events implements Hook_Event_Config_Interface { class Hybrid_Events implements Hook_Event_Config_Interface {
/** /**
* @return array * @inheritDoc
*/ */
public function get_events() { public function get_events(): array {
$events = []; $events = [];
if ( ! function_exists( 'hybrid_get_prefix' ) ) { if ( ! \function_exists( 'hybrid_get_prefix' ) ) {
return $events; return $events;
} }
/** @var string $prefix */
$prefix = hybrid_get_prefix(); $prefix = hybrid_get_prefix();
$events["{$prefix}_before_header"][10] = [ $events = [
'event' => 'Header', [ 'Header', 'theme', "{$prefix}_before_header", "{$prefix}_after_header" ],
'category' => 'theme',
];
$events["{$prefix}_after_header"][10] = [
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
]; ];
/** @var array $sidebars */
$sidebars = get_theme_support( 'hybrid-core-sidebars' ); $sidebars = get_theme_support( 'hybrid-core-sidebars' );
if ( ! empty( $sidebars[0] ) ) { if ( ! empty( $sidebars[0] ) ) {
/** @var string $sidebar */
foreach ( $sidebars[0] as $sidebar ) { foreach ( $sidebars[0] as $sidebar ) {
$events[] = [
$events["{$prefix}_before_{$sidebar}"][10] = [ "Sidebar ({$sidebar})",
'event' => "Sidebar ({$sidebar})", 'theme',
'category' => 'theme', "{$prefix}_before_{$sidebar}",
]; "{$prefix}_after_{$sidebar}",
$events["{$prefix}_after_{$sidebar}"][10] = [
'action' => 'stop',
'event' => "Sidebar ({$sidebar})",
'category' => 'theme',
]; ];
} }
} }
+9 -31
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
@@ -6,42 +7,19 @@ namespace Rarst\Laps\Event;
* Events for Theme Hook Alliance * Events for Theme Hook Alliance
* *
* @link http://zamoose.github.io/themehookalliance/ * @link http://zamoose.github.io/themehookalliance/
*
* @deprecated 3.2:4.0 Events going to be moved to Extension_Events class.
*/ */
class THA_Events implements Hook_Event_Config_Interface { class THA_Events implements Hook_Event_Config_Interface {
/** /**
* @return array * @inheritDoc
*/ */
public function get_events() { public function get_events(): array {
$events = []; return \defined( 'THA_HOOKS_VERSION' ) ? [
[ 'Header', 'theme', 'tha_header_before', 'tha_header_after' ],
if ( ! defined( 'THA_HOOKS_VERSION' ) ) { [ 'Sidebars', 'theme', 'tha_sidebars_before', 'tha_sidebars_after' ],
return $events; ] : [];
}
$events['tha_header_before'][10] = [
'event' => 'Header',
'category' => 'theme',
];
$events['tha_header_after'][10] = [
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
];
$events['tha_sidebars_before'][10] = [
'event' => 'Sidebars',
'category' => 'theme',
];
$events['tha_sidebars_after'][10] = [
'action' => 'stop',
'event' => 'Sidebars',
'category' => 'theme',
];
return $events;
} }
} }
+27
View File
@@ -0,0 +1,27 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Events for WooCommerce plugin
*
* @deprecated 3.2:4.0 Events going to be moved to Extension_Events class.
*/
class WooCommerce_Events implements Hook_Event_Config_Interface {
/**
* @inheritDoc
*/
public function get_events(): array {
return class_exists( 'WooCommerce' ) ? [
[ 'WooCommerce Init', 'plugin', 'before_woocommerce_init', 'woocommerce_init' ],
[ 'WooCommerce Shop Loop', 'plugin', 'woocommerce_before_shop_loop', 'woocommerce_after_shop_loop' ],
[ 'WooCommerce Shop Item', 'plugin', 'woocommerce_before_shop_loop_item', 'woocommerce_after_shop_loop_item' ],
[ 'WooCommerce Single Product', 'plugin', 'woocommerce_before_single_product', 'woocommerce_after_single_product' ],
[ 'WooCommerce Cart', 'plugin', 'woocommerce_before_cart', 'woocommerce_after_cart' ],
[ 'WooCommerce Checkout', 'plugin', 'woocommerce_before_checkout_form', 'woocommerce_after_checkout_form' ],
] : [];
}
}
+8 -20
View File
@@ -1,34 +1,22 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Event; namespace Rarst\Laps\Event;
/** /**
* Events for Yoast plugins * Events for Yoast plugins
*
* @deprecated 3.2:4.0 Events going to be moved to Extension_Events class.
*/ */
class Yoast_Events implements Hook_Event_Config_Interface { class Yoast_Events implements Hook_Event_Config_Interface {
/** /**
* @return array * @inheritDoc
*/ */
public function get_events() { public function get_events(): array {
$stops = []; return class_exists( 'WPSEO_Frontend' ) ? [
[ 'WP SEO head hook', 'plugin', 'wpseo_head' ],
if ( ! class_exists( 'WPSEO_Frontend' ) ) { ] : [];
return $stops;
}
$stops['wpseo_head'][1] = [
'event' => 'WP SEO head hook',
'category' => 'plugin',
];
$stops['wpseo_head'][100] = [
'action' => 'stop',
'event' => 'WP SEO head hook',
'category' => 'plugin',
];
return $stops;
} }
} }
+103
View File
@@ -0,0 +1,103 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Formatter;
/**
* Formatter to adjust WP backtrace output to be more short and concise.
*/
class Backtrace_Formatter {
/** @var string[] $skip String matches to omit. */
protected static $skip = [
'wp-blog-header.php',
'wp-load.php',
'wp-config.php',
'admin.php',
'template-loader.php',
'locate_template',
'load_template',
'_wp_get_current_user',
'WP_Hook',
];
/** @var array $truncate_paths Paths to truncate from includes. */
protected $truncate_paths = [];
/**
* Set up object properties.
*/
public function __construct() {
$this->truncate_paths = [
wp_normalize_path( WP_CONTENT_DIR ),
wp_normalize_path( ABSPATH ),
'wp-admin/',
'themes/',
'plugins/',
];
}
/**
* @param array|string $backtrace Backtrace input as produced by WP.
*
* @return array
*/
public function format( $backtrace ): array {
if ( \is_string( $backtrace ) ) {
$backtrace = explode( ', ', $backtrace );
} else {
$backtrace = array_reverse( $backtrace );
}
$backtrace = array_filter( $backtrace, [ $this, 'filter' ] );
$backtrace = array_map( [ $this, 'shorten_include' ], $backtrace );
// TODO compact redunant function sequences, such as transient into option calls.
return $backtrace;
}
/**
* @param string $item Backtrace item.
*
* @return bool Keep or drop.
*/
protected function filter( string $item ): bool {
foreach ( self::$skip as $match ) {
if ( false !== strpos( $item, $match ) ) {
return false;
}
}
return true;
}
/**
*
* @psalm-suppress PossiblyFalseOperand
*
* @param string $item Backtrace item.
*
* @return string
*/
protected function shorten_include( string $item ): string {
if ( 0 === strpos( $item, 'include' ) || 0 === strpos( $item, 'require' ) ) {
$path = substr( $item, strpos( $item, '(\'' ) + 2, - 2 );
$path = wp_normalize_path( $path );
$path = str_replace( $this->truncate_paths, '', $path );
if ( ':' === $path[1] ) {
$path = substr( $path, 2 );
}
$path = ltrim( $path, '/' );
return $path;
}
return $item;
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Formatter;
/**
* Formatter to process callbacks from a hook instance.
*/
class Hook_Formatter {
/** @var array $truncate_paths Paths to truncate from includes. */
protected $truncate_paths = [];
/**
* Set up object properties.
*/
public function __construct() {
// TODO inject paths instead of reaching for global constants.
$this->truncate_paths = [
wp_normalize_path( WP_CONTENT_DIR ),
wp_normalize_path( ABSPATH ),
'wp-admin/',
];
}
/**
* @psalm-type Callback = string|object|array
* @psalm-type AcceptedArgs = int
* @psalm-type Hook = array{function: Callback, accepted_args: AcceptedArgs}
* @psalm-type Priority = array-key
* @psalm-type Hooks = array<Priority, array<Hook>>
*
* @param \WP_Hook|array $hook Hook instance.
* @psalm-param \WP_Hook|Hooks $hook
*
* @return array
*/
public function format( $hook ): array {
$callbacks = [];
if ( $hook instanceof \WP_Hook ) {
/** @psalm-var Hooks $hook->callbacks */
$hook = $hook->callbacks;
}
if ( empty( $hook ) ) {
return $callbacks;
}
ksort( $hook );
$functions = array_merge( ...$hook );
foreach ( $functions as $function ) {
$callback = $this->get_callback_name( $function['function'], $function['accepted_args'] );
if ( false !== strpos( $callback, 'Hook_Collector' ) ) {
continue;
}
$callbacks[] = $callback;
}
return $callbacks;
}
/**
* @param string|object|array $callback Hook callback.
* @param int $args Number of accepted arguments.
*/
protected function get_callback_name( $callback, int $args ): string {
switch ( gettype( $callback ) ) {
case 'object':
$name = $this->get_class_name( $callback );
break;
case 'array':
/** @psalm-var array{0: object|string, 1: string} $callback */
$class = is_string( $callback[0] )
? $callback[0] . '::'
: $this->get_class_name( $callback[0] ) . '->';
$name = $class . $callback[1];
break;
default:
$name = $callback;
}
return $name . ( ( 1 === $args ) ? '' : "({$args})" );
}
/**
* @param object $object Object to retrieve name for.
*
* @return string
*/
protected function get_class_name( $object ): string {
if ( $object instanceof \Closure ) {
$class = new \ReflectionFunction( $object );
return 'closure from ' . $this->shorten_path( $class->getFileName() ) . ':' . $class->getStartLine();
}
$class = new \ReflectionClass( $object );
$name = $class->getName();
if ( 0 === strpos( $name, 'class@anonymous' ) ) {
return 'anonymous class from ' . $this->shorten_path( $class->getFileName() ) . ':' . $class->getStartLine();
}
return $name;
}
/**
* @param string $path Path to shorten.
*
* @return string
*/
protected function shorten_path( string $path ): string {
$path = wp_normalize_path( $path );
$path = str_replace( $this->truncate_paths, '', $path );
if ( ':' === $path[1] ) {
$path = substr( $path, 2 );
}
$path = ltrim( $path, '/' );
return $path;
}
}
+5 -4
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager; namespace Rarst\Laps\Manager;
@@ -19,13 +20,13 @@ class Asset_Manager {
/** /**
* Registers assets and queues as necessary. * Registers assets and queues as necessary.
*/ */
public function enqueue_scripts() { public function enqueue_scripts(): void {
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; $suffix = ( \defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
wp_register_script( 'laps', plugins_url( "public/js/laps{$suffix}.js", dirname( __DIR__ ) ), [ 'jquery' ], '3.3.1', true ); wp_register_script( 'laps', plugins_url( "public/js/laps{$suffix}.js", dirname( __DIR__ ) ), [ 'jquery' ], '3.4.0', true );
wp_register_style( 'laps', plugins_url( "public/css/laps{$suffix}.css", dirname( __DIR__ ) ) ); wp_register_style( 'laps', plugins_url( "public/css/laps{$suffix}.css", dirname( __DIR__ ) ) );
if ( is_admin_bar_showing() ) { if ( is_admin_bar_showing() && apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
wp_enqueue_script( 'laps' ); wp_enqueue_script( 'laps' );
wp_enqueue_style( 'laps' ); wp_enqueue_style( 'laps' );
} }
+3 -4
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager; namespace Rarst\Laps\Manager;
@@ -20,12 +21,10 @@ class Load_Order_Manager {
* Reorder active plugins so Laps is first and starts timing load early. * Reorder active plugins so Laps is first and starts timing load early.
* *
* @param array $plugins Filtered array of active plugins’ basenames. * @param array $plugins Filtered array of active plugins’ basenames.
*
* @return array
*/ */
public function pre_update_option_active_plugins( $plugins ) { public function pre_update_option_active_plugins( array $plugins ): array {
$plugin = plugin_basename( dirname( dirname( __DIR__ ) ) . '/laps.php' ); $plugin = plugin_basename( \dirname( __DIR__, 2 ) . '/laps.php' );
$key = array_search( $plugin, $plugins, true ); $key = array_search( $plugin, $plugins, true );
if ( false !== $key && $key > 0 ) { if ( false !== $key && $key > 0 ) {
+76
View File
@@ -0,0 +1,76 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager;
use Rarst\Laps\Plugin;
use Rarst\Laps\Record\Collector\Record_Collector_Interface;
use Rarst\Laps\Record\Record_Interface;
/**
* Implements Server Timing API headers.
*/
class Server_Timing_Manager {
/** @var Record_Collector_Interface */
private $collector;
/**
* @param Record_Collector_Interface $collector Collector of all records.
*/
public function __construct( Record_Collector_Interface $collector ) {
$this->collector = $collector;
add_action( 'admin_init', [ $this, 'send_timing_header' ], PHP_INT_MAX );
add_action( 'rest_pre_serve_request', [ $this, 'send_timing_header' ] );
}
/**
* Sends Server Timing API header with records data.
*
* @param mixed $input Pass-through input for attaching to a filter hook.
*
* @return mixed
*/
public function send_timing_header( $input ) {
if ( headers_sent() ) {
return $input;
}
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
$rest = defined( 'REST_REQUEST' ) && REST_REQUEST;
if ( ! $ajax && ! $rest ) {
return $input;
}
// This will be false in REST API w/o nonce, even with valid cookie. Might make sense to filter to true in dev.
if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
return $input;
}
$header = '';
/** @var Record_Interface $record */
foreach ( $this->collector->get_records() as $record ) {
$duration = $record->get_duration() * 1000;
if ( $duration < 1 ) { // Filter out less than 1ms events as too noisy.
continue;
}
$header .= sprintf( '%s;dur=%.2f;desc="%s", ', $record->get_category(), $duration, $record->get_name() );
if ( strlen( $header ) > 2000 ) { // 8+KB headers break proxies, 4+KB nginx, so we cut off at 1/2 that limit.
break;
}
}
// Sanitize to visible US ASCII character range (32-126).
header( 'Server-Timing: ' . preg_replace( '/[^\x20-\x7E]/', '', $header ) );
return $input;
}
}
+20 -12
View File
@@ -1,27 +1,35 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager; namespace Rarst\Laps\Manager;
use Rarst\Laps\Record\Recursive_Record_Iterator; use Mustache_Engine;
use Rarst\Laps\Record\Collector\Record_Collector_Interface;
use Rarst\Laps\Record\Iterator\Recursive_Record_Iterator;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
use Rarst\Laps\Record\Timeline_Iterator; use Rarst\Laps\Record\Iterator\Timeline_Iterator;
/** /**
* Implements toolbar menu and visualization. * Implements toolbar menu and visualization.
*/ */
class Toolbar_Manager { class Toolbar_Manager {
/** @var Plugin $laps */ /** @var Record_Collector_Interface $collector */
protected $laps; protected $collector;
/** @var Mustache_Engine */
private $mustache;
/** /**
* @param Plugin $laps Container instance. * @param Record_Collector_Interface $collector Collector of all records..
* @param Mustache_Engine $mustache Mustache instance.
*/ */
public function __construct( Plugin $laps ) { public function __construct( Record_Collector_Interface $collector, Mustache_Engine $mustache ) {
$this->laps = $laps; $this->collector = $collector;
$this->mustache = $mustache;
add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ], 100 ); add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ], 1000 );
} }
/** /**
@@ -29,7 +37,7 @@ class Toolbar_Manager {
* *
* @param \WP_Admin_Bar $wp_admin_bar WordPress core toolbar object. * @param \WP_Admin_Bar $wp_admin_bar WordPress core toolbar object.
*/ */
public function admin_bar_menu( $wp_admin_bar ) { public function admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ): void {
if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) { if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
return; return;
@@ -45,9 +53,9 @@ class Toolbar_Manager {
$wp_admin_bar->add_node( [ $wp_admin_bar->add_node( [
'id' => 'laps_output', 'id' => 'laps_output',
'parent' => 'laps', 'parent' => 'laps',
'meta' => [ 'meta' => [ // TODO consider doing render lazily, but might not be worth the effort.
'html' => $this->laps['mustache']->render( 'laps', [ 'html' => $this->mustache->render( 'laps', [
'timelines' => new Timeline_Iterator( new Recursive_Record_Iterator( $this->laps['records'] ) ), 'timelines' => new Timeline_Iterator( new Recursive_Record_Iterator( $this->collector->get_records() ) ),
] ), ] ),
], ],
] ); ] );
+3 -1
View File
@@ -1,4 +1,6 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps; namespace Rarst\Laps;
/** /**
@@ -14,7 +16,7 @@ class Mustache_Cache_FrozenCache extends \Mustache_Cache_FilesystemCache {
* *
* @return void * @return void
*/ */
public function cache( $key, $value ) { public function cache( $key, $value ): void {
$this->log( $this->log(
\Mustache_Logger::WARNING, \Mustache_Logger::WARNING,
+5 -7
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps; namespace Rarst\Laps;
@@ -19,6 +20,7 @@ class Plugin extends Container {
/** /**
* @param array $values Configuration values to apply. * @param array $values Configuration values to apply.
* @psalm-param array<string, mixed> $values
*/ */
public function __construct( array $values = [] ) { public function __construct( array $values = [] ) {
@@ -26,7 +28,7 @@ class Plugin extends Container {
$laps = $this; $laps = $this;
$laps['mustache'] = function () { $laps['mustache'] = function (): \Mustache_Engine {
return new \Mustache_Engine( [ return new \Mustache_Engine( [
'loader' => new \Mustache_Loader_FilesystemLoader( dirname( __DIR__ ) . '/src/mustache' ), 'loader' => new \Mustache_Loader_FilesystemLoader( dirname( __DIR__ ) . '/src/mustache' ),
'cache' => new Mustache_Cache_FrozenCache( dirname( __DIR__ ) . '/src/mustache/cache' ), 'cache' => new Mustache_Cache_FrozenCache( dirname( __DIR__ ) . '/src/mustache/cache' ),
@@ -48,7 +50,7 @@ class Plugin extends Container {
* *
* @return $this|static * @return $this|static
*/ */
public function register( ServiceProviderInterface $provider, array $values = [] ) { public function register( ServiceProviderInterface $provider, array $values = [] ): self {
$this->providers[] = $provider; $this->providers[] = $provider;
@@ -60,11 +62,7 @@ class Plugin extends Container {
/** /**
* Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks. * Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks.
*/ */
public function run() { public function run(): void {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
foreach ( $this->providers as $provider ) { foreach ( $this->providers as $provider ) {
+7 -2
View File
@@ -1,15 +1,20 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider; namespace Rarst\Laps\Provider;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
interface Bootable_Provider_Interface { /**
* Interface for providers that need to boot up once plugin is ready to start.
*/
interface Bootable_Provider_Interface extends ServiceProviderInterface {
/** /**
* @param Plugin $laps Container instance. * @param Plugin $laps Container instance.
* *
* @return void * @return void
*/ */
public function boot( Plugin $laps ); public function boot( Plugin $laps ): void;
} }
+14 -7
View File
@@ -1,13 +1,16 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider; namespace Rarst\Laps\Provider;
use Pimple\Container; use Pimple\Container;
use Pimple\ServiceProviderInterface; use Pimple\ServiceProviderInterface;
use Rarst\Laps\Event\Core_Events; use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Event\Extension_Events;
use Rarst\Laps\Event\Genesis_Events; use Rarst\Laps\Event\Genesis_Events;
use Rarst\Laps\Event\Hybrid_Events; use Rarst\Laps\Event\Hybrid_Events;
use Rarst\Laps\Event\THA_Events; use Rarst\Laps\Event\THA_Events;
use Rarst\Laps\Event\WooCommerce_Events;
use Rarst\Laps\Event\Yoast_Events; use Rarst\Laps\Event\Yoast_Events;
/** /**
@@ -17,16 +20,20 @@ class Hook_Event_Provider implements ServiceProviderInterface {
/** /**
* @param Container $pimple Container instance. * @param Container $pimple Container instance.
*
* @psalm-suppress DeprecatedClass
*/ */
public function register( Container $pimple ) { public function register( Container $pimple ): void {
$pimple['hook.events'] = function () { $pimple['hook.events'] = function (): array {
return [ return [
'core' => new Core_Events(), 'core' => new Core_Events(),
'genesis' => new Genesis_Events(), 'extension' => new Extension_Events(),
'hybrid' => new Hybrid_Events(), 'genesis' => new Genesis_Events(),
'tha' => new THA_Events(), 'hybrid' => new Hybrid_Events(),
'yoast' => new Yoast_Events(), 'tha' => new THA_Events(),
'woocommerce' => new WooCommerce_Events(),
'yoast' => new Yoast_Events(),
]; ];
}; };
} }
+7 -4
View File
@@ -1,9 +1,11 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider; namespace Rarst\Laps\Provider;
use Pimple\Container; use Pimple\Container;
use Pimple\ServiceProviderInterface; use Pimple\ServiceProviderInterface;
use Rarst\Laps\Manager\Server_Timing_Manager;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
use Rarst\Laps\Manager\Asset_Manager; use Rarst\Laps\Manager\Asset_Manager;
use Rarst\Laps\Manager\Load_Order_Manager; use Rarst\Laps\Manager\Load_Order_Manager;
@@ -17,13 +19,14 @@ class Manager_Provider implements ServiceProviderInterface, Bootable_Provider_In
/** /**
* @param Container $pimple Container instance. * @param Container $pimple Container instance.
*/ */
public function register( Container $pimple ) { public function register( Container $pimple ): void {
$pimple['managers'] = function ( Plugin $laps ) { $pimple['managers'] = function ( Plugin $laps ): array {
return [ return [
new Load_Order_Manager(), new Load_Order_Manager(),
new Asset_Manager(), new Asset_Manager(),
new Toolbar_Manager( $laps ), new Toolbar_Manager( $laps['records.lazy'], $laps['mustache'] ),
new Server_Timing_Manager( $laps['records.lazy'] ),
]; ];
}; };
} }
@@ -31,7 +34,7 @@ class Manager_Provider implements ServiceProviderInterface, Bootable_Provider_In
/** /**
* @param Plugin $laps Container instance. * @param Plugin $laps Container instance.
*/ */
public function boot( Plugin $laps ) { public function boot( Plugin $laps ): void {
$laps['managers']; $laps['managers'];
} }
} }
+21 -22
View File
@@ -1,15 +1,17 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider; namespace Rarst\Laps\Provider;
use Pimple\Container; use Pimple\Container;
use Pimple\ServiceProviderInterface; use Pimple\ServiceProviderInterface;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
use Rarst\Laps\Record\Core_Load_Collector; use Rarst\Laps\Record\Collector\Core_Load_Collector;
use Rarst\Laps\Record\Hook_Record_Collector; use Rarst\Laps\Record\Collector\Hook_Collector;
use Rarst\Laps\Record\Http_Record_Collector; use Rarst\Laps\Record\Collector\Http_Collector;
use Rarst\Laps\Record\Record_Collector_Interface; use Rarst\Laps\Record\Collector\Lazy_Proxy_Collector;
use Rarst\Laps\Record\Sql_Record_Collector; use Rarst\Laps\Record\Collector\Plugin_Load_Collector;
use Rarst\Laps\Record\Collector\Sql_Collector;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
/** /**
@@ -20,38 +22,35 @@ class Record_Provider implements ServiceProviderInterface, Bootable_Provider_Int
/** /**
* @param Container $pimple Container instance. * @param Container $pimple Container instance.
*/ */
public function register( Container $pimple ) { public function register( Container $pimple ): void {
$pimple['stopwatch'] = $pimple->factory( function () { $pimple['stopwatch'] = $pimple->factory( function (): Stopwatch {
return new Stopwatch( true ); return new Stopwatch( true );
} ); } );
$pimple['collectors'] = function ( Plugin $laps ) { $pimple['collectors'] = function ( Plugin $laps ): array {
return [ return [
'core' => new Core_Load_Collector(), 'core' => new Core_Load_Collector(),
'hook' => new Hook_Record_Collector( $laps['stopwatch'], $laps['hook.events'] ), 'hook' => new Hook_Collector( $laps['stopwatch'], $laps['hook.events'] ),
'http' => new Http_Record_Collector( $laps['stopwatch'] ), 'plugins' => new Plugin_Load_Collector(),
'sql' => new Sql_Record_Collector(), 'http' => new Http_Collector( $laps['stopwatch'] ),
'sql' => new Sql_Collector(),
]; ];
}; };
$pimple['records'] = function ( Plugin $laps ) { $pimple['records.lazy'] = function ( Plugin $laps ): Lazy_Proxy_Collector {
return new Lazy_Proxy_Collector( $laps['collectors'] );
};
$records = []; $pimple['records'] = function ( Plugin $laps ): array {
return $laps['records.lazy']->get_records();
/** @var Record_Collector_Interface $collector */
foreach ( $laps['collectors'] as $collector ) {
$records[] = $collector->get_records();
}
return array_merge( ...$records );
}; };
} }
/** /**
* @param Plugin $laps Container instance. * @param Plugin $laps Container instance.
*/ */
public function boot( Plugin $laps ) { public function boot( Plugin $laps ): void {
$laps['collectors']; $laps['collectors'];
} }
} }
@@ -0,0 +1,79 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Record_Interface;
/**
* Covers pre–plugin load time from available data.
*/
class Core_Load_Collector implements Record_Collector_Interface {
/** @var float $timeload */
protected $timeload;
/** @var bool Flag if Laps is network activated. */
private $network_activated;
/**
* Store plugin load start mark.
*/
public function __construct() {
$this->timeload = microtime( true );
$this->network_activated = ! did_action( 'muplugins_loaded' );
}
/**
* @return Record_Interface[]
*
* @psalm-suppress PossiblyUndefinedArrayOffset
*/
public function get_records(): array {
global $timestart;
$request_time = filter_var( $_SERVER['REQUEST_TIME_FLOAT'], FILTER_VALIDATE_FLOAT );
$php = 'PHP Load – ' . PHP_VERSION;
if ( $this->opcache_api_available() ) {
/**
* This is silenced since it might still emit warning for correct `opcache.resrict_api` configuration.
* @see https://github.com/php/php-src/issues/8799
*/
$zend_status = @opcache_get_status();
$php .= empty( $zend_status['opcache_enabled'] ) ? '' : ' – OPcache';
}
$load = $this->network_activated ? 'Core Load' : 'Core & MU Plugins Load';
/**
* @var float $request_time
* @var float $timestart
*/
return [
new Record( $php, $request_time, $timestart - $request_time, '', 'php' ),
new Record( $load, $timestart, $this->timeload - $timestart, '', 'core' ),
];
}
private function opcache_api_available(): bool {
if ( ! function_exists( 'opcache_get_status' ) ) {
return false;
}
$restrict_api = ini_get( 'opcache.restrict_api' );
if ( ! $restrict_api ) {
return true;
}
/**
* The paths aren't normalized, because it is unclear if they are in PHP itself
* and this should rather be false negative to prevent warning emitted.
*/
return 0 === stripos( __FILE__, $restrict_api );
}
}
+262
View File
@@ -0,0 +1,262 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Event\Hook_Event_Config_Interface;
use Rarst\Laps\Formatter\Hook_Formatter;
use Rarst\Laps\Record\Stopwatch_Record;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* Processes events based on hooked starts and stops.
*/
class Hook_Collector extends Stopwatch_Collector {
/** @var Hook_Event_Config_Interface[] $event_configs */
protected $event_configs = [];
/** @var array $events */
protected $events = [];
/**
* @var array $callbacks
* @psalm-var array<string, \WP_Hook> $callbacks
*/
protected $callbacks = [];
/** @var Hook_Formatter */
protected $formatter;
/**
* @var array $calls Tracks number of calls for recurrent events.
* @psalm-var array<string, int> $calls
*/
private $calls = [];
/**
* @param Stopwatch $stopwatch Stopwatch instance.
* @param Hook_Event_Config_Interface[] $event_configs Starts and stops configuration.
*/
public function __construct( Stopwatch $stopwatch, array $event_configs ) {
parent::__construct( $stopwatch );
if ( did_action( 'muplugins_loaded' ) ) {
$this->start( 'Plugins Load', 'plugin' );
} else {
$this->start( 'Network & MU Plugins Load', 'plugin' );
add_action( 'muplugins_loaded', [ $this, 'muplugins_loaded' ], PHP_INT_MAX );
}
$this->add_events( $event_configs['core']->get_events() );
unset( $event_configs['core'] );
$this->event_configs = $event_configs;
add_action( 'after_setup_theme', [ $this, 'after_setup_theme' ], 15 );
$this->formatter = new Hook_Formatter();
}
/**
* Time the point between network/MU plugins and regular if network activated.
*/
public function muplugins_loaded(): void {
$this->stop( 'Network & MU Plugins Load' );
$this->start( 'Plugins Load', 'plugin' );
}
/**
* Hook events by name and priority from array.
*
* @param array $stops Starts and stops to hook.
*/
public function add_events( array $stops ): void {
$this->events = array_merge( $this->events, $stops );
/**
* @var int|string $key
* @var array $data
*/
foreach ( $stops as $key => $data ) {
if ( is_int( $key ) ) {
$this->add_event( ...$data );
continue;
}
/** @var int $priority */
foreach ( array_keys( $data ) as $priority ) {
add_action( $key, [ $this, 'tick' ], $priority );
}
}
}
/**
* Add a start/stop pair of hook event.
*
* @param string $event Hook event name.
* @param string $category Hook event category.
* @param string $start Start hook name. Pass empty string to ignore.
* @param string|null $stop Stop hook name (defaults to start name). Pass empty string to ignore.
* @param int $start_priority Start hook priority (defaults to -1).
* @param int $stop_priority Stop hook priority (defaults to max int).
*
* @psalm-suppress MissingClosureParamType
*/
private function add_event(
string $event,
string $category,
string $start,
?string $stop = null,
int $start_priority = - 1,
int $stop_priority = PHP_INT_MAX
): void {
if ( null === $stop ) {
$stop = $start;
}
if ( '' !== $start ) {
add_action( $start, function ( $input = null ) use ( $event, $category, $start, $stop ) {
global $wp_filter;
$event .= $this->get_count_suffix( $event, true );
if ( 'Sidebar' === $event ) {
/** @var string $input */
$event = $input;
}
if ( $start === $stop ) {
/** @psalm-var array<string, \WP_Hook> $wp_filter */
$this->callbacks[ $event ] = $wp_filter[ $start ];
}
$this->start( $event, $category );
return $input;
}, $start_priority );
}
if ( '' !== $stop ) {
add_action( $stop, function ( $input = null ) use ( $event ) {
$event .= $this->get_count_suffix( $event );
if ( 'Sidebar' === $event ) {
/** @var string $input */
$event = $input;
}
$this->stop( $event );
return $input;
}, $stop_priority );
}
}
/**
* Get the suffix to use if event occurs multiple times.
*
* @param string $event Event name.
* @param boolean $increment Flag to increment count.
*
* @return string
*/
private function get_count_suffix( string $event, bool $increment = false ): string {
if ( ! isset( $this->calls[ $event ] ) ) {
$this->calls[ $event ] = 0;
}
if ( $increment ) {
$this->calls[ $event ] ++;
}
$count = (int) $this->calls[ $event ];
return $count > 1 ? " ({$count})" : '';
}
/**
* When theme is done possibly add vendor-specific events.
*/
public function after_setup_theme(): void {
foreach ( $this->event_configs as $config ) {
$this->add_events( $config->get_events() );
}
}
/**
* Mark action for the event on Stopwatch.
*
* @deprecated 3.0:4.0 Deprecated in favor of the new format.
* @codeCoverageIgnore
*
* @param mixed $input Pass through if added to filter.
*
* @return mixed
*/
public function tick( $input = null ) {
global $wp_filter;
/** @var string $filter_name */
$filter_name = current_filter();
/**
* @var \WP_Hook|array $filter_instance
* @var array<string,\WP_hook|array> $wp_filter
*/
$filter_instance = $wp_filter[ $filter_name ];
/** @var int $priority */
$priority = $filter_instance instanceof \WP_Hook ? $filter_instance->current_priority() : key( $filter_instance );
// See https://core.trac.wordpress.org/ticket/41185 on broken priority, but more general sanity check.
if ( empty( $this->events[ $filter_name ][ $priority ] ) ) {
return $input;
}
$event = \wp_parse_args( $this->events[ $filter_name ][ $priority ], [
'action' => 'start',
'category' => null,
] );
if ( 'start' === $event['action'] ) {
$this->start( $event['event'], $event['category'] );
} else {
$this->stop( $event['event'] );
}
return $input;
}
/**
* @return Stopwatch_Record[]
*/
public function get_records(): array {
$this->stop( 'Toolbar' );
$this->stop( 'Footer Hook' );
return parent::get_records();
}
/**
* @param string $name Event name.
* @param StopwatchEvent $event Stopwatch event instance.
*
* @return Stopwatch_Record
*/
protected function transform( string $name, StopwatchEvent $event ): Stopwatch_Record {
$description = '';
if ( isset( $this->callbacks[ $name ] ) ) {
$description = implode( '<br />', $this->formatter->format( $this->callbacks[ $name ] ) );
}
return new Stopwatch_Record( $name, $event, $description );
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Formatter\Backtrace_Formatter;
use Rarst\Laps\Record\Stopwatch_Record;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* Captures time of network requests made with HTTP API.
*/
class Http_Collector extends Stopwatch_Collector {
/** @var Backtrace_Formatter */
private $formatter;
/**
* @var array $callers
* @psalm-var array<string, string> $callers
*/
private $callers = [];
/**
* @param Stopwatch $stopwatch Stopwatch instance.
*/
public function __construct( Stopwatch $stopwatch ) {
parent::__construct( $stopwatch );
$this->formatter = new Backtrace_Formatter();
add_action( 'pre_http_request', [ $this, 'pre_http_request' ], 10, 3 );
add_action( 'http_api_debug', [ $this, 'http_api_debug' ], 10, 5 );
}
/**
* Capture start time of HTTP request
*
* @param false|array|\WP_Error $false Whether to preempt an HTTP request's return value. Default false.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return false|array|\WP_Error
*
* @psalm-suppress RedundantCondition
* @noinspection PhpUnusedParameterInspection
*/
public function pre_http_request( $false, $args, $url ) {
if ( $url && is_string( $url ) ) {
$this->start( $url, 'http' );
$this->callers[ $url ] = wp_debug_backtrace_summary( __CLASS__, 5 );
}
return $false;
}
/**
* Capture end time of HTTP request
*
* @param array|\WP_Error $response HTTP response or WP_Error object.
* @param string $type Context under which the hook is fired.
* @param object $class HTTP transport used.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return array|\WP_Error
*
* @psalm-suppress RedundantCondition
* @noinspection PhpUnusedParameterInspection
*/
public function http_api_debug( $response, $type, $class, $args, $url ) {
if ( $url && is_string( $url ) ) {
$this->stop( $url );
}
return $response;
}
/**
* @param string $name Event name.
* @param StopwatchEvent $event Stopwatch event instance.
*
* @return Stopwatch_Record
*/
public function transform( string $name, StopwatchEvent $event ): Stopwatch_Record {
$backtrace = $this->formatter->format( $this->callers[ $name ] );
return new Stopwatch_Record( $name, $event, implode( '<br />', $backtrace ) );
}
}
@@ -0,0 +1,38 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Record_Interface;
/**
* Collects records from concrete collectors set on access.
*/
class Lazy_Proxy_Collector implements Record_Collector_Interface {
/** @var array|Record_Collector_Interface[] */
private $collectors;
/**
* Lazy_Proxy_Collector constructor.
*
* @param Record_Collector_Interface[] $collectors Array of collectors to store for processing.
*/
public function __construct( array $collectors ) {
$this->collectors = $collectors;
}
/**
* @return Record_Interface[]
*/
public function get_records(): array {
$records = [];
/** @var Record_Collector_Interface $collector */
foreach ( $this->collectors as $collector ) {
$records[] = $collector->get_records();
}
return count( $records ) ? array_merge( ...$records ) : [];
}
}
@@ -0,0 +1,76 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Record_Interface;
/**
* Tracks load time of individual plugins.
*/
class Plugin_Load_Collector implements Record_Collector_Interface {
/** @var float */
private $last;
/** @var array */
private $entries = [];
/**
* Sets up start time and tracking callbacks.
*/
public function __construct() {
$this->last = microtime( true );
add_action( 'network_plugin_loaded', [ $this, 'plugin_loaded' ] );
add_action( 'plugin_loaded', [ $this, 'plugin_loaded' ] );
}
/**
* Records time for a plugin that just finished loading.
*
* @param string $plugin Plugin file path.
*
* @psalm-suppress DocblockTypeContradiction
*/
public function plugin_loaded( $plugin ): void {
$time = microtime( true );
if ( ! is_string( $plugin ) ) { // Broken hook input from global.
$this->last = $time;
return;
}
$this->entries[] = [
'name' => $plugin,
'origin' => $this->last,
'duration' => $time - $this->last,
];
$this->last = $time;
}
/**
* @return Record_Interface[]
*/
public function get_records(): array {
return array_map( [ $this, 'create_record' ], $this->entries );
}
/**
* Converts recorded information into a Record instance.
*
* @param array $entry Data entry for the plugin.
*/
private function create_record( array $entry ): Record {
return new Record(
plugin_basename( $entry['name'] ),
$entry['origin'],
$entry['duration'],
'',
'plugin'
);
}
}
@@ -0,0 +1,17 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Record_Interface;
/**
* Interface to retrieve a set of records form collector.
*/
interface Record_Collector_Interface {
/**
* @return Record_Interface[]
*/
public function get_records(): array;
}
+111
View File
@@ -0,0 +1,111 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Formatter\Backtrace_Formatter;
use Rarst\Laps\Record\Record;
/**
* Processes SQL events from data logged by wpdb.
*
* @template QueryData as array{0: string, 1: float, 2: string, 3: float}
*/
class Sql_Collector implements Record_Collector_Interface {
/** @var array $query_starts Log of query start times. */
protected $query_starts = [];
/** @var Backtrace_Formatter $formatter */
protected $formatter;
/**
* Sets up the query start time log.
*/
public function __construct() {
$this->formatter = new Backtrace_Formatter();
if ( $this->is_savequeries() ) {
add_filter( 'query', [ $this, 'query' ], 20 ); // TODO Drop recording time if dropping support for WP <5.1.
}
}
/**
* Capture SQL queries start times
*
* @param string $query SQL query.
*
* @return string
*/
public function query( $query ): string {
global $wpdb;
if ( empty( $this->query_starts ) && ! empty( $wpdb->queries ) ) {
/** @var array $wpdb->queries */
$this->query_starts[ count( $wpdb->queries ) ] = microtime( true );
} else {
$this->query_starts[] = microtime( true );
}
return $query;
}
/**
* @return Record[]
*/
public function get_records(): array {
global $wpdb;
if ( empty( $wpdb->queries ) ) {
return [];
}
/** @var array $wpdb->queries */
/** @psalm-var array<int, QueryData> $wpdb->queries */
/** @psalm-suppress PossiblyInvalidArgument */
$records = array_filter( array_map( [ $this, 'transform' ], array_keys( $wpdb->queries ), $wpdb->queries ) );
return $records;
}
/**
* @return bool
*/
protected function is_savequeries(): bool {
return \defined( 'SAVEQUERIES' ) && SAVEQUERIES;
}
/**
* Transform query data, captured by core, into a Record.
*
* @param int $key Query key in captured data.
* @param array $query_data Array of captured query data.
* @psalm-param array{0: string, 1: float, 2: string, 3: float} $query_data
*
* @return Record
*/
protected function transform( int $key, array $query_data ): ?Record {
[ $sql, $duration, $caller ] = $query_data;
/** @var float $query_start */
$query_start = $query_data[3] ?? $this->query_starts[ $key ] ?? 0;
if ( empty( $query_start ) ) {
return null;
}
$sql = trim( $sql );
$category = 'sql-read';
if ( 0 === stripos( $sql, 'INSERT' ) || 0 === stripos( $sql, 'UPDATE' ) ) {
$category = 'sql-write';
}
$desc_duration = round( $duration * 1000 );
$backtrace = $this->formatter->format( $caller );
$description = $sql . ' – ' . $desc_duration . 'ms<hr />' . implode( '<br />', $backtrace );
return new Record( $sql, $query_start, $duration, $description, $category );
}
}
@@ -1,14 +1,16 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Stopwatch_Record;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent; use Symfony\Component\Stopwatch\StopwatchEvent;
/** /**
* Generic Stopwatch–based collector. Can be used by itself or extended. * Generic Stopwatch–based collector. Can be used by itself or extended.
*/ */
class Stopwatch_Record_Collector implements Record_Collector_Interface { class Stopwatch_Collector implements Record_Collector_Interface {
/** @var Stopwatch $stopwatch */ /** @var Stopwatch $stopwatch */
protected $stopwatch; protected $stopwatch;
@@ -26,7 +28,7 @@ class Stopwatch_Record_Collector implements Record_Collector_Interface {
* *
* @return StopwatchEvent * @return StopwatchEvent
*/ */
public function start( $name, $category = null ) { public function start( $name, $category = null ): StopwatchEvent {
return $this->stopwatch->start( $name, $category ); return $this->stopwatch->start( $name, $category );
} }
@@ -47,16 +49,19 @@ class Stopwatch_Record_Collector implements Record_Collector_Interface {
/** /**
* @return Stopwatch_Record[] * @return Stopwatch_Record[]
*/ */
public function get_records() { public function get_records(): array {
/** @psalm-var array<string, StopwatchEvent> $events */
$events = $this->stopwatch->getSectionEvents( '__root__' ); $events = $this->stopwatch->getSectionEvents( '__root__' );
/** @var string $name */
foreach ( array_keys( $events ) as $name ) { foreach ( array_keys( $events ) as $name ) {
if ( $this->stopwatch->isStarted( $name ) ) { if ( $this->stopwatch->isStarted( $name ) ) {
unset( $events[ $name ] ); unset( $events[ $name ] );
} }
} }
/** @psalm-suppress PossiblyInvalidArgument */
return array_map( [ $this, 'transform' ], array_keys( $events ), $events ); return array_map( [ $this, 'transform' ], array_keys( $events ), $events );
} }
@@ -66,7 +71,7 @@ class Stopwatch_Record_Collector implements Record_Collector_Interface {
* *
* @return Stopwatch_Record * @return Stopwatch_Record
*/ */
protected function transform( $name, StopwatchEvent $event ) { protected function transform( string $name, StopwatchEvent $event ): Stopwatch_Record {
return new Stopwatch_Record( $name, $event ); return new Stopwatch_Record( $name, $event );
} }
-35
View File
@@ -1,35 +0,0 @@
<?php
namespace Rarst\Laps\Record;
/**
* Covers pre–plugin load time from available data.
*/
class Core_Load_Collector implements Record_Collector_Interface {
/** @var float $timeload */
protected $timeload;
/**
* Store core load end, plugin load start mark.
*/
public function __construct() {
$this->timeload = microtime( true );
}
/**
* @return Record_Interface[]
*/
public function get_records() {
global $timestart;
$request_time = $_SERVER['REQUEST_TIME_FLOAT'];
return [
new Record( 'PHP Load', $request_time, $timestart - $request_time, '', 'php' ),
// TODO This includes network plugins on multisite, need conditional label if Laps is network–activated.
new Record( 'Core and MU Plugins Load', $timestart, $this->timeload - $timestart, '', 'core' ),
];
}
}
-104
View File
@@ -1,104 +0,0 @@
<?php
namespace Rarst\Laps\Record;
use Rarst\Laps\Event\Hook_Event_Config_Interface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Processes events based on hooked starts and stops.
*/
class Hook_Record_Collector extends Stopwatch_Record_Collector {
/** @var Hook_Event_Config_Interface[] $event_configs */
protected $event_configs = [];
/** @var array $events */
protected $events = [];
/**
* @param Stopwatch $stopwatch Stopwatch instance.
* @param array $event_configs Starts and stops configuration.
*/
public function __construct( Stopwatch $stopwatch, array $event_configs ) {
parent::__construct( $stopwatch );
$this->start( 'Plugins Load', 'plugin' );
$this->add_events( $event_configs['core']->get_events() );
unset( $event_configs['core'] );
$this->event_configs = $event_configs;
add_action( 'after_setup_theme', [ $this, 'after_setup_theme' ], 15 );
}
/**
* Hook events by name and priority from array.
*
* @param array $stops Starts and stops to hook.
*/
public function add_events( $stops ) {
$this->events = array_merge( $this->events, $stops );
foreach ( $stops as $hook_name => $data ) {
foreach ( array_keys( $data ) as $priority ) {
add_action( $hook_name, [ $this, 'tick' ], $priority );
}
}
}
/**
* When theme is done possibly add vendor-specific events.
*/
public function after_setup_theme() {
foreach ( $this->event_configs as $config ) {
$this->add_events( $config->get_events() );
}
}
/**
* Mark action for the event on Stopwatch.
*
* @param mixed $input Pass through if added to filter.
*
* @return mixed
*/
public function tick( $input = null ) {
global $wp_filter;
$filter_name = current_filter();
$filter_instance = $wp_filter[ $filter_name ];
$priority = $filter_instance instanceof \WP_Hook ? $filter_instance->current_priority() : key( $filter_instance );
// See https://core.trac.wordpress.org/ticket/41185 on broken priority, but more general sanity check.
if ( empty( $this->events[ $filter_name ][ $priority ] ) ) {
return $input;
}
$event = wp_parse_args( $this->events[ $filter_name ][ $priority ], [
'action' => 'start',
'category' => null,
] );
if ( 'start' === $event['action'] ) {
$this->start( $event['event'], $event['category'] );
} else {
$this->stop( $event['event'] );
}
return $input;
}
/**
* @return Stopwatch_Record[]
*/
public function get_records() {
$this->stopwatch->stop( 'Toolbar' );
return parent::get_records();
}
}
-58
View File
@@ -1,58 +0,0 @@
<?php
namespace Rarst\Laps\Record;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Captures time of network requests made with HTTP API.
*/
class Http_Record_Collector extends Stopwatch_Record_Collector {
/**
* @param Stopwatch $stopwatch Stopwatch instance.
*/
public function __construct( Stopwatch $stopwatch ) {
parent::__construct( $stopwatch );
add_action( 'pre_http_request', [ $this, 'pre_http_request' ], 10, 3 );
add_action( 'http_api_debug', [ $this, 'http_api_debug' ], 10, 5 );
}
/**
* Capture start time of HTTP request
*
* @param boolean $false Whether to preempt an HTTP request's return value. Default false.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return boolean
*/
public function pre_http_request( $false, $args, $url ) {
$this->start( $url, 'http' );
return $false;
}
/**
* Capture end time of HTTP request
*
* @param array|\WP_Error $response HTTP response or WP_Error object.
* @param string $type Context under which the hook is fired.
* @param object $class HTTP transport used.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return mixed
*
* @noinspection MoreThanThreeArgumentsInspection
*/
public function http_api_debug( $response, $type, $class, $args, $url ) {
$this->stop( $url );
return $response;
}
}
@@ -1,20 +1,28 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record\Iterator;
use Rarst\Laps\Record\Record_Interface;
/** /**
* Processes records, recursively bumping overlapping ones to children. * Processes records, recursively bumping overlapping ones to children.
*
* @template-extends \ArrayIterator<array-key,Record_Interface>
* @template-implements \RecursiveIterator<array-key,Record_Interface>
*
* @psalm-suppress TooManyTemplateParams
*/ */
class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIterator { class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIterator {
/** @var array $children */ /** @var Record_Interface[] $children */
protected $children = []; protected $children = [];
/** /**
* @param Record_Interface[] $records Records to process. * @param Record_Interface[] $records Records to process.
* @param int $flags Configuration flags. * @psalm-param 0|1|2|3 $flags Configuration flags.
*/ */
public function __construct( array $records, $flags = 0 ) { public function __construct( array $records, int $flags = 0 ) {
usort( $records, [ $this, 'sort_origin' ] ); usort( $records, [ $this, 'sort_origin' ] );
$end = 0; $end = 0;
@@ -43,12 +51,12 @@ class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIter
* *
* @return int * @return int
*/ */
protected function sort_origin( Record_Interface $record_a, Record_Interface $record_b ) { protected function sort_origin( Record_Interface $record_a, Record_Interface $record_b ): int {
$origin_a = $record_a->get_origin(); $origin_a = $record_a->get_origin();
$origin_b = $record_b->get_origin(); $origin_b = $record_b->get_origin();
if ( $origin_a === $origin_b ) { if ( abs( $origin_a - $origin_b ) < 0.0001 ) { // Float math! Makes nesting results more sane.
return 0; return 0;
} }
@@ -58,16 +66,16 @@ class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIter
/** /**
* @return bool * @return bool
*/ */
public function hasChildren() { public function hasChildren(): bool {
return ! empty( $this->children ); return ! empty( $this->children );
} }
/** /**
* @return static * This should be `static` for possible subclassing, but problems hinting that for PHP <8, also don't subclass this.
*/ */
public function getChildren() { public function getChildren(): self {
return new static( $this->children ); return new self( $this->children );
} }
} }
@@ -1,9 +1,17 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record\Iterator;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Record_Interface;
/** /**
* Processes records into sets for display by template. * Processes records into sets for display by template.
*
* @template-implements \Iterator<array>
*
* @psalm-suppress PropertyNotSetInConstructor, TooManyTemplateParams
*/ */
class Timeline_Iterator implements \Iterator { class Timeline_Iterator implements \Iterator {
@@ -21,20 +29,23 @@ class Timeline_Iterator implements \Iterator {
/** /**
* @param Recursive_Record_Iterator $iterator Record iterator. * @param Recursive_Record_Iterator $iterator Record iterator.
*
* @psalm-suppress PossiblyFalsePropertyAssignmentValue, PossiblyUndefinedArrayOffset
*/ */
public function __construct( Recursive_Record_Iterator $iterator ) { public function __construct( Recursive_Record_Iterator $iterator ) {
$this->origin = $_SERVER['REQUEST_TIME_FLOAT']; $this->origin = filter_var( $_SERVER['REQUEST_TIME_FLOAT'], FILTER_VALIDATE_FLOAT );
$this->iterator = $iterator; $this->iterator = $iterator;
} }
/** /**
* @return array * @return array
*/ */
public function current() { public function current(): array {
$data = []; $data = [];
/** @var Record $record */
foreach ( $this->current as $record ) { foreach ( $this->current as $record ) {
$data[] = $this->prepare( $record ); $data[] = $this->prepare( $record );
} }
@@ -45,30 +56,32 @@ class Timeline_Iterator implements \Iterator {
/** /**
* Sets current context to the next set of nested records. * Sets current context to the next set of nested records.
*/ */
public function next() { public function next(): void {
$this->current = $this->current->getChildren(); $this->current = $this->current->getChildren();
} }
/** /**
* Key is unused and is only type hinted to possible `int` for PHP 8.1 `mixed` type compatibility.
*
* @codeCoverageIgnore * @codeCoverageIgnore
* *
* @return void * @return null
*/ */
public function key() { public function key(): ?int {
return null;
} }
/** /**
* @return bool If current set contains records. * @return bool If current set contains records.
*/ */
public function valid() { public function valid(): bool {
return (bool) count( $this->current ); return (bool) count( $this->current );
} }
/** /**
* Rewind to a topmost record set. * Rewind to a topmost record set.
*/ */
public function rewind() { public function rewind(): void {
$this->total = microtime( true ) - $this->origin; $this->total = microtime( true ) - $this->origin;
$this->current = $this->iterator; $this->current = $this->iterator;
} }
@@ -78,7 +91,7 @@ class Timeline_Iterator implements \Iterator {
* *
* @return array Record data for display by the template. * @return array Record data for display by the template.
*/ */
protected function prepare( Record_Interface $record ) { protected function prepare( Record_Interface $record ): array {
$data = [ $data = [
'description' => $record->get_description(), 'description' => $record->get_description(),
+7 -6
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record;
@@ -29,7 +30,7 @@ class Record implements Record_Interface {
* @param string $description Optional description. * @param string $description Optional description.
* @param string $category Optional category. * @param string $category Optional category.
*/ */
public function __construct( $name, $origin, $duration, $description = '', $category = '' ) { public function __construct( string $name, float $origin, float $duration, string $description = '', string $category = '' ) {
$this->name = $name; $this->name = $name;
$this->origin = $origin; $this->origin = $origin;
$this->duration = $duration; $this->duration = $duration;
@@ -40,14 +41,14 @@ class Record implements Record_Interface {
/** /**
* @return string * @return string
*/ */
public function get_name() { public function get_name(): string {
return $this->name; return $this->name;
} }
/** /**
* @return string * @return string
*/ */
public function get_description() { public function get_description(): string {
if ( empty( $this->description ) ) { if ( empty( $this->description ) ) {
$duration = round( $this->get_duration() * 1000 ); $duration = round( $this->get_duration() * 1000 );
@@ -61,21 +62,21 @@ class Record implements Record_Interface {
/** /**
* @return float * @return float
*/ */
public function get_origin() { public function get_origin(): float {
return $this->origin; return $this->origin;
} }
/** /**
* @return float * @return float
*/ */
public function get_duration() { public function get_duration(): float {
return $this->duration; return $this->duration;
} }
/** /**
* @return string * @return string
*/ */
public function get_category() { public function get_category(): string {
return $this->category; return $this->category;
} }
} }
-11
View File
@@ -1,11 +0,0 @@
<?php
namespace Rarst\Laps\Record;
interface Record_Collector_Interface {
/**
* @return Record_Interface[]
*/
public function get_records();
}
+6 -5
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record;
@@ -10,25 +11,25 @@ interface Record_Interface {
/** /**
* @return string * @return string
*/ */
public function get_name(); public function get_name(): string;
/** /**
* @return string * @return string
*/ */
public function get_description(); public function get_description(): string;
/** /**
* @return float Timestamp of record start. * @return float Timestamp of record start.
*/ */
public function get_origin(); public function get_origin(): float;
/** /**
* @return float Record duration in seconds. * @return float Record duration in seconds.
*/ */
public function get_duration(); public function get_duration(): float;
/** /**
* @return string * @return string
*/ */
public function get_category(); public function get_category(): string;
} }
-82
View File
@@ -1,82 +0,0 @@
<?php
namespace Rarst\Laps\Record;
/**
* Processes SQL events from data logged by wpdb.
*/
class Sql_Record_Collector implements Record_Collector_Interface {
/** @var array $query_starts Log of query start times. */
protected $query_starts = [];
/**
* Sets up the query start time log.
*/
public function __construct() {
if ( $this->is_savequeries() ) {
add_filter( 'query', [ $this, 'query' ], 20 );
}
}
/**
* Capture SQL queries start times
*
* @param string $query SQL query.
*
* @return string
*/
public function query( $query ) {
global $wpdb;
if ( empty( $this->query_starts ) && ! empty( $wpdb->queries ) ) {
$this->query_starts[ count( $wpdb->queries ) ] = microtime( true );
} else {
$this->query_starts[] = microtime( true );
}
return $query;
}
/**
* @return Record[]
*/
public function get_records() {
if ( ! $this->is_savequeries() ) {
return [];
}
global $wpdb;
$query_data = [];
$last_query_end = 0;
// TODO process call trace from the data.
foreach ( $wpdb->queries as $key => list( $sql, $duration ) ) {
$query_start = isset( $this->query_starts[ $key ] ) ? $this->query_starts[ $key ] : $last_query_end;
$sql = trim( $sql );
$category = 'sql-read';
if ( 0 === stripos( $sql, 'INSERT' ) || 0 === stripos( $sql, 'UPDATE' ) ) {
$category = 'sql-write';
}
$last_query_end = $query_start + $duration;
$query_data[] = new Record( $sql, $query_start, $duration, '', $category );
}
return $query_data;
}
/**
* @return bool
*/
protected function is_savequeries() {
return defined( 'SAVEQUERIES' ) && SAVEQUERIES;
}
}
+16 -9
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Record; namespace Rarst\Laps\Record;
@@ -15,51 +16,57 @@ class Stopwatch_Record implements Record_Interface {
/** @var StopwatchEvent */ /** @var StopwatchEvent */
protected $stopwatch_event; protected $stopwatch_event;
/** @var string */
protected $description = '';
/** /**
* @param string $name Event name. * @param string $name Event name.
* @param StopwatchEvent $stopwatch_event Stopwatch event instance. * @param StopwatchEvent $stopwatch_event Stopwatch event instance.
* @param string $description Optional description for the event.
*/ */
public function __construct( $name, StopwatchEvent $stopwatch_event ) { public function __construct( string $name, StopwatchEvent $stopwatch_event, string $description = '' ) {
$this->name = $name; $this->name = $name;
$this->stopwatch_event = $stopwatch_event; $this->stopwatch_event = $stopwatch_event;
$this->description = $description;
} }
/** /**
* @return string * @return string
*/ */
public function get_name() { public function get_name(): string {
return $this->name; return $this->name;
} }
/** /**
* @return string * @return string
*/ */
public function get_description() { public function get_description(): string {
$duration = round( $this->stopwatch_event->getDuration() ); $duration = round( $this->stopwatch_event->getDuration() );
$memory = $this->stopwatch_event->getMemory() / 1024 / 1024; $memory = round( $this->stopwatch_event->getMemory() / 1024 / 1024, 2 );
$description = '' !== $this->description ? "<hr />{$this->description}" : '';
return "{$this->name} – {$duration} ms – {$memory} MB"; return "{$this->name} – {$duration} ms – {$memory} MB{$description}";
} }
/** /**
* @return float Timestamp of record start. * @return float Timestamp of record start.
*/ */
public function get_origin() { public function get_origin(): float {
return $this->stopwatch_event->getOrigin() / 1000; // ms to s. return $this->stopwatch_event->getOrigin() / 1000; // ms to s.
} }
/** /**
* @return float Record duration in seconds. * @return float Record duration in seconds.
*/ */
public function get_duration() { public function get_duration(): float {
return $this->stopwatch_event->getDuration() / 1000; // ms to s. return $this->stopwatch_event->getDuration() / 1000; // ms to s.
} }
/** /**
* @return string * @return string
*/ */
public function get_category() { public function get_category(): string {
return $this->stopwatch_event->getCategory(); return $this->stopwatch_event->getCategory();
} }
} }
+19 -5
View File
@@ -7,6 +7,7 @@ use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Event\Genesis_Events; use Rarst\Laps\Event\Genesis_Events;
use Rarst\Laps\Event\Hybrid_Events; use Rarst\Laps\Event\Hybrid_Events;
use Rarst\Laps\Event\THA_Events; use Rarst\Laps\Event\THA_Events;
use Rarst\Laps\Event\WooCommerce_Events;
use Rarst\Laps\Event\Yoast_Events; use Rarst\Laps\Event\Yoast_Events;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
@@ -16,7 +17,7 @@ class EventTest extends LapsTestCase {
$events = ( new Core_Events() )->get_events(); $events = ( new Core_Events() )->get_events();
$this->assertInternalType( 'array', $events ); $this->assertIsArray( $events );
$this->assertNotEmpty( $events ); $this->assertNotEmpty( $events );
} }
@@ -28,7 +29,7 @@ class EventTest extends LapsTestCase {
Functions\expect( 'genesis' )->zeroOrMoreTimes(); Functions\expect( 'genesis' )->zeroOrMoreTimes();
$events = $genesis->get_events(); $events = $genesis->get_events();
$this->assertInternalType( 'array', $events ); $this->assertIsArray( $events );
$this->assertNotEmpty( $events ); $this->assertNotEmpty( $events );
} }
@@ -45,7 +46,7 @@ class EventTest extends LapsTestCase {
$events = $hybrid->get_events(); $events = $hybrid->get_events();
$this->assertInternalType( 'array', $events ); $this->assertIsArray( $events );
$this->assertNotEmpty( $events ); $this->assertNotEmpty( $events );
} }
@@ -58,7 +59,20 @@ class EventTest extends LapsTestCase {
$events = $tha->get_events(); $events = $tha->get_events();
$this->assertInternalType( 'array', $events ); $this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
public function testWooCommerce() {
$woocommerce = new WooCommerce_Events();
$this->assertEmpty( $woocommerce->get_events() );
$this->getMockBuilder( 'WooCommerce' )->allowMockingUnknownTypes()->getMock();
$events = $woocommerce->get_events();
$this->assertIsArray( $events );
$this->assertNotEmpty( $events ); $this->assertNotEmpty( $events );
} }
@@ -71,7 +85,7 @@ class EventTest extends LapsTestCase {
$events = $yoast->get_events(); $events = $yoast->get_events();
$this->assertInternalType( 'array', $events ); $this->assertIsArray( $events );
$this->assertNotEmpty( $events ); $this->assertNotEmpty( $events );
} }
} }
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Rarst\Laps\Tests\Formatter;
use Brain\Monkey\Functions;
use Rarst\Laps\Formatter\Backtrace_Formatter;
use Rarst\Laps\Tests\LapsTestCase;
/**
* @coversDefaultClass Rarst\Laps\Formatter\Backtrace_Formatter
*/
class BacktraceTest extends LapsTestCase {
/**
* @covers ::__construct
*
* @return Backtrace_Formatter
*/
public function test__construct() {
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
return new Backtrace_Formatter();
}
/**
* @depends test__construct
*/
public function testFormat( Backtrace_Formatter $formatter ) {
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$backtrace = [
'template-loader.php',
'WP_Hook->do_action',
'foo',
"include('C:/wp/path.php')",
];
$backtrace = implode( ', ', $backtrace );
$result = $formatter->format( $backtrace );
$this->assertIsArray( $result );
$result = array_values( $result );
$this->assertEquals( 'foo', $result[0] );
$this->assertEquals( 'path.php', $result[1] );
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Formatter;
use Brain\Monkey\Functions;
use Rarst\Laps\Formatter\Hook_Formatter;
use Rarst\Laps\Tests\LapsTestCase;
/**
* @coversDefaultClass \Rarst\Laps\Formatter\Hook_Formatter
*/
class HookTest extends LapsTestCase {
/**
* @covers ::__construct
*
* @return Hook_Formatter
*/
public function test__construct() {
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
return new Hook_Formatter();
}
/**
* @depends test__construct
*/
public function testFormat( Hook_Formatter $formatter ) {
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$hook = [
[ [ 'function' => 'function', 'accepted_args' => 2 ] ],
[ [ 'function' => [ 'class', 'method' ], 'accepted_args' => 1 ] ],
[ [ 'function' => function() {}, 'accepted_args' => 1 ] ],
[ [ 'function' => new class(){}, 'accepted_args' => 1 ] ],
];
$result = $formatter->format( $hook );
$this->assertIsArray( $result );
$this->assertEquals( 'function(2)', $result[0] );
$this->assertEquals( 'class::method', $result[1] );
$this->assertStringStartsWith( 'closure from', $result[2] );
$this->assertStringStartsWith( 'anonymous class from', $result[3] );
$this->assertEquals( [], $formatter->format( [] ) );
}
}
+2 -2
View File
@@ -10,12 +10,12 @@ class LapsTestCase extends TestCase {
use MockeryPHPUnitIntegration; use MockeryPHPUnitIntegration;
protected function setUp() { protected function setUp():void {
parent::setUp(); parent::setUp();
Monkey\setUp(); Monkey\setUp();
} }
protected function tearDown() { protected function tearDown(): void {
Monkey\tearDown(); Monkey\tearDown();
parent::tearDown(); parent::tearDown();
} }
+26 -2
View File
@@ -20,8 +20,8 @@ class AssetTest extends LapsTestCase {
$manager = new Asset_Manager(); $manager = new Asset_Manager();
$this->assertTrue( has_action( 'wp_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) ); $this->assertTrue( (bool) has_action( 'wp_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
$this->assertTrue( has_action( 'admin_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) ); $this->assertTrue( (bool) has_action( 'admin_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
return $manager; return $manager;
} }
@@ -36,9 +36,33 @@ class AssetTest extends LapsTestCase {
Functions\expect( 'wp_register_style' )->once(); Functions\expect( 'wp_register_style' )->once();
Functions\expect( 'plugins_url' )->zeroOrMoreTimes(); Functions\expect( 'plugins_url' )->zeroOrMoreTimes();
Functions\expect( 'is_admin_bar_showing' )->once()->andReturn( true ); Functions\expect( 'is_admin_bar_showing' )->once()->andReturn( true );
Functions\expect( 'current_user_can' )
->once()
->with( 'manage_options' )
->andReturn( true );
Functions\expect( 'wp_enqueue_script' )->once(); Functions\expect( 'wp_enqueue_script' )->once();
Functions\expect( 'wp_enqueue_style' )->once(); Functions\expect( 'wp_enqueue_style' )->once();
$manager->enqueue_scripts(); $manager->enqueue_scripts();
} }
/**
* @covers ::enqueue_scripts
* @depends test__construct
*/
public function testEnqueue_scripts_hidden( Asset_Manager $manager ) {
Functions\expect( 'wp_register_script' )->once();
Functions\expect( 'wp_register_style' )->once();
Functions\expect( 'plugins_url' )->zeroOrMoreTimes();
Functions\expect( 'is_admin_bar_showing' )->once()->andReturn( true );
Functions\expect( 'current_user_can' )
->once()
->with( 'manage_options' )
->andReturn( false );
Functions\expect( 'wp_enqueue_script' )->never();
Functions\expect( 'wp_enqueue_style' )->never();
$manager->enqueue_scripts();
}
} }
+3 -3
View File
@@ -20,8 +20,8 @@ class LoadOrderTest extends LapsTestCase {
$manager = new Load_Order_Manager(); $manager = new Load_Order_Manager();
$this->assertTrue( has_action( 'pre_update_option_active_plugins', [ $manager, 'pre_update_option_active_plugins' ] ) ); $this->assertTrue( (bool) has_action( 'pre_update_option_active_plugins', [ $manager, 'pre_update_option_active_plugins' ] ) );
$this->assertTrue( has_action( 'pre_update_site_option_active_sitewide_plugins', [ $manager, 'pre_update_option_active_plugins' ] ) ); $this->assertTrue( (bool) has_action( 'pre_update_site_option_active_sitewide_plugins', [ $manager, 'pre_update_option_active_plugins' ] ) );
return $manager; return $manager;
@@ -43,7 +43,7 @@ class LoadOrderTest extends LapsTestCase {
$plugins = $manager->pre_update_option_active_plugins( $plugins ); $plugins = $manager->pre_update_option_active_plugins( $plugins );
$this->assertInternalType( 'array', $plugins ); $this->assertIsArray( $plugins );
$this->assertCount( 3, $plugins ); $this->assertCount( 3, $plugins );
$this->assertEquals( 'laps/laps.php', $plugins[0] ); $this->assertEquals( 'laps/laps.php', $plugins[0] );
} }
+6 -5
View File
@@ -1,14 +1,15 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Manager; namespace Rarst\Laps\Tests\Manager;
use Brain\Monkey\Functions; use Brain\Monkey\Functions;
use Rarst\Laps\Manager\Toolbar_Manager; use Rarst\Laps\Manager\Toolbar_Manager;
use Rarst\Laps\Plugin; use Rarst\Laps\Record\Collector\Lazy_Proxy_Collector;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
/** /**
* @coversDefaultClass Rarst\Laps\Manager\Toolbar_Manager * @coversDefaultClass \Rarst\Laps\Manager\Toolbar_Manager
*/ */
class ToolbarTest extends LapsTestCase { class ToolbarTest extends LapsTestCase {
@@ -19,10 +20,10 @@ class ToolbarTest extends LapsTestCase {
*/ */
public function test__construct() { public function test__construct() {
$laps = new Plugin( [ 'records' => [] ] ); $collector = new Lazy_Proxy_Collector( [] );
$manager = new Toolbar_Manager( $laps ); $manager = new Toolbar_Manager( $collector, new \Mustache_Engine() );
$this->assertTrue( has_action( 'admin_bar_menu', [ $manager, 'admin_bar_menu' ] ) ); $this->assertTrue( (bool) has_action( 'admin_bar_menu', [ $manager, 'admin_bar_menu' ] ) );
return $manager; return $manager;
} }
+8 -2
View File
@@ -2,7 +2,7 @@
namespace Rarst\Laps\Tests; namespace Rarst\Laps\Tests;
use Pimple\ServiceProviderInterface; use Brain\Monkey\Functions;
use Rarst\Laps\Provider\Bootable_Provider_Interface; use Rarst\Laps\Provider\Bootable_Provider_Interface;
class PluginTest extends LapsTestCase { class PluginTest extends LapsTestCase {
@@ -11,7 +11,7 @@ class PluginTest extends LapsTestCase {
$container = new \Rarst\Laps\Plugin(); $container = new \Rarst\Laps\Plugin();
$service = $this->getMockBuilder( [ ServiceProviderInterface::class, Bootable_Provider_Interface::class ] ) $service = $this->getMockBuilder( Bootable_Provider_Interface::class )
->getMock(); ->getMock();
$service->expects( $this->once() ) $service->expects( $this->once() )
@@ -24,6 +24,12 @@ class PluginTest extends LapsTestCase {
->method( 'boot' ) ->method( 'boot' )
->with( $container ); ->with( $container );
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$container->run(); $container->run();
} }
} }
+4
View File
@@ -1,7 +1,9 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Provider; namespace Rarst\Laps\Tests\Provider;
use function Brain\Monkey\Functions\expect;
use Pimple\Exception\FrozenServiceException; use Pimple\Exception\FrozenServiceException;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
use Rarst\Laps\Provider\Manager_Provider; use Rarst\Laps\Provider\Manager_Provider;
@@ -14,6 +16,8 @@ class ManagerTest extends LapsTestCase {
*/ */
public function testProvider() { public function testProvider() {
expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$container = new Plugin(); $container = new Plugin();
$provider = new Manager_Provider(); $provider = new Manager_Provider();
+7
View File
@@ -2,6 +2,7 @@
namespace Rarst\Laps\Tests\Provider; namespace Rarst\Laps\Tests\Provider;
use Brain\Monkey\Functions;
use Pimple\Exception\FrozenServiceException; use Pimple\Exception\FrozenServiceException;
use Rarst\Laps\Plugin; use Rarst\Laps\Plugin;
use Rarst\Laps\Provider\Record_Provider; use Rarst\Laps\Provider\Record_Provider;
@@ -24,6 +25,12 @@ class RecordTest extends LapsTestCase {
$this->assertNotSame( $stopwatch_a, $stopwatch_b ); $this->assertNotSame( $stopwatch_a, $stopwatch_b );
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$this->assertArrayHasKey( 'records', $container ); $this->assertArrayHasKey( 'records', $container );
$container->extend( 'collectors', function () {} ); $container->extend( 'collectors', function () {} );
+5 -2
View File
@@ -2,7 +2,7 @@
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Core_Load_Collector; use Rarst\Laps\Record\Collector\Core_Load_Collector;
use Rarst\Laps\Record\Record_Interface; use Rarst\Laps\Record\Record_Interface;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
@@ -10,11 +10,14 @@ class CoreTest extends LapsTestCase {
public function testCollector() { public function testCollector() {
global $timestart;
$timestart = microtime( true );;
$collector = new Core_Load_Collector(); $collector = new Core_Load_Collector();
$records = $collector->get_records(); $records = $collector->get_records();
$this->assertInternalType( 'array', $records ); $this->assertIsArray( $records );
$this->assertCount( 2, $records ); $this->assertCount( 2, $records );
$this->assertInstanceOf( Record_Interface::class, $records[0] ); $this->assertInstanceOf( Record_Interface::class, $records[0] );
} }
+10 -25
View File
@@ -1,10 +1,11 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Brain\Monkey\Functions; use function Brain\Monkey\Functions\expect;
use Rarst\Laps\Event\Core_Events; use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Record\Hook_Record_Collector; use Rarst\Laps\Record\Collector\Hook_Collector;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
@@ -12,32 +13,16 @@ class HookTest extends LapsTestCase {
public function testCollector() { public function testCollector() {
$stopwatch = new Stopwatch(); expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$collector = new Hook_Record_Collector( $stopwatch, [ 'core' => new Core_Events() ] );
$this->assertTrue( $stopwatch->isStarted( 'Plugins Load' ) ); $stopwatch = new Stopwatch();
$this->assertTrue( has_action( 'after_setup_theme', [ $collector, 'after_setup_theme' ] ) ); $collector = new Hook_Collector( $stopwatch, [ 'core' => new Core_Events() ] );
$this->assertTrue( (bool) has_action( 'after_setup_theme', [ $collector, 'after_setup_theme' ] ) );
$this->assertTrue( (bool) has_action( 'plugins_loaded', 'function ($input)' ) );
$collector->after_setup_theme(); $collector->after_setup_theme();
Functions\expect( 'current_filter' )->once()->andReturn( 'plugins_loaded' );
Functions\expect( 'wp_parse_args' )->once()->andReturnFirstArg();
global $wp_filter;
$wp_hook = $this->getMockBuilder( 'WP_Hook' )
->allowMockingUnknownTypes()
->setMethods( [ 'current_priority' ] )
->getMock();
$wp_hook->method( 'current_priority' )->willReturn( - 2 );
$wp_filter['plugins_loaded'] = $wp_hook;
$collector->tick();
$this->assertFalse( $stopwatch->isStarted( 'Plugins Load' ) );
$stopwatch->start( 'Toolbar' ); $stopwatch->start( 'Toolbar' );
$collector->get_records(); $collector->get_records();
+8 -4
View File
@@ -2,7 +2,8 @@
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Http_Record_Collector; use Brain\Monkey\Functions;
use Rarst\Laps\Record\Collector\Http_Collector;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
@@ -10,12 +11,15 @@ class HttpTest extends LapsTestCase {
public function testCollector() { public function testCollector() {
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
Functions\expect( 'wp_debug_backtrace_summary' )->zeroOrMoreTimes()->andReturn( [] );
$stopwatch = new Stopwatch(); $stopwatch = new Stopwatch();
$url = 'https://example.com/'; $url = 'https://example.com/';
$collector = new Http_Record_Collector( $stopwatch ); $collector = new Http_Collector( $stopwatch );
$this->assertTrue( has_action( 'pre_http_request', [ $collector, 'pre_http_request' ] ) ); $this->assertTrue( (bool) has_action( 'pre_http_request', [ $collector, 'pre_http_request' ] ) );
$this->assertTrue( has_action( 'http_api_debug', [ $collector, 'http_api_debug' ] ) ); $this->assertTrue( (bool) has_action( 'http_api_debug', [ $collector, 'http_api_debug' ] ) );
$collector->pre_http_request( false, [], $url ); $collector->pre_http_request( false, [], $url );
+7 -7
View File
@@ -2,17 +2,17 @@
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Recursive_Record_Iterator; use Rarst\Laps\Record\Iterator\Recursive_Record_Iterator;
use Rarst\Laps\Record\Record; use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Timeline_Iterator; use Rarst\Laps\Record\Iterator\Timeline_Iterator;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
class IteratorTest extends LapsTestCase { class IteratorTest extends LapsTestCase {
/** /**
* @covers \Rarst\Laps\Record\Recursive_Record_Iterator * @covers \Rarst\Laps\Record\Iterator\Recursive_Record_Iterator
* *
* @return Recursive_Record_Iterator * @return \Rarst\Laps\Record\Iterator\Recursive_Record_Iterator
*/ */
public function testRecord() { public function testRecord() {
@@ -41,7 +41,7 @@ class IteratorTest extends LapsTestCase {
} }
/** /**
* @covers \Rarst\Laps\Record\Timeline_Iterator * @covers \Rarst\Laps\Record\Iterator\Timeline_Iterator
* *
* @depends testRecord * @depends testRecord
*/ */
@@ -53,7 +53,7 @@ class IteratorTest extends LapsTestCase {
$this->assertTrue( $iterator->valid() ); $this->assertTrue( $iterator->valid() );
$records = $iterator->current(); $records = $iterator->current();
$this->assertInternalType( 'array', $records ); $this->assertIsArray( $records );
$this->assertArrayHasKey( 'description', $records[0] ); $this->assertArrayHasKey( 'description', $records[0] );
$this->assertArrayHasKey( 'category', $records[0] ); $this->assertArrayHasKey( 'category', $records[0] );
@@ -64,7 +64,7 @@ class IteratorTest extends LapsTestCase {
$this->assertTrue( $iterator->valid() ); $this->assertTrue( $iterator->valid() );
$records = $iterator->current(); $records = $iterator->current();
$this->assertInternalType( 'array', $records ); $this->assertIsArray( $records );
$iterator->next(); $iterator->next();
$this->assertFalse( $iterator->valid() ); $this->assertFalse( $iterator->valid() );
+2 -2
View File
@@ -24,8 +24,8 @@ class RecordTest extends LapsTestCase {
$this->assertEquals( $name, $record->get_name() ); $this->assertEquals( $name, $record->get_name() );
$this->assertEquals( $origin, $record->get_origin() ); $this->assertEquals( $origin, $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() ); $this->assertEquals( $duration, $record->get_duration() );
$this->assertContains( $name, $record->get_description() ); $this->assertStringContainsString( $name, $record->get_description() );
$this->assertContains( (string) ( $duration * 1000 ), $record->get_description() ); $this->assertStringContainsString( (string) ( $duration * 1000 ), $record->get_description() );
$this->assertEmpty( $record->get_category() ); $this->assertEmpty( $record->get_category() );
$record = new Record( $name, $origin, $duration, $description, $category ); $record = new Record( $name, $origin, $duration, $description, $category );
+15 -8
View File
@@ -2,22 +2,29 @@
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Brain\Monkey\Functions;
use Rarst\Laps\Record\Record; use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Sql_Record_Collector; use Rarst\Laps\Record\Collector\Sql_Collector;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
class SqlTest extends LapsTestCase { class SqlTest extends LapsTestCase {
/** /**
* @covers \Rarst\Laps\Record\Sql_Record_Collector * @covers \Rarst\Laps\Record\Collector\Sql_Collector
*/ */
public function testCollector() { public function testCollector() {
define( 'SAVEQUERIES', true ); define( 'SAVEQUERIES', true );
$collector = new Sql_Record_Collector(); if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$this->assertTrue( has_filter( 'query', [ $collector, 'query' ] ) ); $collector = new Sql_Collector();
$this->assertTrue( (bool) has_filter( 'query', [ $collector, 'query' ] ) );
$query = 'SELECT * FROM wp_posts'; $query = 'SELECT * FROM wp_posts';
@@ -27,19 +34,19 @@ class SqlTest extends LapsTestCase {
$wpdb = new \stdClass(); $wpdb = new \stdClass();
$duration = 100; $duration = 100;
$wpdb->queries[] = [ $query, $duration ]; $wpdb->queries[] = [ $query, $duration, 'foo(), bar()' ];
$records = $collector->get_records(); $records = $collector->get_records();
$this->assertInternalType( 'array', $records ); $this->assertIsArray( $records );
$this->assertCount( 1, $records ); $this->assertCount( 1, $records );
$record = $records[0]; $record = $records[0];
$this->assertInstanceOf( Record::class, $record ); $this->assertInstanceOf( Record::class, $record );
$this->assertEquals( $query, $record->get_name() ); $this->assertEquals( $query, $record->get_name() );
$this->assertContains( $query, $record->get_description() ); $this->assertStringContainsString( $query, $record->get_description() );
$this->assertInternalType( 'float', $record->get_origin() ); $this->assertIsFloat( $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() ); $this->assertEquals( $duration, $record->get_duration() );
$this->assertEquals( 'sql-read', $record->get_category() ); $this->assertEquals( 'sql-read', $record->get_category() );
} }
+5 -5
View File
@@ -3,7 +3,7 @@
namespace Rarst\Laps\Tests\Record; namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Stopwatch_Record; use Rarst\Laps\Record\Stopwatch_Record;
use Rarst\Laps\Record\Stopwatch_Record_Collector; use Rarst\Laps\Record\Collector\Stopwatch_Collector;
use Rarst\Laps\Tests\LapsTestCase; use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent; use Symfony\Component\Stopwatch\StopwatchEvent;
@@ -11,11 +11,11 @@ use Symfony\Component\Stopwatch\StopwatchEvent;
class StopwatchTest extends LapsTestCase { class StopwatchTest extends LapsTestCase {
/** /**
* @covers \Rarst\Laps\Record\Stopwatch_Record_Collector * @covers \Rarst\Laps\Record\Collector\Stopwatch_Collector
*/ */
public function testCollector() { public function testCollector() {
$collector = new Stopwatch_Record_Collector( new Stopwatch() ); $collector = new Stopwatch_Collector( new Stopwatch() );
$collector->start( 'Event', 'info' ); $collector->start( 'Event', 'info' );
$collector->start( 'Not stopped', 'info' ); $collector->start( 'Not stopped', 'info' );
@@ -26,7 +26,7 @@ class StopwatchTest extends LapsTestCase {
$records = $collector->get_records(); $records = $collector->get_records();
$this->assertInternalType( 'array', $records ); $this->assertIsArray( $records );
$this->assertCount( 1, $records ); $this->assertCount( 1, $records );
$this->assertInstanceOf( Stopwatch_Record::class, $records[0] ); $this->assertInstanceOf( Stopwatch_Record::class, $records[0] );
@@ -47,6 +47,6 @@ class StopwatchTest extends LapsTestCase {
$this->assertEquals( 'info', $record->get_category() ); $this->assertEquals( 'info', $record->get_category() );
$this->assertEquals( $event->getOrigin() / 1000, $record->get_origin() ); $this->assertEquals( $event->getOrigin() / 1000, $record->get_origin() );
$this->assertEquals( $event->getDuration() / 1000, $record->get_duration() ); $this->assertEquals( $event->getDuration() / 1000, $record->get_duration() );
$this->assertContains( 'Event', $record->get_description() ); $this->assertStringContainsString( 'Event', $record->get_description() );
} }
} }