initial
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.19.7] - 2026-06-25
|
||||
### Changed
|
||||
- Defer loading the Stats REST API provider until a REST request is served, reducing the per-request PHP/opcache footprint. [#49806]
|
||||
|
||||
## [0.19.6] - 2026-06-15
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.19.5] - 2026-06-10
|
||||
### Changed
|
||||
- Add `fetchpriority="low"` to the Stats script and remove `dns-prefetch` for `stats.wp.com` to improve LCP in Safari and Firefox. [#47936]
|
||||
|
||||
## [0.19.4] - 2026-06-08
|
||||
### Fixed
|
||||
- Abilities: Allow readonly stats abilities with optional input to run when no input is sent. [#49370]
|
||||
|
||||
## [0.19.3] - 2026-06-01
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.19.2] - 2026-05-25
|
||||
### Fixed
|
||||
- Phan: Address `PhanPluginDuplicateConditionalNullCoalescing` violations. [#48887]
|
||||
|
||||
## [0.19.1] - 2026-05-19
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.19.0] - 2026-05-04
|
||||
### Added
|
||||
- Abilities API: Register jetpack-stats abilities for site overview, top content, post views, visits, followers, and stats configuration. [#48286]
|
||||
|
||||
### Changed
|
||||
- Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
|
||||
|
||||
## [0.18.1] - 2026-02-26
|
||||
### Added
|
||||
- Add `AGENTS.md` for coding agent guidelines compliance. [#47330]
|
||||
|
||||
### Changed
|
||||
- Exclude `AGENTS.md` and `CLAUDE.md` from production builds. [#47333]
|
||||
|
||||
## [0.18.0] - 2026-02-23
|
||||
### Added
|
||||
- Add `Transient_Cleanup` class to periodically clean up expired stats cache transients on sites without persistent object cache. [#47212]
|
||||
|
||||
### Changed
|
||||
- Transient cleanup: Skip scheduling cron job entirely on sites with persistent object cache. [#47270]
|
||||
|
||||
## [0.17.8] - 2026-02-02
|
||||
### Changed
|
||||
- Update dependencies. [#39260]
|
||||
|
||||
## [0.17.7] - 2025-12-15
|
||||
### Changed
|
||||
- Improve Post Stats cache handling for invalid or error data. [#46211]
|
||||
|
||||
## [0.17.6] - 2025-12-08
|
||||
### Fixed
|
||||
- Ensure proper flags are used with `json_encode()`. [#46092]
|
||||
|
||||
## [0.17.5] - 2025-11-12
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.17.4] - 2025-11-10
|
||||
### Changed
|
||||
- Tests: Ensure PHP 8.5 compatibility. [#45769]
|
||||
|
||||
## [0.17.3] - 2025-10-10
|
||||
### Changed
|
||||
- Handle lazy-loading of `WP_User` object properties. [#45450]
|
||||
|
||||
## [0.17.2] - 2025-07-21
|
||||
### Changed
|
||||
- Internal updates. [#39260]
|
||||
|
||||
## [0.17.1] - 2025-06-23
|
||||
### Fixed
|
||||
- Autoloader: Prevent double slash in autoloader path. [#44030]
|
||||
|
||||
## [0.17.0] - 2025-06-23
|
||||
### Added
|
||||
- Add archives endpoint support. [#44028]
|
||||
|
||||
## [0.16.3] - 2025-06-16
|
||||
### Fixed
|
||||
- Fix tracking of search terms and tags. [#43879]
|
||||
- Fix parsing of taxonomies query. [#43879]
|
||||
|
||||
## [0.16.2] - 2025-04-28
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.16.1] - 2025-04-15
|
||||
### Fixed
|
||||
- Fix PHP TypeError. [#43072]
|
||||
|
||||
## [0.16.0] - 2025-04-14
|
||||
### Added
|
||||
- Support tracking of archived pages and searches in tracking pixel. [#42368]
|
||||
|
||||
### Changed
|
||||
- Update home tracking pixel to match archive ones. [#43043]
|
||||
|
||||
## [0.15.9] - 2025-03-31
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.8] - 2025-03-24
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.7] - 2025-03-18
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.6] - 2025-03-17
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.5] - 2025-03-12
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.4] - 2025-03-10
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.15.3] - 2025-03-03
|
||||
### Fixed
|
||||
- Tracking Pixel: Ensure the tracking script can be deferred, so it is not blocking. [#42175]
|
||||
|
||||
## [0.15.2] - 2025-02-24
|
||||
### Changed
|
||||
- Update dependencies. [#39260]
|
||||
|
||||
## [0.15.1] - 2025-02-03
|
||||
### Fixed
|
||||
- Code: Remove extra params on function calls. [#41263]
|
||||
|
||||
## [0.15.0] - 2025-01-06
|
||||
### Added
|
||||
- Stats: Add API support for location stats. [#40852]
|
||||
|
||||
## [0.14.1] - 2024-11-25
|
||||
### Changed
|
||||
- Updated dependencies. [#40286]
|
||||
|
||||
## [0.14.0] - 2024-11-18
|
||||
### Removed
|
||||
- General: Update minimum PHP version to 7.2. [#40147]
|
||||
|
||||
## [0.13.5] - 2024-11-04
|
||||
### Added
|
||||
- Enable test coverage. [#39961]
|
||||
|
||||
## [0.13.4] - 2024-10-29
|
||||
### Changed
|
||||
- Internal updates. [#39260]
|
||||
|
||||
## [0.13.3] - 2024-09-23
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.13.2] - 2024-09-05
|
||||
### Changed
|
||||
- Update dependencies. [#39253]
|
||||
|
||||
## [0.13.1] - 2024-08-23
|
||||
### Changed
|
||||
- Updated package dependencies. [#39004]
|
||||
|
||||
## [0.13.0] - 2024-06-10
|
||||
### Added
|
||||
- Staging: deprecating staging mode and separating the logic into is_development_site and in_safe_mode [#37023]
|
||||
|
||||
## [0.12.5] - 2024-05-06
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.12.4] - 2024-04-26
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.12.3] - 2024-04-25
|
||||
### Changed
|
||||
- Update dependencies.
|
||||
|
||||
## [0.12.2] - 2024-04-22
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.12.1] - 2024-04-08
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.12.0] - 2024-04-01
|
||||
### Added
|
||||
- Add the 'stats/blog' REST endpoint. [#36571]
|
||||
- Composer: added version constant for ststs package. [#36657]
|
||||
|
||||
## [0.11.2] - 2024-03-25
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.11.1] - 2024-03-18
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.11.0] - 2024-02-26
|
||||
### Added
|
||||
- Add new method to convert stats data for external consumption. [#35865]
|
||||
|
||||
## [0.10.1] - 2024-02-19
|
||||
### Fixed
|
||||
- Avoid Fatal errors when saved stats data is a WP_Error object [#35746]
|
||||
|
||||
## [0.10.0] - 2024-02-05
|
||||
### Added
|
||||
- Stats fetching mechanism: add filter allowing one to customize how long we cache results. [#35421]
|
||||
|
||||
### Changed
|
||||
- Permit overriding cache when retrieving post views. [#34557]
|
||||
- Remove pre-6.3 asset enqueuing method, and relying on WordPress Core method instead. [#34072]
|
||||
|
||||
### Removed
|
||||
- Stop requiring the Jetpack Assets Composer package. [#34072]
|
||||
|
||||
## [0.9.0] - 2023-12-25
|
||||
### Added
|
||||
- Stats: added passing select UTM parameters to Tracking Pixel requests. [#34431]
|
||||
|
||||
## [0.8.0] - 2023-12-11
|
||||
### Changed
|
||||
- Permit overriding cache when retrieving top posts. [#34153]
|
||||
|
||||
## [0.7.2] - 2023-12-03
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.7.1] - 2023-11-21
|
||||
|
||||
## [0.7.0] - 2023-11-20
|
||||
### Changed
|
||||
- Updated required PHP version to >= 7.0. [#34192]
|
||||
|
||||
## [0.6.6] - 2023-10-23
|
||||
### Fixed
|
||||
- Stats: Increase timeout to 20s. [#33549]
|
||||
|
||||
## [0.6.5] - 2023-08-28
|
||||
### Changed
|
||||
- Updated package dependencies. [#32605]
|
||||
|
||||
## [0.6.4] - 2023-08-09
|
||||
### Added
|
||||
- Stats: compatibility for AMP for WP plugin [#32328]
|
||||
|
||||
## [0.6.3] - 2023-05-15
|
||||
### Changed
|
||||
- PHP 8.1 compatibility updates [#30517]
|
||||
|
||||
## [0.6.2] - 2023-04-10
|
||||
### Added
|
||||
- Add Jetpack Autoloader package suggestion. [#29988]
|
||||
|
||||
## [0.6.1] - 2023-04-05
|
||||
### Bug fixes
|
||||
- Assets: avoid fatal error with outdated package versions. [#29947]
|
||||
|
||||
## [0.6.0] - 2023-04-04
|
||||
### Changed
|
||||
- Use core WordPress methods to add the Tracking pixel to the page, instead of printing the tracking pixel directly. [#29780]
|
||||
|
||||
### Deprecated
|
||||
- Methods used to retrieve tracking pixels are now deprecated. No replacement needed, we now hook / enqueue the tracking pixels using core WP methods. [#29780]
|
||||
|
||||
## [0.5.2] - 2023-03-20
|
||||
### Changed
|
||||
- Updated package dependencies. [#29480]
|
||||
|
||||
## [0.5.1] - 2023-02-20
|
||||
### Changed
|
||||
- Minor internal updates.
|
||||
|
||||
## [0.5.0] - 2023-02-15
|
||||
### Added
|
||||
- Added new options to store the timestamp when Odyssey is enabled and disabled [#28794]
|
||||
- Stats: Adds support for Notice control [#28857]
|
||||
|
||||
## [0.4.2] - 2023-02-08
|
||||
### Changed
|
||||
- Minor internal updates.
|
||||
|
||||
## [0.4.1] - 2023-01-11
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [0.4.0] - 2022-12-06
|
||||
### Added
|
||||
- Stats: added streak, highlights, insights for WPCOM_Stats [#27604]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#27688]
|
||||
|
||||
## [0.3.3] - 2022-11-28
|
||||
### Changed
|
||||
- Updated package dependencies. [#27043]
|
||||
|
||||
## [0.3.2] - 2022-11-07
|
||||
### Changed
|
||||
- Updated package dependencies. [#27278]
|
||||
|
||||
## [0.3.1] - 2022-10-25
|
||||
### Changed
|
||||
- Updated package dependencies. [#26705]
|
||||
|
||||
## [0.3.0] - 2022-10-19
|
||||
### Changed
|
||||
- Cache errors when fetching stats and reverted cache prefix. [#26922]
|
||||
|
||||
## [0.2.0] - 2022-10-13
|
||||
### Changed
|
||||
- Changed cache prefix [#26719]
|
||||
- Updated readme for Stats package [#26759]
|
||||
|
||||
## 0.1.0 - 2022-10-11
|
||||
### Added
|
||||
- Stats: Add package scaffold [#26312]
|
||||
- Stats package: Add 'jetpack.getBlog' XMLRPC endpoint [#26473]
|
||||
- Stats package: Add entrypoint class for setting hooks and configuration. [#26601]
|
||||
- Stats package: Introduce WPCOM_Stats class [#26530]
|
||||
- Stats package: Manage Stats options [#26431]
|
||||
- Stats Package: Tracking Pixel functionality [#26516]
|
||||
|
||||
### Changed
|
||||
- Add mirror repo [#26750]
|
||||
|
||||
### Fixed
|
||||
- Fixing static method which was called without self reference. [#26640]
|
||||
|
||||
[0.19.7]: https://github.com/Automattic/jetpack-stats/compare/v0.19.6...v0.19.7
|
||||
[0.19.6]: https://github.com/Automattic/jetpack-stats/compare/v0.19.5...v0.19.6
|
||||
[0.19.5]: https://github.com/Automattic/jetpack-stats/compare/v0.19.4...v0.19.5
|
||||
[0.19.4]: https://github.com/Automattic/jetpack-stats/compare/v0.19.3...v0.19.4
|
||||
[0.19.3]: https://github.com/Automattic/jetpack-stats/compare/v0.19.2...v0.19.3
|
||||
[0.19.2]: https://github.com/Automattic/jetpack-stats/compare/v0.19.1...v0.19.2
|
||||
[0.19.1]: https://github.com/Automattic/jetpack-stats/compare/v0.19.0...v0.19.1
|
||||
[0.19.0]: https://github.com/Automattic/jetpack-stats/compare/v0.18.1...v0.19.0
|
||||
[0.18.1]: https://github.com/Automattic/jetpack-stats/compare/v0.18.0...v0.18.1
|
||||
[0.18.0]: https://github.com/Automattic/jetpack-stats/compare/v0.17.8...v0.18.0
|
||||
[0.17.8]: https://github.com/Automattic/jetpack-stats/compare/v0.17.7...v0.17.8
|
||||
[0.17.7]: https://github.com/Automattic/jetpack-stats/compare/v0.17.6...v0.17.7
|
||||
[0.17.6]: https://github.com/Automattic/jetpack-stats/compare/v0.17.5...v0.17.6
|
||||
[0.17.5]: https://github.com/Automattic/jetpack-stats/compare/v0.17.4...v0.17.5
|
||||
[0.17.4]: https://github.com/Automattic/jetpack-stats/compare/v0.17.3...v0.17.4
|
||||
[0.17.3]: https://github.com/Automattic/jetpack-stats/compare/v0.17.2...v0.17.3
|
||||
[0.17.2]: https://github.com/Automattic/jetpack-stats/compare/v0.17.1...v0.17.2
|
||||
[0.17.1]: https://github.com/Automattic/jetpack-stats/compare/v0.17.0...v0.17.1
|
||||
[0.17.0]: https://github.com/Automattic/jetpack-stats/compare/v0.16.3...v0.17.0
|
||||
[0.16.3]: https://github.com/Automattic/jetpack-stats/compare/v0.16.2...v0.16.3
|
||||
[0.16.2]: https://github.com/Automattic/jetpack-stats/compare/v0.16.1...v0.16.2
|
||||
[0.16.1]: https://github.com/Automattic/jetpack-stats/compare/v0.16.0...v0.16.1
|
||||
[0.16.0]: https://github.com/Automattic/jetpack-stats/compare/v0.15.9...v0.16.0
|
||||
[0.15.9]: https://github.com/Automattic/jetpack-stats/compare/v0.15.8...v0.15.9
|
||||
[0.15.8]: https://github.com/Automattic/jetpack-stats/compare/v0.15.7...v0.15.8
|
||||
[0.15.7]: https://github.com/Automattic/jetpack-stats/compare/v0.15.6...v0.15.7
|
||||
[0.15.6]: https://github.com/Automattic/jetpack-stats/compare/v0.15.5...v0.15.6
|
||||
[0.15.5]: https://github.com/Automattic/jetpack-stats/compare/v0.15.4...v0.15.5
|
||||
[0.15.4]: https://github.com/Automattic/jetpack-stats/compare/v0.15.3...v0.15.4
|
||||
[0.15.3]: https://github.com/Automattic/jetpack-stats/compare/v0.15.2...v0.15.3
|
||||
[0.15.2]: https://github.com/Automattic/jetpack-stats/compare/v0.15.1...v0.15.2
|
||||
[0.15.1]: https://github.com/Automattic/jetpack-stats/compare/v0.15.0...v0.15.1
|
||||
[0.15.0]: https://github.com/Automattic/jetpack-stats/compare/v0.14.1...v0.15.0
|
||||
[0.14.1]: https://github.com/Automattic/jetpack-stats/compare/v0.14.0...v0.14.1
|
||||
[0.14.0]: https://github.com/Automattic/jetpack-stats/compare/v0.13.5...v0.14.0
|
||||
[0.13.5]: https://github.com/Automattic/jetpack-stats/compare/v0.13.4...v0.13.5
|
||||
[0.13.4]: https://github.com/Automattic/jetpack-stats/compare/v0.13.3...v0.13.4
|
||||
[0.13.3]: https://github.com/Automattic/jetpack-stats/compare/v0.13.2...v0.13.3
|
||||
[0.13.2]: https://github.com/Automattic/jetpack-stats/compare/v0.13.1...v0.13.2
|
||||
[0.13.1]: https://github.com/Automattic/jetpack-stats/compare/v0.13.0...v0.13.1
|
||||
[0.13.0]: https://github.com/Automattic/jetpack-stats/compare/v0.12.5...v0.13.0
|
||||
[0.12.5]: https://github.com/Automattic/jetpack-stats/compare/v0.12.4...v0.12.5
|
||||
[0.12.4]: https://github.com/Automattic/jetpack-stats/compare/v0.12.3...v0.12.4
|
||||
[0.12.3]: https://github.com/Automattic/jetpack-stats/compare/v0.12.2...v0.12.3
|
||||
[0.12.2]: https://github.com/Automattic/jetpack-stats/compare/v0.12.1...v0.12.2
|
||||
[0.12.1]: https://github.com/Automattic/jetpack-stats/compare/v0.12.0...v0.12.1
|
||||
[0.12.0]: https://github.com/Automattic/jetpack-stats/compare/v0.11.2...v0.12.0
|
||||
[0.11.2]: https://github.com/Automattic/jetpack-stats/compare/v0.11.1...v0.11.2
|
||||
[0.11.1]: https://github.com/Automattic/jetpack-stats/compare/v0.11.0...v0.11.1
|
||||
[0.11.0]: https://github.com/Automattic/jetpack-stats/compare/v0.10.1...v0.11.0
|
||||
[0.10.1]: https://github.com/Automattic/jetpack-stats/compare/v0.10.0...v0.10.1
|
||||
[0.10.0]: https://github.com/Automattic/jetpack-stats/compare/v0.9.0...v0.10.0
|
||||
[0.9.0]: https://github.com/Automattic/jetpack-stats/compare/v0.8.0...v0.9.0
|
||||
[0.8.0]: https://github.com/Automattic/jetpack-stats/compare/v0.7.2...v0.8.0
|
||||
[0.7.2]: https://github.com/Automattic/jetpack-stats/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/Automattic/jetpack-stats/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/Automattic/jetpack-stats/compare/v0.6.6...v0.7.0
|
||||
[0.6.6]: https://github.com/Automattic/jetpack-stats/compare/v0.6.5...v0.6.6
|
||||
[0.6.5]: https://github.com/Automattic/jetpack-stats/compare/v0.6.4...v0.6.5
|
||||
[0.6.4]: https://github.com/Automattic/jetpack-stats/compare/v0.6.3...v0.6.4
|
||||
[0.6.3]: https://github.com/Automattic/jetpack-stats/compare/v0.6.2...v0.6.3
|
||||
[0.6.2]: https://github.com/Automattic/jetpack-stats/compare/v0.6.1...v0.6.2
|
||||
[0.6.1]: https://github.com/Automattic/jetpack-stats/compare/v0.6.0...v0.6.1
|
||||
[0.6.0]: https://github.com/Automattic/jetpack-stats/compare/v0.5.2...v0.6.0
|
||||
[0.5.2]: https://github.com/Automattic/jetpack-stats/compare/v0.5.1...v0.5.2
|
||||
[0.5.1]: https://github.com/Automattic/jetpack-stats/compare/v0.5.0...v0.5.1
|
||||
[0.5.0]: https://github.com/Automattic/jetpack-stats/compare/v0.4.2...v0.5.0
|
||||
[0.4.2]: https://github.com/Automattic/jetpack-stats/compare/v0.4.1...v0.4.2
|
||||
[0.4.1]: https://github.com/Automattic/jetpack-stats/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/Automattic/jetpack-stats/compare/v0.3.3...v0.4.0
|
||||
[0.3.3]: https://github.com/Automattic/jetpack-stats/compare/v0.3.2...v0.3.3
|
||||
[0.3.2]: https://github.com/Automattic/jetpack-stats/compare/v0.3.1...v0.3.2
|
||||
[0.3.1]: https://github.com/Automattic/jetpack-stats/compare/v0.3.0...v0.3.1
|
||||
[0.3.0]: https://github.com/Automattic/jetpack-stats/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/Automattic/jetpack-stats/compare/v0.1.0...v0.2.0
|
||||
+1412
File diff suppressed because it is too large
Load Diff
+266
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats Main
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Stats\Abilities\Stats_Abilities;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Visitor;
|
||||
use WP_User;
|
||||
|
||||
/**
|
||||
* Stats Main class.
|
||||
*
|
||||
* Entrypoint for Stats.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
class Main {
|
||||
/**
|
||||
* Stats version.
|
||||
* Mostly needed for backwards compatibility.
|
||||
*/
|
||||
const STATS_VERSION = '9';
|
||||
|
||||
/**
|
||||
* Singleton Main instance.
|
||||
*
|
||||
* @var Main
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
* Used to configure the stats package, eg when called via the Config package.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function init() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new Main();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct() {
|
||||
/**
|
||||
* This avoids conflicts when running Stats package with older versions of the Jetpack plugin.
|
||||
*
|
||||
* On JP version 11.5-a.2 the hooks below were removed from the Jetpack plugin and it is safe
|
||||
* to register them in the Stats package.
|
||||
*/
|
||||
$jp_plugin_version = Constants::get_constant( 'JETPACK__VERSION' );
|
||||
if ( $jp_plugin_version && version_compare( $jp_plugin_version, '11.5-a.2', '<' ) ) {
|
||||
return;
|
||||
}
|
||||
// Generate the tracking code after wp() has queried for posts.
|
||||
add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ), 1 );
|
||||
|
||||
add_action( 'wp_head', array( __CLASS__, 'hide_smile_css' ) );
|
||||
add_action( 'embed_head', array( __CLASS__, 'hide_smile_css' ) );
|
||||
|
||||
// Map stats caps.
|
||||
add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_caps' ), 10, 3 );
|
||||
|
||||
XMLRPC_Provider::init();
|
||||
|
||||
/*
|
||||
* REST_Provider only registers its routes on REST init, so defer
|
||||
* constructing it (and autoloading the class) until a REST request is
|
||||
* served. A closure is used because rest_api_init passes the REST server
|
||||
* to callbacks, which would otherwise be read as REST_Provider::init()'s
|
||||
* $new_instance argument.
|
||||
*/
|
||||
add_action(
|
||||
'rest_api_init',
|
||||
static function () {
|
||||
REST_Provider::init();
|
||||
},
|
||||
0
|
||||
);
|
||||
Transient_Cleanup::init();
|
||||
|
||||
// Clean up transient cron on module deactivation.
|
||||
add_action( 'jetpack_deactivate_module_stats', array( Transient_Cleanup::class, 'unschedule_cleanup' ) );
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
// Register WP Abilities API surface. Gated behind the
|
||||
// `jetpack_wp_abilities_enabled` filter inside Registrar::init(),
|
||||
// which defaults to false — so this call is safe to make unconditionally
|
||||
// and still opt-in per-site until the flag is flipped.
|
||||
Stats_Abilities::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if filter is set and dnt is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function jetpack_is_dnt_enabled() {
|
||||
/**
|
||||
* Filter the option which decides honor DNT or not.
|
||||
*
|
||||
* @module stats
|
||||
* @since-jetpack 6.1.0
|
||||
*
|
||||
* @param bool false Honors DNT for clients who don't want to be tracked. Defaults to false. Set to true to enable.
|
||||
*/
|
||||
if ( false === apply_filters( 'jetpack_honor_dnt_header_for_stats', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $_SERVER as $name => $value ) {
|
||||
if ( 'http_dnt' === strtolower( $name ) && 1 === (int) $value ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps view_stats cap to read cap as needed.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $caps Caps.
|
||||
* @param mixed $cap Cap.
|
||||
* @param mixed $user_id User ID.
|
||||
* @return array Possibly mapped capabilities for meta capability.
|
||||
*/
|
||||
public static function map_meta_caps( $caps, $cap, $user_id ) {
|
||||
// Map view_stats to exists.
|
||||
if ( 'view_stats' === $cap ) {
|
||||
$user = new WP_User( $user_id );
|
||||
// WordPress 6.9 introduced lazy-loading of some WP_User properties, including `roles`.
|
||||
// It also made said properties protected, so we can't modify keys directly.
|
||||
$user_roles = $user->roles;
|
||||
$user_role = array_shift( $user_roles ); // Work with the copy
|
||||
$stats_roles = Options::get_option( 'roles' );
|
||||
|
||||
// Is the users role in the available stats roles?
|
||||
if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles, true ) ) {
|
||||
$caps = array( 'read' );
|
||||
}
|
||||
}
|
||||
|
||||
return $caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Template Redirect.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function template_redirect() {
|
||||
if ( ! self::should_track() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'wp_enqueue_scripts', array( Tracking_Pixel::class, 'enqueue_stats_script' ), 101 );
|
||||
add_action( 'wp_footer', array( Tracking_Pixel::class, 'add_amp_pixel' ), 101 );
|
||||
add_action( 'web_stories_print_analytics', array( Tracking_Pixel::class, 'add_amp_pixel' ), 101 );
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS to hide the tracking pixel smiley.
|
||||
* It is now hidden for everyone (used to be visible if you had set the hide_smile option).
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function hide_smile_css() {
|
||||
if ( ! self::should_track() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<style>img#wpstats{display:none}</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we should add the tracking pixel.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_track() {
|
||||
global $current_user;
|
||||
|
||||
// Not connected sites should not generate tracking stats.
|
||||
if ( ! ( new Connection_Manager() )->is_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the stats module is disabled we should not generate tracking stats.
|
||||
if ( ! ( new Modules() )->is_active( 'stats' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not generate tracking stats for feeds, robots, embeds, previews
|
||||
// or to honour the DNT headers.
|
||||
if (
|
||||
is_feed()
|
||||
|| is_robots()
|
||||
|| is_embed()
|
||||
|| is_trackback()
|
||||
|| is_preview()
|
||||
|| self::jetpack_is_dnt_enabled()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sites in Safe Mode should not generate tracking stats.
|
||||
$status = new Status();
|
||||
if ( $status->in_safe_mode() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we be counting this user's views?
|
||||
if ( ! empty( $current_user->ID ) ) {
|
||||
$count_roles = Options::get_option( 'count_roles' );
|
||||
if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow excluding specific IP addresses from being tracked in Stats.
|
||||
* Note: for this to work well, visitors' IP addresses must:
|
||||
* - be stored and returned properly in IP address headers;
|
||||
* - not be impacted by any caching setup on your site.
|
||||
*
|
||||
* @module stats
|
||||
*
|
||||
* @since-jetpack 10.6
|
||||
*
|
||||
* @param array $excluded_ips An array of IP address strings to exclude from tracking.
|
||||
*/
|
||||
$excluded_ips = (array) apply_filters( 'jetpack_stats_excluded_ips', array() );
|
||||
|
||||
// Should we be counting views for this IP address?
|
||||
$current_user_ip = ( new Visitor() )->get_ip( true );
|
||||
if (
|
||||
! empty( $excluded_ips )
|
||||
&& in_array( $current_user_ip, $excluded_ips, true )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats Options
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* Stats Options class.
|
||||
*/
|
||||
class Options {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string $option_name The 'stats' option name
|
||||
*/
|
||||
const OPTION_NAME = 'stats_options';
|
||||
|
||||
/**
|
||||
* Stats Options.
|
||||
*
|
||||
* @var array $options An array of associated Stats options (default empty)
|
||||
*/
|
||||
protected static $options = array();
|
||||
|
||||
/**
|
||||
* Stats Get Options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_options() {
|
||||
// Make sure we only get options from the database once per connection.
|
||||
if ( array() !== self::$options ) {
|
||||
return self::$options;
|
||||
}
|
||||
|
||||
self::$options = get_option( self::OPTION_NAME, array() );
|
||||
self::$options = array_merge( self::get_defaults(), self::$options );
|
||||
|
||||
if ( self::$options['version'] < Main::STATS_VERSION ) {
|
||||
self::upgrade_options( self::$options );
|
||||
}
|
||||
|
||||
return self::$options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Stats Option.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function get_option( $option ) {
|
||||
if ( 'blog_id' === $option ) {
|
||||
return Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
|
||||
$options = self::get_options();
|
||||
|
||||
if ( isset( $options[ $option ] ) ) {
|
||||
return $options[ $option ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Set Option.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
* @param mixed $value The option Value.
|
||||
* @return bool
|
||||
*/
|
||||
public static function set_option( $option, $value ) {
|
||||
$options = self::get_options();
|
||||
|
||||
$options[ $option ] = $value;
|
||||
|
||||
return self::set_options( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Set Options.
|
||||
*
|
||||
* @access public
|
||||
* @param array $options Options.
|
||||
* @return bool
|
||||
*/
|
||||
public static function set_options( $options ) {
|
||||
if ( ! is_array( $options ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaults = self::get_defaults();
|
||||
$stored_options = get_option( self::OPTION_NAME, array() );
|
||||
$all_options = array_merge( $defaults, $stored_options );
|
||||
$options = array_merge( $all_options, $options );
|
||||
|
||||
$allowed_options = array_keys( $defaults );
|
||||
foreach ( $options as $option_name => $option_value ) {
|
||||
if ( ! in_array( $option_name, $allowed_options, true ) ) {
|
||||
unset( $options[ $option_name ] );
|
||||
}
|
||||
}
|
||||
|
||||
$options['blog_id'] = Jetpack_Options::get_option( 'id' );
|
||||
$options['version'] = Main::STATS_VERSION;
|
||||
|
||||
$success = update_option( self::OPTION_NAME, $options );
|
||||
|
||||
if ( true === $success ) {
|
||||
self::$options = $options;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Upgrade Options.
|
||||
*
|
||||
* Ideally this should be a protected method but keeping it public
|
||||
* to maintain backwards compatibility with stats_upgrade_options.
|
||||
*
|
||||
* @access public
|
||||
* @param array $options The stats options.
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function upgrade_options( $options ) {
|
||||
if ( isset( $options['reg_users'] ) ) {
|
||||
if ( ! function_exists( 'get_editable_roles' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||||
}
|
||||
if ( $options['reg_users'] ) {
|
||||
$options['count_roles'] = array_keys( get_editable_roles() );
|
||||
}
|
||||
unset( $options['reg_users'] );
|
||||
}
|
||||
|
||||
if ( false === self::set_options( $options ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::$options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Stats related options.
|
||||
*
|
||||
* Exposed so consumers (e.g. the Abilities API surface) can introspect
|
||||
* each option's default value and infer its type without redeclaring
|
||||
* a parallel allow-list of bool/array fields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_defaults() {
|
||||
return array(
|
||||
'admin_bar' => true,
|
||||
'roles' => array( 'administrator' ),
|
||||
'count_roles' => array(),
|
||||
'do_not_track' => true, // @todo
|
||||
'blog_id' => Jetpack_Options::get_option( 'id' ),
|
||||
'version' => Main::STATS_VERSION,
|
||||
'collapse_nudges' => false,
|
||||
'enable_odyssey_stats' => true,
|
||||
'odyssey_stats_changed_at' => 0,
|
||||
'notices' => array(),
|
||||
'views' => 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '0.19.7';
|
||||
|
||||
const PACKAGE_SLUG = 'stats';
|
||||
|
||||
/**
|
||||
* Adds the package slug and version to the package version tracker's data.
|
||||
*
|
||||
* @param array $package_versions The package version array.
|
||||
*
|
||||
* @return array The package version array.
|
||||
*/
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
|
||||
return $package_versions;
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* The Stats REST Provider class.
|
||||
*
|
||||
* @package @automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack\Connection\REST_Connector;
|
||||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* The REST API provider class.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*/
|
||||
class REST_Provider {
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var REST_Provider
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*
|
||||
* Use the static::init() method to get an instance.
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'rest_api_init', array( $this, 'initialize_rest_api' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class and get back a singleton instance.
|
||||
*
|
||||
* @param bool $new_instance Force create new instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function init( $new_instance = false ) {
|
||||
if ( null === self::$instance || $new_instance ) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the REST API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize_rest_api() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/stats/blog',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_blog' ),
|
||||
'permission_callback' => array( $this, 'get_blog_permission_check' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stats blog data.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_blog() {
|
||||
return XMLRPC_Provider::init()->get_blog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permissions for the `/stats/blog` endpoint.
|
||||
*
|
||||
* @return WP_Error|true
|
||||
*/
|
||||
public function get_blog_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_stats_get_blog', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats Tracking_Pixel
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Jetpack_Options;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Stats Tracking_Pixel class.
|
||||
*
|
||||
* Responsible for embedding the Stats tracking pixel.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
class Tracking_Pixel {
|
||||
|
||||
/**
|
||||
* Array name.
|
||||
*
|
||||
* @var string $array_name The 'stats' array name
|
||||
*/
|
||||
const STATS_ARRAY_TO_STRING_FILTER = 'stats_array';
|
||||
|
||||
const TRACKED_UTM_PARAMETERS = array(
|
||||
'utm_id',
|
||||
'utm_source',
|
||||
'utm_medium',
|
||||
'utm_campaign',
|
||||
'utm_term',
|
||||
'utm_content',
|
||||
'utm_source_platform',
|
||||
'utm_creative_format',
|
||||
'utm_marketing_tactic',
|
||||
);
|
||||
|
||||
/**
|
||||
* Stats Build View Data.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function build_view_data() {
|
||||
global $wp_the_query;
|
||||
|
||||
$blog = Jetpack_Options::get_option( 'id' );
|
||||
$tz = get_option( 'gmt_offset' );
|
||||
$v = 'ext';
|
||||
$blog_url = wp_parse_url( site_url() );
|
||||
$srv = $blog_url['host'];
|
||||
$is_not_post = false;
|
||||
if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
|
||||
// Store and reset the queried_object and queried_object_id
|
||||
// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
|
||||
// Repro:
|
||||
// 1. Set home_url = https://ExamPle.com/
|
||||
// 2. Set show_on_front = page
|
||||
// 3. Set page_on_front = something
|
||||
// 4. Visit https://example.com/ !
|
||||
$queried_object = $wp_the_query->queried_object ?? null;
|
||||
$queried_object_id = $wp_the_query->queried_object_id ?? null;
|
||||
try {
|
||||
$post_obj = $wp_the_query->get_queried_object();
|
||||
$post = $post_obj instanceof WP_Post ? $post_obj->ID : '0';
|
||||
} finally {
|
||||
$wp_the_query->queried_object = $queried_object;
|
||||
$wp_the_query->queried_object_id = $queried_object_id;
|
||||
}
|
||||
} else {
|
||||
$post = '0';
|
||||
$is_not_post = true;
|
||||
}
|
||||
$view_data = compact( 'v', 'blog', 'post', 'tz', 'srv' );
|
||||
// Batcache removes some of the UTM params from $_GET, we need to extract them from uri directly instead.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're sanitizing individual params in the loop.
|
||||
$url_query = wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ), PHP_URL_QUERY );
|
||||
parse_str( (string) $url_query, $url_params );
|
||||
foreach ( self::TRACKED_UTM_PARAMETERS as $utm_parameter ) {
|
||||
if ( isset( $url_params[ $utm_parameter ] ) && is_scalar( $url_params[ $utm_parameter ] ) ) {
|
||||
$view_data[ $utm_parameter ] = substr( sanitize_textarea_field( wp_unslash( $url_params[ $utm_parameter ] ) ), 0, 255 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $is_not_post ) {
|
||||
if ( $wp_the_query->is_home() ) {
|
||||
$view_data['arch_home'] = '1';
|
||||
} elseif ( $wp_the_query->is_search() ) {
|
||||
$search_term = $wp_the_query->query['s'] ?? $wp_the_query->query_vars['s'] ?? '';
|
||||
$view_data['arch_search'] = sanitize_text_field( $search_term );
|
||||
$view_data['arch_filters'] = sanitize_text_field( self::build_search_filters( $wp_the_query ) );
|
||||
$view_data['arch_results'] = $wp_the_query->posts ? $wp_the_query->post_count : 0;
|
||||
} elseif ( $wp_the_query->is_archive() ) {
|
||||
if ( $wp_the_query->is_date ) {
|
||||
$query = $wp_the_query->query;
|
||||
$date_parts = array_filter( array( $query['year'] ?? null, $query['monthnum'] ?? null, $query['day'] ?? null ) );
|
||||
$date = implode( '/', $date_parts );
|
||||
$view_data['arch_date'] = $date;
|
||||
}
|
||||
if ( $wp_the_query->is_category ) {
|
||||
$view_data['arch_cat'] = $wp_the_query->query['category_name'] ?? $wp_the_query->query_vars['category_name'] ?? '';
|
||||
}
|
||||
if ( $wp_the_query->is_tag ) {
|
||||
$view_data['arch_tag'] = $wp_the_query->query['tag'] ?? $wp_the_query->query_vars['tag'] ?? '';
|
||||
}
|
||||
if ( $wp_the_query->is_author ) {
|
||||
$view_data['arch_author'] = $wp_the_query->query['author_name'] ?? '';
|
||||
}
|
||||
if ( $wp_the_query->is_tax ) {
|
||||
$query = $wp_the_query->query;
|
||||
if ( is_array( $query ) && count( $query ) === 1 ) {
|
||||
$view_data[ 'arch_tax_' . array_keys( $query )[0] ] = array_values( $query )[0];
|
||||
}
|
||||
}
|
||||
$view_data['arch_results'] = $wp_the_query->posts ? $wp_the_query->post_count : 0;
|
||||
} elseif ( $wp_the_query->is_404() ) {
|
||||
$view_data['arch_err'] = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
|
||||
} else {
|
||||
$view_data['arch_other'] = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
|
||||
}
|
||||
}
|
||||
return $view_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the tracking data for a search page.
|
||||
*
|
||||
* @access private
|
||||
* @param \WP_Query $query The WP_Query object to parse all the filters from.
|
||||
* @return string The search filters in a URL query string format.
|
||||
*/
|
||||
private static function build_search_filters( $query ) {
|
||||
$data = array(
|
||||
'posts_per_page' => $query->get( 'posts_per_page' ),
|
||||
'paged' => ( $query->get( 'paged' ) ) ? absint( $query->get( 'paged' ) ) : 1,
|
||||
'orderby' => $query->get( 'orderby' ),
|
||||
'order' => $query->get( 'order' ),
|
||||
);
|
||||
|
||||
if ( $query->get( 'author_name' ) ) {
|
||||
$data['author_name'] = $query->get( 'author_name' );
|
||||
}
|
||||
$filters = http_build_query( $data );
|
||||
|
||||
$the_tax_query = $query->tax_query;
|
||||
$terms = array();
|
||||
if ( ! empty( $the_tax_query->queried_terms ) && is_array( $the_tax_query->queried_terms ) ) {
|
||||
foreach ( $the_tax_query->queries as $tax_query ) {
|
||||
if ( ! is_array( $tax_query ) || ! isset( $tax_query['taxonomy'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$taxonomy = $tax_query['taxonomy'];
|
||||
if ( ! isset( $terms[ $taxonomy ] ) || ! is_array( $terms[ $taxonomy ] ) ) {
|
||||
$terms[ $taxonomy ] = array();
|
||||
}
|
||||
$terms[ $taxonomy ] = array_merge( $terms[ $taxonomy ], $tax_query['terms'] );
|
||||
}
|
||||
}
|
||||
if ( ! empty( $terms ) ) {
|
||||
$filters .= '&terms=' . wp_json_encode( $terms, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
|
||||
}
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Stats tracking details.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @access private
|
||||
* @param array $data Array of data for the AMP pixel tracker.
|
||||
* @return string
|
||||
*/
|
||||
private static function build_stats_details( $data ) {
|
||||
$data_stats_array = self::stats_array_to_string( $data );
|
||||
|
||||
return sprintf(
|
||||
'_stq = window._stq || [];
|
||||
_stq.push([ "view", %1$s ]);
|
||||
_stq.push([ "clickTrackerInit", "%2$s", "%3$s" ]);',
|
||||
$data_stats_array,
|
||||
$data['blog'],
|
||||
$data['post']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fetchpriority="low" to the Stats script attributes.
|
||||
*
|
||||
* Reduces network contention with resources in the critical rendering path (e.g., the LCP
|
||||
* element image). This benefits Safari and Firefox, which don't automatically assign low
|
||||
* priority to async/defer scripts (unlike Chrome).
|
||||
*
|
||||
* @since 0.19.5
|
||||
*
|
||||
* @param array $attributes Script tag attributes.
|
||||
* @return array Modified attributes.
|
||||
*/
|
||||
public static function add_low_fetchpriority( $attributes ) {
|
||||
// WordPress derives the tag id from the enqueue handle as "{handle}-js", so the
|
||||
// 'jetpack-stats' script (registered in enqueue_stats_script()) prints as
|
||||
// 'jetpack-stats-js'. Keep this in sync if the handle is ever renamed.
|
||||
if ( isset( $attributes['id'] ) && 'jetpack-stats-js' === $attributes['id'] ) {
|
||||
$attributes['fetchpriority'] = 'low';
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the dns-prefetch resource hint for stats.wp.com.
|
||||
*
|
||||
* WordPress automatically adds dns-prefetch hints for enqueued script hosts via
|
||||
* wp_dependencies_unique_hosts(). Since we're deprioritizing the stats script,
|
||||
* the dns-prefetch is counterproductive — it front-loads DNS resolution for a
|
||||
* resource we're intentionally delaying.
|
||||
*
|
||||
* @since 0.19.5
|
||||
*
|
||||
* @param array $urls Array of resource hint URLs.
|
||||
* @param string $relation_type The relation type (dns-prefetch, preconnect, etc.).
|
||||
* @return array Filtered URLs.
|
||||
*/
|
||||
public static function remove_stats_dns_prefetch( $urls, $relation_type ) {
|
||||
if ( 'dns-prefetch' !== $relation_type ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$urls,
|
||||
static function ( $url ) {
|
||||
// Resource hints can be arrays that carry the URL under an 'href' key.
|
||||
if ( is_array( $url ) ) {
|
||||
$candidate = ( isset( $url['href'] ) && is_string( $url['href'] ) ) ? $url['href'] : '';
|
||||
} elseif ( is_string( $url ) ) {
|
||||
$candidate = $url;
|
||||
} else {
|
||||
return true; // Unknown entry shape; leave it untouched.
|
||||
}
|
||||
|
||||
// dns-prefetch entries arrive in several shapes: WordPress core emits bare
|
||||
// hosts ('stats.wp.com') via wp_dependencies_unique_hosts(), while other
|
||||
// filters may add scheme-relative ('//stats.wp.com') or full URLs. Normalize
|
||||
// each to a host so we drop stats.wp.com exactly without removing look-alike
|
||||
// hosts such as 'mystats.wp.com' or 'stats.wp.com.evil.tld'.
|
||||
if ( str_starts_with( $candidate, '//' ) ) {
|
||||
$host = wp_parse_url( 'https:' . $candidate, PHP_URL_HOST );
|
||||
} elseif ( str_contains( $candidate, '://' ) ) {
|
||||
$host = wp_parse_url( $candidate, PHP_URL_HOST );
|
||||
} else {
|
||||
$host = $candidate; // Bare host form, e.g. 'stats.wp.com'.
|
||||
}
|
||||
|
||||
return ! is_string( $host ) || 'stats.wp.com' !== strtolower( $host );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the Stats pixel.
|
||||
* Do not use this function directly, it is hooked into `wp_enqueue_scripts`.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_stats_script() {
|
||||
if ( self::is_amp_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'jetpack-stats',
|
||||
'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js',
|
||||
array(),
|
||||
null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- The version is set in the URL.
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'strategy' => 'defer',
|
||||
)
|
||||
);
|
||||
add_filter( 'wp_script_attributes', array( static::class, 'add_low_fetchpriority' ) );
|
||||
add_filter( 'wp_resource_hints', array( static::class, 'remove_stats_dns_prefetch' ), 100, 2 );
|
||||
|
||||
$data = self::build_view_data();
|
||||
|
||||
/**
|
||||
* Filter the parameters added to the JavaScript stats tracking code.
|
||||
*
|
||||
* @module stats
|
||||
*
|
||||
* @since-jetpack 10.9
|
||||
*
|
||||
* @param array $data Array of options about the site and page you're on.
|
||||
*/
|
||||
$data = (array) apply_filters( 'jetpack_stats_footer_js_data', $data );
|
||||
|
||||
$triggers = self::build_stats_details( $data );
|
||||
wp_add_inline_script(
|
||||
'jetpack-stats',
|
||||
$triggers,
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stats footer for AMP output.
|
||||
*
|
||||
* @access private
|
||||
* @param array $data Array of data for the AMP pixel tracker.
|
||||
* @return string Returns the footer to add for the Stats tracker in an AMP scenario.
|
||||
*/
|
||||
private static function get_amp_footer( $data ) {
|
||||
/**
|
||||
* Filter the parameters added to the AMP pixel tracking code.
|
||||
*
|
||||
* @module stats
|
||||
*
|
||||
* @since-jetpack 10.9
|
||||
*
|
||||
* @param array $data Array of options about the site and page you're on.
|
||||
*/
|
||||
$data = (array) apply_filters( 'jetpack_stats_footer_amp_data', $data );
|
||||
|
||||
$data['host'] = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; // input var ok.
|
||||
$data['rand'] = 'RANDOM'; // AMP placeholder.
|
||||
$data['ref'] = 'DOCUMENT_REFERRER'; // AMP placeholder.
|
||||
$data = array_map( 'rawurlencode', $data );
|
||||
$pixel_url = add_query_arg( $data, 'https://pixel.wp.com/g.gif' );
|
||||
return '<amp-pixel src="' . esc_url( $pixel_url ) . '"></amp-pixel>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an AMP pixel.
|
||||
* Do not use this function directly, it is hooked into `wp_footer`.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function add_amp_pixel() {
|
||||
$data = self::build_view_data();
|
||||
if ( ! self::is_amp_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pixel = self::get_amp_footer( $data );
|
||||
echo $pixel; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Footer.
|
||||
*
|
||||
* @deprecated 0.6.0
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function add_to_footer() {
|
||||
_deprecated_function( __METHOD__, '0.6.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the footer to add for the Stats tracker.
|
||||
*
|
||||
* @deprecated 0.6.0
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Array of data for the JS stats tracker.
|
||||
* @return void
|
||||
*/
|
||||
public static function get_footer_to_add( $data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
_deprecated_function( __METHOD__, '0.6.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the stats footer. Kept for backward compatibility on legacy AMF views.
|
||||
*
|
||||
* @deprecated 0.6.0
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Array of data for the JS stats tracker.
|
||||
*/
|
||||
public static function render_footer( $data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
_deprecated_function( __METHOD__, '0.6.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the stats footer for AMP output. Kept for backward compatibility.
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Array of data for the AMP pixel tracker.
|
||||
*/
|
||||
public static function render_amp_footer( $data ) {
|
||||
print self::get_amp_footer( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the "array" string used as part of the JS tracker.
|
||||
*
|
||||
* @access private
|
||||
* @param array $kvs Array of options about the site and page you're on.
|
||||
* @return string
|
||||
*/
|
||||
private static function stats_array_to_string( $kvs ) {
|
||||
/**
|
||||
* Filters the options added to the JavaScript Stats tracking code.
|
||||
*
|
||||
* @since-jetpack 1.1.0
|
||||
*
|
||||
* @param array $kvs Array of options about the site and page you're on.
|
||||
*/
|
||||
$kvs = (array) apply_filters( self::STATS_ARRAY_TO_STRING_FILTER, $kvs );
|
||||
$kvs = array_map( 'strval', $kvs );
|
||||
|
||||
// Encode into JSON object for direct use in JS.
|
||||
return wp_json_encode( $kvs, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the page return AMP content.
|
||||
*
|
||||
* @return bool $is_amp_request Are we on AMP view.
|
||||
*/
|
||||
private static function is_amp_request() {
|
||||
$is_amp_request = ( function_exists( 'amp_is_request' ) && amp_is_request() );
|
||||
$is_amp_request = $is_amp_request || ( function_exists( 'ampforwp_is_amp_endpoint' ) && ampforwp_is_amp_endpoint() );
|
||||
|
||||
/**
|
||||
* Returns true if the current request should return valid AMP content.
|
||||
*
|
||||
* @since 6.2.0
|
||||
*
|
||||
* @param boolean $is_amp_request Is this request supposed to return valid AMP content?
|
||||
*/
|
||||
return apply_filters( 'jetpack_is_amp_request', $is_amp_request );
|
||||
}
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats Transient Cleanup
|
||||
*
|
||||
* Handles cleanup of expired stats cache transients to prevent database bloat
|
||||
* on sites without a persistent object cache.
|
||||
*
|
||||
* Adapted from Brute Force Protection transient cleanup.
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
/**
|
||||
* Transient_Cleanup class.
|
||||
*
|
||||
* Provides a scheduled cron job to clean up expired stats transients.
|
||||
* WordPress transient garbage collection is "lazy" - expired transients are only
|
||||
* deleted when accessed via get_transient(). Stats transients use dynamic cache keys
|
||||
* based on query parameters, so expired entries are rarely accessed again and
|
||||
* accumulate indefinitely in wp_options on sites without an external object cache.
|
||||
*
|
||||
* @since 0.18.0
|
||||
*/
|
||||
class Transient_Cleanup {
|
||||
|
||||
/**
|
||||
* Cron hook name.
|
||||
*/
|
||||
const CRON_HOOK = 'jetpack_stats_transient_cleanup';
|
||||
|
||||
/**
|
||||
* Batch size for transient cleanup.
|
||||
*
|
||||
* Used as the SQL LIMIT per prefix and as the threshold for stopping iteration.
|
||||
* Actual deletions per run may slightly exceed this when processing multiple prefixes.
|
||||
*/
|
||||
const BATCH_SIZE = 5000;
|
||||
|
||||
/**
|
||||
* Cron interval in seconds (8 hours).
|
||||
*/
|
||||
const CRON_INTERVAL = 28800;
|
||||
|
||||
/**
|
||||
* Initialize the transient cleanup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
// Register the cron hook.
|
||||
add_action( self::CRON_HOOK, array( static::class, 'run_cleanup' ) );
|
||||
|
||||
// Register custom cron schedule.
|
||||
// phpcs:ignore WordPress.WP.CronInterval.ChangeDetected -- 8 hours is intentional for batched transient cleanup.
|
||||
add_filter( 'cron_schedules', array( static::class, 'add_cron_schedule' ) );
|
||||
|
||||
// Schedule the cron job if not already scheduled.
|
||||
add_action( 'admin_init', array( static::class, 'schedule_cleanup' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom cron schedule for 8-hour intervals.
|
||||
*
|
||||
* @param array $schedules Existing cron schedules.
|
||||
* @return array Modified cron schedules.
|
||||
*/
|
||||
public static function add_cron_schedule( $schedules ) {
|
||||
if ( ! isset( $schedules['jetpack_stats_eight_hours'] ) ) {
|
||||
$schedules['jetpack_stats_eight_hours'] = array(
|
||||
'interval' => self::CRON_INTERVAL,
|
||||
'display' => __( 'Every Eight Hours', 'jetpack-stats' ),
|
||||
);
|
||||
}
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the cleanup cron job.
|
||||
*
|
||||
* Skips scheduling on sites with persistent object cache since transients
|
||||
* auto-expire there and cleanup is unnecessary.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function schedule_cleanup() {
|
||||
// Skip scheduling on sites with persistent object cache - not needed there.
|
||||
if ( wp_using_ext_object_cache() ) {
|
||||
// Unschedule if it was previously scheduled (e.g., object cache was added later).
|
||||
if ( wp_next_scheduled( self::CRON_HOOK ) ) {
|
||||
wp_clear_scheduled_hook( self::CRON_HOOK );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
|
||||
wp_schedule_event( time() + self::CRON_INTERVAL, 'jetpack_stats_eight_hours', self::CRON_HOOK );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule the cleanup cron job.
|
||||
* Hooked to 'jetpack_deactivate_module_stats' in Main::__construct().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unschedule_cleanup() {
|
||||
wp_clear_scheduled_hook( self::CRON_HOOK );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of transient prefixes to clean up.
|
||||
*
|
||||
* @return array List of transient prefixes.
|
||||
*/
|
||||
public static function get_transient_prefixes() {
|
||||
$prefixes = array(
|
||||
WPCOM_Stats::STATS_CACHE_TRANSIENT_PREFIX, // jetpack_restapi_stats_cache_
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the list of transient prefixes to clean up.
|
||||
*
|
||||
* @since 0.18.0
|
||||
*
|
||||
* @param array $prefixes List of transient prefixes.
|
||||
*/
|
||||
$filtered = apply_filters( 'jetpack_stats_transient_cleanup_prefixes', $prefixes );
|
||||
|
||||
// Normalize filtered value: ensure array, filter to non-empty strings, dedupe.
|
||||
if ( ! is_array( $filtered ) ) {
|
||||
$filtered = $prefixes;
|
||||
}
|
||||
|
||||
$filtered = array_filter(
|
||||
$filtered,
|
||||
function ( $prefix ) {
|
||||
return is_string( $prefix ) && '' !== $prefix;
|
||||
}
|
||||
);
|
||||
|
||||
return array_unique( $filtered );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the transient cleanup.
|
||||
*
|
||||
* @return int|false Number of deleted transient options (two entries per transient), or false if skipped.
|
||||
*/
|
||||
public static function run_cleanup() {
|
||||
/**
|
||||
* Filter to disable transient cleanup.
|
||||
*
|
||||
* @since 0.18.0
|
||||
*
|
||||
* @param bool $disabled Whether to disable transient cleanup. Default false.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_stats_transient_cleanup_disabled', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if using external object cache - transients auto-expire there.
|
||||
if ( wp_using_ext_object_cache() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$total_deleted = 0;
|
||||
$prefixes = self::get_transient_prefixes();
|
||||
|
||||
foreach ( $prefixes as $prefix ) {
|
||||
$deleted = self::purge_expired_transients( $prefix );
|
||||
$total_deleted += $deleted;
|
||||
|
||||
// Stop processing additional prefixes once we've reached the batch threshold.
|
||||
// Note: total may exceed BATCH_SIZE since each prefix can delete up to BATCH_SIZE.
|
||||
if ( $total_deleted >= self::BATCH_SIZE ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $total_deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge expired transients for a specific prefix.
|
||||
*
|
||||
* @param string $prefix The transient prefix to clean up.
|
||||
* @return int Number of deleted transient options, two entries for each transient.
|
||||
*/
|
||||
private static function purge_expired_transients( $prefix ) {
|
||||
global $wpdb;
|
||||
|
||||
$now = time();
|
||||
$timeout_prefix = '_transient_timeout_' . $prefix;
|
||||
$like_pattern = $wpdb->esc_like( $timeout_prefix ) . '%';
|
||||
|
||||
// Find expired transients by querying timeout entries.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$transients = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUBSTRING(option_name, %d) AS transient_name FROM {$wpdb->options} WHERE option_name LIKE %s AND option_value < %d LIMIT %d",
|
||||
strlen( $timeout_prefix ) + 1,
|
||||
$like_pattern,
|
||||
$now,
|
||||
self::BATCH_SIZE
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $transients ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build list of option names to delete (both transient and timeout entries).
|
||||
$option_names = array();
|
||||
foreach ( $transients as $transient ) {
|
||||
$option_names[] = '_transient_' . $prefix . $transient;
|
||||
$option_names[] = '_transient_timeout_' . $prefix . $transient;
|
||||
}
|
||||
|
||||
// Delete in chunks to avoid excessively long SQL queries.
|
||||
// Each option name can be ~80 chars, so 50 items ≈ 4KB per query.
|
||||
$chunks = array_chunk( $option_names, 50 );
|
||||
$total_deleted = 0;
|
||||
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $chunk ), '%s' ) );
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options} WHERE option_name IN ($placeholders)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders is a list of %s.
|
||||
$chunk
|
||||
)
|
||||
);
|
||||
|
||||
if ( false !== $result ) {
|
||||
$total_deleted += $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $total_deleted;
|
||||
}
|
||||
}
|
||||
+623
@@ -0,0 +1,623 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats WPCOM_Stats
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Stats WPCOM_Stats class.
|
||||
*
|
||||
* Responsible for fetching Stats related data from WPCOM.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
class WPCOM_Stats {
|
||||
/**
|
||||
* Transient prefix for storing Stats results from the REST API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATS_CACHE_TRANSIENT_PREFIX = 'jetpack_restapi_stats_cache_';
|
||||
|
||||
/**
|
||||
* Time, in minutes, to cache stats results from the REST API.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const STATS_CACHE_EXPIRATION_IN_MINUTES = 5;
|
||||
|
||||
/**
|
||||
* Stats REST API version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATS_REST_API_VERSION = '1.1';
|
||||
|
||||
/**
|
||||
* The stats resource to fetch results for.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $resource;
|
||||
|
||||
/**
|
||||
* If the site is on WPCOM Simple.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_wpcom_simple;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->is_wpcom_simple = ( new Host() )->is_wpcom_simple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's stats.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array| WP_Error
|
||||
*/
|
||||
public function get_stats( $args = array() ) {
|
||||
$this->resource = '';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's summarized views, visitors, likes and comments.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/summary/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_stats_summary( $args = array() ) {
|
||||
$this->resource = 'summary';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's top posts and pages by views.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/top-posts/
|
||||
* @param array $args Optional query parameters.
|
||||
* @param bool $override_cache Optional override cache.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_top_posts( $args = array(), $override_cache = false ) {
|
||||
$this->resource = 'top-posts';
|
||||
|
||||
// Needed for the Top Posts block, so users can preview changes instantly.
|
||||
if ( $override_cache ) {
|
||||
return $this->fetch_remote_stats( $this->build_endpoint(), $args );
|
||||
}
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's archive pages by views.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/archives/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_archives( $args = array() ) {
|
||||
$this->resource = 'archives';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the details of a single video.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/video/%24post_id/
|
||||
* @param int $post_id The video's ID.
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_video_details( $post_id, $args = array() ) {
|
||||
$this->resource = sprintf( 'video/%d', $post_id );
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's referrers.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/referrers/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_referrers( $args = array() ) {
|
||||
$this->resource = 'referrers';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's outbound clicks.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/clicks/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_clicks( $args = array() ) {
|
||||
$this->resource = 'clicks';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's views by tags and categories.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/tags/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_tags( $args = array() ) {
|
||||
$this->resource = 'tags';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's top authors.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/top-authors/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_top_authors( $args = array() ) {
|
||||
$this->resource = 'top-authors';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's top comment authors and most-commented posts.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/comments/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_top_comments( $args = array() ) {
|
||||
$this->resource = 'comments';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's video plays.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/video-plays/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_video_plays( $args = array() ) {
|
||||
$this->resource = 'video-plays';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's file downloads.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/file-downloads/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_file_downloads( $args = array() ) {
|
||||
$this->resource = 'file-downloads';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a post's views.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/post/%24post_id/
|
||||
* @param int $post_id The post's ID.
|
||||
* @param array $args Optional query parameters.
|
||||
* @param bool $cache_in_meta Optional should cache in post meta.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_post_views( $post_id, $args = array(), $cache_in_meta = false ) {
|
||||
$this->resource = sprintf( 'post/%d', $post_id );
|
||||
|
||||
if ( $cache_in_meta ) {
|
||||
return $this->fetch_post_stats( $args, $post_id );
|
||||
}
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's views by country.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/country-views/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_views_by_country( $args = array() ) {
|
||||
|
||||
$this->resource = 'country-views';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's views by location.
|
||||
*
|
||||
* @param string $geo_mode The type of location to fetch views for (country, region, city).
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_views_by_location( $geo_mode, $args = array() ) {
|
||||
$this->resource = sprintf( 'location-views/%s', $geo_mode );
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's followers.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/followers/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_followers( $args = array() ) {
|
||||
|
||||
$this->resource = 'followers';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's comment followers.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/comment-followers/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_comment_followers( $args = array() ) {
|
||||
|
||||
$this->resource = 'comment-followers';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site's publicize follower counts.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/publicize/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_publicize_followers( $args = array() ) {
|
||||
|
||||
$this->resource = 'publicize';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search terms used to find the site.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/search-terms/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_search_terms( $args = array() ) {
|
||||
|
||||
$this->resource = 'search-terms';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of views for each post.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/views/posts/
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_total_post_views( $args = array() ) {
|
||||
if ( $this->is_wpcom_simple ) {
|
||||
$post_ids = isset( $args['post_ids'] ) ? explode( ',', $args['post_ids'] ) : array();
|
||||
$escaped_post_ids = implode( ',', array_map( 'esc_sql', $post_ids ) );
|
||||
|
||||
$number_of_days = isset( $args['num'] ) ? absint( $args['num'] ) : 1;
|
||||
// It's the same function used in WPCOM simple.
|
||||
// @phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
$end_date = $args['end'] ?? date( 'Y-m-d' );
|
||||
|
||||
$stats = $this->fetch_stats_on_wpcom_simple( $end_date, $number_of_days, $escaped_post_ids );
|
||||
|
||||
$post_views = $stats['-'] ?? array();
|
||||
|
||||
$posts = array_map(
|
||||
function ( $post_id ) use ( $post_views ) {
|
||||
return array(
|
||||
'ID' => $post_id,
|
||||
'views' => $post_views[ $post_id ] ?? 0,
|
||||
);
|
||||
},
|
||||
$post_ids
|
||||
);
|
||||
|
||||
return array( 'posts' => $posts );
|
||||
}
|
||||
|
||||
$this->resource = 'views/posts';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of visits for the site.
|
||||
*
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_visits( $args = array() ) {
|
||||
|
||||
$this->resource = 'visits';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get streaks for the site.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/streak/
|
||||
*
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_streak( $args = array() ) {
|
||||
|
||||
$this->resource = 'streak';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highlights for the site.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/highlights/
|
||||
*
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_highlights( $args = array() ) {
|
||||
|
||||
$this->resource = 'highlights';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of visits for the site.
|
||||
*
|
||||
* @param array $args Optional query parameters.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_insights( $args = array() ) {
|
||||
|
||||
$this->resource = 'insights';
|
||||
|
||||
return $this->fetch_stats( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build WPCOM REST API endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function build_endpoint() {
|
||||
$resource = ltrim( $this->resource, '/' );
|
||||
|
||||
return sprintf( '/sites/%d/stats/%s', Jetpack_Options::get_option( 'id' ), $resource );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches stats data from WPCOM or local Cache. Caches locally for 5 minutes.
|
||||
*
|
||||
* @param array $args Optional query parameters.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
protected function fetch_stats( $args = array() ) {
|
||||
$endpoint = $this->build_endpoint();
|
||||
$api_version = self::STATS_REST_API_VERSION;
|
||||
$cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args, JSON_UNESCAPED_SLASHES ) ) ) );
|
||||
$transient_name = self::STATS_CACHE_TRANSIENT_PREFIX . $cache_key;
|
||||
$stats_cache = get_transient( $transient_name );
|
||||
|
||||
if ( $stats_cache ) {
|
||||
$time = key( $stats_cache );
|
||||
$data = $stats_cache[ $time ]; // WP_Error or string (JSON encoded object).
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return array_merge( array( 'cached_at' => $time ), (array) json_decode( $data, true ) );
|
||||
}
|
||||
|
||||
$wpcom_stats = $this->fetch_remote_stats( $endpoint, $args );
|
||||
|
||||
// To reduce size in storage: store with time as key, store JSON encoded data.
|
||||
$cached_value = is_wp_error( $wpcom_stats ) ? $wpcom_stats : wp_json_encode( $wpcom_stats, JSON_UNESCAPED_SLASHES );
|
||||
|
||||
/**
|
||||
* Filters the expiration time for the stats cache.
|
||||
*
|
||||
* @module stats
|
||||
*
|
||||
* @since 0.10.0
|
||||
*
|
||||
* @param int $expiration The expiration time in minutes.
|
||||
*/
|
||||
$expiration = apply_filters(
|
||||
'jetpack_fetch_stats_cache_expiration',
|
||||
self::STATS_CACHE_EXPIRATION_IN_MINUTES * MINUTE_IN_SECONDS
|
||||
);
|
||||
set_transient( $transient_name, array( time() => $cached_value ), $expiration );
|
||||
|
||||
return $wpcom_stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches stats data from WPCOM or local Cache. Caches locally for 5 minutes.
|
||||
*
|
||||
* Unlike the above function, this caches data in the post meta table. As such,
|
||||
* it prevents wp_options from blowing up when retrieving views for large numbers
|
||||
* of posts at the same time.
|
||||
*
|
||||
* This function returns valid arrays and WP_Error objects from cache if within the expiration period.
|
||||
* If the cached entry is malformed or invalid, a refresh is triggered regardless of cache time.
|
||||
* This self-healing behavior reduces API calls when remote fetch fails, but ensures data validity.
|
||||
*
|
||||
* @param array $args Query parameters.
|
||||
* @param int $post_id Post ID to acquire stats for.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
protected function fetch_post_stats( $args, $post_id ) {
|
||||
$endpoint = $this->build_endpoint();
|
||||
$meta_name = '_' . self::STATS_CACHE_TRANSIENT_PREFIX;
|
||||
$stats_cache = get_post_meta( $post_id, $meta_name, false );
|
||||
|
||||
if ( $stats_cache ) {
|
||||
$data = reset( $stats_cache );
|
||||
|
||||
// Check if we have a valid cache structure with a time key.
|
||||
if ( is_array( $data ) && ! empty( $data ) ) {
|
||||
$time = key( $data );
|
||||
|
||||
// If we have a numeric time, check if cache is still valid.
|
||||
if ( is_numeric( $time ) ) {
|
||||
/** This filter is already documented in projects/packages/stats/src/class-wpcom-stats.php */
|
||||
$expiration = apply_filters(
|
||||
'jetpack_fetch_stats_cache_expiration',
|
||||
self::STATS_CACHE_EXPIRATION_IN_MINUTES * MINUTE_IN_SECONDS
|
||||
);
|
||||
|
||||
// If within cache period, return cached data after type validation.
|
||||
if ( ( time() - $time ) < $expiration ) {
|
||||
$cached_value = $data[ $time ];
|
||||
|
||||
// If it's an array or WP_Error, handle appropriately.
|
||||
if ( is_wp_error( $cached_value ) ) {
|
||||
return $cached_value;
|
||||
}
|
||||
if ( is_array( $cached_value ) ) {
|
||||
return array_merge( array( 'cached_at' => $time ), $cached_value );
|
||||
}
|
||||
|
||||
// For any other unexpected type, treat as malformed cache.
|
||||
// Fall through to refresh.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache doesn't exist, is expired, or is malformed - refresh it.
|
||||
return $this->refresh_post_stats_cache( $endpoint, $args, $post_id, $meta_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force fetch stats from WPCOM, and always update cache.
|
||||
*
|
||||
* This function will cache the result regardless of whether the fetch succeeds
|
||||
* or fails. This ensures that failed requests are also cached, reducing the
|
||||
* frequency of API calls when the remote service is experiencing issues.
|
||||
*
|
||||
* @param string $endpoint The stats endpoint.
|
||||
* @param array $args The query arguments.
|
||||
* @param int $post_id The post ID.
|
||||
* @param string $meta_name The meta name.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
protected function refresh_post_stats_cache( $endpoint, $args, $post_id, $meta_name ) {
|
||||
$wpcom_stats = $this->fetch_remote_stats( $endpoint, $args );
|
||||
|
||||
// Always cache the result, even if it's an error or empty.
|
||||
update_post_meta( $post_id, $meta_name, array( time() => $wpcom_stats ) );
|
||||
|
||||
return $wpcom_stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches stats data from WPCOM.
|
||||
*
|
||||
* @link https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
|
||||
* @param string $endpoint The stats endpoint.
|
||||
* @param array $args The query arguments.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
protected function fetch_remote_stats( $endpoint, $args ) {
|
||||
if ( is_array( $args ) && ! empty( $args ) ) {
|
||||
$endpoint .= '?' . http_build_query( $args );
|
||||
}
|
||||
$response = Client::wpcom_json_api_request_as_blog( $endpoint, self::STATS_REST_API_VERSION, array( 'timeout' => 20 ) );
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
$response_body = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response_body ) ) {
|
||||
return is_wp_error( $response ) ? $response : new WP_Error( 'stats_error', 'Failed to fetch Stats from WPCOM' );
|
||||
}
|
||||
|
||||
return json_decode( $response_body, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the stats when executed in WPCOM Simple.
|
||||
*
|
||||
* @param string $end_date The end date.
|
||||
* @param int $number_of_days The number of days.
|
||||
* @param string $escaped_post_ids The escaped post ids.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fetch_stats_on_wpcom_simple( $end_date, $number_of_days, $escaped_post_ids ) {
|
||||
return stats_get_daily_history( null, get_current_blog_id(), 'postviews', 'post_id', $end_date, $number_of_days, " AND post_id IN ($escaped_post_ids)", 0, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert stats array to object after sanity checking the array is valid.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*
|
||||
* @param array $stats_array The stats array.
|
||||
* @return WP_Error|object|null
|
||||
*/
|
||||
public function convert_stats_array_to_object( $stats_array ) {
|
||||
|
||||
if ( is_wp_error( $stats_array ) ) {
|
||||
return $stats_array;
|
||||
}
|
||||
$encoded_array = wp_json_encode( $stats_array, JSON_UNESCAPED_SLASHES );
|
||||
if ( ! $encoded_array ) {
|
||||
return new WP_Error( 'stats_encoding_error', 'Failed to encode stats array' );
|
||||
}
|
||||
return json_decode( $encoded_array );
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* The Stats XMLRPC Provider class
|
||||
*
|
||||
* @package automattic/jetpack-stats
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Stats;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Modules;
|
||||
|
||||
/**
|
||||
* Stats XMLRPC Provider.
|
||||
*
|
||||
* Adds additional methods to the WordPress XML-RPC API for handling Stats specific features.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
class XMLRPC_Provider {
|
||||
|
||||
/**
|
||||
* Singleton XMLRPC_Provider instance.
|
||||
*
|
||||
* @var XMLRPC_Provider
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Private XMLRPC_Provider constructor.
|
||||
*
|
||||
* Use the XMLRPC_Provider::init() method to get an instance.
|
||||
*/
|
||||
private function __construct() {
|
||||
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( $this, 'xmlrpc_methods' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class and get back a singleton instance.
|
||||
*
|
||||
* @param bool $new_instance Force create new instance.
|
||||
*
|
||||
* @return XMLRPC_Provider
|
||||
*/
|
||||
public static function init( $new_instance = false ) {
|
||||
if ( null === self::$instance || $new_instance ) {
|
||||
self::$instance = new XMLRPC_Provider();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional methods to the WordPress xmlrpc API for handling Stats specific features.
|
||||
*
|
||||
* @param array $methods The Jetpack API methods.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function xmlrpc_methods( $methods ) {
|
||||
if ( ! ( new Connection_Manager() )->is_connected() ) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
if ( ! ( new Modules() )->is_active( 'stats' ) ) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
$methods['jetpack.getBlog'] = array( $this, 'get_blog' );
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stats Get Blog.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_blog() {
|
||||
$home = wp_parse_url( trailingslashit( get_option( 'home' ) ) );
|
||||
$blog = array(
|
||||
'host' => $home['host'],
|
||||
'path' => $home['path'],
|
||||
'blogname' => get_option( 'blogname' ),
|
||||
'blogdescription' => get_option( 'blogdescription' ),
|
||||
'siteurl' => get_option( 'siteurl' ),
|
||||
'gmt_offset' => get_option( 'gmt_offset' ),
|
||||
'timezone_string' => get_option( 'timezone_string' ),
|
||||
'stats_version' => Main::STATS_VERSION,
|
||||
'stats_api' => 'jetpack',
|
||||
'page_on_front' => get_option( 'page_on_front' ),
|
||||
'permalink_structure' => get_option( 'permalink_structure' ),
|
||||
'category_base' => get_option( 'category_base' ),
|
||||
'tag_base' => get_option( 'tag_base' ),
|
||||
);
|
||||
$blog = array_merge( Options::get_options(), $blog );
|
||||
unset( $blog['roles'], $blog['blog_id'] );
|
||||
|
||||
add_filter( 'esc_html', array( $this, 'filter_esc_html_check_if_string' ), 10, 2 );
|
||||
$blog = map_deep( $blog, 'esc_html' );
|
||||
remove_filter( 'esc_html', array( $this, 'filter_esc_html_check_if_string' ) );
|
||||
|
||||
return $blog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we are only escaping html if the input is a string.
|
||||
* Used for `esc_html` filter-hook.
|
||||
*
|
||||
* @param string $safe_text The output after esc_html has been applied.
|
||||
* @param mixed $text The initial input.
|
||||
* @return mixed
|
||||
*/
|
||||
public function filter_esc_html_check_if_string( $safe_text, $text ) {
|
||||
if ( is_string( $text ) ) {
|
||||
return $safe_text;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user