62 Commits
Author SHA1 Message Date
sandros fad92f739c Fix Hook_Formatter::format to accept WP_Hook object as argument
Tests / tests (push) Successful in 5s
Release / upload-asset (push) Successful in 4s
2026-09-27 23:12:44 +02:00
sandros a30722c751 Add Hungarian language support and update author and repository metadata
Tests / tests (push) Successful in 5s
Release / upload-asset (push) Successful in 4s
2026-09-27 23:08:51 +02:00
sandros 7a0aa56433 Bump version to 3.4.1
Tests / tests (push) Successful in 6s
Release / upload-asset (push) Successful in 5s
2026-09-27 22:50:59 +02:00
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
56 changed files with 998 additions and 914 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
+1
View File
@@ -1,2 +1,3 @@
/vendor
/release
.phpunit.result.cache
+77 -1
View File
@@ -6,6 +6,82 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## 3.4.1 - 2026-09-27
### Added
- Hungarian language support (hu_HU) with .pot, .po, and .mo files
### Changed
- credited sandros & Rarst as authors and updated repository homepage
- adjusted requirements to `Requires at least: 6.2` to support ClassicPress 2.x and WordPress 6.2+
- updated documentation with Hungarian translation
## 3.4.0 - 2026-09-26
### Changed
- raised minimum required PHP version to 7.4
- checked user permissions (`laps_can_see`) before enqueueing assets in `Asset_Manager`
- added Gitea Actions workflows for tests and automated releases
## 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
@@ -17,7 +93,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- tracking of load events when network–activated
### Deprecated
- dedicated classes for extension events.
- dedicated classes for extension events
## 3.1 - 2019-01-22
+45 -30
View File
@@ -1,47 +1,62 @@
![Laps v3 toolbar screenshot](https://i.imgur.com/NtgAxhp.png)
# Laps — light WordPress profiler
# Laps — könnyűsúlyú WordPress profilozó
_Make a site, make it fast._
> *Készíts egy weboldalt, és tedd gyorssá.*
[![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)
[![Download Laps](https://img.shields.io/badge/dynamic/json.svg?label=download&url=https://api.github.com/repos/rarst/laps/releases/latest&query=$.assets%5B0%5D.name)](https://www.rarst.net/download/laps)
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).
Laps is a plugin that **shows performance information** about WordPress page load.
## Főbb funkciók
It provides a visual summary in toolbar that is quick and easy to inspect.
### Oldalprofilozás
A Laps automatikusan méri és megjeleníti a kulcsfontosságú eseményeket:
- PHP, WordPress mag, bővítmények és sablonok betöltési ideje, fő ciklus (loop), oldalsávok (sidebars)
- Adatbázis-lekérdezések és hívási láncok részletes listázása (ha a `SAVEQUERIES` konstans értéke `true`)
- Külső hálózati kérések (HTTP API hívások más oldalakra)
## Page profiling
Laps automatically tracks many events, such as:
- PHP, core, plugins, and themes load, main posts loop, sidebars
- database queries (with [`SAVEQUERIES` constant](http://codex.wordpress.org/Editing_wp-config.php#Save_queries_for_analysis) enabled)
- network requests to other sites
## API profiling
For Ajax and REST API — Laps outputs performance information by Server Timing API, for use with clients such as Chrome Dev Tools.
### 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).
![Laps v3 dev tools screenshot](https://i.imgur.com/hkl1Qk9.png)
### API authentication
### Jogosultságok
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).
API requests need to be authenticated as admin for performance data to be sent. For Ajax requests cookies are sufficient. REST API requests also [need nonce passed](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/#cookie-authentication).
## Telepítés
`laps_can_see` check can be filtered to relax required permissions on the plugin’s side.
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.
## Installation
## Követelmények
- **WordPress / ClassicPress:** WordPress 6.2+ vagy ClassicPress 2.0+ (pl. 2.7.3)
- **PHP:** 7.4 vagy újabb
| [Composer](https://getcomposer.org/) (recommended) | Release archive |
| -------------------------------------------------- | -------- |
| `composer require rarst/laps` | [![Download Laps](https://img.shields.io/badge/dynamic/json.svg?label=download&url=https%3A%2F%2Fapi.github.com%2Frepos%2Frarst%2Flaps%2Freleases%2Flatest&query=%24.assets[0].name&style=for-the-badge)](https://www.rarst.net/download/laps) |
---
## 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
MIT
+89 -16
View File
@@ -1,4 +1,5 @@
<?php
declare( strict_types=1 );
require __DIR__ . '/vendor/autoload.php';
@@ -15,13 +16,13 @@ class RoboFile extends \Robo\Tasks {
public function watch() {
$this->taskWatch()
->monitor( [ 'public/css/variables.less', 'public/css/laps.less' ], function () {
$this->makeCss();
} )
->monitor( 'public/js/source.js', function () {
$this->makeJs();
} )
->run();
->monitor( [ 'public/css/variables.less', 'public/css/laps.less' ], function () {
$this->makeCss();
} )
->monitor( 'public/js/source.js', function () {
$this->makeJs();
} )
->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->taskMinify( 'public/css/laps.css' )
->run();
->run();
}
/**
@@ -54,11 +55,11 @@ class RoboFile extends \Robo\Tasks {
'vendor/twbs/bootstrap/js/tooltip.js',
'public/js/source.js',
] )
->to( 'public/js/laps.js' )
->run();
->to( 'public/js/laps.js' )
->run();
$this->taskMinify( 'public/js/laps.js' )
->run();
->run();
}
/**
@@ -68,14 +69,65 @@ class RoboFile extends \Robo\Tasks {
$dir = __DIR__ . '/src/mustache/cache';
$this->_cleanDir( $dir );
$mustache = new \Mustache_Engine(
array(
[
'loader' => new \Mustache_Loader_FilesystemLoader( __DIR__ . '/src/mustache' ),
'cache' => $dir,
)
]
);
$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.
*
@@ -83,12 +135,13 @@ class RoboFile extends \Robo\Tasks {
*/
public function tag( $version ) {
$this->versionSet( $version );
$this->makeVersion( $version );
$this->taskGitStack()
->stopOnFail()
->add( 'CHANGELOG.md' )
->add( 'laps.php' )
->commit( "Updated header version to {$version}" )
->commit( "Released version $version" )
->tag( $version )
->run();
}
@@ -98,11 +151,31 @@ class RoboFile extends \Robo\Tasks {
*
* @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' )
->regex( '|^Version:.*$|m' )
->to( 'Version: ' . $version )
->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}" ) );
}
}
+21 -8
View File
@@ -3,16 +3,20 @@
"description": "Light WordPress profiler.",
"keywords" : ["wordpress", "performance"],
"type" : "wordpress-plugin",
"homepage" : "https://github.com/Rarst/laps",
"homepage" : "https://git.sandros.hu/sandros/laps",
"license" : "MIT",
"authors" : [
{
"name" : "sandros",
"homepage": "https://git.sandros.hu/sandros"
},
{
"name" : "Andrey Savchenko",
"homepage": "http://www.Rarst.net/"
"homepage": "https://www.Rarst.net/"
}
],
"support" : {
"issues": "https://github.com/Rarst/laps/issues"
"issues": "https://git.sandros.hu/sandros/laps/issues"
},
"autoload" : {
"psr-4": {
@@ -25,16 +29,17 @@
}
},
"require" : {
"php" : "^7.1.3",
"composer/installers": "^1.6",
"php" : "^7.4 || ^8",
"composer/installers": "^1.6 || ^2",
"symfony/stopwatch" : "^4.2.1",
"mustache/mustache" : "^2.12",
"pimple/pimple" : "^3.2.3"
"mustache/mustache" : "^2.14.1",
"pimple/pimple" : "^3.5"
},
"require-dev": {
"twbs/bootstrap": "^3.4",
"brain/monkey" : "^2.2",
"pds/skeleton": "^1.0"
"pds/skeleton": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.5"
},
"extra" : {
"light-ignore-plugin": {
@@ -49,5 +54,13 @@
"Tests"
]
}
},
"config": {
"allow-plugins": {
"composer/installers": true
},
"platform": {
"php": "7.4"
}
}
}
Generated
-704
View File
@@ -1,704 +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#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "185526a596d05cc666426641f175a314",
"packages": [
{
"name": "composer/installers",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/cfcca6b1b60bc4974324efb5783c13dca6932b5b",
"reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b",
"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": "2018-08-27T06:10:37+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/contracts",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/contracts.git",
"reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf",
"reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"require-dev": {
"psr/cache": "^1.0",
"psr/container": "^1.0"
},
"suggest": {
"psr/cache": "When using the Cache contracts",
"psr/container": "When using the Service contracts",
"symfony/cache-contracts-implementation": "",
"symfony/service-contracts-implementation": "",
"symfony/translation-contracts-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\": ""
},
"exclude-from-classmap": [
"**/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A set of abstractions extracted out of the Symfony components",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"time": "2018-12-05T08:06:11+00:00"
},
{
"name": "symfony/stopwatch",
"version": "v4.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "ec076716412274e51f8a7ea675d9515e5c311123"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/ec076716412274e51f8a7ea675d9515e5c311123",
"reference": "ec076716412274e51f8a7ea675d9515e5c311123",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/contracts": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-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-11-11T19:52:12+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.2.0",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "100633629bf76d57430b86b7098cd6beb996a35a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a",
"reference": "100633629bf76d57430b86b7098cd6beb996a35a",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "~2.0",
"lib-pcre": ">=7.0",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.7.10|~6.5|~7.0"
},
"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",
"homepage": "https://github.com/mockery/mockery",
"keywords": [
"BDD",
"TDD",
"library",
"mock",
"mock objects",
"mockery",
"stub",
"test",
"test double",
"testing"
],
"time": "2018-10-02T21:52:37+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.4.0",
"source": {
"type": "git",
"url": "https://github.com/twbs/bootstrap.git",
"reference": "4c547f2175b9a05aa43551f6927da83fe249ce42"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/4c547f2175b9a05aa43551f6927da83fe249ce42",
"reference": "4c547f2175b9a05aa43551f6927da83fe249ce42",
"shasum": ""
},
"replace": {
"twitter/bootstrap": "self.version"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4.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": "https://getbootstrap.com/",
"keywords": [
"JS",
"css",
"framework",
"front-end",
"less",
"mobile-first",
"responsive",
"web"
],
"time": "2018-12-13T23:45:51+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.1.3"
},
"platform-dev": []
}
Binary file not shown.
+150
View File
@@ -0,0 +1,150 @@
# Hungarian translation for Laps.
# Copyright (C) 2026 sandros & Rarst
# This file is distributed under the MIT license.
#
msgid ""
msgstr ""
"Project-Id-Version: Laps 3.4.1\n"
"Report-Msgid-Bugs-To: https://git.sandros.hu/sandros/laps/issues\n"
"POT-Creation-Date: 2026-09-27 23:05+0200\n"
"PO-Revision-Date: 2026-09-27 23:06+0200\n"
"Last-Translator: sandros <me@sandros.hu>\n"
"Language-Team: Hungarian <hu_HU@li.org>\n"
"Language: hu_HU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. Description of the plugin
#: laps.php:5
msgid "Light WordPress profiler."
msgstr "Könnyűsúlyú WordPress profilozó."
#. translators: %s: page load time in seconds
#: src/Manager/Toolbar_Manager.php:51
msgid "Lap: %ss"
msgstr "Kör: %ss"
#. translators: %s: PHP version
#: src/Record/Collector/Core_Load_Collector.php:40
msgid "PHP Load – %s"
msgstr "PHP betöltés – %s"
#: src/Record/Collector/Core_Load_Collector.php:48
msgid "OPcache"
msgstr "OPcache"
#: src/Record/Collector/Core_Load_Collector.php:51
msgid "Core Load"
msgstr "WordPress mag betöltése"
#: src/Record/Collector/Core_Load_Collector.php:51
msgid "Core & MU Plugins Load"
msgstr "WordPress mag és MU bővítmények betöltése"
#: src/Record/Collector/Hook_Collector.php:46
msgid "Network & MU Plugins Load"
msgstr "Hálózati és MU bővítmények betöltése"
#: src/Event/Core_Events.php:16
msgid "Plugins Load"
msgstr "Bővítmények betöltése"
#: src/Event/Core_Events.php:17
msgid "Plugins Loaded Hook"
msgstr "Plugins Loaded hook"
#: src/Event/Core_Events.php:18
msgid "Themes Load"
msgstr "Sablonok betöltése"
#: src/Event/Core_Events.php:19
msgid "Init Hook"
msgstr "Init hook"
#: src/Event/Core_Events.php:20
msgid "WP Loaded Hook"
msgstr "WP Loaded hook"
#: src/Event/Core_Events.php:21
msgid "Query Setup"
msgstr "Lekérdezés előkészítése"
#: src/Event/Core_Events.php:22
msgid "Template Loader"
msgstr "Sablon betöltő"
#: src/Event/Core_Events.php:23
msgid "Admin Init Hook"
msgstr "Admin Init hook"
#: src/Event/Core_Events.php:24
msgid "Admin Menu"
msgstr "Admin menü"
#: src/Event/Core_Events.php:25
msgid "Admin Print Scripts"
msgstr "Admin scriptek betöltése"
#: src/Event/Core_Events.php:26
msgid "Head Hook"
msgstr "Fejléc hook (wp_head)"
#: src/Event/Core_Events.php:27
msgid "Main Loop"
msgstr "Fő ciklus (loop)"
#: src/Event/Core_Events.php:28
msgid "Sidebar"
msgstr "Oldalsáv (sidebar)"
#: src/Event/Core_Events.php:29
msgid "Toolbar"
msgstr "Eszköztár (toolbar)"
#: src/Event/Core_Events.php:30
msgid "Footer Hook"
msgstr "Lábléc hook (wp_footer)"
#. translators: 1: file path, 2: line number
#: src/Formatter/Hook_Formatter.php:94
msgid "closure from %1$s:%2$d"
msgstr "closure innen: %1$s:%2$d"
#. translators: 1: file path, 2: line number
#: src/Formatter/Hook_Formatter.php:107
msgid "anonymous class from %1$s:%2$d"
msgstr "névtelen osztály innen: %1$s:%2$d"
#: src/Event/Extension_Events.php:28
msgid "Beaver Builder Post Content"
msgstr "Beaver Builder bejegyzéstartalom"
#: src/Event/Extension_Events.php:33
msgid "Beaver Builder Module"
msgstr "Beaver Builder modul"
#: src/Event/Extension_Events.php:40
msgid "Beaver Builder Top Bar"
msgstr "Beaver Builder felső sáv"
#: src/Event/Extension_Events.php:41
msgid "Beaver Builder Header"
msgstr "Beaver Builder fejléc"
#: src/Event/Extension_Events.php:42
msgid "Beaver Builder Page Content"
msgstr "Beaver Builder oldaltartalom"
#: src/Event/Extension_Events.php:43
msgid "Beaver Builder Post"
msgstr "Beaver Builder bejegyzés"
#: src/Event/Extension_Events.php:44
msgid "Beaver Builder Footer Widgets"
msgstr "Beaver Builder lábléc widgetek"
#: src/Event/Extension_Events.php:45
msgid "Beaver Builder Footer"
msgstr "Beaver Builder lábléc"
+148
View File
@@ -0,0 +1,148 @@
# Copyright (C) 2026 sandros & Rarst
# This file is distributed under the MIT license.
msgid ""
msgstr ""
"Project-Id-Version: Laps 3.4.1\n"
"Report-Msgid-Bugs-To: https://git.sandros.hu/sandros/laps/issues\n"
"POT-Creation-Date: 2026-09-27 23:05+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2026-YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. Description of the plugin
#: laps.php:5
msgid "Light WordPress profiler."
msgstr ""
#. translators: %s: page load time in seconds
#: src/Manager/Toolbar_Manager.php:51
msgid "Lap: %ss"
msgstr ""
#. translators: %s: PHP version
#: src/Record/Collector/Core_Load_Collector.php:40
msgid "PHP Load – %s"
msgstr ""
#: src/Record/Collector/Core_Load_Collector.php:48
msgid "OPcache"
msgstr ""
#: src/Record/Collector/Core_Load_Collector.php:51
msgid "Core Load"
msgstr ""
#: src/Record/Collector/Core_Load_Collector.php:51
msgid "Core & MU Plugins Load"
msgstr ""
#: src/Record/Collector/Hook_Collector.php:46
msgid "Network & MU Plugins Load"
msgstr ""
#: src/Event/Core_Events.php:16
msgid "Plugins Load"
msgstr ""
#: src/Event/Core_Events.php:17
msgid "Plugins Loaded Hook"
msgstr ""
#: src/Event/Core_Events.php:18
msgid "Themes Load"
msgstr ""
#: src/Event/Core_Events.php:19
msgid "Init Hook"
msgstr ""
#: src/Event/Core_Events.php:20
msgid "WP Loaded Hook"
msgstr ""
#: src/Event/Core_Events.php:21
msgid "Query Setup"
msgstr ""
#: src/Event/Core_Events.php:22
msgid "Template Loader"
msgstr ""
#: src/Event/Core_Events.php:23
msgid "Admin Init Hook"
msgstr ""
#: src/Event/Core_Events.php:24
msgid "Admin Menu"
msgstr ""
#: src/Event/Core_Events.php:25
msgid "Admin Print Scripts"
msgstr ""
#: src/Event/Core_Events.php:26
msgid "Head Hook"
msgstr ""
#: src/Event/Core_Events.php:27
msgid "Main Loop"
msgstr ""
#: src/Event/Core_Events.php:28
msgid "Sidebar"
msgstr ""
#: src/Event/Core_Events.php:29
msgid "Toolbar"
msgstr ""
#: src/Event/Core_Events.php:30
msgid "Footer Hook"
msgstr ""
#. translators: 1: file path, 2: line number
#: src/Formatter/Hook_Formatter.php:94
msgid "closure from %1$s:%2$d"
msgstr ""
#. translators: 1: file path, 2: line number
#: src/Formatter/Hook_Formatter.php:107
msgid "anonymous class from %1$s:%2$d"
msgstr ""
#: src/Event/Extension_Events.php:28
msgid "Beaver Builder Post Content"
msgstr ""
#: src/Event/Extension_Events.php:33
msgid "Beaver Builder Module"
msgstr ""
#: src/Event/Extension_Events.php:40
msgid "Beaver Builder Top Bar"
msgstr ""
#: src/Event/Extension_Events.php:41
msgid "Beaver Builder Header"
msgstr ""
#: src/Event/Extension_Events.php:42
msgid "Beaver Builder Page Content"
msgstr ""
#: src/Event/Extension_Events.php:43
msgid "Beaver Builder Post"
msgstr ""
#: src/Event/Extension_Events.php:44
msgid "Beaver Builder Footer Widgets"
msgstr ""
#: src/Event/Extension_Events.php:45
msgid "Beaver Builder Footer"
msgstr ""
+14 -7
View File
@@ -2,13 +2,16 @@
/*
Plugin Name: Laps
Plugin URI: https://github.com/Rarst/laps
Plugin URI: https://git.sandros.hu/sandros/laps
Description: Light WordPress profiler.
Author: Andrey “Rarst” Savchenko
Version: 3.2
Author URI: https://www.rarst.net/
Author: sandros & Rarst
Author URI: https://git.sandros.hu/sandros/laps
Version: 3.4.1
Requires at least: 6.2
Requires PHP: 7.4
Update URI: false
Text Domain: laps
Domain Path: /lang
Domain Path: /languages
License: MIT
*/
@@ -20,10 +23,14 @@ 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 < 70103 ) {
trigger_error( 'Laps requires PHP version >=7.1.3.', E_USER_ERROR );
if ( PHP_VERSION_ID < 70400 ) {
trigger_error( 'Laps requires PHP version >=7.4.', E_USER_ERROR );
}
add_action( 'plugins_loaded', function (): void {
load_plugin_textdomain( 'laps', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
} );
$laps = new \Rarst\Laps\Plugin();
$laps->run();
+4 -24
View File
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<psalm
totallyTyped="true"
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"/>
@@ -16,28 +16,8 @@
<issueHandlers>
<LessSpecificReturnType errorLevel="info"/>
<UndefinedFunction errorLevel="suppress"/>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="WP_Admin_Bar"/>
<referencedClass name="WP_Error"/>
<referencedClass name="WP_Hook"/>
</errorLevel>
</UndefinedClass>
<MixedArgument>
<errorLevel type="suppress">
<file name="src/Provider/Record_Provider.php" />
<file name="src/Record/Collector/Hook_Collector.php" />
</errorLevel>
</MixedArgument>
<MissingClosureParamType>
<errorLevel type="suppress">
<file name="src/Record/Collector/Hook_Collector.php" />
</errorLevel>
</MissingClosureParamType>
<MissingClosureReturnType>
<errorLevel type="suppress">
<file name="src/Record/Collector/Hook_Collector.php" />
</errorLevel>
</MissingClosureReturnType>
<UndefinedClass errorLevel="suppress"/>
<UndefinedDocblockClass errorLevel="suppress"/>
<UndefinedConstant errorLevel="suppress"/>
</issueHandlers>
</psalm>
+1 -1
View File
@@ -9,7 +9,7 @@ namespace Rarst\Laps\Event;
class Core_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+2 -2
View File
@@ -9,7 +9,7 @@ namespace Rarst\Laps\Event;
class Extension_Events implements Hook_Event_Config_Interface {
/**
* @return array[]
* @inheritDoc
*/
public function get_events(): array {
@@ -19,7 +19,7 @@ class Extension_Events implements Hook_Event_Config_Interface {
/**
* Beaver Builder events.
*
* @return array
* @return array[]
*/
private function beaver_builder(): array {
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Rarst\Laps\Event;
class Genesis_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Rarst\Laps\Event;
class Hybrid_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Rarst\Laps\Event;
class THA_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Rarst\Laps\Event;
class WooCommerce_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Rarst\Laps\Event;
class Yoast_Events implements Hook_Event_Config_Interface {
/**
* @return array
* @inheritDoc
*/
public function get_events(): array {
+2 -3
View File
@@ -76,13 +76,12 @@ class Backtrace_Formatter {
}
/**
*
* @psalm-suppress PossiblyFalseOperand
*
* @param string $item Backtrace item.
*
* @return string
*
* @psalm-suppress PossiblyFalseOperand
* @psalm-suppress MixedInferredReturnType
*/
protected function shorten_include( string $item ): string {
+55 -15
View File
@@ -3,6 +3,8 @@ declare( strict_types=1 );
namespace Rarst\Laps\Formatter;
use WP_Hook;
/**
* Formatter to process callbacks from a hook instance.
*/
@@ -16,6 +18,7 @@ class Hook_Formatter {
*/
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 ),
@@ -24,7 +27,14 @@ class Hook_Formatter {
}
/**
* @param \WP_Hook|array $hook Hook instance.
* @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 or callbacks array.
* @psalm-param WP_Hook|Hooks $hook
*
* @return array
*/
@@ -32,14 +42,35 @@ class Hook_Formatter {
$callbacks = [];
if ( $hook instanceof \WP_Hook ) {
if ( $hook instanceof WP_Hook ) {
/** @psalm-var Hooks $hook->callbacks */
$hook = $hook->callbacks;
}
if ( empty( $hook ) ) {
return $callbacks;
}
ksort( $hook );
/**
* Empty priorities produce empty arrays in PHP 7.2 and unpack operator doesn't accept empty array in 7.3.
*
* @see https://wiki.php.net/rfc/compact-empty-array-de-optimization
* @see https://bugs.php.net/bug.php?id=76045
*/
$hook = array_filter( $hook );
if ( empty( $hook ) ) {
return $callbacks;
}
/** @psalm-suppress InvalidArgument */
$functions = array_merge( ...$hook );
/** @psalm-var Hook[] $functions */
foreach ( $functions as $function ) {
/** @psalm-var Hook $function */
$callback = $this->get_callback_name( $function['function'], $function['accepted_args'] );
if ( false !== strpos( $callback, 'Hook_Collector' ) ) {
@@ -55,27 +86,27 @@ class Hook_Formatter {
/**
* @param string|object|array $callback Hook callback.
* @param int $args Number of accepted arguments.
*
* @return string
*/
protected function get_callback_name( $callback, int $args ): string {
switch ( gettype( $callback ) ) {
case 'object':
$callback = $this->get_class_name( $callback );
$name = $this->get_class_name( $callback );
break;
case 'array':
$class = is_string( $callback[0] )
/** @psalm-var array{0: object|string, 1: string} $callback */
$class = is_string( $callback[0] )
? $callback[0] . '::'
: $this->get_class_name( $callback[0] ) . '->';
$callback = $class . $callback[1];
$name = $class . $callback[1];
break;
default:
$name = $callback;
}
$callback .= ( 1 === $args ) ? '' : "({$args})";
return $callback;
return $name . ( ( 1 === $args ) ? '' : "({$args})" );
}
/**
@@ -85,17 +116,27 @@ class Hook_Formatter {
*/
protected function get_class_name( $object ): string {
if ( is_a( $object, 'CLosure' ) ) {
if ( $object instanceof \Closure ) {
$class = new \ReflectionFunction( $object );
return 'closure from ' . $this->shorten_path( $class->getFileName() ) . ':' . $class->getStartLine();
return sprintf(
/* translators: 1: file path, 2: line number */
__( 'closure from %1$s:%2$d', 'laps' ),
$this->shorten_path( (string) $class->getFileName() ),
(int) $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 sprintf(
/* translators: 1: file path, 2: line number */
__( 'anonymous class from %1$s:%2$d', 'laps' ),
$this->shorten_path( (string) $class->getFileName() ),
(int) $class->getStartLine()
);
}
return $name;
@@ -109,7 +150,7 @@ class Hook_Formatter {
protected function shorten_path( string $path ): string {
$path = wp_normalize_path( $path );
$path = str_replace( $this->truncate_paths, '', $path );
if ( ':' === $path[1] ) {
if ( isset( $path[1] ) && ':' === $path[1] ) {
$path = substr( $path, 2 );
}
$path = ltrim( $path, '/' );
@@ -117,4 +158,3 @@ class Hook_Formatter {
return $path;
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ class Asset_Manager {
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__ ) ) );
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_style( 'laps' );
}
-2
View File
@@ -21,8 +21,6 @@ class Load_Order_Manager {
* Reorder active plugins so Laps is first and starts timing load early.
*
* @param array $plugins Filtered array of active plugins’ basenames.
*
* @return array
*/
public function pre_update_option_active_plugins( array $plugins ): array {
+7 -2
View File
@@ -57,14 +57,19 @@ class Server_Timing_Manager {
foreach ( $this->collector->get_records() as $record ) {
$duration = $record->get_duration() * 1000;
if ( $duration < 1 ) {
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;
}
}
header( 'Server-Timing: ' . preg_replace( '/\R/', '', $header ) );
// Sanitize to visible US ASCII character range (32-126).
header( 'Server-Timing: ' . preg_replace( '/[^\x20-\x7E]/', '', $header ) );
return $input;
}
+2 -3
View File
@@ -36,8 +36,6 @@ class Toolbar_Manager {
* Render interface and add to the toolbar.
*
* @param \WP_Admin_Bar $wp_admin_bar WordPress core toolbar object.
*
* @psalm-suppress MixedArgument
*/
public function admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ): void {
@@ -49,7 +47,8 @@ class Toolbar_Manager {
$wp_admin_bar->add_node( [
'id' => 'laps',
'title' => sprintf( 'Lap: %ss', round( microtime( true ) - $timestart, 3 ) ),
/* translators: %s: page load time in seconds */
'title' => sprintf( __( 'Lap: %ss', 'laps' ), round( microtime( true ) - $timestart, 3 ) ),
] );
$wp_admin_bar->add_node( [
-2
View File
@@ -15,8 +15,6 @@ class Mustache_Cache_FrozenCache extends \Mustache_Cache_FilesystemCache {
* @param string $value Value.
*
* @return void
*
* @psalm-suppress InvalidScalarArgument
*/
public function cache( $key, $value ): void {
+1 -5
View File
@@ -20,9 +20,7 @@ class Plugin extends Container {
/**
* @param array $values Configuration values to apply.
*
* @psalm-suppress MixedArgument
* @psalm-suppress MixedAssignment
* @psalm-param array<string, mixed> $values
*/
public function __construct( array $values = [] ) {
@@ -63,8 +61,6 @@ class Plugin extends Container {
/**
* Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks.
*
* @psalm-suppress MixedAssignment
*/
public function run(): void {
+2 -1
View File
@@ -3,12 +3,13 @@ declare( strict_types=1 );
namespace Rarst\Laps\Provider;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Plugin;
/**
* Interface for providers that need to boot up once plugin is ready to start.
*/
interface Bootable_Provider_Interface {
interface Bootable_Provider_Interface extends ServiceProviderInterface {
/**
* @param Plugin $laps Container instance.
+2
View File
@@ -20,6 +20,8 @@ class Hook_Event_Provider implements ServiceProviderInterface {
/**
* @param Container $pimple Container instance.
*
* @psalm-suppress DeprecatedClass
*/
public function register( Container $pimple ): void {
+6 -4
View File
@@ -10,6 +10,7 @@ use Rarst\Laps\Record\Collector\Core_Load_Collector;
use Rarst\Laps\Record\Collector\Hook_Collector;
use Rarst\Laps\Record\Collector\Http_Collector;
use Rarst\Laps\Record\Collector\Lazy_Proxy_Collector;
use Rarst\Laps\Record\Collector\Plugin_Load_Collector;
use Rarst\Laps\Record\Collector\Sql_Collector;
use Symfony\Component\Stopwatch\Stopwatch;
@@ -29,10 +30,11 @@ class Record_Provider implements ServiceProviderInterface, Bootable_Provider_Int
$pimple['collectors'] = function ( Plugin $laps ): array {
return [
'core' => new Core_Load_Collector(),
'hook' => new Hook_Collector( $laps['stopwatch'], $laps['hook.events'] ),
'http' => new Http_Collector( $laps['stopwatch'] ),
'sql' => new Sql_Collector(),
'core' => new Core_Load_Collector(),
'hook' => new Hook_Collector( $laps['stopwatch'], $laps['hook.events'] ),
'plugins' => new Plugin_Load_Collector(),
'http' => new Http_Collector( $laps['stopwatch'] ),
'sql' => new Sql_Collector(),
];
};
+30 -5
View File
@@ -27,6 +27,8 @@ class Core_Load_Collector implements Record_Collector_Interface {
/**
* @return Record_Interface[]
*
* @psalm-suppress PossiblyUndefinedArrayOffset
*/
public function get_records(): array {
@@ -34,14 +36,19 @@ class Core_Load_Collector implements Record_Collector_Interface {
$request_time = filter_var( $_SERVER['REQUEST_TIME_FLOAT'], FILTER_VALIDATE_FLOAT );
$php = 'PHP Load – ' . PHP_VERSION;
/* translators: %s: PHP version */
$php = sprintf( __( 'PHP Load – %s', 'laps' ), PHP_VERSION );
if ( function_exists( 'opcache_get_status' ) ) {
$zend_status = opcache_get_status();
$php .= empty( $zend_status['opcache_enabled'] ) ? '' : ' – OPcache';
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', 'laps' );
}
$load = $this->network_activated ? 'Core Load' : 'Core & MU Plugins Load';
$load = $this->network_activated ? __( 'Core Load', 'laps' ) : __( 'Core & MU Plugins Load', 'laps' );
/**
* @var float $request_time
@@ -52,4 +59,22 @@ class Core_Load_Collector implements Record_Collector_Interface {
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 );
}
}
+15 -5
View File
@@ -20,13 +20,19 @@ class Hook_Collector extends Stopwatch_Collector {
/** @var array $events */
protected $events = [];
/** @var array */
/**
* @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. */
/**
* @var array $calls Tracks number of calls for recurrent events.
* @psalm-var array<string, int> $calls
*/
private $calls = [];
/**
@@ -55,7 +61,7 @@ class Hook_Collector extends Stopwatch_Collector {
/**
* Time the point between network/MU plugins and regular if network activated.
*/
public function muplugins_loaded() {
public function muplugins_loaded(): void {
$this->stop( 'Network & MU Plugins Load' );
$this->start( 'Plugins Load', 'plugin' );
}
@@ -76,7 +82,6 @@ class Hook_Collector extends Stopwatch_Collector {
foreach ( $stops as $key => $data ) {
if ( is_int( $key ) ) {
/** @var array{0:string} $data */
$this->add_event( ...$data );
continue;
}
@@ -97,6 +102,8 @@ class Hook_Collector extends Stopwatch_Collector {
* @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,
@@ -112,15 +119,17 @@ class Hook_Collector extends Stopwatch_Collector {
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 ) {
global $wp_filter;
/** @psalm-var array<string, \WP_Hook> $wp_filter */
$this->callbacks[ $event ] = $wp_filter[ $start ];
}
@@ -136,6 +145,7 @@ class Hook_Collector extends Stopwatch_Collector {
$event .= $this->get_count_suffix( $event );
if ( 'Sidebar' === $event ) {
/** @var string $input */
$event = $input;
}
+20 -9
View File
@@ -16,7 +16,10 @@ class Http_Collector extends Stopwatch_Collector {
/** @var Backtrace_Formatter */
private $formatter;
/** @var array */
/**
* @var array $callers
* @psalm-var array<string, string> $callers
*/
private $callers = [];
/**
@@ -39,12 +42,17 @@ class Http_Collector extends Stopwatch_Collector {
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return boolean
* @return false|array|\WP_Error
*
* @psalm-suppress RedundantCondition
* @noinspection PhpUnusedParameterInspection
*/
public function pre_http_request( $false, array $args, string $url ) {
public function pre_http_request( $false, $args, $url ) {
$this->start( $url, 'http' );
$this->callers[ $url ] = wp_debug_backtrace_summary( __CLASS__, 5 );
if ( $url && is_string( $url ) ) {
$this->start( $url, 'http' );
$this->callers[ $url ] = wp_debug_backtrace_summary( __CLASS__, 5 );
}
return $false;
}
@@ -58,13 +66,16 @@ class Http_Collector extends Stopwatch_Collector {
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*
* @return mixed
* @return array|\WP_Error
*
* @noinspection MoreThanThreeArgumentsInspection
* @psalm-suppress RedundantCondition
* @noinspection PhpUnusedParameterInspection
*/
public function http_api_debug( $response, string $type, $class, array $args, string $url ) {
public function http_api_debug( $response, $type, $class, $args, $url ) {
$this->stop( $url );
if ( $url && is_string( $url ) ) {
$this->stop( $url );
}
return $response;
}
@@ -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'
);
}
}
+14 -11
View File
@@ -8,6 +8,8 @@ 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 {
@@ -25,7 +27,7 @@ class Sql_Collector implements Record_Collector_Interface {
$this->formatter = new Backtrace_Formatter();
if ( $this->is_savequeries() ) {
add_filter( 'query', [ $this, 'query' ], 20 ); // TODO refactor when core provides time start data in 5.0.
add_filter( 'query', [ $this, 'query' ], 20 ); // TODO Drop recording time if dropping support for WP <5.1.
}
}
@@ -35,8 +37,6 @@ class Sql_Collector implements Record_Collector_Interface {
* @param string $query SQL query.
*
* @return string
*
* @psalm-suppress MixedPropertyFetch
*/
public function query( $query ): string {
@@ -64,7 +64,11 @@ class Sql_Collector implements Record_Collector_Interface {
}
/** @var array $wpdb->queries */
return array_map( [ $this, 'transform' ], array_keys( $wpdb->queries ), $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;
}
/**
@@ -80,25 +84,24 @@ class Sql_Collector implements Record_Collector_Interface {
*
* @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} $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 {
static $last_query_end = 0;
protected function transform( int $key, array $query_data ): ?Record {
[ $sql, $duration, $caller ] = $query_data;
/** @var float $query_start */
$query_start = $this->query_starts[ $key ] ?? $last_query_end;
$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';
}
$last_query_end = $query_start + $duration;
$desc_duration = round( $duration * 1000 );
$backtrace = $this->formatter->format( $caller );
$description = $sql . ' – ' . $desc_duration . 'ms<hr />' . implode( '<br />', $backtrace );
@@ -51,6 +51,7 @@ class Stopwatch_Collector implements Record_Collector_Interface {
*/
public function get_records(): array {
/** @psalm-var array<string, StopwatchEvent> $events */
$events = $this->stopwatch->getSectionEvents( '__root__' );
/** @var string $name */
@@ -60,6 +61,7 @@ class Stopwatch_Collector implements Record_Collector_Interface {
}
}
/** @psalm-suppress PossiblyInvalidArgument */
return array_map( [ $this, 'transform' ], array_keys( $events ), $events );
}
@@ -7,6 +7,11 @@ use Rarst\Laps\Record\Record_Interface;
/**
* 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 {
@@ -15,7 +20,7 @@ class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIter
/**
* @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, int $flags = 0 ) {
@@ -67,10 +72,10 @@ class Recursive_Record_Iterator extends \ArrayIterator implements \RecursiveIter
}
/**
* @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 );
}
}
+9 -6
View File
@@ -5,12 +5,13 @@ namespace Rarst\Laps\Record\Iterator;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Record_Interface;
use Rarst\Laps\Record\Iterator\Recursive_Record_Iterator;
/**
* Processes records into sets for display by template.
*
* @psalm-suppress PropertyNotSetInConstructor
* @template-implements \Iterator<array>
*
* @psalm-suppress PropertyNotSetInConstructor, TooManyTemplateParams
*/
class Timeline_Iterator implements \Iterator {
@@ -29,7 +30,7 @@ class Timeline_Iterator implements \Iterator {
/**
* @param Recursive_Record_Iterator $iterator Record iterator.
*
* @psalm-suppress PossiblyFalsePropertyAssignmentValue
* @psalm-suppress PossiblyFalsePropertyAssignmentValue, PossiblyUndefinedArrayOffset
*/
public function __construct( Recursive_Record_Iterator $iterator ) {
@@ -60,12 +61,14 @@ class Timeline_Iterator implements \Iterator {
}
/**
* Key is unused and is only type hinted to possible `int` for PHP 8.1 `mixed` type compatibility.
*
* @codeCoverageIgnore
*
* @return void
* @return null
*/
public function key(): void {
public function key(): ?int {
return null;
}
/**
+2 -1
View File
@@ -52,8 +52,9 @@ class Record implements Record_Interface {
if ( empty( $this->description ) ) {
$duration = round( $this->get_duration() * 1000 );
$name = __( $this->name, 'laps' );
return "{$this->name} – {$duration}&nbsp;ms";
return "{$name} – {$duration}&nbsp;ms";
}
return $this->description;
+2 -1
View File
@@ -45,8 +45,9 @@ class Stopwatch_Record implements Record_Interface {
$duration = round( $this->stopwatch_event->getDuration() );
$memory = round( $this->stopwatch_event->getMemory() / 1024 / 1024, 2 );
$description = '' !== $this->description ? "<hr />{$this->description}" : '';
$name = __( $this->name, 'laps' );
return "{$this->name} – {$duration} ms – {$memory} MB{$description}";
return "{$name} – {$duration} ms – {$memory} MB{$description}";
}
/**
+6 -6
View File
@@ -17,7 +17,7 @@ class EventTest extends LapsTestCase {
$events = ( new Core_Events() )->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
@@ -29,7 +29,7 @@ class EventTest extends LapsTestCase {
Functions\expect( 'genesis' )->zeroOrMoreTimes();
$events = $genesis->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
@@ -46,7 +46,7 @@ class EventTest extends LapsTestCase {
$events = $hybrid->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
@@ -59,7 +59,7 @@ class EventTest extends LapsTestCase {
$events = $tha->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
@@ -72,7 +72,7 @@ class EventTest extends LapsTestCase {
$events = $woocommerce->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
@@ -85,7 +85,7 @@ class EventTest extends LapsTestCase {
$events = $yoast->get_events();
$this->assertInternalType( 'array', $events );
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
}
+4 -2
View File
@@ -51,8 +51,10 @@ class BacktraceTest extends LapsTestCase {
$result = $formatter->format( $backtrace );
$this->assertInternalType( 'array', $result );
$this->assertArraySubset( [ 'foo', 'path.php' ], array_values( $result ) );
$this->assertIsArray( $result );
$result = array_values( $result );
$this->assertEquals( 'foo', $result[0] );
$this->assertEquals( 'path.php', $result[1] );
}
}
+27 -3
View File
@@ -50,10 +50,34 @@ class HookTest extends LapsTestCase {
$result = $formatter->format( $hook );
$this->assertInternalType( 'array', $result );
$this->assertArraySubset( [ 'function(2)', 'class::method' ], $result );
$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( [] ) );
}
/**
* @depends test__construct
*/
public function testFormatWpHook( Hook_Formatter $formatter ) {
if ( ! class_exists( 'WP_Hook' ) ) {
eval( 'class WP_Hook { public $callbacks = []; }' );
}
$wp_hook = new \WP_Hook();
$wp_hook->callbacks = [
10 => [
[ 'function' => 'trim', 'accepted_args' => 1 ],
],
];
$result = $formatter->format( $wp_hook );
$this->assertIsArray( $result );
$this->assertEquals( [ 'trim' ], $result );
}
}
+3 -2
View File
@@ -10,12 +10,13 @@ class LapsTestCase extends TestCase {
use MockeryPHPUnitIntegration;
protected function setUp() {
protected function setUp():void {
parent::setUp();
Monkey\setUp();
Monkey\Functions\stubTranslationFunctions();
}
protected function tearDown() {
protected function tearDown(): void {
Monkey\tearDown();
parent::tearDown();
}
+26 -2
View File
@@ -20,8 +20,8 @@ class AssetTest extends LapsTestCase {
$manager = new Asset_Manager();
$this->assertTrue( has_action( 'wp_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
$this->assertTrue( has_action( 'admin_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
$this->assertTrue( (bool) has_action( 'wp_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
$this->assertTrue( (bool) has_action( 'admin_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
return $manager;
}
@@ -36,9 +36,33 @@ class AssetTest extends LapsTestCase {
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( true );
Functions\expect( 'wp_enqueue_script' )->once();
Functions\expect( 'wp_enqueue_style' )->once();
$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();
$this->assertTrue( 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_option_active_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;
@@ -43,7 +43,7 @@ class LoadOrderTest extends LapsTestCase {
$plugins = $manager->pre_update_option_active_plugins( $plugins );
$this->assertInternalType( 'array', $plugins );
$this->assertIsArray( $plugins );
$this->assertCount( 3, $plugins );
$this->assertEquals( 'laps/laps.php', $plugins[0] );
}
+1 -1
View File
@@ -23,7 +23,7 @@ class ToolbarTest extends LapsTestCase {
$collector = new Lazy_Proxy_Collector( [] );
$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;
}
+1 -2
View File
@@ -3,7 +3,6 @@
namespace Rarst\Laps\Tests;
use Brain\Monkey\Functions;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Provider\Bootable_Provider_Interface;
class PluginTest extends LapsTestCase {
@@ -12,7 +11,7 @@ class PluginTest extends LapsTestCase {
$container = new \Rarst\Laps\Plugin();
$service = $this->getMockBuilder( [ ServiceProviderInterface::class, Bootable_Provider_Interface::class ] )
$service = $this->getMockBuilder( Bootable_Provider_Interface::class )
->getMock();
$service->expects( $this->once() )
+1 -1
View File
@@ -17,7 +17,7 @@ class CoreTest extends LapsTestCase {
$records = $collector->get_records();
$this->assertInternalType( 'array', $records );
$this->assertIsArray( $records );
$this->assertCount( 2, $records );
$this->assertInstanceOf( Record_Interface::class, $records[0] );
}
+2 -2
View File
@@ -18,9 +18,9 @@ class HookTest extends LapsTestCase {
$stopwatch = new Stopwatch();
$collector = new Hook_Collector( $stopwatch, [ 'core' => new Core_Events() ] );
$this->assertTrue( has_action( 'after_setup_theme', [ $collector, 'after_setup_theme' ] ) );
$this->assertTrue( (bool) has_action( 'after_setup_theme', [ $collector, 'after_setup_theme' ] ) );
$this->assertTrue( has_action( 'plugins_loaded', 'function ($input)' ) );
$this->assertTrue( (bool) has_action( 'plugins_loaded', 'function ($input)' ) );
$collector->after_setup_theme();
$stopwatch->start( 'Toolbar' );
+2 -2
View File
@@ -18,8 +18,8 @@ class HttpTest extends LapsTestCase {
$url = 'https://example.com/';
$collector = new Http_Collector( $stopwatch );
$this->assertTrue( 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( 'pre_http_request', [ $collector, 'pre_http_request' ] ) );
$this->assertTrue( (bool) has_action( 'http_api_debug', [ $collector, 'http_api_debug' ] ) );
$collector->pre_http_request( false, [], $url );
+2 -2
View File
@@ -53,7 +53,7 @@ class IteratorTest extends LapsTestCase {
$this->assertTrue( $iterator->valid() );
$records = $iterator->current();
$this->assertInternalType( 'array', $records );
$this->assertIsArray( $records );
$this->assertArrayHasKey( 'description', $records[0] );
$this->assertArrayHasKey( 'category', $records[0] );
@@ -64,7 +64,7 @@ class IteratorTest extends LapsTestCase {
$this->assertTrue( $iterator->valid() );
$records = $iterator->current();
$this->assertInternalType( 'array', $records );
$this->assertIsArray( $records );
$iterator->next();
$this->assertFalse( $iterator->valid() );
+2 -2
View File
@@ -24,8 +24,8 @@ class RecordTest extends LapsTestCase {
$this->assertEquals( $name, $record->get_name() );
$this->assertEquals( $origin, $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() );
$this->assertContains( $name, $record->get_description() );
$this->assertContains( (string) ( $duration * 1000 ), $record->get_description() );
$this->assertStringContainsString( $name, $record->get_description() );
$this->assertStringContainsString( (string) ( $duration * 1000 ), $record->get_description() );
$this->assertEmpty( $record->get_category() );
$record = new Record( $name, $origin, $duration, $description, $category );
+4 -4
View File
@@ -24,7 +24,7 @@ class SqlTest extends LapsTestCase {
$collector = new Sql_Collector();
$this->assertTrue( has_filter( 'query', [ $collector, 'query' ] ) );
$this->assertTrue( (bool) has_filter( 'query', [ $collector, 'query' ] ) );
$query = 'SELECT * FROM wp_posts';
@@ -38,15 +38,15 @@ class SqlTest extends LapsTestCase {
$records = $collector->get_records();
$this->assertInternalType( 'array', $records );
$this->assertIsArray( $records );
$this->assertCount( 1, $records );
$record = $records[0];
$this->assertInstanceOf( Record::class, $record );
$this->assertEquals( $query, $record->get_name() );
$this->assertContains( $query, $record->get_description() );
$this->assertInternalType( 'float', $record->get_origin() );
$this->assertStringContainsString( $query, $record->get_description() );
$this->assertIsFloat( $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() );
$this->assertEquals( 'sql-read', $record->get_category() );
}
+2 -2
View File
@@ -26,7 +26,7 @@ class StopwatchTest extends LapsTestCase {
$records = $collector->get_records();
$this->assertInternalType( 'array', $records );
$this->assertIsArray( $records );
$this->assertCount( 1, $records );
$this->assertInstanceOf( Stopwatch_Record::class, $records[0] );
@@ -47,6 +47,6 @@ class StopwatchTest extends LapsTestCase {
$this->assertEquals( 'info', $record->get_category() );
$this->assertEquals( $event->getOrigin() / 1000, $record->get_origin() );
$this->assertEquals( $event->getDuration() / 1000, $record->get_duration() );
$this->assertContains( 'Event', $record->get_description() );
$this->assertStringContainsString( 'Event', $record->get_description() );
}
}