201 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
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
Andrey Savchenko c874c19c91 Updated documentation for v2 release. 2018-02-26 10:42:11 +02:00
Andrey Savchenko 221a902671 Adjusted SQL and default colors. 2018-02-26 09:17:39 +02:00
Andrey Savchenko ac0a187ec4 Excluded mustache cache from code coverage. 2018-02-23 17:06:53 +02:00
Andrey Savchenko 06d2136528 Dropped flat style from badges. 2018-02-23 17:06:37 +02:00
Andrey Savchenko beb68aede2 Added explicit PHP version requirement and updated badges. 2018-02-23 16:38:15 +02:00
Andrey Savchenko b837508181 Updated event colors. 2018-02-23 16:28:55 +02:00
Andrey Savchenko d92a778d10 Adjusted structure for PDS skeleton. 2018-02-23 15:32:18 +02:00
Andrey Savchenko 4238509861 Added PHP and core load collector. 2018-02-23 15:12:29 +02:00
Andrey Savchenko e74777c2a9 Added generic constructor–centric record class. 2018-02-23 14:40:45 +02:00
Andrey Savchenko 3cae2765cb Fixed stopwatch record duration test that didn’t test properly. 2018-02-22 18:19:34 +02:00
Andrey Savchenko 35e8caf1e4 Fixed stopwatch record test for origin time. 2018-02-22 18:04:00 +02:00
Andrey Savchenko 7cc65ca1a2 Fixed stacking of events that are just very close to each other. 2018-02-22 18:01:46 +02:00
Andrey Savchenko 1e75033f25 Normalized on operating in float seconds rather than milliseconds. 2018-02-22 13:14:15 +02:00
Andrey Savchenko 624e282905 Added container test. 2018-02-22 12:34:47 +02:00
Andrey Savchenko 56e9447dce Renamed manager tests for consistency. 2018-02-22 12:25:14 +02:00
Andrey Savchenko 560eb85f4b Added provider tests. 2018-02-22 12:23:47 +02:00
Andrey Savchenko 801f6585cb Added missing namespace in test. 2018-02-21 16:16:57 +02:00
Andrey Savchenko 61dd387484 Added SQL and iterators tests. 2018-02-21 16:12:34 +02:00
Andrey Savchenko 420305143b Added generic stopwatch collector and tests. 2018-02-20 18:09:44 +02:00
Andrey Savchenko 7020e84b2d Added hook event tests. 2018-02-19 16:50:38 +02:00
Andrey Savchenko 5ec0f6c677 Added manager tests. 2018-02-19 16:12:01 +02:00
Andrey Savchenko 1bf5e5c5ad Set up unit tests. 2018-02-16 20:19:16 +02:00
Andrey Savchenko 8cd280f12a Added inline documentation. 2018-02-14 17:54:28 +02:00
Andrey Savchenko d850fa1b02 Renamed main class to Plugin for more clear role. 2018-02-13 18:22:35 +02:00
Andrey Savchenko a19e4fb019 Moved timeline iterator to record namespace. 2018-02-13 18:18:36 +02:00
Andrey Savchenko 53efcd28af Added keys to collectors array. 2018-02-13 18:16:41 +02:00
Andrey Savchenko c74051e87d Adjusted hook events for a simpler interface and wrapped in a provider. 2018-02-13 17:26:41 +02:00
Andrey Savchenko 06e3bb4657 Added proper providers to handle registration and boot. 2018-02-13 11:37:54 +02:00
Andrey Savchenko 48b3383e06 Introduced explicit record interface for event results. 2018-02-12 18:30:00 +02:00
Andrey Savchenko ce7cf8eade Reorganized code to designate events as input and records as results. 2018-02-12 17:12:58 +02:00
Andrey Savchenko 67d686f601 Split functionality from container to manager classes. 2018-02-12 17:02:17 +02:00
Andrey Savchenko 2c074c1cea Simplified processing nested events to a single pass. 2018-02-12 15:53:40 +02:00
Andrey Savchenko a9484ed4ee Added timeline iterator for automatic splitting of nested events. 2018-02-09 18:47:13 +02:00
Andrey Savchenko bee765071b Implemented initial handling of arbitrary nesting events.
See #11
2018-02-09 15:15:23 +02:00
Andrey Savchenko baf5d293a1 Fixed screen reader text. 2018-02-08 21:17:25 +02:00
Andrey Savchenko 36e0d14c48 Fixed empty timelines filter. 2018-02-08 19:11:14 +02:00
Andrey Savchenko 228ea99b43 Dropped any events that aren't properly stopped. 2018-02-08 19:10:57 +02:00
Andrey Savchenko 79e197c4cc Moved another leftover action. 2018-02-08 18:36:31 +02:00
Andrey Savchenko 8396888ee2 Moved leftover event stop to the provider. 2018-02-08 18:10:01 +02:00
Andrey Savchenko d8febfeb3a Moved SQL events to a provider and simplified template. 2018-02-08 18:08:01 +02:00
Andrey Savchenko 31b80d0413 Adjusted from returning raw Stopwatch event objects from providers. 2018-02-08 17:07:35 +02:00
Andrey Savchenko c93249875b Changed Stopwatch service to a factory. 2018-02-08 16:20:30 +02:00
Andrey Savchenko 054bf2e0df Moved HTTP requests events to a provider. 2018-02-08 16:08:39 +02:00
Andrey Savchenko 267f389a40 Moved hooks events to a provider. 2018-02-08 15:00:57 +02:00
Andrey Savchenko 39f03e02bf Added provider interfaces. 2018-02-08 14:58:43 +02:00
Andrey Savchenko 6fc8a648ad Made main class into Pimple container. 2018-02-08 14:29:10 +02:00
Andrey Savchenko b709f968f9 Moved events classes. 2018-02-08 10:57:20 +02:00
Andrey Savchenko c966fb1552 Dropped Thematic events as the theme is obsolete. 2018-02-07 22:55:51 +02:00
Andrey Savchenko 62ea1f4246 Changed main class to concrete instance. 2018-02-07 22:08:41 +02:00
Andrey Savchenko 9527ad1508 Bumped Stopwatch version and updated dependencies. 2018-02-07 21:57:19 +02:00
Andrey Savchenko a77dd9e4c8 Renamed class files for PSR-4. 2018-02-07 21:53:22 +02:00
Andrey Savchenko 03ff2b61ff Updated header version to 1.4.4 2017-07-05 21:45:39 +03:00
Andrey Savchenko 3c0ae866e0 Added Robo tag task and updated changelog for 1.4.4
Fixes #10
2017-07-05 21:45:27 +03:00
Andrey Savchenko d2bece68e5 Added sanity check for invalid priority returned by core
Fixes #9
2017-06-28 14:25:45 +03:00
Andrey Savchenko f50faf6ba6 Added downloads badge to readme. 2016-12-06 21:32:31 +02:00
Andrey Savchenko bca3f42ee5 Updated version and change log for 1.4.2 release. 2016-12-02 19:18:58 +02:00
Andrey Savchenko 68302633c0 Restored functionality on WP 4.7 beta with new current_priority() method. 2016-12-02 19:18:41 +02:00
Andrey Savchenko 67ad1370ef Added Scrutinizer badge to readme. 2016-12-01 22:10:39 +02:00
Andrey Savchenko 4b7ee062c8 Updated change log and version for release 1.4.1. 2016-12-01 20:35:19 +02:00
Andrey Savchenko 2e7a948bfe Added access modifier to methods. 2016-12-01 20:34:15 +02:00
Andrey Savchenko 68ae657962 Added check for Toolbar event start before closing it. 2016-12-01 20:33:55 +02:00
Andrey Savchenko 3edaf2dbc9 Added check for hook priority failing on WP 4.7. 2016-12-01 20:30:48 +02:00
Andrey Savchenko fa155f00e7 Fixed return type hint. 2016-12-01 20:30:15 +02:00
Andrey Savchenko 82b3d1e4e3 Moved license to a separate file. 2016-12-01 20:29:52 +02:00
Andrey Savchenko 0984debd79 Release 1.4 2016-10-01 20:17:36 +03:00
Andrey Savchenko 2ddbd04b29 Colorized SQL writes as warnings. 2016-10-01 20:16:17 +03:00
Andrey Savchenko a16eebd026 Corrected change log heading markup. 2016-10-01 15:34:47 +03:00
Andrey Savchenko 274d294a65 Added screen reader text to events. 2016-10-01 15:29:06 +03:00
Andrey Savchenko ec5adbf47a Recompiled assets. 2016-10-01 14:59:11 +03:00
Andrey Savchenko b26ea48216 Updated dependencies. 2016-10-01 14:58:55 +03:00
Andrey Savchenko 711371c226 Removed release Robo task. 2016-10-01 14:52:48 +03:00
Andrey Savchenko 72ff60fe6e Updated change log to keepachangelog format. 2016-10-01 14:51:22 +03:00
Andrey Savchenko 357753f84b Updated changelog and version for 1.3.2 2015-08-27 14:06:08 +03:00
Andrey Savchenko 9d1dddb462 Normalized slashing on adding files to release zip file. 2015-08-27 14:05:48 +03:00
Andrey Savchenko d88a566aee Fixed dynamic method call to prevent crash on PHP 7. 2015-08-27 14:05:19 +03:00
Andrey Savchenko b976b26454 Updated version in header 2015-08-15 21:06:35 +03:00
Andrey Savchenko a00b11f78a Updated changelog for 1.3.1 2015-08-12 00:15:24 +03:00
Andrey Savchenko 32f300af7f Added check to catch event stops without matching starts
Fixes #7
2015-08-12 00:14:33 +03:00
89 changed files with 4148 additions and 1456 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 /vendor
/release /release
.phpunit.result.cache
+224 -9
View File
@@ -1,30 +1,245 @@
# Changelog # Change Log
All notable changes to this project will be documented in this file.
## 1.3 The format is based on [Keep a Changelog](http://keepachangelog.com/)
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
- 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
### Added
- unified processing of events from arbitrary sources
- unit tests
- tracking PHP and WP core load times
### Changed
- minimum PHP version requirement to >=5.6
- class files names to PSR–4
- main class into concrete instance and Pimple container
- timeline to unified processing with as necessary nesting
- directory structure to PDS skeleton
- baseline timings to float in seconds (from milliseconds)
- color scheme
### Removed
- Thematic hook events
### Fixed
- handling or not properly started/stopped hook events
## 1.4.4 - 2017-07-05
### Added
- Robo task to update header version and tag in Git
### Fixed
- version in header
## 1.4.3 - 2017-06-28
### Fixed
- sanity check for invalid priority returned by core
## 1.4.2 - 2016-12-02
### Fixed
- back to fully functional on WP 4.7 beta
## 1.4.1 - 2016-12-01
### Fixed
- errors and crash on WP 4.7 beta (not fully functional yet)
## 1.4 - 2016-10-01
### Added
- added screen reader text to events
- colorized writing SQL queries to warning color
### Changed
- updated change log format to Keep a Changelog
- updated dependencies (Stopwatch 2.8.11, Mustache 2.11.1, Bootstrap 3.3.7)
- updated assets
### Removed
- removed release Robo task
## 1.3.2 - 2015-08-27
### Fixed
- fixed dynamic method call crash on PHP 7
- fixed issue with slashes in generated release archives
## 1.3.1 - 2015-08-12
### Fixed
- fixed crash on event stop without matching start
## 1.3 - 2015-06-12
### Added
- implemented minified assets - implemented minified assets
- implemented pre-cached template - implemented pre-cached template
- fixed crash with upcoming Hybrid Core v3
- implemented release build command - implemented release build command
- split plugins_loaded hook into separate event
- added WP SEO event - added WP SEO event
## 1.2.1 ### Changed
- split plugins_loaded hook into separate event
### Fixed
- fixed crash with upcoming Hybrid Core v3
## 1.2.1 - 2014-07-12
### Fixed
- disabled tooltip animation to fix JS errors and UI disappearing from under cursor - disabled tooltip animation to fix JS errors and UI disappearing from under cursor
## 1.2 ## 1.2 - 2014-05-18
- merged RP with additional core events in admin ### Added
- implemented (ballpark) toolbar event - implemented (ballpark) toolbar event
### Changed
- merged RP with additional core events in admin
### Fixed
- fixed issue with event offsets in timelines - fixed issue with event offsets in timelines
## 1.1 ## 1.1 - 2013-12-11
### Added
- added SQL timeline - added SQL timeline
- added HTTP timeline - added HTTP timeline
### Changed
- switched tooltips implementation to Bootstrap script - switched tooltips implementation to Bootstrap script
## 1.0 ## 1.0 - 2013-04-22
### Added
- Initial public release - Initial public release
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013 Andrey Savchenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+45 -29
View File
@@ -1,46 +1,62 @@
# Laps — light WordPress profiler ![Laps v3 toolbar screenshot](https://i.imgur.com/NtgAxhp.png)
![Laps screenshot](http://i.imgur.com/zFokmkU.png) # Laps — könnyűsúlyú WordPress profilozó
Laps is light WordPress profiler (in a plugin), which aims to be: > *Készíts egy weboldalt, és tedd gyorssá.*
- always on and zero click (just hover on toolbar entry) away 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).
- quick indicator of what hogs page in general or right now
It is no match or replacement for *real* profiler, but is friendly and cute. ## Főbb funkciók
Out of the box Laps supports common stage of WordPress page life cycle: ### 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)
- plugins load ### API mérés (Ajax & REST API)
- themes load 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).
- core init
- main loop
And some of third party hooks conventions for themes using: ![Laps v3 dev tools screenshot](https://i.imgur.com/hkl1Qk9.png)
- [Theme Hook Alliance](http://zamoose.github.io/themehookalliance/) ### Jogosultságok
- [Hybrid](http://themehybrid.com/) 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).
- [Genesis](http://my.studiopress.com/themes/genesis/)
- [Thematic](http://thematictheme.com/)
There are also additional optional timelines, displaying: ## Telepítés
- SQL queries (with [`SAVEQUERIES` constant](http://codex.wordpress.org/Editing_wp-config.php#Save_queries_for_analysis) enabled) 1. Töltsd le a legfrissebb `laps.zip` archívumot a **Releases** oldalról.
- HTTP requests performed 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
Download plugin archive from [releases section](https://github.com/Rarst/laps/releases). ---
Or install in plugin directory via [Composer](https://getcomposer.org/): ## English
composer create-project rarst/laps --no-dev # Laps — Light WordPress Profiler
## License Info > *Make a site, make it fast.*
Laps own code is licensed under MIT and it makes use of code from: Laps is a lightweight plugin that displays real-time page load and performance information directly in the admin toolbar.
- Composer Installers (MIT) ### Features
- Symfony Stopwatch (MIT) - **Page profiling:** Automatically tracks PHP baseline, core initialization, plugin and theme load times, the main query loop, and sidebars.
- Mustache.php (MIT) - **Database queries:** Detailed query logs and execution times (when `SAVEQUERIES` is defined and set to `true`).
- Twitter Bootstrap (MIT) - **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
+88 -65
View File
@@ -1,4 +1,5 @@
<?php <?php
declare( strict_types=1 );
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
@@ -15,10 +16,10 @@ class RoboFile extends \Robo\Tasks {
public function watch() { public function watch() {
$this->taskWatch() $this->taskWatch()
->monitor( [ 'css/variables.less', 'css/laps.less' ], function () { ->monitor( [ 'public/css/variables.less', 'public/css/laps.less' ], function () {
$this->makeCss(); $this->makeCss();
} ) } )
->monitor( 'js/source.js', function () { ->monitor( 'public/js/source.js', function () {
$this->makeJs(); $this->makeJs();
} ) } )
->run(); ->run();
@@ -39,9 +40,9 @@ class RoboFile extends \Robo\Tasks {
*/ */
public function makeCss() { public function makeCss() {
$this->taskExec( 'lessc css/laps.less css/laps.css --source-map=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( 'css/laps.css' ) $this->taskMinify( 'public/css/laps.css' )
->run(); ->run();
} }
@@ -52,12 +53,12 @@ class RoboFile extends \Robo\Tasks {
$this->taskConcat( [ $this->taskConcat( [
'vendor/twbs/bootstrap/js/tooltip.js', 'vendor/twbs/bootstrap/js/tooltip.js',
'js/source.js', 'public/js/source.js',
] ) ] )
->to( 'js/laps.js' ) ->to( 'public/js/laps.js' )
->run(); ->run();
$this->taskMinify( 'js/laps.js' ) $this->taskMinify( 'public/js/laps.js' )
->run(); ->run();
} }
@@ -65,30 +66,84 @@ class RoboFile extends \Robo\Tasks {
* Compiles plugin's mustache template * Compiles plugin's mustache template
*/ */
public function makeMustache() { public function makeMustache() {
$dir = __DIR__ . '/views/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__ . '/views' ), 'loader' => new \Mustache_Loader_FilesystemLoader( __DIR__ . '/src/mustache' ),
'cache' => $dir, 'cache' => $dir,
) ]
); );
$mustache->loadTemplate( 'laps' ); $mustache->loadTemplate( 'laps' );
} }
/** /**
* @return string * Creates release zip
*
* @param string $php PHP version to target.
*/ */
private function versionGet() { public function makeArchive( $php = '7.4' ): void {
$content = file_get_contents( __DIR__ . '/laps.php' ); $composer = json_decode( file_get_contents( __DIR__ . '/composer.json' ) );
$package = $composer->name;
if ( false !== preg_match( '|^Version: (?P<version>.+)$|m', $content, $matches ) ) { [ $vendor, $name ] = explode( '/', $package );
return trim( $matches['version'] );
if ( empty( $vendor ) || empty( $name ) ) {
return;
} }
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.
*
* @param string $version Version string.
*/
public function tag( $version ) {
$this->makeVersion( $version );
$this->taskGitStack()
->stopOnFail()
->add( 'CHANGELOG.md' )
->add( 'laps.php' )
->commit( "Released version $version" )
->tag( $version )
->run();
} }
/** /**
@@ -96,7 +151,12 @@ 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' )
@@ -104,55 +164,18 @@ class RoboFile extends \Robo\Tasks {
->run(); ->run();
} }
/** public function upload() {
* Creates release zip $version = $this->latestTag();
* $zipFile = __DIR__ . '/release/laps.zip';
* @param string $version
*/
public function makeRelease( $version = '' ) {
if ( empty( $version ) ) { $this->makeArchive();
$version = $this->versionGet(); $this->_exec("gh release create $version $zipFile");
$this->_remove( $zipFile );
} }
$this->taskFileSystemStack() private function latestTag(): string {
->mkdir( 'release' ) $commit = escapeshellarg( exec( 'git rev-list --tags --max-count=1' ) );
->run();
$this->taskCleanDir( 'release' )->run(); return escapeshellarg( exec( "git describe --tags {$commit}" ) );
$this->taskExec( 'composer' )
->dir( __DIR__ . '/release' )
->arg( 'create-project rarst/laps laps ' . $version )
->arg( '--prefer-dist --no-dev' )
->run();
$this->taskExec( 'composer' )
->dir( __DIR__ . '/release/laps' )
->arg( 'dump-autoload --optimize' )
->run();
$zipFile = "release/laps-{$version}.zip";
$zipArchive = new ZipArchive();
if ( ! $zipArchive->open( $zipFile, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) ) {
die( "Failed to create archive\n" );
}
$finder = new Symfony\Component\Finder\Finder();
$finder->files()->in( 'release/laps' )->ignoreDotFiles( false );
/** @var \Symfony\Component\Finder\SplFileInfo $file */
foreach ( $finder as $file ) {
$zipArchive->addFile( $file->getRealPath(), 'laps/' . $file->getRelativePathname() );
}
if ( ! $zipArchive->status === ZIPARCHIVE::ER_OK ) {
echo "Failed to write files to zip\n";
}
$zipArchive->close();
$this->taskDeleteDir( 'release/laps' )->run();
} }
} }
+46 -9
View File
@@ -3,27 +3,64 @@
"description": "Light WordPress profiler.", "description": "Light WordPress profiler.",
"keywords" : ["wordpress", "performance"], "keywords" : ["wordpress", "performance"],
"type" : "wordpress-plugin", "type" : "wordpress-plugin",
"homepage" : "https://github.com/Rarst/laps", "homepage" : "https://git.sandros.hu/sandros/laps",
"license" : "MIT", "license" : "MIT",
"authors" : [ "authors" : [
{
"name" : "sandros",
"homepage": "https://git.sandros.hu/sandros"
},
{ {
"name" : "Andrey Savchenko", "name" : "Andrey Savchenko",
"email" : "contact@rarst.net", "homepage": "https://www.Rarst.net/"
"homepage": "http://www.Rarst.net/"
} }
], ],
"support" : { "support" : {
"issues": "https://github.com/Rarst/laps/issues" "issues": "https://git.sandros.hu/sandros/laps/issues"
}, },
"autoload" : { "autoload" : {
"classmap": ["src/"] "psr-4": {
"Rarst\\Laps\\" : "src/"
}
},
"autoload-dev": {
"psr-4": {
"Rarst\\Laps\\Tests\\": "tests/"
}
}, },
"require" : { "require" : {
"composer/installers": "~1.0", "php" : "^7.4 || ^8",
"symfony/stopwatch" : "~2.4", "composer/installers": "^1.6 || ^2",
"mustache/mustache" : "~2.4" "symfony/stopwatch" : "^4.2.1",
"mustache/mustache" : "^2.14.1",
"pimple/pimple" : "^3.5"
}, },
"require-dev": { "require-dev": {
"twbs/bootstrap": "~3.2" "twbs/bootstrap": "^3.4",
"brain/monkey" : "^2.2",
"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
-260
View File
@@ -1,260 +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"
],
"hash": "7495421e8df337988c9906e05aab637b",
"packages": [
{
"name": "composer/installers",
"version": "v1.0.21",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "d64e23fce42a4063d63262b19b8e7c0f3b5e4c45"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/d64e23fce42a4063d63262b19b8e7c0f3b5e4c45",
"reference": "d64e23fce42a4063d63262b19b8e7c0f3b5e4c45",
"shasum": ""
},
"replace": {
"roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
"composer/composer": "1.0.*@dev",
"phpunit/phpunit": "4.1.*"
},
"type": "composer-installer",
"extra": {
"class": "Composer\\Installers\\Installer",
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Composer\\Installers\\": "src/"
}
},
"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": "http://composer.github.com/installers/",
"keywords": [
"Craft",
"Dolibarr",
"Hurad",
"MODX Evo",
"OXID",
"SMF",
"Thelia",
"WolfCMS",
"agl",
"aimeos",
"annotatecms",
"bitrix",
"cakephp",
"chef",
"codeigniter",
"concrete5",
"croogo",
"dokuwiki",
"drupal",
"elgg",
"fuelphp",
"grav",
"installer",
"joomla",
"kohana",
"laravel",
"lithium",
"magento",
"mako",
"mediawiki",
"modulework",
"moodle",
"phpbb",
"piwik",
"ppi",
"puppet",
"roundcube",
"shopware",
"silverstripe",
"symfony",
"typo3",
"wordpress",
"zend",
"zikula"
],
"time": "2015-02-18 17:17:01"
},
{
"name": "mustache/mustache",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/mustache.php.git",
"reference": "64c28cc73e01d5edcef7de8cb2b16782615f648a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/64c28cc73e01d5edcef7de8cb2b16782615f648a",
"reference": "64c28cc73e01d5edcef7de8cb2b16782615f648a",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"require-dev": {
"fabpot/php-cs-fixer": "~1.6",
"phpunit/phpunit": "~3.7|~4.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": "2015-04-01 06:37:33"
},
{
"name": "symfony/stopwatch",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
"reference": "7702945bceddc0e1f744519abb8a2baeb94bd5ce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Stopwatch/zipball/7702945bceddc0e1f744519abb8a2baeb94bd5ce",
"reference": "7702945bceddc0e1f744519abb8a2baeb94bd5ce",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Stopwatch\\": ""
}
},
"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": "2015-05-02 15:21:08"
}
],
"packages-dev": [
{
"name": "twbs/bootstrap",
"version": "v3.3.4",
"source": {
"type": "git",
"url": "https://github.com/twbs/bootstrap.git",
"reference": "a10eb60bc0b07b747fa0c4ebd8821eb7307bd07f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/a10eb60bc0b07b747fa0c4ebd8821eb7307bd07f",
"reference": "a10eb60bc0b07b747fa0c4ebd8821eb7307bd07f",
"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": "2015-03-16 15:44:41"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
-1
View File
@@ -1 +0,0 @@
{"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/tooltip.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":"AAKA,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;ECRC,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,yBAAA;EACA,kBAAA;ECsCA,sDAAA;EACQ,8CAAA;EFjCR,aAAA;EACA,kBAAA;;AAhCF,WA6BC,eAKC;ECHA,WAAA;EACA,SAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;EACA,cAAA;EACA,kBAAA;EACA,yBAAA;ECyBA,sDAAA;EACQ,8CAAA;EAyHR,mCAAA;EACK,8BAAA;EACG,2BAAA;EFvJP,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,WGCA;EACE,kBAAA;EACA,eAAA;EACA,cAAA;EAEA,aCiCwB,8CDjCxB;EACA,eAAA;EACA,mBAAA;EACA,gBAAA;EEXA,UAAA;EAGA,wBAAA;;AFWA,WAXF,SAWG;EEdD,YAAA;EAGA,yBAAA;;AFYA,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;;AHhBhC,WGoBA;EACE,gBAAA;EACA,gBAAA;EACA,cAAA;EACA,kBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;;AH3BF,WG+BA;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;;AH9FJ,WA4DC,SAAQ;EK9DP,UAAA;EAGA,0BAAA;;ALDF,WAgEC;EACC,iBAAA;EACA,gBAAA","file":"laps.css"}
-1
View File
@@ -1 +0,0 @@
#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:#ffffff;text-align:center;background-color:#5bc0de;-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-theme{background-color:#5cb85c}#wpadminbar .laps-timeline .event-plugin{background-color:#428bca}#wpadminbar .laps-timeline .event-core{background-color:#f0ad4e}#wpadminbar .laps-timeline .event-query{background-color:#5bc0de}#wpadminbar .laps-timeline .event-http{background-color:#d9534f}#wpadminbar .tooltip{position:absolute;z-index:100000;display:block;font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-size:12px;font-weight:normal;line-height:1.4;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:#cccccc;text-align:center;text-decoration:none;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}
-5
View File
@@ -1,5 +0,0 @@
+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.t$=null;this.init('tooltip',t,e)};e.VERSION='3.3.4';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,s){this.enabled=!0;this.type=e;this.t$=t(i);this.options=this.getOptions(s);this.o$=this.options.viewport&&t(this.options.viewport.selector||this.options.viewport);if(this.t$[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 n=r.length;n--;){var o=r[n];if(o=='click'){this.t$.on('click.'+this.type,this.options.selector,t.proxy(this.toggle,this))}
else if(o!='manual'){var a=o=='hover'?'mouseenter':'focusin',l=o=='hover'?'mouseleave':'focusout';this.t$.on(a+'.'+this.type,this.options.selector,t.proxy(this.enter,this));this.t$.on(l+'.'+this.type,this.options.selector,t.proxy(this.leave,this))}};this.options.selector?(this.i$=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.t$.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.i$&&t.each(this.i$,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.e$&&i.e$.is(':visible')){i.hoverState='in';return};if(!i){i=new this.constructor(e.currentTarget,this.getDelegateOptions());t(e.currentTarget).data('bs.'+this.type,i)};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.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)};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 u=t.Event('show.bs.'+this.type);if(this.hasContent()&&this.enabled){this.t$.trigger(u);var d=t.contains(this.t$[0].ownerDocument.documentElement,this.t$[0]);if(u.isDefaultPrevented()||!d)return;var s=this,o=this.tip(),c=this.getUID(this.type);this.setContent();o.attr('id',c);this.t$.attr('aria-describedby',c);if(this.options.animation)o.addClass('fade');var i=typeof this.options.placement=='function'?this.options.placement.call(this,o[0],this.t$[0]):this.options.placement,f=/\s?auto?\s?/i,h=f.test(i);if(h)i=i.replace(f,'')||'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.t$);var n=this.getPosition(),a=o[0].offsetWidth,l=o[0].offsetHeight;if(h){var g=i,y=this.options.container?t(this.options.container):this.t$.parent(),r=this.getPosition(y);i=i=='bottom'&&n.bottom+l>r.bottom?'top':i=='top'&&n.top-l<r.top?'bottom':i=='right'&&n.right+a>r.width?'left':i=='left'&&n.left-a<r.left?'right':i;o.removeClass(g).addClass(i)};var v=this.getCalculatedOffset(i,n,a,l);this.applyPlacement(v,i);var p=function(){var t=s.hoverState;s.t$.trigger('shown.bs.'+s.type);s.hoverState=null;if(t=='out')s.leave(s)};t.support.transition&&this.e$.hasClass('fade')?o.one('bsTransitionEnd',p).emulateTransitionEnd(e.TRANSITION_DURATION):p()}};e.prototype.applyPlacement=function(e,o){var i=this.tip(),u=i[0].offsetWidth,p=i[0].offsetHeight,r=parseInt(i.css('margin-top'),10),l=parseInt(i.css('margin-left'),10);if(isNaN(r))r=0;if(isNaN(l))l=0;e.top=e.top+r;e.left=e.left+l;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,n=i[0].offsetHeight;if(o=='top'&&n!=p){e.top=e.top+p-n};var s=this.getViewportAdjustedDelta(o,e,h,n);if(s.left)e.left+=s.left;else e.top+=s.top;var a=/top|bottom/.test(o),f=a?s.left*2-u+h:s.top*2-p+n,c=a?'offsetWidth':'offsetHeight';i.offset(e);this.replaceArrow(f,i[0][c],a)};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 s=this,o=t(this.e$),r=t.Event('hide.bs.'+this.type);function n(){if(s.hoverState!='in')o.detach();s.t$.removeAttr('aria-describedby').trigger('hidden.bs.'+s.type);i&&i()};this.t$.trigger(r);if(r.isDefaultPrevented())return;o.removeClass('in');t.support.transition&&o.hasClass('fade')?o.one('bsTransitionEnd',n).emulateTransitionEnd(e.TRANSITION_DURATION):n();this.hoverState=null;return this};e.prototype.fixTitle=function(){var t=this.t$;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.getTitle()};e.prototype.getPosition=function(e){e=e||this.t$;var s=e[0],o=s.tagName=='BODY',i=s.getBoundingClientRect();if(i.width==null){i=t.extend({},i,{width:i.right-i.left,height:i.bottom-i.top})};var n=o?{top:0,left:0}:e.offset();var a={scroll:o?document.documentElement.scrollTop||document.body.scrollTop:e.scrollTop()};var r=o?{width:t(window).width(),height:t(window).height()}:null;retuLine truncated
else if(r>e.top+e.height){o.top=e.top+e.height-r}}
else{var p=i.left-s,a=i.left+s+n;if(p<e.left){o.left=e.left-p}
else if(a>e.width){o.left=e.left+e.width-a}};return o};e.prototype.getTitle=function(){var i,e=this.t$,t=this.options;i=e.attr('data-original-title')||(typeof t.title=='function'?t.title.call(e[0]):t.title);return i};e.prototype.getUID=function(t){do t+=~~(Math.random()*1000000);while(document.getElementById(t))return t};e.prototype.tip=function(){return(this.e$=this.e$||t(this.options.template))};e.prototype.arrow=function(){return(this.s$=this.s$||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)}};i.tip().hasClass('in')?i.leave(i):i.enter(i)};e.prototype.destroy=function(){var t=this;clearTimeout(this.timeout);this.hide(function(){t.t$.off('.'+t.type).removeData('bs.'+t.type)})};function i(i){return this.each(function(){var s=t(this),o=s.data('bs.tooltip'),n=typeof i=='object'&&i;if(!o&&/destroy|hide/.test(i))return;if(!o)s.data('bs.tooltip',(o=new e(this,n)));if(typeof i=='string')o[i]()})};var o=t.fn.tooltip;t.fn.tooltip=i;t.fn.tooltip.Constructor=e;t.fn.tooltip.noConflict=function(){t.fn.tooltip=o;return this}}(jQuery);jQuery.fn.lapstooltip=jQuery.fn.tooltip.noConflict();jQuery(document).ready(function(){jQuery('.laps-timeline .event').lapstooltip({container:'#wpadminbar',placement:'bottom',html:!0,animation:!1})});
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 ""
+24 -24
View File
@@ -1,36 +1,36 @@
<?php <?php
/* /*
Plugin Name: Laps Plugin Name: Laps
Plugin URI: https://github.com/Rarst/laps Plugin URI: https://git.sandros.hu/sandros/laps
Description: Light WordPress profiler. Description: Light WordPress profiler.
Author: Andrey "Rarst" Savchenko Author: sandros & Rarst
Version: 1.3 Author URI: https://git.sandros.hu/sandros/laps
Author URI: http://www.rarst.net/ Version: 3.4.1
Requires at least: 6.2
Requires PHP: 7.4
Update URI: false
Text Domain: laps Text Domain: laps
Domain Path: /lang Domain Path: /languages
License: MIT License: MIT
Copyright (c) 2013 Andrey "Rarst" Savchenko
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/ */
if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) { if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
} }
Rarst\Laps\Laps::on_load(); 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 );
}
add_action( 'plugins_loaded', function (): void {
load_plugin_textdomain( 'laps', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
} );
$laps = new \Rarst\Laps\Plugin();
$laps->run();
+15
View File
@@ -0,0 +1,15 @@
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Laps">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
<exclude>
<directory suffix=".php">src/mustache/cache</directory>
</exclude>
</whitelist>
</filter>
</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>
+65 -35
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);
@@ -33,9 +33,9 @@
height: 100%; height: 100%;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
color: #ffffff; color: #fff;
text-align: center; text-align: center;
background-color: #5bc0de; background-color: #AAAAAA;
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); -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); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-transition: width 0.6s ease; -webkit-transition: width 0.6s ease;
@@ -45,67 +45,79 @@
border-left: 1px solid #f5f5f5; border-left: 1px solid #f5f5f5;
min-width: 2px; min-width: 2px;
} }
#wpadminbar .laps-timeline .event-php {
background-color: #B10DC9;
}
#wpadminbar .laps-timeline .event-theme { #wpadminbar .laps-timeline .event-theme {
background-color: #5cb85c; background-color: #2ECC40;
} }
#wpadminbar .laps-timeline .event-plugin { #wpadminbar .laps-timeline .event-plugin {
background-color: #428bca; background-color: #0074D9;
} }
#wpadminbar .laps-timeline .event-core { #wpadminbar .laps-timeline .event-core {
background-color: #f0ad4e; background-color: #FF851B;
} }
#wpadminbar .laps-timeline .event-query { #wpadminbar .laps-timeline .event-sql-read {
background-color: #5bc0de; background-color: #7FDBFF;
}
#wpadminbar .laps-timeline .event-sql-write {
background-color: #FF4136;
} }
#wpadminbar .laps-timeline .event-http { #wpadminbar .laps-timeline .event-http {
background-color: #d9534f; 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 { #wpadminbar .tooltip {
position: absolute; position: absolute;
z-index: 100000; z-index: 100000;
display: block; display: block;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 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; font-size: 12px;
font-weight: normal;
line-height: 1.4;
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: #cccccc;
text-align: center;
text-decoration: none;
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;
@@ -115,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;
@@ -163,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
View File
@@ -0,0 +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;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"}
+21 -6
View File
@@ -1,6 +1,7 @@
@import (reference) "../vendor/twbs/bootstrap/less/variables.less"; @import (reference) "../../vendor/twbs/bootstrap/less/variables.less";
@import (reference) "../vendor/twbs/bootstrap/less/mixins.less"; @import (reference) "../../vendor/twbs/bootstrap/less/mixins.less";
@import (reference) "../vendor/twbs/bootstrap/less/progress-bars.less"; @import (reference) "../../vendor/twbs/bootstrap/less/progress-bars.less";
@import (reference) "../../vendor/twbs/bootstrap/less/scaffolding.less";
@import "variables.less"; @import "variables.less";
#wpadminbar { #wpadminbar {
@@ -44,6 +45,9 @@
min-width: 2px; min-width: 2px;
} }
.event-php {
background-color: @event-php-color;
}
.event-theme { .event-theme {
background-color: @event-theme-color; background-color: @event-theme-color;
} }
@@ -53,15 +57,22 @@
.event-core { .event-core {
background-color: @event-core-color; background-color: @event-core-color;
} }
.event-query { .event-sql-read {
background-color: @event-info-color; background-color: @event-sql-color;
}
.event-sql-write {
background-color: @event-warning-color;
} }
.event-http { .event-http {
background-color: @event-warning-color; background-color: @event-warning-color;
} }
.sr-only {
.sr-only;
}
} }
@import "../vendor/twbs/bootstrap/less/tooltip.less"; @import "../../vendor/twbs/bootstrap/less/tooltip.less";
.tooltip.in { .tooltip.in {
.opacity(1); .opacity(1);
@@ -70,6 +81,10 @@
.tooltip-inner { .tooltip-inner {
line-height: 20px; line-height: 20px;
text-align: left; text-align: left;
br {
line-height: inherit;
}
} }
} }
+1
View File
@@ -0,0 +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{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}
@@ -3,18 +3,20 @@
// colors // colors
@timeline-bg: #f5f5f5; @timeline-bg: #f5f5f5;
@event-theme-color: #5cb85c; @event-php-color: #B10DC9;
@event-plugin-color: #428bca; @event-theme-color: #2ECC40;
@event-core-color: #f0ad4e; @event-plugin-color: #0074D9;
@event-info-color: #5bc0de; @event-core-color: #FF851B;
@event-warning-color: #d9534f; @event-sql-color: #7FDBFF;
@event-info-color: #AAAAAA;
@event-warning-color: #FF4136;
@progress-bg: @timeline-bg; @progress-bg: @timeline-bg;
@progress-bar-bg: @event-info-color; @progress-bar-bg: @event-info-color;
// because admin bar is 99999 // because admin bar is 99999
@zindex-tooltip: 100000; @zindex-tooltip: 100000;
@tooltip-max-width: 300px; @tooltip-max-width: 300px;
@tooltip-color: #ccc; @tooltip-color: #eee;
@tooltip-bg: #464646; @tooltip-bg: #464646;
@tooltip-arrow-width: 5px; @tooltip-arrow-width: 5px;
@tooltip-arrow-color: @tooltip-bg; @tooltip-arrow-color: @tooltip-bg;
+69 -23
View File
@@ -1,9 +1,9 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: tooltip.js v3.3.4 * 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-2015 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)
* ======================================================================== */ * ======================================================================== */
@@ -21,11 +21,12 @@
this.timeout = null this.timeout = null
this.hoverState = null this.hoverState = null
this.$element = null this.$element = null
this.inState = null
this.init('tooltip', element, options) this.init('tooltip', element, options)
} }
Tooltip.VERSION = '3.3.4' Tooltip.VERSION = '3.4.0'
Tooltip.TRANSITION_DURATION = 150 Tooltip.TRANSITION_DURATION = 150
@@ -50,7 +51,8 @@
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 && $(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 }
if (this.$element[0] instanceof document.constructor && !this.options.selector) { 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!') throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
@@ -109,16 +111,20 @@
var self = obj instanceof this.constructor ? var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type) obj : $(obj.currentTarget).data('bs.' + this.type)
if (self && self.$tip && self.$tip.is(':visible')) {
self.hoverState = 'in'
return
}
if (!self) { if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self) $(obj.currentTarget).data('bs.' + this.type, self)
} }
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
}
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in'
return
}
clearTimeout(self.timeout) clearTimeout(self.timeout)
self.hoverState = 'in' self.hoverState = 'in'
@@ -130,6 +136,14 @@
}, self.options.delay.show) }, self.options.delay.show)
} }
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (obj) { Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ? var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type) obj : $(obj.currentTarget).data('bs.' + this.type)
@@ -139,6 +153,12 @@
$(obj.currentTarget).data('bs.' + this.type, self) $(obj.currentTarget).data('bs.' + this.type, self)
} }
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
}
if (self.isInStateTrue()) return
clearTimeout(self.timeout) clearTimeout(self.timeout)
self.hoverState = 'out' self.hoverState = 'out'
@@ -184,7 +204,8 @@
.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)
var pos = this.getPosition() var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth var actualWidth = $tip[0].offsetWidth
@@ -192,13 +213,12 @@
if (autoPlace) { if (autoPlace) {
var orgPlacement = placement var orgPlacement = placement
var $container = this.options.container ? $(this.options.container) : this.$element.parent() var viewportDim = this.getPosition(this.$viewport)
var containerDim = this.getPosition($container)
placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' : placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' : placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' : placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' : placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement placement
$tip $tip
@@ -239,8 +259,8 @@
if (isNaN(marginTop)) marginTop = 0 if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0 if (isNaN(marginLeft)) marginLeft = 0
offset.top = offset.top + marginTop offset.top += marginTop
offset.left = offset.left + marginLeft offset.left += marginLeft
// $.fn.offset doesn't round pixel values // $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0 // so we use setOffset directly with our own function B-0
@@ -297,9 +317,11 @@
function complete() { function complete() {
if (that.hoverState != 'in') $tip.detach() if (that.hoverState != 'in') $tip.detach()
if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
that.$element that.$element
.removeAttr('aria-describedby') .removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type) .trigger('hidden.bs.' + that.type)
}
callback && callback() callback && callback()
} }
@@ -322,7 +344,7 @@
Tooltip.prototype.fixTitle = function () { Tooltip.prototype.fixTitle = function () {
var $e = this.$element var $e = this.$element
if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') { if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '') $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
} }
} }
@@ -342,7 +364,10 @@
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
} }
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() var isSvg = window.SVGElement && el instanceof window.SVGElement
// Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
// See https://github.com/twbs/bootstrap/issues/20280
var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
@@ -377,7 +402,7 @@
var rightEdgeOffset = pos.left + viewportPadding + actualWidth var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
} }
} }
@@ -403,7 +428,13 @@
} }
Tooltip.prototype.tip = function () { Tooltip.prototype.tip = function () {
return (this.$tip = this.$tip || $(this.options.template)) if (!this.$tip) {
this.$tip = $(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
} }
Tooltip.prototype.arrow = function () { Tooltip.prototype.arrow = function () {
@@ -432,14 +463,27 @@
} }
} }
if (e) {
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self) self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
} }
}
Tooltip.prototype.destroy = function () { Tooltip.prototype.destroy = function () {
var that = this var that = this
clearTimeout(this.timeout) clearTimeout(this.timeout)
this.hide(function () { this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type) that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach()
}
that.$tip = null
that.$arrow = null
that.$viewport = null
that.$element = null
}) })
} }
@@ -475,6 +519,7 @@
}(jQuery); }(jQuery);
jQuery.fn.lapstooltip = jQuery.fn.tooltip.noConflict(); jQuery.fn.lapstooltip = jQuery.fn.tooltip.noConflict();
jQuery(document).ready(function () { jQuery(document).ready(function () {
@@ -485,3 +530,4 @@ jQuery(document).ready(function () {
animation: false animation: false
}); });
}); });
+6
View File
@@ -0,0 +1,6 @@
+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(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{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{i.tip().hasClass('in')?i.leave(i):i.enter(i)}};e.prototype.destroy=function(){var t=this;clearTimeout(this.timeout);this.hide(function(){t.$element.off('.'+t.type).removeData('bs.'+t.type);if(t.$tip){t.$tip.detach()};t.$tip=null;t.$arrow=null;t.$viewport=null;t.$element=null})};function o(i){return this.each(function(){var n=t(this),o=n.data('bs.tooltip'),s=typeof i=='object'&&i;if(!o&&/destroy|hide/.test(i))return;if(!o)n.data('bs.tooltip',(o=new e(this,s)));if(typeof i=='string')o[i]()})};var i=t.fn.tooltip;t.fn.tooltip=o;t.fn.tooltip.Constructor=e;t.fn.tooltip.noConflict=function(){t.fn.tooltip=i;return this}}(jQuery);jQuery.fn.lapstooltip=jQuery.fn.tooltip.noConflict();jQuery(document).ready(function(){jQuery('.laps-timeline .event').lapstooltip({container:'#wpadminbar',placement:'bottom',html:!0,animation:!1})});
File renamed without changes.
+34
View File
@@ -0,0 +1,34 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Main set of events for WordPress core
*/
class Core_Events implements Hook_Event_Config_Interface {
/**
* @inheritDoc
*/
public function get_events(): array {
return [
[ 'Plugins Load', 'plugin', '', 'plugins_loaded', - 1, - 2 ],
[ 'Plugins Loaded Hook', 'plugin', 'plugins_loaded' ],
[ 'Themes Load', 'theme', 'setup_theme', 'after_setup_theme' ],
[ 'Init Hook', 'core', 'init' ],
[ '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' ],
];
}
}
+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 );
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Events for Genesis Framework based themes
*
* @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 {
/**
* @inheritDoc
*/
public function get_events(): array {
return \function_exists( 'genesis' ) ? [
[ 'Header', 'theme', 'genesis_before_header', 'genesis_after_header' ],
[ 'Sidebar', 'theme', 'genesis_before_sidebar_widget_area', 'genesis_after_sidebar_widget_area' ],
[
'Sidebar (alternate)',
'theme',
'genesis_before_sidebar_alt_widget_area',
'genesis_after_sidebar_alt_widget_area',
],
] : [];
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
declare( strict_types=1 );
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 {
/**
* @see Hook_Collector::add_event()
*
* @return array[]
*/
public function get_events(): array;
}
+50
View File
@@ -0,0 +1,50 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Events for Hybrid Core based themes
*
* @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 {
/**
* @inheritDoc
*/
public function get_events(): array {
$events = [];
if ( ! \function_exists( 'hybrid_get_prefix' ) ) {
return $events;
}
/** @var string $prefix */
$prefix = hybrid_get_prefix();
$events = [
[ 'Header', 'theme', "{$prefix}_before_header", "{$prefix}_after_header" ],
];
/** @var array $sidebars */
$sidebars = get_theme_support( 'hybrid-core-sidebars' );
if ( ! empty( $sidebars[0] ) ) {
/** @var string $sidebar */
foreach ( $sidebars[0] as $sidebar ) {
$events[] = [
"Sidebar ({$sidebar})",
'theme',
"{$prefix}_before_{$sidebar}",
"{$prefix}_after_{$sidebar}",
];
}
}
return $events;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* Events for Theme Hook Alliance
*
* @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 {
/**
* @inheritDoc
*/
public function get_events(): array {
return \defined( 'THA_HOOKS_VERSION' ) ? [
[ 'Header', 'theme', 'tha_header_before', 'tha_header_after' ],
[ 'Sidebars', 'theme', 'tha_sidebars_before', 'tha_sidebars_after' ],
] : [];
}
}
+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' ],
] : [];
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Event;
/**
* 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 {
/**
* @inheritDoc
*/
public function get_events(): array {
return class_exists( 'WPSEO_Frontend' ) ? [
[ 'WP SEO head hook', 'plugin', 'wpseo_head' ],
] : [];
}
}
+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;
}
}
+160
View File
@@ -0,0 +1,160 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Formatter;
use WP_Hook;
/**
* 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 or callbacks array.
* @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 );
/**
* 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' ) ) {
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 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 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;
}
/**
* @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 ( isset( $path[1] ) && ':' === $path[1] ) {
$path = substr( $path, 2 );
}
$path = ltrim( $path, '/' );
return $path;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager;
/**
* Implements load of necessary front–end assets.
*/
class Asset_Manager {
/**
* Sets up the hooks.
*/
public function __construct() {
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Registers assets and queues as necessary.
*/
public function enqueue_scripts(): void {
$suffix = ( \defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
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() && apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
wp_enqueue_script( 'laps' );
wp_enqueue_style( 'laps' );
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager;
/**
* Reorders plugin load to put Laps first and time this core boot stage.
*/
class Load_Order_Manager {
/**
* Sets up the hooks.
*/
public function __construct() {
add_action( 'pre_update_option_active_plugins', [ $this, 'pre_update_option_active_plugins' ] );
add_action( 'pre_update_site_option_active_sitewide_plugins', [ $this, 'pre_update_option_active_plugins' ] );
}
/**
* Reorder active plugins so Laps is first and starts timing load early.
*
* @param array $plugins Filtered array of active plugins’ basenames.
*/
public function pre_update_option_active_plugins( array $plugins ): array {
$plugin = plugin_basename( \dirname( __DIR__, 2 ) . '/laps.php' );
$key = array_search( $plugin, $plugins, true );
if ( false !== $key && $key > 0 ) {
unset( $plugins[ $key ] );
array_unshift( $plugins, $plugin );
$plugins = array_values( $plugins );
}
return $plugins;
}
}
+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;
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Manager;
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\Record\Iterator\Timeline_Iterator;
/**
* Implements toolbar menu and visualization.
*/
class Toolbar_Manager {
/** @var Record_Collector_Interface $collector */
protected $collector;
/** @var Mustache_Engine */
private $mustache;
/**
* @param Record_Collector_Interface $collector Collector of all records..
* @param Mustache_Engine $mustache Mustache instance.
*/
public function __construct( Record_Collector_Interface $collector, Mustache_Engine $mustache ) {
$this->collector = $collector;
$this->mustache = $mustache;
add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ], 1000 );
}
/**
* Render interface and add to the toolbar.
*
* @param \WP_Admin_Bar $wp_admin_bar WordPress core toolbar object.
*/
public function admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ): void {
if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
return;
}
global $timestart;
$wp_admin_bar->add_node( [
'id' => 'laps',
/* translators: %s: page load time in seconds */
'title' => sprintf( __( 'Lap: %ss', 'laps' ), round( microtime( true ) - $timestart, 3 ) ),
] );
$wp_admin_bar->add_node( [
'id' => 'laps_output',
'parent' => 'laps',
'meta' => [ // TODO consider doing render lazily, but might not be worth the effort.
'html' => $this->mustache->render( 'laps', [
'timelines' => new Timeline_Iterator( new Recursive_Record_Iterator( $this->collector->get_records() ) ),
] ),
],
] );
}
}
@@ -1,4 +1,6 @@
<?php <?php
declare( strict_types=1 );
namespace Rarst\Laps; namespace Rarst\Laps;
/** /**
@@ -9,12 +11,12 @@ class Mustache_Cache_FrozenCache extends \Mustache_Cache_FilesystemCache {
/** /**
* Cache and load a compiled Mustache_Template class. * Cache and load a compiled Mustache_Template class.
* *
* @param string $key * @param string $key Key.
* @param string $value * @param string $value Value.
* *
* @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,
+74
View File
@@ -0,0 +1,74 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Provider\Bootable_Provider_Interface;
use Rarst\Laps\Provider\Hook_Event_Provider;
use Rarst\Laps\Provider\Manager_Provider;
use Rarst\Laps\Provider\Record_Provider;
/**
* Main plugin's class.
*/
class Plugin extends Container {
/** @var array $providers */
protected $providers = [];
/**
* @param array $values Configuration values to apply.
* @psalm-param array<string, mixed> $values
*/
public function __construct( array $values = [] ) {
parent::__construct();
$laps = $this;
$laps['mustache'] = function (): \Mustache_Engine {
return new \Mustache_Engine( [
'loader' => new \Mustache_Loader_FilesystemLoader( dirname( __DIR__ ) . '/src/mustache' ),
'cache' => new Mustache_Cache_FrozenCache( dirname( __DIR__ ) . '/src/mustache/cache' ),
] );
};
$laps->register( new Manager_Provider() );
$laps->register( new Hook_Event_Provider() );
$laps->register( new Record_Provider() );
foreach ( $values as $key => $value ) {
$this->offsetSet( $key, $value );
}
}
/**
* @param ServiceProviderInterface $provider Provider.
* @param array $values Optional configuration.
*
* @return $this|static
*/
public function register( ServiceProviderInterface $provider, array $values = [] ): self {
$this->providers[] = $provider;
parent::register( $provider, $values );
return $this;
}
/**
* Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks.
*/
public function run(): void {
foreach ( $this->providers as $provider ) {
if ( $provider instanceof Bootable_Provider_Interface ) {
$provider->boot( $this );
}
}
}
}
@@ -0,0 +1,20 @@
<?php
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 extends ServiceProviderInterface {
/**
* @param Plugin $laps Container instance.
*
* @return void
*/
public function boot( Plugin $laps ): void;
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Event\Extension_Events;
use Rarst\Laps\Event\Genesis_Events;
use Rarst\Laps\Event\Hybrid_Events;
use Rarst\Laps\Event\THA_Events;
use Rarst\Laps\Event\WooCommerce_Events;
use Rarst\Laps\Event\Yoast_Events;
/**
* Registers hook event configuration for bundled vendor events.
*/
class Hook_Event_Provider implements ServiceProviderInterface {
/**
* @param Container $pimple Container instance.
*
* @psalm-suppress DeprecatedClass
*/
public function register( Container $pimple ): void {
$pimple['hook.events'] = function (): array {
return [
'core' => new Core_Events(),
'extension' => new Extension_Events(),
'genesis' => new Genesis_Events(),
'hybrid' => new Hybrid_Events(),
'tha' => new THA_Events(),
'woocommerce' => new WooCommerce_Events(),
'yoast' => new Yoast_Events(),
];
};
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Manager\Server_Timing_Manager;
use Rarst\Laps\Plugin;
use Rarst\Laps\Manager\Asset_Manager;
use Rarst\Laps\Manager\Load_Order_Manager;
use Rarst\Laps\Manager\Toolbar_Manager;
/**
* Registers manager classes that implement functionality.
*/
class Manager_Provider implements ServiceProviderInterface, Bootable_Provider_Interface {
/**
* @param Container $pimple Container instance.
*/
public function register( Container $pimple ): void {
$pimple['managers'] = function ( Plugin $laps ): array {
return [
new Load_Order_Manager(),
new Asset_Manager(),
new Toolbar_Manager( $laps['records.lazy'], $laps['mustache'] ),
new Server_Timing_Manager( $laps['records.lazy'] ),
];
};
}
/**
* @param Plugin $laps Container instance.
*/
public function boot( Plugin $laps ): void {
$laps['managers'];
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Provider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Rarst\Laps\Plugin;
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;
/**
* Registers record collectors (responsible to gather and compile event data) and their dependencies.
*/
class Record_Provider implements ServiceProviderInterface, Bootable_Provider_Interface {
/**
* @param Container $pimple Container instance.
*/
public function register( Container $pimple ): void {
$pimple['stopwatch'] = $pimple->factory( function (): Stopwatch {
return new Stopwatch( true );
} );
$pimple['collectors'] = function ( Plugin $laps ): array {
return [
'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(),
];
};
$pimple['records.lazy'] = function ( Plugin $laps ): Lazy_Proxy_Collector {
return new Lazy_Proxy_Collector( $laps['collectors'] );
};
$pimple['records'] = function ( Plugin $laps ): array {
return $laps['records.lazy']->get_records();
};
}
/**
* @param Plugin $laps Container instance.
*/
public function boot( Plugin $laps ): void {
$laps['collectors'];
}
}
@@ -0,0 +1,80 @@
<?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 );
/* translators: %s: PHP version */
$php = sprintf( __( 'PHP Load – %s', 'laps' ), 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', 'laps' );
}
$load = $this->network_activated ? __( 'Core Load', 'laps' ) : __( 'Core & MU Plugins Load', 'laps' );
/**
* @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 );
}
}
@@ -0,0 +1,78 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Collector;
use Rarst\Laps\Record\Stopwatch_Record;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* Generic Stopwatch–based collector. Can be used by itself or extended.
*/
class Stopwatch_Collector implements Record_Collector_Interface {
/** @var Stopwatch $stopwatch */
protected $stopwatch;
/**
* @param Stopwatch $stopwatch Stopwatch instance.
*/
public function __construct( Stopwatch $stopwatch ) {
$this->stopwatch = $stopwatch;
}
/**
* @param string $name The event name.
* @param string $category The event category.
*
* @return StopwatchEvent
*/
public function start( $name, $category = null ): StopwatchEvent {
return $this->stopwatch->start( $name, $category );
}
/**
* @param string $name The event name.
*
* @return bool|StopwatchEvent
*/
public function stop( $name ) {
if ( $this->stopwatch->isStarted( $name ) ) {
return $this->stopwatch->stop( $name );
}
return false;
}
/**
* @return Stopwatch_Record[]
*/
public function get_records(): array {
/** @psalm-var array<string, StopwatchEvent> $events */
$events = $this->stopwatch->getSectionEvents( '__root__' );
/** @var string $name */
foreach ( array_keys( $events ) as $name ) {
if ( $this->stopwatch->isStarted( $name ) ) {
unset( $events[ $name ] );
}
}
/** @psalm-suppress PossiblyInvalidArgument */
return array_map( [ $this, 'transform' ], array_keys( $events ), $events );
}
/**
* @param string $name Event name.
* @param StopwatchEvent $event Stopwatch event instance.
*
* @return Stopwatch_Record
*/
protected function transform( string $name, StopwatchEvent $event ): Stopwatch_Record {
return new Stopwatch_Record( $name, $event );
}
}
@@ -0,0 +1,81 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Iterator;
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 {
/** @var Record_Interface[] $children */
protected $children = [];
/**
* @param Record_Interface[] $records Records to process.
* @psalm-param 0|1|2|3 $flags Configuration flags.
*/
public function __construct( array $records, int $flags = 0 ) {
usort( $records, [ $this, 'sort_origin' ] );
$end = 0;
foreach ( $records as $key => $record ) {
$origin = $record->get_origin();
$overlaps = $origin < $end; // Float comparison alarm.
$borders = abs( $origin - $end ) < 0.0001; // If effectively equal.
if ( $overlaps && ! $borders ) {
unset( $records[ $key ] );
$this->children[] = $record;
continue;
}
$end = $origin + $record->get_duration();
}
parent::__construct( $records, $flags );
}
/**
* @param Record_Interface $record_a Record to compare.
* @param Record_Interface $record_b Record to compare.
*
* @return int
*/
protected function sort_origin( Record_Interface $record_a, Record_Interface $record_b ): int {
$origin_a = $record_a->get_origin();
$origin_b = $record_b->get_origin();
if ( abs( $origin_a - $origin_b ) < 0.0001 ) { // Float math! Makes nesting results more sane.
return 0;
}
return ( $origin_a < $origin_b ) ? - 1 : 1;
}
/**
* @return bool
*/
public function hasChildren(): bool {
return ! empty( $this->children );
}
/**
* This should be `static` for possible subclassing, but problems hinting that for PHP <8, also don't subclass this.
*/
public function getChildren(): self {
return new self( $this->children );
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record\Iterator;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Record_Interface;
/**
* Processes records into sets for display by template.
*
* @template-implements \Iterator<array>
*
* @psalm-suppress PropertyNotSetInConstructor, TooManyTemplateParams
*/
class Timeline_Iterator implements \Iterator {
/** @var float $origin Start point for the timeline. */
protected $origin;
/** @var float $end Timeline duration. */
protected $total;
/** @var Recursive_Record_Iterator */
protected $iterator;
/** @var Recursive_Record_Iterator */
protected $current;
/**
* @param Recursive_Record_Iterator $iterator Record iterator.
*
* @psalm-suppress PossiblyFalsePropertyAssignmentValue, PossiblyUndefinedArrayOffset
*/
public function __construct( Recursive_Record_Iterator $iterator ) {
$this->origin = filter_var( $_SERVER['REQUEST_TIME_FLOAT'], FILTER_VALIDATE_FLOAT );
$this->iterator = $iterator;
}
/**
* @return array
*/
public function current(): array {
$data = [];
/** @var Record $record */
foreach ( $this->current as $record ) {
$data[] = $this->prepare( $record );
}
return $data;
}
/**
* Sets current context to the next set of nested records.
*/
public function next(): void {
$this->current = $this->current->getChildren();
}
/**
* Key is unused and is only type hinted to possible `int` for PHP 8.1 `mixed` type compatibility.
*
* @codeCoverageIgnore
*
* @return null
*/
public function key(): ?int {
return null;
}
/**
* @return bool If current set contains records.
*/
public function valid(): bool {
return (bool) count( $this->current );
}
/**
* Rewind to a topmost record set.
*/
public function rewind(): void {
$this->total = microtime( true ) - $this->origin;
$this->current = $this->iterator;
}
/**
* @param Record_Interface $record Record instance.
*
* @return array Record data for display by the template.
*/
protected function prepare( Record_Interface $record ): array {
$data = [
'description' => $record->get_description(),
'category' => $record->get_category(),
'offset' => round( ( $record->get_origin() - $this->origin ) / $this->total * 100, 2 ),
'width' => round( $record->get_duration() / $this->total * 100, 2 ),
];
return $data;
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record;
/**
* Generic record with all properties set by constructor.
*/
class Record implements Record_Interface {
/** @var string $name */
protected $name;
/** @var float $origin */
protected $origin;
/** @var float $duration */
protected $duration;
/** @var string $description */
protected $description;
/** @var string $category */
protected $category;
/**
* @param string $name Name.
* @param float $origin Start time.
* @param float $duration Duration.
* @param string $description Optional description.
* @param string $category Optional category.
*/
public function __construct( string $name, float $origin, float $duration, string $description = '', string $category = '' ) {
$this->name = $name;
$this->origin = $origin;
$this->duration = $duration;
$this->description = $description;
$this->category = $category;
}
/**
* @return string
*/
public function get_name(): string {
return $this->name;
}
/**
* @return string
*/
public function get_description(): string {
if ( empty( $this->description ) ) {
$duration = round( $this->get_duration() * 1000 );
$name = __( $this->name, 'laps' );
return "{$name} – {$duration}&nbsp;ms";
}
return $this->description;
}
/**
* @return float
*/
public function get_origin(): float {
return $this->origin;
}
/**
* @return float
*/
public function get_duration(): float {
return $this->duration;
}
/**
* @return string
*/
public function get_category(): string {
return $this->category;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record;
/**
* Interface for recorded results of individual event.
*/
interface Record_Interface {
/**
* @return string
*/
public function get_name(): string;
/**
* @return string
*/
public function get_description(): string;
/**
* @return float Timestamp of record start.
*/
public function get_origin(): float;
/**
* @return float Record duration in seconds.
*/
public function get_duration(): float;
/**
* @return string
*/
public function get_category(): string;
}
+73
View File
@@ -0,0 +1,73 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Record;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* Record wrapper for an event recorded with Stopwatch.
*/
class Stopwatch_Record implements Record_Interface {
/** @var string $name */
protected $name;
/** @var StopwatchEvent */
protected $stopwatch_event;
/** @var string */
protected $description = '';
/**
* @param string $name Event name.
* @param StopwatchEvent $stopwatch_event Stopwatch event instance.
* @param string $description Optional description for the event.
*/
public function __construct( string $name, StopwatchEvent $stopwatch_event, string $description = '' ) {
$this->name = $name;
$this->stopwatch_event = $stopwatch_event;
$this->description = $description;
}
/**
* @return string
*/
public function get_name(): string {
return $this->name;
}
/**
* @return string
*/
public function get_description(): string {
$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 "{$name} – {$duration} ms – {$memory} MB{$description}";
}
/**
* @return float Timestamp of record start.
*/
public function get_origin(): float {
return $this->stopwatch_event->getOrigin() / 1000; // ms to s.
}
/**
* @return float Record duration in seconds.
*/
public function get_duration(): float {
return $this->stopwatch_event->getDuration() / 1000; // ms to s.
}
/**
* @return string
*/
public function get_category(): string {
return $this->stopwatch_event->getCategory();
}
}
-106
View File
@@ -1,106 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Main set of events for WordPress core
*/
class Core_Events extends Laps_Events {
/**
* @return array
*/
public function get_events() {
$stops = array();
$stops['plugins_loaded'][ - 2 ] = array(
'action' => 'stop',
'event' => 'Plugins Load',
'category' => 'plugin',
);
$stops['plugins_loaded'][ - 1 ] = array(
'event' => 'Plugins Loaded Hook',
'category' => 'plugin',
);
$stops['plugins_loaded'][20] = array(
'action' => 'stop',
'event' => 'Plugins Loaded Hook',
'category' => 'plugin',
);
$stops['setup_theme'][0] = array(
'event' => 'Themes Load',
'category' => 'theme',
);
$stops['after_setup_theme'][20] = array(
'action' => 'stop',
'event' => 'Themes Load',
'category' => 'theme',
);
$stops['init'][- 1] = array(
'event' => 'Core Init',
'category' => 'core',
);
$stops['wp_loaded'][20] = array(
'action' => 'stop',
'event' => 'Core Init',
'category' => 'core',
);
$stops['admin_init'][- 1] = array(
'event' => 'Core Admin Init',
'category' => 'core',
);
$stops['admin_init'][ PHP_INT_MAX -1 ] = array(
'action' => 'stop',
'event' => 'Core Admin Init',
'category' => 'core',
);
$stops['_admin_menu'][- 1] = array(
'event' => 'Admin Menu',
'category' => 'core',
);
$stops['admin_menu'][ PHP_INT_MAX -1 ] = array(
'action' => 'stop',
'event' => 'Admin Menu',
'category' => 'core',
);
$stops['admin_bar_menu'][-1] = array(
'event' => 'Toolbar',
'category' => 'core',
);
$stops['loop_start'][10] = array(
'event' => 'Main Loop',
'category' => 'theme',
);
$stops['loop_end'][10] = array(
'action' => 'stop',
'event' => 'Main Loop',
'category' => 'theme',
);
$stops['admin_enqueue_scripts'][10] = array(
'event' => 'Admin Print Scripts',
'category' => 'core',
);
$stops['admin_print_scripts'][10] = array(
'action' => 'stop',
'event' => 'Admin Print Scripts',
'category' => 'core',
);
return $stops;
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Base class for bulk event providers
*/
abstract class Laps_Events {
/**
* Retrieve array of event data
*
* @return array
*/
public function get() {
if ( $this->is_applicable() ) {
return $this->get_events();
}
return array();
}
/**
* Is event pack relevant to current runtime
*
* @return bool
*/
protected function is_applicable() {
return true;
}
/**
* Inherit with internal events compiling and return
*
* @return array()
*/
abstract protected function get_events();
}
-62
View File
@@ -1,62 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Events for Genesis Framework based themes
*
* @link http://my.studiopress.com/themes/genesis/
*/
class Genesis_Events extends Laps_Events {
/**
* @return bool
*/
protected function is_applicable() {
return function_exists( 'genesis' );
}
/**
* @return array
*/
protected function get_events() {
$events = array();
$events['genesis_before_header'][10] = array(
'event' => 'Header',
'category' => 'theme',
);
$events['genesis_after_header'][10] = array(
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
);
$events['genesis_before_sidebar_widget_area'][10] = array(
'event' => 'Sidebar',
'category' => 'theme',
);
$events['genesis_after_sidebar_widget_area'][10] = array(
'action' => 'stop',
'event' => 'Sidebar',
'category' => 'theme',
);
$events['genesis_before_sidebar_alt_widget_area'][10] = array(
'event' => 'Sidebar (alternate)',
'category' => 'theme',
);
$events['genesis_after_sidebar_alt_widget_area'][10] = array(
'action' => 'stop',
'event' => 'Sidebar (alternate)',
'category' => 'theme',
);
return $events;
}
}
-59
View File
@@ -1,59 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Events for Hybrid Core based themes
*
* @link http://themehybrid.com/hybrid-core
*/
class Hybrid_Events extends Laps_Events {
/**
* @return bool
*/
protected function is_applicable() {
return defined( 'HYBRID_VERSION' ) && function_exists( 'hybrid_get_prefix' );
}
/**
* @return array
*/
protected function get_events() {
$events = array();
$prefix = hybrid_get_prefix();
$events["{$prefix}_before_header"][10] = array(
'event' => 'Header',
'category' => 'theme',
);
$events["{$prefix}_after_header"][10] = array(
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
);
$sidebars = get_theme_support( 'hybrid-core-sidebars' );
if ( ! empty( $sidebars[0] ) ) {
foreach ( $sidebars[0] as $sidebar ) {
$events["{$prefix}_before_{$sidebar}"][10] = array(
'event' => "Sidebar ({$sidebar})",
'category' => 'theme',
);
$events["{$prefix}_after_{$sidebar}"][10] = array(
'action' => 'stop',
'event' => "Sidebar ({$sidebar})",
'category' => 'theme',
);
}
}
return $events;
}
}
-300
View File
@@ -1,300 +0,0 @@
<?php
namespace Rarst\Laps;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Main plugin's class.
*/
class Laps {
/** @var Stopwatch $stopwatch */
public static $stopwatch;
public static $events = array();
public static $query_starts = array();
/**
* Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks.
*/
static function on_load() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
self::$stopwatch = new Stopwatch();
self::$stopwatch->start( 'Plugins Load', 'plugin' );
$events = new Core_Events();
self::add_events( $events->get() );
add_action( 'pre_update_option_active_plugins', array( __CLASS__, 'pre_update_option_active_plugins' ) );
add_action( 'pre_update_site_option_active_sitewide_plugins', array( __CLASS__, 'pre_update_option_active_plugins' ) );
add_action( 'pre_http_request', array( __CLASS__, 'pre_http_request' ), 10, 3 );
add_action( 'http_api_debug', array( __CLASS__, 'http_api_debug' ), 10, 5 );
add_action( 'after_setup_theme', array( __CLASS__, 'after_setup_theme' ), 15 );
add_action( 'init', array( __CLASS__, 'init' ) );
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
add_filter( 'query', array( __CLASS__, 'query' ), 20 );
}
}
/**
* Hook events by name and priority from array.
*
* @param array $stops
*/
static function add_events( $stops ) {
self::$events = array_merge( self::$events, $stops );
foreach ( $stops as $hook_name => $data ) {
foreach ( array_keys( $data ) as $priority ) {
add_action( $hook_name, array( __CLASS__, 'tick' ), $priority );
}
}
}
/**
* Reorder active plugins so Laps is first and starts timing load early.
*
* @param array $plugins
*
* @return array
*/
static function pre_update_option_active_plugins( $plugins ) {
$plugin = plugin_basename( dirname( __DIR__ ) . '/laps.php' );
$key = array_search( $plugin, $plugins );
if ( false !== $key && $key > 0 ) {
unset( $plugins[ $key ] );
array_unshift( $plugins, $plugin );
$plugins = array_values( $plugins );
}
return $plugins;
}
/**
* Mark action for the event on Stopwatch.
*
* @param mixed $input pass through if added to filter
*
* @return mixed
*/
static function tick( $input = null ) {
global $wp_filter;
$filter = current_filter();
$priority = key( $wp_filter[ $filter ] );
$event = wp_parse_args(
self::$events[ $filter ][ $priority ],
array(
'action' => 'start',
'category' => null,
)
);
self::$stopwatch->$event['action']( $event['event'], $event['category'] );
return $input;
}
/**
* Capture SQL queries start times
*
* @param string $query
*
* @return string
*/
static function query( $query ) {
global $wpdb;
if ( empty( self::$query_starts ) && ! empty( $wpdb->queries ) ) {
self::$query_starts[ count( $wpdb->queries ) ] = microtime( true ) * 1000;
} else {
self::$query_starts[] = microtime( true ) * 1000;
}
return $query;
}
/**
* Capture start time of HTTP request
*
* @param boolean $false
* @param array $args
* @param string $url
*
* @return boolean
*/
static function pre_http_request( $false, $args, $url ) {
self::$stopwatch->start( $url, 'http' );
return $false;
}
/**
* Capture end time of HTTP request
*
* @param array|\WP_Error $response
* @param string $type
* @param object $class
* @param array $args
* @param string $url
*
* @return mixed
*/
static function http_api_debug( $response, $type, $class, $args, $url ) {
self::$stopwatch->stop( $url );
return $response;
}
/**
* When theme is done possibly add vendor-specific events.
*/
static function after_setup_theme() {
foreach ( array( 'THA', 'Hybrid', 'Genesis', 'Thematic', 'Yoast' ) as $vendor ) {
$class = "Rarst\\Laps\\{$vendor}_Events";
/** @var Laps_Events $events */
$events = new $class;
self::add_events( $events->get() );
}
}
static function init() {
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
add_action( 'admin_bar_menu', array( __CLASS__, 'admin_bar_menu' ), 100 );
}
static function enqueue_scripts() {
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
wp_register_script( 'laps', plugins_url( "js/laps{$suffix}.js", __DIR__ ), array( 'jquery' ), '3.3.1', true );
wp_register_style( 'laps', plugins_url( "css/laps{$suffix}.css", __DIR__ ) );
if ( is_admin_bar_showing() ) {
wp_enqueue_script( 'laps' );
wp_enqueue_style( 'laps' );
}
}
/**
* Render interface and add to the toolbar.
*
* @param \WP_Admin_Bar $wp_admin_bar
*/
static function admin_bar_menu( $wp_admin_bar ) {
if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) {
return;
}
global $timestart, $wpdb;
$mustache = new \Mustache_Engine(
array(
'loader' => new \Mustache_Loader_FilesystemLoader( dirname( __DIR__ ) . '/views' ),
'cache' => new Mustache_Cache_FrozenCache( dirname( __DIR__ ) . '/views/cache' ),
)
);
self::$stopwatch->stop( 'Toolbar' );
$events = self::$stopwatch->getSectionEvents( '__root__' );
$start = $timestart * 1000;
$end = microtime( true ) * 1000;
$total = $end - $start;
$event_data = array();
$http_data = array();
foreach ( $events as $name => $event ) {
$offset = round( ( $event->getOrigin() - $start ) / $total * 100, 2 );
$duration = $event->getDuration();
$width = round( $duration / $total * 100, 2 );
$category = $event->getCategory();
if ( 'http' === $category ) {
$http_data[] = compact( 'name', 'offset', 'duration', 'width', 'category' );
continue;
}
$memory = $event->getMemory() / 1024 / 1024;
$event_data[] = compact( 'name', 'offset', 'duration', 'width', 'category', 'memory' );
}
$query_data = array();
$last_query_end = 0;
$last_offset = 0;
$last_duration = 0;
$category = 'query';
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
foreach ( $wpdb->queries as $key => $query ) {
$query_start = isset( self::$query_starts[ $key ] ) ? self::$query_starts[ $key ] : $last_query_end;
list( $sql, $duration, $trace ) = $query;
$duration *= 1000;
$last_query_end = $query_start + $duration;
$offset = round( ( $query_start - $start ) / $total * 100, 2 );
// if query is indistinguishably close to previous then stack it
if ( $offset === $last_offset ) {
$key = count( $query_data ) - 1;
$query_data[ $key ]['sql'] .= '<br />' . $sql;
$last_duration += $duration;
$width = round( $last_duration / $total * 100, 2 );
$query_data[ $key ]['width'] = $width;
continue;
}
$width = round( $duration / $total * 100, 2 );
$last_offset = $offset;
$last_duration = $duration;
$query_data[] = compact( 'sql', 'duration', 'offset', 'width', 'category' );
}
}
$html = $mustache->render(
'laps',
array(
'events' => $event_data,
'queries' => $query_data,
'savequeries' => defined( 'SAVEQUERIES' ) && SAVEQUERIES,
'http' => $http_data,
'savehttp' => ! empty( $http_data ),
)
);
$wp_admin_bar->add_node( array(
'id' => 'laps',
'title' => sprintf( 'Lap: %ss', round( $total / 1000, 3 ) ),
) );
$wp_admin_bar->add_node( array(
'id' => 'laps_output',
'parent' => 'laps',
'meta' => array( 'html' => $html ),
) );
}
}
-51
View File
@@ -1,51 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Events for Theme Hook Alliance
*
* @link http://zamoose.github.io/themehookalliance/
*/
class THA_Events extends Laps_Events {
/**
* @return bool
*/
protected function is_applicable() {
return defined( 'THA_HOOKS_VERSION' );
}
/**
* @return array
*/
protected function get_events() {
$events = array();
$events['tha_header_before'][10] = array(
'event' => 'Header',
'category' => 'theme',
);
$events['tha_header_after'][10] = array(
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
);
$events['tha_sidebars_before'][10] = array(
'event' => 'Sidebars',
'category' => 'theme',
);
$events['tha_sidebars_after'][10] = array(
'action' => 'stop',
'event' => 'Sidebars',
'category' => 'theme',
);
return $events;
}
}
-62
View File
@@ -1,62 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Events for Thematic Framework based themes
*
* @link http://thematictheme.com/
*/
class Thematic_Events extends Laps_Events {
/**
* @return bool
*/
protected function is_applicable() {
return function_exists( 'thematic_init' );
}
/**
* @return array
*/
protected function get_events() {
$events = array();
$events['thematic_aboveheader'][10] = array(
'event' => 'Header',
'category' => 'theme',
);
$events['thematic_belowheader'][10] = array(
'action' => 'stop',
'event' => 'Header',
'category' => 'theme',
);
$events['widget_area_primary_aside'][10] = array(
'event' => 'Sidebar (primary aside)',
'category' => 'theme',
);
$events['thematic_betweenmainasides'][10] = array(
'action' => 'stop',
'event' => 'Sidebar (primary aside)',
'category' => 'theme',
);
$events['widget_area_secondary_aside'][10] = array(
'event' => 'Sidebar (secondary aside)',
'category' => 'theme',
);
$events['thematic_belowmainasides'][10] = array(
'action' => 'stop',
'event' => 'Sidebar (secondary aside)',
'category' => 'theme',
);
return $events;
}
}
-38
View File
@@ -1,38 +0,0 @@
<?php
namespace Rarst\Laps;
/**
* Events for Yoast plugins
*/
class Yoast_Events extends Laps_Events {
/**
* @return bool
*/
protected function is_applicable() {
return class_exists( 'WPSEO_Frontend' );
}
/**
* @return array
*/
public function get_events() {
$stops = array();
$stops['wpseo_head'][1] = array(
'event' => 'WP SEO head hook',
'category' => 'plugin',
);
$stops['wpseo_head'][100] = array(
'action' => 'stop',
'event' => 'WP SEO head hook',
'category' => 'plugin',
);
return $stops;
}
}
@@ -0,0 +1,111 @@
<?php
class __Mustache_a04731de0bb9e043ecde3814b3a901bb extends Mustache_Template
{
private $lambdaHelper;
public function renderInternal(Mustache_Context $context, $indent = '')
{
$this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
$buffer = '';
// 'timelines' section
$value = $context->find('timelines');
$buffer .= $this->sectionAdac57c50f147efcda3ea1c17e7a6c61($context, $indent, $value);
return $buffer;
}
private function section9f1840fed1e29fb14453f0ea2bfe30ad(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="event event-{{category}}" data-toggle="tooltip" title="{{description}}" style="width:{{width}}%; left:{{offset}}%;">
<span class="sr-only">{{description}}</span>
</div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . ' <div class="event event-';
$value = $this->resolveValue($context->find('category'), $context);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" data-toggle="tooltip" title="';
$value = $this->resolveValue($context->find('description'), $context);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" style="width:';
$value = $this->resolveValue($context->find('width'), $context);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%; left:';
$value = $this->resolveValue($context->find('offset'), $context);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%;">
';
$buffer .= $indent . ' <span class="sr-only">';
$value = $this->resolveValue($context->find('description'), $context);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '</span>
';
$buffer .= $indent . ' </div>
';
$context->pop();
}
}
return $buffer;
}
private function sectionAdac57c50f147efcda3ea1c17e7a6c61(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="laps-timeline">
{{#.}}
<div class="event event-{{category}}" data-toggle="tooltip" title="{{description}}" style="width:{{width}}%; left:{{offset}}%;">
<span class="sr-only">{{description}}</span>
</div>
{{/.}}
</div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . ' <div class="laps-timeline">
';
// '.' section
$value = $context->last('.');
$buffer .= $this->section9f1840fed1e29fb14453f0ea2bfe30ad($context, $indent, $value);
$buffer .= $indent . ' </div>
';
$context->pop();
}
}
return $buffer;
}
}
+9
View File
@@ -0,0 +1,9 @@
{{#timelines}}
<div class="laps-timeline">
{{#.}}
<div class="event event-{{category}}" data-toggle="tooltip" title="{{description}}" style="width:{{width}}%; left:{{offset}}%;">
<span class="sr-only">{{description}}</span>
</div>
{{/.}}
</div>
{{/timelines}}
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace Rarst\Laps\Tests\Event;
use Brain\Monkey\Functions;
use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Event\Genesis_Events;
use Rarst\Laps\Event\Hybrid_Events;
use Rarst\Laps\Event\THA_Events;
use Rarst\Laps\Event\WooCommerce_Events;
use Rarst\Laps\Event\Yoast_Events;
use Rarst\Laps\Tests\LapsTestCase;
class EventTest extends LapsTestCase {
public function testCore() {
$events = ( new Core_Events() )->get_events();
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
public function testGenesis() {
$genesis = new Genesis_Events();
$this->assertEmpty( $genesis->get_events() );
Functions\expect( 'genesis' )->zeroOrMoreTimes();
$events = $genesis->get_events();
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
public function testHybrid() {
$hybrid = new Hybrid_Events();
$this->assertEmpty( $hybrid->get_events() );
Functions\expect( 'hybrid_get_prefix' )->once()->andReturn( 'hybrid' );
Functions\expect( 'get_theme_support' )
->once()
->with( 'hybrid-core-sidebars' )
->andReturn( [ [ 'primary' ] ] );
$events = $hybrid->get_events();
$this->assertIsArray( $events );
$this->assertNotEmpty( $events );
}
public function testTha() {
$tha = new THA_Events();
$this->assertEmpty( $tha->get_events() );
define( 'THA_HOOKS_VERSION', '1.0' );
$events = $tha->get_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 );
}
public function testYoast() {
$yoast = new Yoast_Events();
$this->assertEmpty( $yoast->get_events() );
$this->getMockBuilder( 'WPSEO_Frontend' )->allowMockingUnknownTypes()->getMock();
$events = $yoast->get_events();
$this->assertIsArray( $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] );
}
}
+83
View File
@@ -0,0 +1,83 @@
<?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( [] ) );
}
/**
* @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 );
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rarst\Laps\Tests;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase;
use Brain\Monkey;
class LapsTestCase extends TestCase {
use MockeryPHPUnitIntegration;
protected function setUp():void {
parent::setUp();
Monkey\setUp();
Monkey\Functions\stubTranslationFunctions();
}
protected function tearDown(): void {
Monkey\tearDown();
parent::tearDown();
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace Rarst\Laps\Tests\Manager;
use Brain\Monkey\Functions;
use Rarst\Laps\Manager\Asset_Manager;
use Rarst\Laps\Tests\LapsTestCase;
/**
* @coversDefaultClass Rarst\Laps\Manager\Asset_Manager
*/
class AssetTest extends LapsTestCase {
/**
* @covers ::__construct
*
* @return Asset_Manager
*/
public function test__construct() {
$manager = new Asset_Manager();
$this->assertTrue( (bool) has_action( 'wp_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
$this->assertTrue( (bool) has_action( 'admin_enqueue_scripts', [ $manager, 'enqueue_scripts' ] ) );
return $manager;
}
/**
* @covers ::enqueue_scripts
* @depends test__construct
*/
public function testEnqueue_scripts( 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( 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();
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Rarst\Laps\Tests\Manager;
use Brain\Monkey\Functions;
use Rarst\Laps\Manager\Load_Order_Manager;
use Rarst\Laps\Tests\LapsTestCase;
/**
* @coversDefaultClass Rarst\Laps\Manager\Load_Order_Manager
*/
class LoadOrderTest extends LapsTestCase {
/**
* @covers ::__construct
*
* @return Load_Order_Manager
*/
public function test__construct() {
$manager = new Load_Order_Manager();
$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;
}
/**
* @covers ::pre_update_option_active_plugins
* @depends test__construct
*/
public function testPre_update_option_active_plugins( Load_Order_Manager $manager ) {
$plugins = [
'foo/bar.php',
'laps/laps.php',
'baz/qux.php',
];
Functions\expect( 'plugin_basename' )->once()->andReturn( 'laps/laps.php' );
$plugins = $manager->pre_update_option_active_plugins( $plugins );
$this->assertIsArray( $plugins );
$this->assertCount( 3, $plugins );
$this->assertEquals( 'laps/laps.php', $plugins[0] );
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Manager;
use Brain\Monkey\Functions;
use Rarst\Laps\Manager\Toolbar_Manager;
use Rarst\Laps\Record\Collector\Lazy_Proxy_Collector;
use Rarst\Laps\Tests\LapsTestCase;
/**
* @coversDefaultClass \Rarst\Laps\Manager\Toolbar_Manager
*/
class ToolbarTest extends LapsTestCase {
/**
* @covers ::__construct
*
* @return Toolbar_Manager
*/
public function test__construct() {
$collector = new Lazy_Proxy_Collector( [] );
$manager = new Toolbar_Manager( $collector, new \Mustache_Engine() );
$this->assertTrue( (bool) has_action( 'admin_bar_menu', [ $manager, 'admin_bar_menu' ] ) );
return $manager;
}
/**
* @covers ::admin_bar_menu
*
* @depends test__construct
*/
public function testAdmin_bar_menu( Toolbar_Manager $manager ) {
$wp_admin_bar = $this->getMockBuilder( 'WP_Admin_Bar' )
->allowMockingUnknownTypes()
->setMethods( [ 'add_node' ] )
->getMock();
$wp_admin_bar->method( 'add_node' )->willReturn( null );
$wp_admin_bar->expects( $this->exactly( 2 ) )
->method( 'add_node' )
->withConsecutive(
[ $this->isType( 'array' ) ],
[ $this->isType( 'array' ) ]
);
Functions\expect( 'current_user_can' )
->once()
->with( 'manage_options' )
->andReturn( true );
$manager->admin_bar_menu( $wp_admin_bar );
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Rarst\Laps\Tests;
use Brain\Monkey\Functions;
use Rarst\Laps\Provider\Bootable_Provider_Interface;
class PluginTest extends LapsTestCase {
public function testContainer() {
$container = new \Rarst\Laps\Plugin();
$service = $this->getMockBuilder( Bootable_Provider_Interface::class )
->getMock();
$service->expects( $this->once() )
->method( 'register' )
->with( $container );
$container->register( $service );
$service->expects( $this->once() )
->method( 'boot' )
->with( $container );
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$container->run();
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Rarst\Laps\Tests\Provider;
use Pimple\Container;
use Rarst\Laps\Event\Hook_Event_Config_Interface;
use Rarst\Laps\Provider\Hook_Event_Provider;
use Rarst\Laps\Tests\LapsTestCase;
class EventTest extends LapsTestCase {
public function testProvider() {
$container = new Container();
$provider = new Hook_Event_Provider();
$provider->register( $container );
$this->assertArrayHasKey( 'hook.events', $container );
foreach ( $container['hook.events'] as $config ) {
$this->assertInstanceOf( Hook_Event_Config_Interface::class, $config );
}
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Provider;
use function Brain\Monkey\Functions\expect;
use Pimple\Exception\FrozenServiceException;
use Rarst\Laps\Plugin;
use Rarst\Laps\Provider\Manager_Provider;
use Rarst\Laps\Tests\LapsTestCase;
class ManagerTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Provider\Manager_Provider
*/
public function testProvider() {
expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$container = new Plugin();
$provider = new Manager_Provider();
$provider->register( $container );
$this->assertArrayHasKey( 'managers', $container );
$container->extend( 'managers', function () {} );
$provider->boot( $container );
$this->expectException( FrozenServiceException::class );
$container->extend( 'managers', function () {} );
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Rarst\Laps\Tests\Provider;
use Brain\Monkey\Functions;
use Pimple\Exception\FrozenServiceException;
use Rarst\Laps\Plugin;
use Rarst\Laps\Provider\Record_Provider;
use Rarst\Laps\Tests\LapsTestCase;
class RecordTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Provider\Record_Provider
*/
public function testProvider() {
$container = new Plugin();
$provider = new Record_Provider();
$provider->register( $container );
$stopwatch_a = $container['stopwatch'];
$stopwatch_b = $container['stopwatch'];
$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 );
$container->extend( 'collectors', function () {} );
$provider->boot( $container );
$this->expectException( FrozenServiceException::class );
$container->extend( 'collectors', function () {} );
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Collector\Core_Load_Collector;
use Rarst\Laps\Record\Record_Interface;
use Rarst\Laps\Tests\LapsTestCase;
class CoreTest extends LapsTestCase {
public function testCollector() {
global $timestart;
$timestart = microtime( true );;
$collector = new Core_Load_Collector();
$records = $collector->get_records();
$this->assertIsArray( $records );
$this->assertCount( 2, $records );
$this->assertInstanceOf( Record_Interface::class, $records[0] );
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare( strict_types=1 );
namespace Rarst\Laps\Tests\Record;
use function Brain\Monkey\Functions\expect;
use Rarst\Laps\Event\Core_Events;
use Rarst\Laps\Record\Collector\Hook_Collector;
use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch;
class HookTest extends LapsTestCase {
public function testCollector() {
expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$stopwatch = new Stopwatch();
$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();
$stopwatch->start( 'Toolbar' );
$collector->get_records();
$this->assertFalse( $stopwatch->isStarted( 'Toolbar' ) );
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Brain\Monkey\Functions;
use Rarst\Laps\Record\Collector\Http_Collector;
use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch;
class HttpTest extends LapsTestCase {
public function testCollector() {
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
Functions\expect( 'wp_debug_backtrace_summary' )->zeroOrMoreTimes()->andReturn( [] );
$stopwatch = new Stopwatch();
$url = 'https://example.com/';
$collector = new Http_Collector( $stopwatch );
$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 );
$this->assertTrue( $stopwatch->isStarted( $url ) );
$collector->http_api_debug( [], '', new \stdClass(), [], $url );
$this->assertFalse( $stopwatch->isStarted( $url ) );
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Iterator\Recursive_Record_Iterator;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Iterator\Timeline_Iterator;
use Rarst\Laps\Tests\LapsTestCase;
class IteratorTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Record\Iterator\Recursive_Record_Iterator
*
* @return \Rarst\Laps\Record\Iterator\Recursive_Record_Iterator
*/
public function testRecord() {
$record_a = new Record( 'Parent', 0, 100 );
$record_b = new Record( 'Child', 50, 100 );
$records = [ $record_b, $record_a ];
$iterator = new Recursive_Record_Iterator( $records );
$this->assertCount( 1, $iterator->getArrayCopy() );
foreach ( $iterator as $record ) {
$this->assertEquals( 'Parent', $record->get_name() );
}
$this->assertTrue( $iterator->hasChildren() );
$children = $iterator->getChildren();
$this->assertCount( 1, $children );
foreach ( $children as $record ) {
$this->assertEquals( 'Child', $record->get_name() );
}
return $iterator;
}
/**
* @covers \Rarst\Laps\Record\Iterator\Timeline_Iterator
*
* @depends testRecord
*/
public function testTimeline( Recursive_Record_Iterator $iterator ) {
$iterator = new Timeline_Iterator( $iterator );
$iterator->rewind();
$this->assertTrue( $iterator->valid() );
$records = $iterator->current();
$this->assertIsArray( $records );
$this->assertArrayHasKey( 'description', $records[0] );
$this->assertArrayHasKey( 'category', $records[0] );
$this->assertArrayHasKey( 'offset', $records[0] );
$this->assertArrayHasKey( 'width', $records[0] );
$iterator->next();
$this->assertTrue( $iterator->valid() );
$records = $iterator->current();
$this->assertIsArray( $records );
$iterator->next();
$this->assertFalse( $iterator->valid() );
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Tests\LapsTestCase;
class RecordTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Record\Record
*/
public function testRecord() {
$name = 'Name';
$origin = 0;
$duration = 1;
$description = 'description';
$category = 'info';
$record = new Record( $name, $origin, $duration );
$this->assertEquals( $name, $record->get_name() );
$this->assertEquals( $origin, $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() );
$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 );
$this->assertEquals( $description, $record->get_description() );
$this->assertEquals( $category, $record->get_category() );
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Brain\Monkey\Functions;
use Rarst\Laps\Record\Record;
use Rarst\Laps\Record\Collector\Sql_Collector;
use Rarst\Laps\Tests\LapsTestCase;
class SqlTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Record\Collector\Sql_Collector
*/
public function testCollector() {
define( 'SAVEQUERIES', true );
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/wp' );
define( 'WP_CONTENT_DIR', '/wp-content/' );
}
Functions\expect( 'wp_normalize_path' )->zeroOrMoreTimes()->andReturnFirstArg();
$collector = new Sql_Collector();
$this->assertTrue( (bool) has_filter( 'query', [ $collector, 'query' ] ) );
$query = 'SELECT * FROM wp_posts';
$collector->query( $query );
global $wpdb;
$wpdb = new \stdClass();
$duration = 100;
$wpdb->queries[] = [ $query, $duration, 'foo(), bar()' ];
$records = $collector->get_records();
$this->assertIsArray( $records );
$this->assertCount( 1, $records );
$record = $records[0];
$this->assertInstanceOf( Record::class, $record );
$this->assertEquals( $query, $record->get_name() );
$this->assertStringContainsString( $query, $record->get_description() );
$this->assertIsFloat( $record->get_origin() );
$this->assertEquals( $duration, $record->get_duration() );
$this->assertEquals( 'sql-read', $record->get_category() );
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Rarst\Laps\Tests\Record;
use Rarst\Laps\Record\Stopwatch_Record;
use Rarst\Laps\Record\Collector\Stopwatch_Collector;
use Rarst\Laps\Tests\LapsTestCase;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
class StopwatchTest extends LapsTestCase {
/**
* @covers \Rarst\Laps\Record\Collector\Stopwatch_Collector
*/
public function testCollector() {
$collector = new Stopwatch_Collector( new Stopwatch() );
$collector->start( 'Event', 'info' );
$collector->start( 'Not stopped', 'info' );
usleep( 1000 );
$event = $collector->stop( 'Event' );
$this->assertFalse( $collector->stop( 'Not event' ) );
$records = $collector->get_records();
$this->assertIsArray( $records );
$this->assertCount( 1, $records );
$this->assertInstanceOf( Stopwatch_Record::class, $records[0] );
return $event;
}
/**
* @depends testCollector
* @covers \Rarst\Laps\Record\Stopwatch_Record
*
* @param StopwatchEvent $event Event instance.
*/
public function testRecord( StopwatchEvent $event ) {
$record = new Stopwatch_Record( 'Event', $event );
$this->assertEquals( 'Event', $record->get_name() );
$this->assertEquals( 'info', $record->get_category() );
$this->assertEquals( $event->getOrigin() / 1000, $record->get_origin() );
$this->assertEquals( $event->getDuration() / 1000, $record->get_duration() );
$this->assertStringContainsString( 'Event', $record->get_description() );
}
}
@@ -1,241 +0,0 @@
<?php
class __Mustache_37c708633787b0bcaf1bd7b4adc8b13a extends Mustache_Template
{
private $lambdaHelper;
public function renderInternal(Mustache_Context $context, $indent = '')
{
$this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
$buffer = '';
$newContext = array();
$buffer .= $indent . '<div class="laps-timeline">
';
// 'events' section
$value = $context->find('events');
$buffer .= $this->section83708baddd3627fe4d26675ddf7eaeb5($context, $indent, $value);
$buffer .= $indent . '</div>
';
$buffer .= $indent . '
';
// 'savequeries' section
$value = $context->find('savequeries');
$buffer .= $this->section9756eb1d93d100052bdb80ee49b09ad6($context, $indent, $value);
$buffer .= $indent . '
';
// 'savehttp' section
$value = $context->find('savehttp');
$buffer .= $this->section3669ef98d4f6390c8df9645c96a328ee($context, $indent, $value);
return $buffer;
}
private function section83708baddd3627fe4d26675ddf7eaeb5(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="event event-{{category}}" data-toggle="tooltip" title="{{name}} - {{duration}} ms - {{memory}} MB" style="width:{{width}}%; left:{{offset}}%;"></div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . ' <div class="event event-';
$value = $this->resolveValue($context->find('category'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" data-toggle="tooltip" title="';
$value = $this->resolveValue($context->find('name'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= ' - ';
$value = $this->resolveValue($context->find('duration'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= ' ms - ';
$value = $this->resolveValue($context->find('memory'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= ' MB" style="width:';
$value = $this->resolveValue($context->find('width'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%; left:';
$value = $this->resolveValue($context->find('offset'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%;"></div>
';
$context->pop();
}
}
return $buffer;
}
private function section8dd7d0439d6fbb51dfb44aaa94780d5c(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="event event-{{category}}" title="{{sql}}" style="width:{{width}}%; left:{{offset}}%;"></div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . ' <div class="event event-';
$value = $this->resolveValue($context->find('category'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" title="';
$value = $this->resolveValue($context->find('sql'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" style="width:';
$value = $this->resolveValue($context->find('width'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%; left:';
$value = $this->resolveValue($context->find('offset'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%;"></div>
';
$context->pop();
}
}
return $buffer;
}
private function section9756eb1d93d100052bdb80ee49b09ad6(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="laps-timeline">
{{#queries}}
<div class="event event-{{category}}" title="{{sql}}" style="width:{{width}}%; left:{{offset}}%;"></div>
{{/queries}}
</div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . '<div class="laps-timeline">
';
// 'queries' section
$value = $context->find('queries');
$buffer .= $this->section8dd7d0439d6fbb51dfb44aaa94780d5c($context, $indent, $value);
$buffer .= $indent . '</div>
';
$context->pop();
}
}
return $buffer;
}
private function sectionFb9cb3f90496dc38ba05f46e0ac2ad5d(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="event event-{{category}}" title="{{name}} - {{duration}} ms" style="width:{{width}}%; left:{{offset}}%;"></div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . ' <div class="event event-';
$value = $this->resolveValue($context->find('category'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '" title="';
$value = $this->resolveValue($context->find('name'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= ' - ';
$value = $this->resolveValue($context->find('duration'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= ' ms" style="width:';
$value = $this->resolveValue($context->find('width'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%; left:';
$value = $this->resolveValue($context->find('offset'), $context, $indent);
$buffer .= htmlspecialchars($value, 2, 'UTF-8');
$buffer .= '%;"></div>
';
$context->pop();
}
}
return $buffer;
}
private function section3669ef98d4f6390c8df9645c96a328ee(Mustache_Context $context, $indent, $value)
{
$buffer = '';
if (!is_string($value) && is_callable($value)) {
$source = '
<div class="laps-timeline">
{{#http}}
<div class="event event-{{category}}" title="{{name}} - {{duration}} ms" style="width:{{width}}%; left:{{offset}}%;"></div>
{{/http}}
</div>
';
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, '{{') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
$buffer .= $indent . '<div class="laps-timeline">
';
// 'http' section
$value = $context->find('http');
$buffer .= $this->sectionFb9cb3f90496dc38ba05f46e0ac2ad5d($context, $indent, $value);
$buffer .= $indent . '</div>
';
$context->pop();
}
}
return $buffer;
}
}
-21
View File
@@ -1,21 +0,0 @@
<div class="laps-timeline">
{{#events}}
<div class="event event-{{category}}" data-toggle="tooltip" title="{{name}} - {{duration}} ms - {{memory}} MB" style="width:{{width}}%; left:{{offset}}%;"></div>
{{/events}}
</div>
{{#savequeries}}
<div class="laps-timeline">
{{#queries}}
<div class="event event-{{category}}" title="{{sql}}" style="width:{{width}}%; left:{{offset}}%;"></div>
{{/queries}}
</div>
{{/savequeries}}
{{#savehttp}}
<div class="laps-timeline">
{{#http}}
<div class="event event-{{category}}" title="{{name}} - {{duration}} ms" style="width:{{width}}%; left:{{offset}}%;"></div>
{{/http}}
</div>
{{/savehttp}}