updated plugin Jetpack Protect
version 4.0.0
@ -5,6 +5,351 @@ 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).
|
||||
|
||||
## [5.9.1] - 2025-03-24
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [5.9.0] - 2025-03-24
|
||||
### Added
|
||||
- Add an email input to the social login form, allowing users to log into Jetpack seamlessly through a magic link. [#42600]
|
||||
- Add a new social login form to the onboarding screen for first-time Jetpack connections. [#42561]
|
||||
|
||||
## [5.8.0] - 2025-03-21
|
||||
### Added
|
||||
- Introduce a new onboarding screen to provide clear, step-by-step instructions for new users connecting to Jetpack. [#42523]
|
||||
|
||||
### Fixed
|
||||
- Enable screen readers to read Boost score. [#42306]
|
||||
|
||||
## [5.7.3] - 2025-03-19
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [5.7.2] - 2025-03-18
|
||||
### Changed
|
||||
- Update package dependencies. [#42509] [#42511]
|
||||
|
||||
### Fixed
|
||||
- My Jetpack: Add legacy properties back to `get_info()` function. [#42542]
|
||||
- Product Interstitial Modal: Fix not running custom onClick events if a customModalTrigger was used. [#42527]
|
||||
|
||||
## [5.7.1] - 2025-03-17
|
||||
### Fixed
|
||||
- Allow screen readers to read stats. [#42275]
|
||||
|
||||
## [5.7.0] - 2025-03-17
|
||||
### Changed
|
||||
- Call Protect and VideoPress product data on the frontend and remove from window state. [#42411]
|
||||
- My Jetpack: Move Red Bubble notifications out of myJetpackInitialState. [#42271]
|
||||
- UI: Upgrade CTAs on the Jetpack Boost admin now opens a modal instead of navigating to the upgrade page. [#42309]
|
||||
|
||||
### Fixed
|
||||
- My Jetpack: Fix interstitial modal that was displaying the discounted price when user had already used up the discount. [#42349]
|
||||
|
||||
## [5.6.0] - 2025-03-12
|
||||
### Added
|
||||
- Add QueryProvider to ProductInterstitialModal for extendability. [#42307]
|
||||
- Provide connection data to footer component. [#42000]
|
||||
- Stats: Add highlights heading level as prop. [#42165]
|
||||
|
||||
### Changed
|
||||
- Load agency data from frontend instead of backend. [#42330]
|
||||
|
||||
## [5.5.3] - 2025-03-10
|
||||
### Changed
|
||||
- Persist cookies for dismissable banners longer than session. [#42305]
|
||||
|
||||
## [5.5.2] - 2025-03-05
|
||||
### Changed
|
||||
- Remove purchases from window state and query entirely using state query on front end. [#42154]
|
||||
- Update package dependencies. [#42162]
|
||||
|
||||
## [5.5.1] - 2025-03-03
|
||||
### Added
|
||||
- Update interstitial modal to accept custom trigger. [#41621]
|
||||
|
||||
### Changed
|
||||
- Load product data requiring an http request async on the frontend. [#41965]
|
||||
- Move the getting of product ownership data entirely to the frontend. [#42080]
|
||||
- Move update to historically active modules to frontend. [#42133]
|
||||
- Update package dependencies. [#42081] [#42163]
|
||||
|
||||
### Fixed
|
||||
- Fix skip to main content feature [#42042]
|
||||
|
||||
## [5.5.0] - 2025-02-24
|
||||
### Changed
|
||||
- Allow users to manage user connection in My Jetpack. [#41398]
|
||||
- Move backup endpoint to product class. [#41730]
|
||||
- Update package dependencies. [#41955]
|
||||
|
||||
### Removed
|
||||
- Script data: Remove unused property. [#41890]
|
||||
|
||||
### Fixed
|
||||
- Code: Prevent dynamic class properties. [#41857]
|
||||
- Fix My Jetpack display for non-admin users. [#41398]
|
||||
- Improve accessibility for product card actions with ARIA labelling. [#41896]
|
||||
- Increase product card status contrast ratio. [#41896]
|
||||
- Move product card status before action for screen readers. [#41896]
|
||||
|
||||
## [5.4.5] - 2025-02-17
|
||||
### Changed
|
||||
- Social: Update manage module link to point to the new Social admin page. [#41741]
|
||||
|
||||
## [5.4.4] - 2025-02-12
|
||||
### Changed
|
||||
- Performance: Cache scan calls if no threats are found. [#41614]
|
||||
|
||||
## [5.4.3] - 2025-02-11
|
||||
### Added
|
||||
- My Jetpack: Allow product notices to be closed with persistence. [#41617]
|
||||
|
||||
### Changed
|
||||
- Make entire row of dataview clickable when on mobile [#41643]
|
||||
|
||||
## [5.4.2] - 2025-02-10
|
||||
### Added
|
||||
- Add filter to unowned list of products. [#41312]
|
||||
- Add mobile CTA to DataViews table. [#41554]
|
||||
|
||||
### Changed
|
||||
- Cache calls to backup API in My Jetpack. [#41608]
|
||||
- Update package dependencies. [#41491] [#41577]
|
||||
- Update the unowned section from a product grid to a product list. [#41312]
|
||||
|
||||
### Fixed
|
||||
- Fix bug where firewall was displayed as active if automatic rules were enabled but firewall was off. [#41560]
|
||||
|
||||
## [5.4.1] - 2025-02-03
|
||||
### Added
|
||||
- My Jetpack: Add red bubble and notice when pain plan is missing plugin. [#41013]
|
||||
|
||||
### Changed
|
||||
- Make Action Button component more reusable. [#41361]
|
||||
- Replace Jetpack AI upgrade page with a modal. [#41301]
|
||||
- Update package dependencies. [#41286]
|
||||
- Update My Jetpack interstitial modal with new styles and layout. [#41300]
|
||||
|
||||
### Fixed
|
||||
- AI: Avoid using relative URLs in admin URLs to support sites where WordPress is installed in a subdirectory. [#41459]
|
||||
- Code: Remove extra params on function calls. [#41263]
|
||||
- My Jetpack: Fix secondary action of Protect card when plugin is not installed. [#41347]
|
||||
|
||||
## [5.4.0] - 2025-01-23
|
||||
### Added
|
||||
- Adding new modal based interstitial component. [#40945]
|
||||
|
||||
### Fixed
|
||||
- Fix bug where My Jetpack would throw critical error if only a standalone plugin is not installed. [#41192]
|
||||
|
||||
## [5.3.3] - 2025-01-20
|
||||
### Added
|
||||
- Add caching for the red bubble alerts for My Jetpack. [#41131]
|
||||
- Add option for devs to reset jetpack options from My Jetpack footer. [#40943]
|
||||
- Add sandboxed tag to My Jetpack. [#40971]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#41099]
|
||||
|
||||
## [5.3.2] - 2025-01-14
|
||||
### Fixed
|
||||
- Fix bug where description doesn't show up on backup card in specific scenarios. [#40904]
|
||||
|
||||
## [5.3.1] - 2025-01-10
|
||||
### Added
|
||||
- Add new WAF status on Protect card for when WAF is unsupported. [#40880]
|
||||
|
||||
### Changed
|
||||
- Show an upgrade CTA anytime a product has an available upgrade. [#40900]
|
||||
|
||||
## [5.3.0] - 2025-01-06
|
||||
### Added
|
||||
- My Jetpack: Added a new status for when Protect detects threats on the site. [#40628]
|
||||
- My Jetpack: Adds a red bubble and notice when Protect threats are detected. [#40719]
|
||||
- My Jetpack: introduce feature cards for recommendations in My Jetpack. [#40639]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#40705] [#40798] [#40810] [#40841]
|
||||
|
||||
### Fixed
|
||||
- Tests: Fix failure on 31 December. [#40781]
|
||||
|
||||
## [5.2.0] - 2024-12-23
|
||||
### Added
|
||||
- My Jetpack: add features as possible modules to the recommendations list. [#40492]
|
||||
|
||||
### Changed
|
||||
- My Jetpack: Add 'Needs attention' status to Backup product card when Backups are failing. [#40454]
|
||||
- My Jetpack: Add red bubble and notice/banner when Backup has 'needs-attention' status. [#40512]
|
||||
- My Jetpack: Plans section: Improvements to how we display plan expiration date. [#40575]
|
||||
- My Jetpack: Protect card- Fixed Tooltip placement & content issues. [#40691]
|
||||
- Unify connection flows in My Jetpack. [#40632]
|
||||
|
||||
### Fixed
|
||||
- Fix an issue where high posts counts would cause backend issues for the get_raw_post_type_breakdown function used in My Jetpack. Sites with over 100,000 posts can now have this query managed remotely. [#40635]
|
||||
- Fix issue where backup card was not updating after site connection in some situations. [#40653]
|
||||
|
||||
## [5.1.2] - 2024-12-16
|
||||
### Added
|
||||
- Add AI to Complete feature copy. [#40577]
|
||||
|
||||
### Changed
|
||||
- Remove purchase related elements when Complete is on site. [#40554]
|
||||
- Updated package dependencies. [#40564]
|
||||
|
||||
### Fixed
|
||||
- Fixed lints following ESLint rule changes for TS. [#40584]
|
||||
- My Jetpack: fix animation flick on connection screen in My Jetpack. [#40533]
|
||||
|
||||
## [5.1.1] - 2024-12-04
|
||||
### Changed
|
||||
- Updated package dependencies. [#40363]
|
||||
|
||||
## [5.1.0] - 2024-12-02
|
||||
### Added
|
||||
- Add animation during site connection. [#40343]
|
||||
- Add "loading" animation to recommendations step. [#40405]
|
||||
|
||||
### Changed
|
||||
- Fix usage of random() in animation to prevent build step from generating a different CSS file every time. [#40413]
|
||||
|
||||
### Removed
|
||||
- Remove experiment code. [#40406]
|
||||
|
||||
### Fixed
|
||||
- Fix My Jetpack recommendation card styling on mobile [#40370]
|
||||
|
||||
## [5.0.4] - 2024-11-28
|
||||
### Added
|
||||
- Added "Expired" & "Expires soon" statuses to My Jetpack product cards. [#39816]
|
||||
|
||||
### Changed
|
||||
- Social | Changed My Jetpack CTA for Social from "Learn more" to "Activate" [#40359]
|
||||
|
||||
### Fixed
|
||||
- Fix stats not showing sale discount [#40348]
|
||||
|
||||
## [5.0.3] - 2024-11-26
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [5.0.2] - 2024-11-25
|
||||
### Added
|
||||
- Add bundles to recommendations and add interstitials for them [#40281]
|
||||
- Add growth upsell to Stats and Social interstitials [#40236]
|
||||
|
||||
### Changed
|
||||
- Notices: do not display the Jetpack Manage banners for accounts enrolled into our agency program. [#40251]
|
||||
- Remove creator card and update paid plan checks to account for growth [#40192]
|
||||
- Updated dependencies. [#40286]
|
||||
- Updated feature for stats in growth to 10K instead of 100K [#40312]
|
||||
- Updated package dependencies. [#40288]
|
||||
|
||||
## [5.0.1] - 2024-11-18
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [5.0.0] - 2024-11-14
|
||||
### Removed
|
||||
- General: Update minimum PHP version to 7.2. [#40147]
|
||||
|
||||
## [4.37.0] - 2024-11-11
|
||||
### Added
|
||||
- My Jetpack: update the recommendations section in My Jetpack to include a slider interaction for the cards. [#39850]
|
||||
|
||||
### Changed
|
||||
- Admin: Updating deprecation notices. [#39567]
|
||||
- Updated package dependencies. [#39999] [#40000] [#40060]
|
||||
|
||||
## [4.36.0] - 2024-11-04
|
||||
### Added
|
||||
- Enable test coverage. [#39961]
|
||||
|
||||
### Changed
|
||||
- My Jetpack: Add experiment to the post-connection flow in My Jetpack. [#39902]
|
||||
- Skip pricing page when connecting via block editor. [#39865]
|
||||
|
||||
### Removed
|
||||
- My Jetpack: Remove A/B test code in My Jetpack. [#39928]
|
||||
|
||||
## [4.35.16] - 2024-10-29
|
||||
### Changed
|
||||
- Components: Add __nextHasNoMarginBottom to BaseControl-based components, preventing deprecation notices. [#39877]
|
||||
|
||||
## [4.35.15] - 2024-10-17
|
||||
### Fixed
|
||||
- Fix the "Missing site connection" notice. [#39809]
|
||||
|
||||
## [4.35.14] - 2024-10-15
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [4.35.13] - 2024-10-14
|
||||
### Changed
|
||||
- Only include `wp-polyfill` as a script dependency when needed. [#39629]
|
||||
|
||||
## [4.35.12] - 2024-10-10
|
||||
### Changed
|
||||
- Update Boost's pricing table to include latest feature list. [#39130]
|
||||
- Updated package dependencies. [#39669] [#39707]
|
||||
|
||||
### Fixed
|
||||
- Fixed My Jetpack recommendations VideoPress product card not showing Purchase and Learn more buttons. [#39612]
|
||||
|
||||
## [4.35.11] - 2024-10-07
|
||||
### Changed
|
||||
- Updated package dependencies. [#39594]
|
||||
|
||||
## [4.35.10] - 2024-10-02
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [4.35.9] - 2024-09-30
|
||||
### Changed
|
||||
- Fix parameters to allow for connectAfterCheckout flow from recommendation card [#39578]
|
||||
- My Jetpack Welcome Flow: Display default recommendations upfront first, then offer optional survey for customized recommendations. [#39485]
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug where the purchases and highlights APIs were being called without a valid Jetpack connection [#39522]
|
||||
- My Jetpack: visual update to make the GlobalNotice component look better on mobile. [#39537]
|
||||
|
||||
## [4.35.8] - 2024-09-25
|
||||
### Changed
|
||||
- Update dependencies. [#38910]
|
||||
|
||||
## [4.35.7] - 2024-09-23
|
||||
### Changed
|
||||
- Get active element from tooltip button's document rather than the global `document`. [#39364]
|
||||
- My Jetpack product interstitial: Don't show intro offer price if user is not eligible for the offer. [#39403]
|
||||
- Send non-connected users to a "connect after checkout" flow [#39444]
|
||||
|
||||
### Fixed
|
||||
- Fix issue on interstitials show both buttons loading when only one is pressed [#39356]
|
||||
- Fix issue where recommendations are showing slightly before the welcome banner dismisses [#39383]
|
||||
|
||||
## [4.35.6] - 2024-09-16
|
||||
### Added
|
||||
- Add new action myjetpack_enqueue_scripts [#39380]
|
||||
|
||||
### Changed
|
||||
- My Jetpack: Always show the purchase link regardless of the number of plans owned. [#39299]
|
||||
- Updated package dependencies. [#39332]
|
||||
|
||||
## [4.35.5] - 2024-09-10
|
||||
### Changed
|
||||
- Updated package dependencies. [#39302]
|
||||
|
||||
## [4.35.4] - 2024-09-09
|
||||
### Added
|
||||
- Jetpack AI: add fair usage policy link to the Jetpack AI product interstitial. [#39281]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#39278]
|
||||
|
||||
### Fixed
|
||||
- Jetpack AI: fix default_content filter so it doesn't enforce parameter type [#39276]
|
||||
|
||||
## [4.35.3] - 2024-09-06
|
||||
### Fixed
|
||||
- Optimize repeated requests for unavailable WPCOM. [#39218]
|
||||
@ -710,8 +1055,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [3.4.3] - 2023-09-04
|
||||
### Changed
|
||||
- Updated package dependencies. [#32803]
|
||||
- Updated package dependencies. [#32804]
|
||||
- Updated package dependencies. [#32803] [#32804]
|
||||
|
||||
## [3.4.2] - 2023-08-23
|
||||
### Changed
|
||||
@ -767,8 +1111,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
- My Jetpack: changed Stats features wording [#32046]
|
||||
- Updated package dependencies. [#31999]
|
||||
- Updated package dependencies. [#32040]
|
||||
- Updated package dependencies. [#31999] [#32040]
|
||||
|
||||
### Fixed
|
||||
- Make Jetpack logo in footer smaller [#31627]
|
||||
@ -1710,6 +2053,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- Created package
|
||||
|
||||
[5.9.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.9.0...5.9.1
|
||||
[5.9.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.8.0...5.9.0
|
||||
[5.8.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.7.3...5.8.0
|
||||
[5.7.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.7.2...5.7.3
|
||||
[5.7.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.7.1...5.7.2
|
||||
[5.7.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.7.0...5.7.1
|
||||
[5.7.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.6.0...5.7.0
|
||||
[5.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.5.3...5.6.0
|
||||
[5.5.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.5.2...5.5.3
|
||||
[5.5.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.5.1...5.5.2
|
||||
[5.5.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.5.0...5.5.1
|
||||
[5.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.5...5.5.0
|
||||
[5.4.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.4...5.4.5
|
||||
[5.4.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.3...5.4.4
|
||||
[5.4.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.2...5.4.3
|
||||
[5.4.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.1...5.4.2
|
||||
[5.4.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.4.0...5.4.1
|
||||
[5.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.3.3...5.4.0
|
||||
[5.3.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.3.2...5.3.3
|
||||
[5.3.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.3.1...5.3.2
|
||||
[5.3.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.3.0...5.3.1
|
||||
[5.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.2.0...5.3.0
|
||||
[5.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.1.2...5.2.0
|
||||
[5.1.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.1.1...5.1.2
|
||||
[5.1.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.1.0...5.1.1
|
||||
[5.1.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.0.4...5.1.0
|
||||
[5.0.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.0.3...5.0.4
|
||||
[5.0.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.0.2...5.0.3
|
||||
[5.0.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.0.1...5.0.2
|
||||
[5.0.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/5.0.0...5.0.1
|
||||
[5.0.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.37.0...5.0.0
|
||||
[4.37.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.36.0...4.37.0
|
||||
[4.36.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.16...4.36.0
|
||||
[4.35.16]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.15...4.35.16
|
||||
[4.35.15]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.14...4.35.15
|
||||
[4.35.14]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.13...4.35.14
|
||||
[4.35.13]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.12...4.35.13
|
||||
[4.35.12]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.11...4.35.12
|
||||
[4.35.11]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.10...4.35.11
|
||||
[4.35.10]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.9...4.35.10
|
||||
[4.35.9]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.8...4.35.9
|
||||
[4.35.8]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.7...4.35.8
|
||||
[4.35.7]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.6...4.35.7
|
||||
[4.35.6]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.5...4.35.6
|
||||
[4.35.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.4...4.35.5
|
||||
[4.35.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.3...4.35.4
|
||||
[4.35.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.2...4.35.3
|
||||
[4.35.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.1...4.35.2
|
||||
[4.35.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.35.0...4.35.1
|
||||
|
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.6647 15.0275C17.3774 15.6563 17.0374 16.2352 16.6434 16.7674C16.1064 17.4929 15.6667 17.9952 15.3279 18.274C14.8026 18.7318 14.2398 18.9662 13.6371 18.9796C13.2045 18.9796 12.6827 18.8629 12.0753 18.6262C11.466 18.3907 10.906 18.274 10.394 18.274C9.85695 18.274 9.28102 18.3907 8.66499 18.6262C8.04801 18.8629 7.55099 18.9862 7.17098 18.9985C6.59305 19.0218 6.017 18.7807 5.442 18.274C5.07501 17.9707 4.61598 17.4507 4.06607 16.7141C3.47607 15.9274 2.991 15.0152 2.61099 13.9753C2.20402 12.852 2 11.7642 2 10.7112C2 9.50486 2.27507 8.46444 2.82603 7.59258C3.25904 6.89227 3.83509 6.33984 4.55606 5.9343C5.27703 5.52875 6.05605 5.32209 6.89497 5.30887C7.35401 5.30887 7.95597 5.44342 8.70403 5.70786C9.44998 5.97319 9.92895 6.10774 10.1389 6.10774C10.2959 6.10774 10.828 5.95041 11.73 5.63675C12.583 5.34587 13.3029 5.22543 13.8927 5.27287C15.4908 5.39509 16.6915 5.99207 17.49 7.0676C16.0607 7.88824 15.3537 9.03765 15.3677 10.5122C15.3806 11.6607 15.8203 12.6164 16.6844 13.3753C17.0761 13.7275 17.5134 13.9997 18 14.193C17.8945 14.483 17.7831 14.7608 17.6647 15.0275ZM13.9994 1.3601C13.9994 2.26031 13.6524 3.10083 12.9606 3.8788C12.1258 4.80366 11.116 5.33809 10.021 5.25376C10.007 5.14577 9.99894 5.0321 9.99894 4.91266C9.99894 4.04847 10.396 3.1236 11.101 2.3674C11.453 1.98453 11.9006 1.66617 12.4435 1.41221C12.9852 1.16204 13.4976 1.02369 13.9795 1C13.9936 1.12034 13.9994 1.24071 13.9994 1.3601Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 485 KiB |
@ -0,0 +1,15 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5707_3451)">
|
||||
<mask id="mask0_5707_3451" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<path d="M20 0H0V19.5918H20V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_5707_3451)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.9702 0C4.45694 0 0 4.4898 0 10.0443C0 14.4843 2.85572 18.2426 6.81735 19.5728C7.31265 19.6728 7.49408 19.3567 7.49408 19.0908C7.49408 18.858 7.47776 18.0598 7.47776 17.2282C4.70428 17.8269 4.12674 16.0308 4.12674 16.0308C3.68102 14.8667 3.02061 14.5676 3.02061 14.5676C2.11285 13.9522 3.08674 13.9522 3.08674 13.9522C4.09367 14.0187 4.62204 14.9833 4.62204 14.9833C5.51326 16.5131 6.94939 16.0808 7.52715 15.8147C7.60959 15.1661 7.87387 14.7172 8.1545 14.4678C5.94245 14.2349 3.61511 13.3702 3.61511 9.51204C3.61511 8.41449 4.01102 7.51653 4.63837 6.81816C4.53939 6.56878 4.19265 5.53755 4.73755 4.15735C4.73755 4.15735 5.57939 3.89122 7.47755 5.18837C8.29022 4.96851 9.12832 4.85665 9.9702 4.85572C10.812 4.85572 11.6702 4.97224 12.4626 5.18837C14.3611 3.89122 15.2028 4.15735 15.2028 4.15735C15.7478 5.53755 15.4008 6.56878 15.3018 6.81816C15.9457 7.51653 16.3253 8.41449 16.3253 9.51204C16.3253 13.3702 13.998 14.2182 11.7694 14.4678C12.1326 14.7837 12.4461 15.3822 12.4461 16.3302C12.4461 17.6772 12.4298 18.7582 12.4298 19.0906C12.4298 19.3567 12.6115 19.6728 13.1065 19.5731C17.0682 18.2424 19.9239 14.4843 19.9239 10.0443C19.9402 4.4898 15.4669 0 9.9702 0Z" fill="#24292F"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5707_3451">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.36 10.1979C18.36 9.58042 18.3046 8.98667 18.2017 8.41667H10V11.7892H14.6867C14.4808 12.8738 13.8633 13.7921 12.9371 14.4096V16.6025H15.7633C17.41 15.0825 18.36 12.85 18.36 10.1979Z" fill="#4285F4"/>
|
||||
<path d="M10 18.7083C12.3512 18.7083 14.3225 17.9325 15.7633 16.6025L12.9371 14.4096C12.1612 14.9321 11.1717 15.2488 10 15.2488C7.73583 15.2488 5.81208 13.7208 5.12333 11.6625H2.22583V13.9108C3.65875 16.7529 6.59583 18.7083 10 18.7083Z" fill="#34A853"/>
|
||||
<path d="M5.12329 11.6546C4.94913 11.1321 4.84621 10.5779 4.84621 9.99999C4.84621 9.42208 4.94913 8.86791 5.12329 8.34541V6.09708H2.22579C1.63204 7.26874 1.29163 8.59083 1.29163 9.99999C1.29163 11.4092 1.63204 12.7312 2.22579 13.9029L4.48204 12.1454L5.12329 11.6546Z" fill="#FBBC05"/>
|
||||
<path d="M10 4.75917C11.2825 4.75917 12.4225 5.20251 13.3329 6.05751L15.8267 3.56376C14.3146 2.15459 12.3512 1.29167 10 1.29167C6.59583 1.29167 3.65875 3.24709 2.22583 6.09709L5.12333 8.34542C5.81208 6.28709 7.73583 4.75917 10 4.75917Z" fill="#EA4335"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 251 KiB |
@ -0,0 +1,4 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.1196 21.1787C16.9186 21.1787 21.6196 16.4777 21.6196 10.6787C21.6196 4.87972 16.9186 0.178711 11.1196 0.178711C5.32064 0.178711 0.619629 4.87972 0.619629 10.6787C0.619629 16.4777 5.32064 21.1787 11.1196 21.1787Z" fill="#069E08"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6797 2.26256V12.4219H5.44971L10.6797 2.26256ZM11.7412 19.0953V8.91588H16.9912L11.7412 19.0953Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 510 B |
After Width: | Height: | Size: 488 KiB |
Before Width: | Height: | Size: 185 KiB |
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('jetpack-connection', 'jetpack-script-data', 'react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '1c3aa81dbb7f888e2783');
|
||||
<?php return array('dependencies' => array('jetpack-connection', 'jetpack-script-data', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-warning'), 'version' => 'd453c42f6395d98c7008');
|
||||
|
@ -6,15 +6,18 @@
|
||||
* @see https://github.com/kvz/phpjs/blob/ffe1356af23a6f2512c84c954dd4e828e92579fa/functions/strings/number_format.js
|
||||
*/
|
||||
|
||||
/*!
|
||||
* cookie
|
||||
* Copyright(c) 2012-2014 Roman Shtylman
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.2.1
|
||||
* @remix-run/router v1.21.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
@ -25,7 +28,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.6.2
|
||||
* React Router DOM v6.28.1
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
@ -36,7 +39,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.6.2
|
||||
* React Router v6.28.1
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
|
@ -4,26 +4,27 @@
|
||||
"type": "jetpack-library",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"automattic/jetpack-admin-ui": "^0.4.5",
|
||||
"automattic/jetpack-assets": "^2.3.7",
|
||||
"automattic/jetpack-boost-speed-score": "^0.3.12",
|
||||
"automattic/jetpack-connection": "^4.0.1",
|
||||
"automattic/jetpack-explat": "^0.1.7",
|
||||
"automattic/jetpack-jitm": "^3.1.21",
|
||||
"automattic/jetpack-licensing": "^2.0.9",
|
||||
"automattic/jetpack-plugins-installer": "^0.4.3",
|
||||
"automattic/jetpack-redirect": "^2.0.4",
|
||||
"automattic/jetpack-constants": "^2.0.4",
|
||||
"automattic/jetpack-plans": "^0.4.10",
|
||||
"automattic/jetpack-status": "^4.0.1",
|
||||
"automattic/jetpack-sync": "^3.10.0",
|
||||
"automattic/jetpack-protect-status": "^0.1.5"
|
||||
"php": ">=7.2",
|
||||
"automattic/jetpack-admin-ui": "^0.5.7",
|
||||
"automattic/jetpack-assets": "^4.0.14",
|
||||
"automattic/jetpack-boost-speed-score": "^0.4.6",
|
||||
"automattic/jetpack-connection": "^6.8.1",
|
||||
"automattic/jetpack-explat": "^0.2.13",
|
||||
"automattic/jetpack-jitm": "^4.2.7",
|
||||
"automattic/jetpack-licensing": "^3.0.8",
|
||||
"automattic/jetpack-plugins-installer": "^0.5.4",
|
||||
"automattic/jetpack-redirect": "^3.0.5",
|
||||
"automattic/jetpack-constants": "^3.0.5",
|
||||
"automattic/jetpack-plans": "^0.6.1",
|
||||
"automattic/jetpack-status": "^5.0.10",
|
||||
"automattic/jetpack-sync": "^4.9.2",
|
||||
"automattic/jetpack-protect-status": "^0.5.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"yoast/phpunit-polyfills": "^1.1.1",
|
||||
"automattic/jetpack-changelogger": "^4.2.6",
|
||||
"automattic/wordbless": "@dev"
|
||||
"yoast/phpunit-polyfills": "^3.0.0",
|
||||
"automattic/jetpack-changelogger": "^6.0.2",
|
||||
"automattic/jetpack-test-environment": "@dev",
|
||||
"automattic/phpunit-select-config": "^1.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
@ -36,8 +37,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"phpunit": [
|
||||
"./vendor/phpunit/phpunit/phpunit --colors=always"
|
||||
"phpunit-select-config phpunit.#.xml.dist --colors=always"
|
||||
],
|
||||
"test-coverage": "pnpm concurrently --names php,js 'php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"' 'pnpm:test-coverage'",
|
||||
"test-php": [
|
||||
"@composer phpunit"
|
||||
],
|
||||
@ -57,9 +59,7 @@
|
||||
"watch": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"pnpm run watch"
|
||||
],
|
||||
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
|
||||
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
@ -71,7 +71,7 @@
|
||||
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "4.35.x-dev"
|
||||
"dev-trunk": "5.9.x-dev"
|
||||
},
|
||||
"version-constants": {
|
||||
"::PACKAGE_VERSION": "src/class-initializer.php"
|
||||
|
@ -10,6 +10,8 @@ declare module '@wordpress/compose';
|
||||
declare module '@wordpress/icons';
|
||||
declare module '@automattic/jetpack-connection';
|
||||
declare module '@wordpress/url';
|
||||
declare module '@wordpress/i18n';
|
||||
declare module '@wordpress/element';
|
||||
|
||||
type ProductStatus =
|
||||
| 'active'
|
||||
@ -22,7 +24,10 @@ type ProductStatus =
|
||||
| 'needs_activation'
|
||||
| 'needs_first_site_connection'
|
||||
| 'user_connection_error'
|
||||
| 'can_upgrade';
|
||||
| 'can_upgrade'
|
||||
| 'needs_attention'
|
||||
| 'expired'
|
||||
| 'expiring';
|
||||
|
||||
type JetpackModule =
|
||||
| 'anti-spam'
|
||||
@ -33,13 +38,31 @@ type JetpackModule =
|
||||
| 'extras'
|
||||
| 'ai'
|
||||
| 'jetpack-ai'
|
||||
| 'protect'
|
||||
| 'scan'
|
||||
| 'search'
|
||||
| 'social'
|
||||
| 'security'
|
||||
| 'protect'
|
||||
| 'stats'
|
||||
| 'videopress'
|
||||
| 'stats';
|
||||
| 'security'
|
||||
| 'growth'
|
||||
| 'complete'
|
||||
| 'site-accelerator'
|
||||
| 'newsletter'
|
||||
| 'related-posts'
|
||||
| 'brute-force';
|
||||
|
||||
type JetpackModuleWithCard =
|
||||
| 'anti-spam'
|
||||
| 'backup'
|
||||
| 'boost'
|
||||
| 'crm'
|
||||
| 'jetpack-ai'
|
||||
| 'protect'
|
||||
| 'search'
|
||||
| 'social'
|
||||
| 'stats'
|
||||
| 'videopress';
|
||||
|
||||
type ThreatItem = {
|
||||
// Protect API properties (free plan)
|
||||
@ -67,6 +90,390 @@ type ScanItem = {
|
||||
version: string;
|
||||
};
|
||||
|
||||
type RewindStatus =
|
||||
| 'missing_plan'
|
||||
| 'no_connected_jetpack'
|
||||
| 'no_connected_jetpack_with_credentials'
|
||||
| 'vp_active_on_site'
|
||||
| 'vp_can_transfer'
|
||||
| 'host_not_supported'
|
||||
| 'multisite_not_supported'
|
||||
| 'no_site_found';
|
||||
|
||||
type BackupStatus =
|
||||
| 'started'
|
||||
| 'finished'
|
||||
| 'no-credentials'
|
||||
| 'backups-deactivated'
|
||||
| 'no-credentials-atomic'
|
||||
| 'credential-error'
|
||||
| 'http-only-error'
|
||||
| 'not-accessible'
|
||||
| 'backup-deactivated'
|
||||
| 'Kill switch active'
|
||||
| 'error'
|
||||
| 'error-will-retry';
|
||||
|
||||
type JetpackPlanSlug =
|
||||
| 'jetpack_premium'
|
||||
| 'jetpack_business'
|
||||
| 'jetpack_free'
|
||||
| 'jetpack_premium_monthly'
|
||||
| 'jetpack_business_monthly'
|
||||
| 'jetpack_personal'
|
||||
| 'jetpack_personal_monthly'
|
||||
| 'jetpack_security_daily'
|
||||
| 'jetpack_security_daily_monthly'
|
||||
| 'jetpack_security_realtime'
|
||||
| 'jetpack_security_realtime_monthly'
|
||||
| 'jetpack_growth_bi_yearly'
|
||||
| 'jetpack_growth_yearly'
|
||||
| 'jetpack_growth_monthly'
|
||||
| 'jetpack_complete_bi_yearly'
|
||||
| 'jetpack_complete'
|
||||
| 'jetpack_complete_monthly'
|
||||
| 'jetpack_security_t1_bi_yearly'
|
||||
| 'jetpack_security_t1_yearly'
|
||||
| 'jetpack_security_t1_monthly'
|
||||
| 'jetpack_security_t2_yearly'
|
||||
| 'jetpack_security_t2_monthly'
|
||||
| 'jetpack_starter_yearly'
|
||||
| 'jetpack_starter_monthly'
|
||||
| 'jetpack_backup_daily'
|
||||
| 'jetpack_backup_daily_monthly'
|
||||
| 'jetpack_backup_realtime'
|
||||
| 'jetpack_backup_realtime_monthly'
|
||||
| 'jetpack_search_bi_yearly'
|
||||
| 'jetpack_search'
|
||||
| 'jetpack_search_monthly'
|
||||
| 'jetpack_scan_bi_yearly'
|
||||
| 'jetpack_scan'
|
||||
| 'jetpack_scan_monthly'
|
||||
| 'jetpack_scan_realtime'
|
||||
| 'jetpack_scan_realtime_monthly'
|
||||
| 'jetpack_anti_spam_bi_yearly'
|
||||
| 'jetpack_anti_spam'
|
||||
| 'jetpack_anti_spam_monthly'
|
||||
| 'jetpack_backup_t1_bi_yearly'
|
||||
| 'jetpack_backup_t1_yearly'
|
||||
| 'jetpack_backup_t1_monthly'
|
||||
| 'jetpack_backup_t2_yearly'
|
||||
| 'jetpack_backup_t2_monthly'
|
||||
| 'jetpack_backup_addon_storage_10gb_monthly'
|
||||
| 'jetpack_backup_addon_storage_100gb_monthly'
|
||||
| 'jetpack_backup_addon_storage_1tb_monthly'
|
||||
| 'jetpack_backup_addon_storage_3tb_monthly'
|
||||
| 'jetpack_backup_addon_storage_5tb_monthly'
|
||||
| 'jetpack_videopress_bi_yearly'
|
||||
| 'jetpack_videopress'
|
||||
| 'jetpack_videopress_monthly'
|
||||
| 'jetpack_backup_t0_yearly'
|
||||
| 'jetpack_backup_t0_monthly'
|
||||
| 'jetpack_search_free'
|
||||
| 'jetpack_backup_one_time'
|
||||
| 'jetpack_stats_free_yearly'
|
||||
| 'jetpack_stats_pwyw_yearly'
|
||||
| 'jetpack_stats_monthly'
|
||||
| 'jetpack_stats_yearly'
|
||||
| 'jetpack_stats_bi_yearly'
|
||||
| 'jetpack_monitor_monthly'
|
||||
| 'jetpack_monitor_yearly'
|
||||
| 'jetpack_boost_bi_yearly'
|
||||
| 'jetpack_boost_yearly'
|
||||
| 'jetpack_boost_monthly'
|
||||
| 'jetpack_ai_monthly'
|
||||
| 'jetpack_ai_yearly'
|
||||
| 'jetpack_ai_bi_yearly'
|
||||
| 'jetpack_social_monthly'
|
||||
| 'jetpack_social_basic_bi_yearly'
|
||||
| 'jetpack_social_basic_yearly'
|
||||
| 'jetpack_social_basic_monthly'
|
||||
| 'jetpack_social_advanced_bi_yearly'
|
||||
| 'jetpack_social_advanced_yearly'
|
||||
| 'jetpack_social_advanced_monthly'
|
||||
| 'jetpack_golden_token_lifetime'
|
||||
| 'jetpack_creator_monthly'
|
||||
| 'jetpack_creator_yearly'
|
||||
| 'jetpack_creator_bi_yearly'
|
||||
| 'jetpack_social_v1_monthly'
|
||||
| 'jetpack_social_v1_yearly'
|
||||
| 'jetpack_social_v1_bi_yearly';
|
||||
|
||||
type BadInstallPluginSlug =
|
||||
| 'jetpack-beta'
|
||||
| 'jetpack-videopress'
|
||||
| 'jetpack-boost'
|
||||
| 'jetpack-protect'
|
||||
| 'jetpack-crm'
|
||||
| 'jetpack-search'
|
||||
| 'vaultpress'
|
||||
| 'jetpack-social'
|
||||
| 'jetpack'
|
||||
| 'jetpack-starter'
|
||||
| 'jetpack-vaultpress-backup';
|
||||
|
||||
type JetpackPluginDisplayName =
|
||||
| 'Jetpack Beta'
|
||||
| 'Jetpack VideoPress'
|
||||
| 'Jetpack Boost'
|
||||
| 'Jetpack Protect'
|
||||
| 'Jetpack CRM'
|
||||
| 'Jetpack Search'
|
||||
| 'VaultPress'
|
||||
| 'Jetpack Social'
|
||||
| 'Jetpack'
|
||||
| 'Jetpack Starter'
|
||||
| 'Jetpack VaultPress Backup';
|
||||
|
||||
type JetpackProductName =
|
||||
| 'Security Bundle'
|
||||
| 'CRM'
|
||||
| 'Newsletter'
|
||||
| 'Site Accelerator'
|
||||
| 'Social'
|
||||
| 'VideoPress'
|
||||
| 'Related Posts'
|
||||
| 'Starter'
|
||||
| 'Stats'
|
||||
| 'Akismet Anti-spam'
|
||||
| 'Growth Bundle'
|
||||
| 'Search'
|
||||
| 'AI'
|
||||
| 'VaultPress Backup'
|
||||
| 'Boost'
|
||||
| 'Extras'
|
||||
| 'Complete Bundle'
|
||||
| 'Protect'
|
||||
| 'Creator'
|
||||
| 'Scan';
|
||||
|
||||
type PurchaseProductName =
|
||||
| 'Jetpack Premium'
|
||||
| 'Jetpack Personal'
|
||||
| 'Jetpack Free'
|
||||
| 'Jetpack Professional'
|
||||
| 'Jetpack Security Daily'
|
||||
| 'Jetpack Security Real-time'
|
||||
| 'Jetpack Complete'
|
||||
| 'Jetpack Security (10GB)'
|
||||
| 'Jetpack Security (1TB)'
|
||||
| 'Jetpack Growth'
|
||||
| 'Jetpack Starter'
|
||||
| 'Jetpack Creator'
|
||||
| 'Jetpack Search Free'
|
||||
| 'Jetpack Search'
|
||||
| 'Jetpack Scan Daily'
|
||||
| 'Jetpack Scan Realtime'
|
||||
| 'Jetpack Akismet Anti-spam'
|
||||
| 'Jetpack VaultPress Backup (1GB)'
|
||||
| 'Jetpack VaultPress Backup (10GB)'
|
||||
| 'Jetpack VaultPress Backup (1TB)'
|
||||
| 'Jetpack VaultPress Backup (One-time)'
|
||||
| 'Jetpack VaultPress Backup Add-on Storage (10GB)'
|
||||
| 'Jetpack VaultPress Backup Add-on Storage (100GB)'
|
||||
| 'Jetpack Anti-spam'
|
||||
| 'Jetpack Backup'
|
||||
| 'Jetpack Security'
|
||||
| 'Jetpack CRM'
|
||||
| 'Jetpack Social'
|
||||
| 'Jetpack Boost'
|
||||
| 'Jetpack Stats'
|
||||
| 'Jetpack Protect'
|
||||
| 'Jetpack VideoPress';
|
||||
|
||||
type PlanExpirationAlert = {
|
||||
product_slug: JetpackPlanSlug;
|
||||
product_name?: PurchaseProductName;
|
||||
expiry_date?: string;
|
||||
expiry_message?: string;
|
||||
manage_url?: string;
|
||||
products_effected?: JetpackProductName[];
|
||||
};
|
||||
|
||||
type PlanExpiredAlerts = Record< `${ JetpackPlanSlug }--plan_expired`, PlanExpirationAlert >;
|
||||
|
||||
type MissingConnectionAlertData = {
|
||||
type: 'site' | 'user';
|
||||
is_error: boolean;
|
||||
};
|
||||
|
||||
type MissingConnectionAlert = Record< 'missing-connection', MissingConnectionAlertData >;
|
||||
|
||||
type WelcomeBannerActiveAlert = Record< 'welcome-banner-active', null >;
|
||||
|
||||
type BackupFailureAlertData = {
|
||||
type: 'warning' | 'error';
|
||||
data: BackupNeedsAttentionData;
|
||||
};
|
||||
|
||||
type BackupFailureAlert = Record< 'backup_failure', BackupFailureAlertData >;
|
||||
|
||||
type ProtectHasThreatsAlertData = {
|
||||
type: 'warning' | 'error';
|
||||
data: ProtectNeedsAttentionData;
|
||||
};
|
||||
|
||||
type ProtectHasThreatsAlert = Record< 'protect_has_threats', ProtectHasThreatsAlertData >;
|
||||
|
||||
type PluginsNeedingInstallAlertData = {
|
||||
needs_installed?: JetpackModule[];
|
||||
needs_activated_only?: JetpackModule[];
|
||||
};
|
||||
|
||||
type PluginsNeedingInstallAlert = Record<
|
||||
`${ JetpackPlanSlug }--plugins_needing_installed_activated`,
|
||||
PluginsNeedingInstallAlertData
|
||||
>;
|
||||
|
||||
type RedBubbleAlerts = MissingConnectionAlert &
|
||||
WelcomeBannerActiveAlert &
|
||||
PlanExpiredAlerts &
|
||||
BackupFailureAlert &
|
||||
ProtectHasThreatsAlert &
|
||||
PluginsNeedingInstallAlert;
|
||||
|
||||
type BackupNeedsAttentionData = {
|
||||
source: 'rewind' | 'last_backup';
|
||||
status: RewindStatus | BackupStatus;
|
||||
last_updated: string;
|
||||
};
|
||||
|
||||
type ProtectNeedsAttentionData = {
|
||||
threat_count: number;
|
||||
critical_threat_count: number;
|
||||
fixable_threat_ids: number[];
|
||||
};
|
||||
|
||||
type Purchase = {
|
||||
ID: string;
|
||||
user_id: string;
|
||||
blog_id: string;
|
||||
product_id: string;
|
||||
subscribed_date: string;
|
||||
renew: string;
|
||||
auto_renew: string;
|
||||
renew_date: string;
|
||||
inactive_date: string | null;
|
||||
active: string;
|
||||
meta: string | object;
|
||||
ownership_id: string;
|
||||
most_recent_renew_date: string;
|
||||
amount: number;
|
||||
expiry_date: string;
|
||||
expiry_message: string;
|
||||
expiry_sub_message: string;
|
||||
expiry_status: string;
|
||||
partner_name: string | null;
|
||||
partner_slug: string | null;
|
||||
partner_key_id: string | null;
|
||||
subscription_status: string;
|
||||
product_name: string;
|
||||
product_slug: string;
|
||||
product_type: string;
|
||||
blog_created_date: string;
|
||||
blogname: string;
|
||||
domain: string;
|
||||
description: string;
|
||||
attached_to_purchase_id: string | null;
|
||||
included_domain: string;
|
||||
included_domain_purchase_amount: number;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
renewal_price_tier_slug: string | null;
|
||||
renewal_price_tier_usage_quantity: number | null;
|
||||
current_price_tier_slug: string | null;
|
||||
current_price_tier_usage_quantity: number | null;
|
||||
price_tier_list: Array< object >;
|
||||
price_text: string;
|
||||
bill_period_label: string;
|
||||
bill_period_days: number;
|
||||
regular_price_text: string;
|
||||
regular_price_integer: number;
|
||||
product_display_price: string;
|
||||
price_integer: number;
|
||||
is_cancelable: boolean;
|
||||
can_explicit_renew: boolean;
|
||||
can_disable_auto_renew: boolean;
|
||||
can_reenable_auto_renewal: boolean;
|
||||
iap_purchase_management_link: string | null;
|
||||
is_iap_purchase: boolean;
|
||||
is_locked: boolean;
|
||||
is_refundable: boolean;
|
||||
refund_period_in_days: number;
|
||||
is_renewable: boolean;
|
||||
is_renewal: boolean;
|
||||
has_private_registration: boolean;
|
||||
refund_amount: number;
|
||||
refund_integer: number;
|
||||
refund_currency_symbol: string;
|
||||
refund_text: string;
|
||||
refund_options: object | null;
|
||||
total_refund_amount: number;
|
||||
total_refund_integer: number;
|
||||
total_refund_currency: string;
|
||||
total_refund_text: string;
|
||||
check_dns: boolean;
|
||||
};
|
||||
|
||||
type ProtectData = {
|
||||
scanData: {
|
||||
core: ScanItem;
|
||||
current_progress?: string;
|
||||
data_source: string;
|
||||
database: string[];
|
||||
error: boolean;
|
||||
error_code?: string;
|
||||
error_message?: string;
|
||||
files: string[];
|
||||
has_unchecked_items: boolean;
|
||||
last_checked: string;
|
||||
num_plugins_threats: number;
|
||||
num_themes_threats: number;
|
||||
num_threats: number;
|
||||
plugins: ScanItem[];
|
||||
status: string;
|
||||
themes: ScanItem[];
|
||||
threats?: ThreatItem[];
|
||||
};
|
||||
wafConfig: {
|
||||
automatic_rules_available: boolean;
|
||||
blocked_logins: number;
|
||||
bootstrap_path: string;
|
||||
brute_force_protection: boolean;
|
||||
jetpack_waf_automatic_rules: '1' | '';
|
||||
jetpack_waf_ip_allow_list: '1' | '';
|
||||
jetpack_waf_ip_block_list: boolean;
|
||||
jetpack_waf_ip_list: boolean;
|
||||
jetpack_waf_share_data: '1' | '';
|
||||
jetpack_waf_share_debug_data: boolean;
|
||||
standalone_mode: boolean;
|
||||
waf_supported: boolean;
|
||||
waf_enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type VideopressData = {
|
||||
featuredStats?: {
|
||||
label: string;
|
||||
period: 'day' | 'year';
|
||||
data: {
|
||||
views: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
impressions: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
watch_time: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
videoCount: number;
|
||||
};
|
||||
|
||||
interface Window {
|
||||
myJetpackInitialState?: {
|
||||
siteSuffix: string;
|
||||
@ -88,17 +495,15 @@ interface Window {
|
||||
blogID: string;
|
||||
fileSystemWriteAccess: 'yes' | 'no';
|
||||
isStatsModuleActive: string;
|
||||
canUserViewStats: boolean;
|
||||
isUserFromKnownHost: string;
|
||||
jetpackManage: {
|
||||
isAgencyAccount: boolean;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
loadAddLicenseScreen: string;
|
||||
myJetpackCheckoutUri: string;
|
||||
myJetpackFlags: {
|
||||
showFullJetpackStatsCard: boolean;
|
||||
videoPressStats: boolean;
|
||||
};
|
||||
purchaseToken: string;
|
||||
lifecycleStats: {
|
||||
historicallyActiveModules: JetpackModule[];
|
||||
brokenModules: {
|
||||
@ -108,10 +513,7 @@ interface Window {
|
||||
isSiteConnected: boolean;
|
||||
isUserConnected: boolean;
|
||||
jetpackPlugins: Array< string >;
|
||||
ownedProducts: JetpackModule[];
|
||||
unownedProducts: JetpackModule[];
|
||||
modules: Array< string >;
|
||||
purchases: Array< string >;
|
||||
};
|
||||
myJetpackUrl: string;
|
||||
myJetpackVersion: string;
|
||||
@ -139,13 +541,17 @@ interface Window {
|
||||
[ key: string ]: {
|
||||
class: string;
|
||||
description: string;
|
||||
category: 'security' | 'performance' | 'growth' | 'create' | 'management';
|
||||
disclaimers: Array< string[] >;
|
||||
features: string[];
|
||||
has_free_offering: boolean;
|
||||
feature_identifying_paid_plan: string;
|
||||
has_paid_plan_for_product: boolean;
|
||||
features_by_tier: Array< string >;
|
||||
is_bundle: boolean;
|
||||
is_feature: boolean;
|
||||
is_plugin_active: boolean;
|
||||
is_tiered_pricing: boolean;
|
||||
is_upgradable: boolean;
|
||||
is_upgradable_by_bundle: string[];
|
||||
long_description: string;
|
||||
@ -154,6 +560,8 @@ interface Window {
|
||||
plugin_slug: string;
|
||||
post_activation_url: string;
|
||||
post_checkout_url?: string;
|
||||
manage_paid_plan_purchase_url?: string;
|
||||
renew_paid_plan_purchase_url?: string;
|
||||
pricing_for_ui?: {
|
||||
available: boolean;
|
||||
wpcom_product_slug: string;
|
||||
@ -161,7 +569,9 @@ interface Window {
|
||||
product_term: string;
|
||||
currency_code: string;
|
||||
full_price: number;
|
||||
full_price_per_month?: number;
|
||||
discount_price: number;
|
||||
discount_price_per_month?: number;
|
||||
coupon_discount: number;
|
||||
is_introductory_offer: boolean;
|
||||
introductory_offer?: {
|
||||
@ -171,13 +581,20 @@ interface Window {
|
||||
should_prorate_when_offer_ends: boolean;
|
||||
transition_after_renewal_count: number;
|
||||
usage_limit?: number;
|
||||
reason?: {
|
||||
errors: {
|
||||
introductoryOfferRemovedSubscriptionFound: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
tiers?: {
|
||||
[ key: string ]: {
|
||||
available: boolean;
|
||||
currencyCode: string;
|
||||
discountPrice: number;
|
||||
discountPricePerMonth?: number;
|
||||
fullPrice: number;
|
||||
fullPricePerMonth?: number;
|
||||
introductoryOffer?: {
|
||||
costPerInterval: number;
|
||||
intervalCount: number;
|
||||
@ -185,6 +602,11 @@ interface Window {
|
||||
shouldProrateWhenOfferEnds: boolean;
|
||||
transitionAfterRenewalCount: number;
|
||||
usageLimit?: number;
|
||||
reason?: {
|
||||
errors: {
|
||||
introductoryOfferRemovedSubscriptionFound: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
isIntroductoryOffer: boolean;
|
||||
productTerm: string;
|
||||
@ -195,7 +617,7 @@ interface Window {
|
||||
};
|
||||
purchase_url?: string;
|
||||
requires_user_connection: boolean;
|
||||
slug: string;
|
||||
slug: JetpackModule;
|
||||
standalone_plugin_info: {
|
||||
has_standalone_plugin: boolean;
|
||||
is_standalone_installed: boolean;
|
||||
@ -206,150 +628,20 @@ interface Window {
|
||||
tiers: string[];
|
||||
title: string;
|
||||
wpcom_product_slug: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
protect: {
|
||||
scanData: {
|
||||
core: ScanItem;
|
||||
current_progress?: string;
|
||||
data_source: string;
|
||||
database: string[];
|
||||
error: boolean;
|
||||
error_code?: string;
|
||||
error_message?: string;
|
||||
files: string[];
|
||||
has_unchecked_items: boolean;
|
||||
last_checked: string;
|
||||
num_plugins_threats: number;
|
||||
num_themes_threats: number;
|
||||
num_threats: number;
|
||||
plugins: ScanItem[];
|
||||
status: string;
|
||||
themes: ScanItem[];
|
||||
};
|
||||
wafConfig: {
|
||||
automatic_rules_available: boolean;
|
||||
blocked_logins: number;
|
||||
bootstrap_path: string;
|
||||
brute_force_protection: boolean;
|
||||
jetpack_waf_automatic_rules: '1' | '';
|
||||
jetpack_waf_ip_allow_list: '1' | '';
|
||||
jetpack_waf_ip_block_list: boolean;
|
||||
jetpack_waf_ip_list: boolean;
|
||||
jetpack_waf_share_data: '1' | '';
|
||||
jetpack_waf_share_debug_data: boolean;
|
||||
standalone_mode: boolean;
|
||||
};
|
||||
};
|
||||
videopress: {
|
||||
featuredStats?: {
|
||||
label: string;
|
||||
period: 'day' | 'year';
|
||||
data: {
|
||||
views: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
impressions: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
watch_time: {
|
||||
current: number;
|
||||
previous: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
videoCount: number;
|
||||
};
|
||||
purchases: {
|
||||
items: Array< {
|
||||
ID: string;
|
||||
user_id: string;
|
||||
blog_id: string;
|
||||
product_id: string;
|
||||
subscribed_date: string;
|
||||
renew: string;
|
||||
auto_renew: string;
|
||||
renew_date: string;
|
||||
inactive_date: string | null;
|
||||
active: string;
|
||||
meta: string | object;
|
||||
ownership_id: string;
|
||||
most_recent_renew_date: string;
|
||||
amount: number;
|
||||
expiry_date: string;
|
||||
expiry_message: string;
|
||||
expiry_sub_message: string;
|
||||
expiry_status: string;
|
||||
partner_name: string | null;
|
||||
partner_slug: string | null;
|
||||
partner_key_id: string | null;
|
||||
subscription_status: string;
|
||||
product_name: string;
|
||||
product_slug: string;
|
||||
product_type: string;
|
||||
blog_created_date: string;
|
||||
blogname: string;
|
||||
domain: string;
|
||||
description: string;
|
||||
attached_to_purchase_id: string | null;
|
||||
included_domain: string;
|
||||
included_domain_purchase_amount: number;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
renewal_price_tier_slug: string | null;
|
||||
renewal_price_tier_usage_quantity: number | null;
|
||||
current_price_tier_slug: string | null;
|
||||
current_price_tier_usage_quantity: number | null;
|
||||
price_tier_list: Array< object >;
|
||||
price_text: string;
|
||||
bill_period_label: string;
|
||||
bill_period_days: number;
|
||||
regular_price_text: string;
|
||||
regular_price_integer: number;
|
||||
product_display_price: string;
|
||||
price_integer: number;
|
||||
is_cancelable: boolean;
|
||||
can_explicit_renew: boolean;
|
||||
can_disable_auto_renew: boolean;
|
||||
can_reenable_auto_renewal: boolean;
|
||||
iap_purchase_management_link: string | null;
|
||||
is_iap_purchase: boolean;
|
||||
is_locked: boolean;
|
||||
is_refundable: boolean;
|
||||
refund_period_in_days: number;
|
||||
is_renewable: boolean;
|
||||
is_renewal: boolean;
|
||||
has_private_registration: boolean;
|
||||
refund_amount: number;
|
||||
refund_integer: number;
|
||||
refund_currency_symbol: string;
|
||||
refund_text: string;
|
||||
refund_options: object | null;
|
||||
total_refund_amount: number;
|
||||
total_refund_integer: number;
|
||||
total_refund_currency: string;
|
||||
total_refund_text: string;
|
||||
check_dns: boolean;
|
||||
} >;
|
||||
};
|
||||
redBubbleAlerts: {
|
||||
'missing-connection'?: {
|
||||
type: string;
|
||||
is_error: boolean;
|
||||
};
|
||||
'welcome-banner-active'?: null;
|
||||
[ key: `${ string }-bad-installation` ]: {
|
||||
data: {
|
||||
plugin: string;
|
||||
doesModuleNeedAttention:
|
||||
| false
|
||||
| {
|
||||
type: 'warning' | 'error';
|
||||
data: BackupNeedsAttentionData | ProtectNeedsAttentionData;
|
||||
status?: BackupStatus | RewindStatus;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
recommendedModules: {
|
||||
modules: JetpackModule[] | null;
|
||||
dismissed: boolean;
|
||||
isFirstRun: boolean;
|
||||
};
|
||||
themes: {
|
||||
[ key: string ]: {
|
||||
@ -368,8 +660,9 @@ interface Window {
|
||||
};
|
||||
topJetpackMenuItemUrl: string;
|
||||
isAtomic: boolean;
|
||||
sandboxedDomain: string;
|
||||
isDevVersion: boolean;
|
||||
userIsAdmin: string;
|
||||
userIsNewToJetpack: string;
|
||||
};
|
||||
JP_CONNECTION_INITIAL_STATE: {
|
||||
apiRoot: string;
|
||||
@ -399,6 +692,8 @@ interface Window {
|
||||
blogId: number;
|
||||
wpcomUser: {
|
||||
avatar: boolean;
|
||||
display_name: string;
|
||||
email: string;
|
||||
};
|
||||
gravatar: string;
|
||||
permissions: {
|
||||
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Historically Active Modules rest api endpoint and helper functions
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Registers REST route for updating historically active modules
|
||||
* and includes all helper functions for triggering an update elsewhere
|
||||
*/
|
||||
class Historically_Active_Modules {
|
||||
public const UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY = 'update-historically-active-jetpack-modules';
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/update-historically-active-modules',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::rest_trigger_historically_active_modules_update',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user capabilities to access historically active modules.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update historically active Jetpack plugins
|
||||
* Historically active is defined as the Jetpack plugins that are installed and active with the required connections
|
||||
* This array will consist of any plugins that were active at one point in time and are still enabled on the site
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_historically_active_jetpack_modules() {
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
$products = Products::get_products();
|
||||
$product_classes = Products::get_products_classes();
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$product_slug = $product['slug'];
|
||||
$status = $product_classes[ $product_slug ]::get_status();
|
||||
// We want to leave modules in the array if they've been active in the past
|
||||
// and were not manually disabled by the user.
|
||||
if ( in_array( $status, Products::$broken_module_statuses, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the module is active and not already in the array, add it
|
||||
if (
|
||||
in_array( $status, Products::$active_module_statuses, true ) &&
|
||||
! in_array( $product_slug, $historically_active_modules, true )
|
||||
) {
|
||||
$historically_active_modules[] = $product_slug;
|
||||
}
|
||||
|
||||
// If the module has been disabled due to a manual user action,
|
||||
// or because of a missing plan error, remove it from the array
|
||||
if ( in_array( $status, Products::$disabled_module_statuses, true ) ) {
|
||||
$historically_active_modules = array_values( array_diff( $historically_active_modules, array( $product_slug ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_option( 'historically_active_modules', array_unique( $historically_active_modules ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API endpoint to trigger an update to the historically active Jetpack modules
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function rest_trigger_historically_active_modules_update() {
|
||||
self::update_historically_active_jetpack_modules();
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
return rest_ensure_response( $historically_active_modules );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transient to queue an update to the historically active Jetpack modules on the next wp-admin load
|
||||
*
|
||||
* @param string $plugin The plugin that triggered the update. This will be present if the function was queued by a plugin activation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_historically_active_jetpack_modules_update( $plugin = null ) {
|
||||
$plugin_filenames = Products::get_all_plugin_filenames();
|
||||
|
||||
if ( ! $plugin || in_array( $plugin, $plugin_filenames, true ) ) {
|
||||
set_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY, true );
|
||||
}
|
||||
}
|
||||
}
|
@ -21,14 +21,11 @@ use Automattic\Jetpack\JITMS\JITM;
|
||||
use Automattic\Jetpack\Licensing;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Plugins_Installer;
|
||||
use Automattic\Jetpack\Protect_Status\Status as Protect_Status;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host as Status_Host;
|
||||
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
|
||||
use Automattic\Jetpack\Terms_Of_Service;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Automattic\Jetpack\VideoPress\Stats as VideoPress_Stats;
|
||||
use Automattic\Jetpack\Waf\Waf_Runner;
|
||||
use Jetpack;
|
||||
use WP_Error;
|
||||
|
||||
@ -42,14 +39,14 @@ class Initializer {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PACKAGE_VERSION = '4.35.3';
|
||||
const PACKAGE_VERSION = '5.9.1';
|
||||
|
||||
/**
|
||||
* HTML container ID for the IDC screen on My Jetpack page.
|
||||
*/
|
||||
const IDC_CONTAINER_ID = 'my-jetpack-identity-crisis-container';
|
||||
private const IDC_CONTAINER_ID = 'my-jetpack-identity-crisis-container';
|
||||
|
||||
const JETPACK_PLUGIN_SLUGS = array(
|
||||
public const JETPACK_PLUGIN_SLUGS = array(
|
||||
'jetpack-backup',
|
||||
'jetpack-boost',
|
||||
'zerobscrm',
|
||||
@ -60,11 +57,7 @@ class Initializer {
|
||||
'jetpack-search',
|
||||
);
|
||||
|
||||
const MY_JETPACK_SITE_INFO_TRANSIENT_KEY = 'my-jetpack-site-info';
|
||||
const UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY = 'update-historically-active-jetpack-modules';
|
||||
const MISSING_CONNECTION_NOTIFICATION_KEY = 'missing-connection';
|
||||
const VIDEOPRESS_STATS_KEY = 'my-jetpack-videopress-stats';
|
||||
const VIDEOPRESS_PERIOD_KEY = 'my-jetpack-videopress-period';
|
||||
private const MY_JETPACK_SITE_INFO_TRANSIENT_KEY = 'my-jetpack-site-info';
|
||||
|
||||
/**
|
||||
* Holds info/data about the site (from the /sites/%d endpoint)
|
||||
@ -99,16 +92,8 @@ class Initializer {
|
||||
// Add custom WP REST API endoints.
|
||||
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) );
|
||||
|
||||
$page_suffix = Admin_Menu::add_menu(
|
||||
__( 'My Jetpack', 'jetpack-my-jetpack' ),
|
||||
__( 'My Jetpack', 'jetpack-my-jetpack' ),
|
||||
'edit_posts',
|
||||
'my-jetpack',
|
||||
array( __CLASS__, 'admin_page' ),
|
||||
-1
|
||||
);
|
||||
add_action( 'admin_menu', array( __CLASS__, 'add_my_jetpack_menu_item' ) );
|
||||
|
||||
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
|
||||
add_action( 'admin_init', array( __CLASS__, 'setup_historically_active_jetpack_modules_sync' ) );
|
||||
// This is later than the admin-ui package, which runs on 1000
|
||||
add_action( 'admin_init', array( __CLASS__, 'maybe_show_red_bubble' ), 1001 );
|
||||
@ -166,6 +151,23 @@ class Initializer {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add My Jetpack menu item to the admin menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add_my_jetpack_menu_item() {
|
||||
$page_suffix = Admin_Menu::add_menu(
|
||||
__( 'My Jetpack', 'jetpack-my-jetpack' ),
|
||||
__( 'My Jetpack', 'jetpack-my-jetpack' ),
|
||||
'edit_posts',
|
||||
'my-jetpack',
|
||||
array( __CLASS__, 'admin_page' ),
|
||||
-1
|
||||
);
|
||||
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the load my jetpack page hook.
|
||||
*
|
||||
@ -192,12 +194,20 @@ class Initializer {
|
||||
|
||||
return $tracking->should_enable_tracking( new Terms_Of_Service(), $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin page assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
/**
|
||||
* Fires after the My Jetpack page is initialized.
|
||||
* Allows for enqueuing additional scripts only on the My Jetpack page.
|
||||
*
|
||||
* @since 4.35.7
|
||||
*/
|
||||
do_action( 'myjetpack_enqueue_scripts' );
|
||||
Assets::register_script(
|
||||
'my_jetpack_main_app',
|
||||
'../build/index.js',
|
||||
@ -217,12 +227,12 @@ class Initializer {
|
||||
$previous_score = $speed_score_history->latest( 1 );
|
||||
}
|
||||
$latest_score['previousScores'] = $previous_score['scores'] ?? array();
|
||||
$scan_data = Protect_Status::get_status();
|
||||
self::update_historically_active_jetpack_modules();
|
||||
|
||||
$waf_config = array();
|
||||
if ( class_exists( 'Automattic\Jetpack\Waf\Waf_Runner' ) ) {
|
||||
$waf_config = Waf_Runner::get_config();
|
||||
$sandboxed_domain = '';
|
||||
$is_dev_version = false;
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
$is_dev_version = Jetpack::is_development_version();
|
||||
$sandboxed_domain = defined( 'JETPACK__SANDBOX_DOMAIN' ) ? JETPACK__SANDBOX_DOMAIN : '';
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
@ -232,9 +242,6 @@ class Initializer {
|
||||
'products' => array(
|
||||
'items' => Products::get_products(),
|
||||
),
|
||||
'purchases' => array(
|
||||
'items' => array(),
|
||||
),
|
||||
'plugins' => Plugins_Installer::get_plugins(),
|
||||
'themes' => Sync_Functions::get_themes(),
|
||||
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
|
||||
@ -250,40 +257,25 @@ class Initializer {
|
||||
'adminUrl' => esc_url( admin_url() ),
|
||||
'IDCContainerID' => static::get_idc_container_id(),
|
||||
'userIsAdmin' => current_user_can( 'manage_options' ),
|
||||
'userIsNewToJetpack' => self::is_jetpack_user_new(),
|
||||
'lifecycleStats' => array(
|
||||
'jetpackPlugins' => self::get_installed_jetpack_plugins(),
|
||||
'historicallyActiveModules' => \Jetpack_Options::get_option( 'historically_active_modules', array() ),
|
||||
'ownedProducts' => Products::get_products_by_ownership( 'owned' ),
|
||||
'unownedProducts' => Products::get_products_by_ownership( 'unowned' ),
|
||||
'brokenModules' => self::check_for_broken_modules(),
|
||||
'brokenModules' => Red_Bubble_Notifications::check_for_broken_modules(),
|
||||
'isSiteConnected' => $connection->is_connected(),
|
||||
'isUserConnected' => $connection->is_user_connected(),
|
||||
'purchases' => self::get_purchases(),
|
||||
'modules' => self::get_active_modules(),
|
||||
),
|
||||
'redBubbleAlerts' => self::get_red_bubble_alerts(),
|
||||
'recommendedModules' => array(
|
||||
'modules' => self::get_recommended_modules(),
|
||||
'dismissed' => \Jetpack_Options::get_option( 'dismissed_recommendations', false ),
|
||||
'modules' => self::get_recommended_modules(),
|
||||
'isFirstRun' => \Jetpack_Options::get_option( 'recommendations_first_run', true ),
|
||||
'dismissed' => \Jetpack_Options::get_option( 'dismissed_recommendations', false ),
|
||||
),
|
||||
'isStatsModuleActive' => $modules->is_active( 'stats' ),
|
||||
'isUserFromKnownHost' => self::is_user_from_known_host(),
|
||||
'isCommercial' => self::is_commercial_site(),
|
||||
'canUserViewStats' => current_user_can( 'manage_options' ) || current_user_can( 'view_stats' ),
|
||||
'sandboxedDomain' => $sandboxed_domain,
|
||||
'isDevVersion' => $is_dev_version,
|
||||
'isAtomic' => ( new Status_Host() )->is_woa_site(),
|
||||
'jetpackManage' => array(
|
||||
'isEnabled' => Jetpack_Manage::could_use_jp_manage(),
|
||||
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
|
||||
),
|
||||
'latestBoostSpeedScores' => $latest_score,
|
||||
'protect' => array(
|
||||
'scanData' => $scan_data,
|
||||
'wafConfig' => array_merge(
|
||||
$waf_config,
|
||||
array( 'blocked_logins' => (int) get_site_option( 'jetpack_protect_blocked_attempts', 0 ) )
|
||||
),
|
||||
),
|
||||
'videopress' => self::get_videopress_stats(),
|
||||
)
|
||||
);
|
||||
|
||||
@ -305,86 +297,6 @@ class Initializer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats for VideoPress
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_videopress_stats() {
|
||||
$video_count = array_sum( (array) wp_count_attachments( 'video' ) );
|
||||
|
||||
if ( ! class_exists( 'Automattic\Jetpack\VideoPress\Stats' ) ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
$featured_stats = get_transient( self::VIDEOPRESS_STATS_KEY );
|
||||
|
||||
if ( $featured_stats ) {
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
$stats_period = get_transient( self::VIDEOPRESS_PERIOD_KEY );
|
||||
$videopress_stats = new VideoPress_Stats();
|
||||
|
||||
// If the stats period exists, retrieve that information without checking the view count.
|
||||
// If it does not, check the view count of monthly stats and determine if we want to show yearly or monthly stats.
|
||||
if ( $stats_period ) {
|
||||
if ( $stats_period === 'day' ) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
|
||||
if (
|
||||
! is_wp_error( $featured_stats ) &&
|
||||
$featured_stats &&
|
||||
( $featured_stats['data']['views']['current'] < 500 || $featured_stats['data']['views']['previous'] < 500 )
|
||||
) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_wp_error( $featured_stats ) || ! $featured_stats ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
set_transient( self::VIDEOPRESS_PERIOD_KEY, $featured_stats['period'], WEEK_IN_SECONDS );
|
||||
set_transient( self::VIDEOPRESS_STATS_KEY, $featured_stats, DAY_IN_SECONDS );
|
||||
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product slugs of the active purchases
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_purchases() {
|
||||
$purchases = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $purchase ) {
|
||||
return $purchase->product_slug;
|
||||
},
|
||||
(array) $purchases
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get installed Jetpack plugins
|
||||
*
|
||||
@ -474,16 +386,6 @@ class Initializer {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user has come from a host we can recognize.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function is_user_from_known_host() {
|
||||
// Known (external) host is the one that has been determined and is not dotcom.
|
||||
return ! in_array( ( new Status_Host() )->get_known_host_guess(), array( 'unknown', 'wpcom' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build flags for My Jetpack UI
|
||||
*
|
||||
@ -516,10 +418,14 @@ class Initializer {
|
||||
new REST_Products();
|
||||
new REST_Purchases();
|
||||
new REST_Zendesk_Chat();
|
||||
new REST_Product_Data();
|
||||
new REST_AI();
|
||||
new REST_Recommendations_Evaluation();
|
||||
|
||||
Products::register_product_endpoints();
|
||||
Historically_Active_Modules::register_rest_endpoints();
|
||||
Jetpack_Manage::register_rest_endpoints();
|
||||
Red_Bubble_Notifications::register_rest_endpoints();
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site',
|
||||
@ -578,21 +484,6 @@ class Initializer {
|
||||
return apply_filters( 'jetpack_my_jetpack_should_initialize', $should );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transient to queue an update to the historically active Jetpack modules on the next wp-admin load
|
||||
*
|
||||
* @param string $plugin The plugin that triggered the update. This will be present if the function was queued by a plugin activation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_historically_active_jetpack_modules_update( $plugin = null ) {
|
||||
$plugin_filenames = Products::get_all_plugin_filenames();
|
||||
|
||||
if ( ! $plugin || in_array( $plugin, $plugin_filenames, true ) ) {
|
||||
set_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into several connection-based actions to update the historically active Jetpack modules
|
||||
* If the transient that indicates the list needs to be synced, update it and delete the transient
|
||||
@ -600,9 +491,11 @@ class Initializer {
|
||||
* @return void
|
||||
*/
|
||||
public static function setup_historically_active_jetpack_modules_sync() {
|
||||
if ( get_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY ) && ! wp_doing_ajax() ) {
|
||||
self::update_historically_active_jetpack_modules();
|
||||
delete_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY );
|
||||
// yummmm. ham.
|
||||
$ham = new Historically_Active_Modules();
|
||||
if ( get_transient( $ham::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY ) && ! wp_doing_ajax() ) {
|
||||
$ham::update_historically_active_jetpack_modules();
|
||||
delete_transient( $ham::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY );
|
||||
}
|
||||
|
||||
$actions = array(
|
||||
@ -612,49 +505,11 @@ class Initializer {
|
||||
);
|
||||
|
||||
foreach ( $actions as $action ) {
|
||||
add_action( $action, array( __CLASS__, 'queue_historically_active_jetpack_modules_update' ), 5 );
|
||||
add_action( $action, array( $ham, 'queue_historically_active_jetpack_modules_update' ), 5 );
|
||||
}
|
||||
|
||||
// Modules are often updated async, so we need to update them right away as there will sometimes be no page reload.
|
||||
add_action( 'jetpack_activate_module', array( __CLASS__, 'update_historically_active_jetpack_modules' ), 5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update historically active Jetpack plugins
|
||||
* Historically active is defined as the Jetpack plugins that are installed and active with the required connections
|
||||
* This array will consist of any plugins that were active at one point in time and are still enabled on the site
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_historically_active_jetpack_modules() {
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
$products = Products::get_products();
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$status = $product['status'];
|
||||
$product_slug = $product['slug'];
|
||||
// We want to leave modules in the array if they've been active in the past
|
||||
// and were not manually disabled by the user.
|
||||
if ( in_array( $status, Products::$broken_module_statuses, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the module is active and not already in the array, add it
|
||||
if (
|
||||
in_array( $status, Products::$active_module_statuses, true ) &&
|
||||
! in_array( $product_slug, $historically_active_modules, true )
|
||||
) {
|
||||
$historically_active_modules[] = $product_slug;
|
||||
}
|
||||
|
||||
// If the module has been disabled due to a manual user action,
|
||||
// or because of a missing plan error, remove it from the array
|
||||
if ( in_array( $status, Products::$disabled_module_statuses, true ) ) {
|
||||
$historically_active_modules = array_values( array_diff( $historically_active_modules, array( $product_slug ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_option( 'historically_active_modules', array_unique( $historically_active_modules ) );
|
||||
add_action( 'jetpack_activate_module', array( $ham, 'update_historically_active_jetpack_modules' ), 5 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -794,12 +649,20 @@ class Initializer {
|
||||
*/
|
||||
public static function maybe_show_red_bubble() {
|
||||
global $menu;
|
||||
|
||||
// Don't show red bubble alerts for non-admin users
|
||||
// These alerts are generally only actionable for admins
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
$rbn = new Red_Bubble_Notifications();
|
||||
|
||||
// filters for the items in this file
|
||||
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( __CLASS__, 'add_red_bubble_alerts' ) );
|
||||
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( $rbn, 'add_red_bubble_alerts' ) );
|
||||
$red_bubble_alerts = array_filter(
|
||||
self::get_red_bubble_alerts(),
|
||||
$rbn::get_red_bubble_alerts(),
|
||||
function ( $alert ) {
|
||||
// We don't want to show silent alerts
|
||||
// We don't want to show the red bubble for silent alerts
|
||||
return empty( $alert['is_silent'] );
|
||||
}
|
||||
);
|
||||
@ -816,24 +679,6 @@ class Initializer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all possible alerts that we might use a red bubble notification for
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_red_bubble_alerts() {
|
||||
static $red_bubble_alerts = array();
|
||||
|
||||
// using a static cache since we call this function more than once in the class
|
||||
if ( ! empty( $red_bubble_alerts ) ) {
|
||||
return $red_bubble_alerts;
|
||||
}
|
||||
// go find the alerts
|
||||
$red_bubble_alerts = apply_filters( 'my_jetpack_red_bubble_notification_slugs', $red_bubble_alerts );
|
||||
|
||||
return $red_bubble_alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of module names sorted by their recommendation score
|
||||
*
|
||||
@ -850,111 +695,4 @@ class Initializer {
|
||||
|
||||
return array_keys( $recommendations_evaluation ); // Get only module names
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for features broken by a disconnected user or site
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function check_for_broken_modules() {
|
||||
$connection = new Connection_Manager();
|
||||
$is_user_connected = $connection->is_user_connected() || $connection->has_connected_owner();
|
||||
$is_site_connected = $connection->is_connected();
|
||||
$broken_modules = array(
|
||||
'needs_site_connection' => array(),
|
||||
'needs_user_connection' => array(),
|
||||
);
|
||||
|
||||
if ( $is_user_connected && $is_site_connected ) {
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
$products = Products::get_products_classes();
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
if ( ! in_array( $product::$slug, $historically_active_modules, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product::$requires_user_connection && ! $is_user_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_user_connection'], true ) ) {
|
||||
$broken_modules['needs_user_connection'][] = $product::$slug;
|
||||
}
|
||||
} elseif ( ! $is_site_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_site_connection'], true ) ) {
|
||||
$broken_modules['needs_site_connection'][] = $product::$slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add relevant red bubble notifications
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_red_bubble_alerts( array $red_bubble_slugs ) {
|
||||
if ( wp_doing_ajax() ) {
|
||||
return array();
|
||||
}
|
||||
$connection = new Connection_Manager();
|
||||
$welcome_banner_dismissed = \Jetpack_Options::get_option( 'dismissed_welcome_banner', false );
|
||||
if ( self::is_jetpack_user_new() && ! $welcome_banner_dismissed ) {
|
||||
$red_bubble_slugs['welcome-banner-active'] = array(
|
||||
'is_silent' => $connection->is_connected(), // we don't display the red bubble if the user is connected
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
} else {
|
||||
return self::alert_if_missing_connection( $red_bubble_slugs );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if the site is missing a site connection
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_missing_connection( array $red_bubble_slugs ) {
|
||||
$broken_modules = self::check_for_broken_modules();
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
if ( ! empty( $broken_modules['needs_user_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'user',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! empty( $broken_modules['needs_site_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! $connection->is_user_connected() && ! $connection->has_connected_owner() ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'user',
|
||||
'is_error' => false,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! $connection->is_connected() ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => false,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use WP_Error;
|
||||
use WP_Rest_Response;
|
||||
|
||||
/**
|
||||
* Jetpack Manage features in My Jetpack.
|
||||
@ -25,6 +27,35 @@ class Jetpack_Manage {
|
||||
add_action( 'admin_menu', array( self::class, 'add_submenu_jetpack' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'jetpack-manage/data',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_jetpack_manage_data',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user capabilities to access historically active modules.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* The page to be added to submenu
|
||||
*
|
||||
@ -121,4 +152,21 @@ class Jetpack_Manage {
|
||||
|
||||
return $partner->partner_type === 'agency';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Jetpack Manage data for REST API.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public static function get_jetpack_manage_data() {
|
||||
$is_enabled = self::could_use_jp_manage();
|
||||
$is_agency_account = self::is_agency_account();
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'isEnabled' => $is_enabled,
|
||||
'isAgencyAccount' => $is_agency_account,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,17 +16,21 @@ class Products {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_SITE_CONNECTION_ERROR = 'site_connection_error';
|
||||
const STATUS_USER_CONNECTION_ERROR = 'user_connection_error';
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_CAN_UPGRADE = 'can_upgrade';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
const STATUS_MODULE_DISABLED = 'module_disabled';
|
||||
const STATUS_PLUGIN_ABSENT = 'plugin_absent';
|
||||
const STATUS_PLUGIN_ABSENT_WITH_PLAN = 'plugin_absent_with_plan';
|
||||
const STATUS_NEEDS_PLAN = 'needs_plan';
|
||||
const STATUS_NEEDS_ACTIVATION = 'needs_activation';
|
||||
const STATUS_NEEDS_FIRST_SITE_CONNECTION = 'needs_first_site_connection';
|
||||
public const STATUS_SITE_CONNECTION_ERROR = 'site_connection_error';
|
||||
public const STATUS_USER_CONNECTION_ERROR = 'user_connection_error';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_CAN_UPGRADE = 'can_upgrade';
|
||||
public const STATUS_EXPIRING_SOON = 'expiring';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
public const STATUS_INACTIVE = 'inactive';
|
||||
public const STATUS_MODULE_DISABLED = 'module_disabled';
|
||||
public const STATUS_PLUGIN_ABSENT = 'plugin_absent';
|
||||
public const STATUS_PLUGIN_ABSENT_WITH_PLAN = 'plugin_absent_with_plan';
|
||||
public const STATUS_NEEDS_PLAN = 'needs_plan';
|
||||
public const STATUS_NEEDS_ACTIVATION = 'needs_activation';
|
||||
public const STATUS_NEEDS_FIRST_SITE_CONNECTION = 'needs_first_site_connection';
|
||||
public const STATUS_NEEDS_ATTENTION__WARNING = 'needs_attention_warning';
|
||||
public const STATUS_NEEDS_ATTENTION__ERROR = 'needs_attention_error';
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as disabled
|
||||
@ -64,6 +68,8 @@ class Products {
|
||||
self::STATUS_USER_CONNECTION_ERROR,
|
||||
self::STATUS_PLUGIN_ABSENT_WITH_PLAN,
|
||||
self::STATUS_NEEDS_PLAN,
|
||||
self::STATUS_NEEDS_ATTENTION__ERROR,
|
||||
self::STATUS_NEEDS_ATTENTION__WARNING,
|
||||
);
|
||||
|
||||
/**
|
||||
@ -76,6 +82,16 @@ class Products {
|
||||
self::STATUS_CAN_UPGRADE,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as active
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $expiring_or_expired_module_statuses = array(
|
||||
self::STATUS_EXPIRING_SOON,
|
||||
self::STATUS_EXPIRED,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of all statuses that a product can have
|
||||
*
|
||||
@ -86,6 +102,8 @@ class Products {
|
||||
self::STATUS_USER_CONNECTION_ERROR,
|
||||
self::STATUS_ACTIVE,
|
||||
self::STATUS_CAN_UPGRADE,
|
||||
self::STATUS_EXPIRING_SOON,
|
||||
self::STATUS_EXPIRED,
|
||||
self::STATUS_INACTIVE,
|
||||
self::STATUS_MODULE_DISABLED,
|
||||
self::STATUS_PLUGIN_ABSENT,
|
||||
@ -93,6 +111,8 @@ class Products {
|
||||
self::STATUS_NEEDS_PLAN,
|
||||
self::STATUS_NEEDS_ACTIVATION,
|
||||
self::STATUS_NEEDS_FIRST_SITE_CONNECTION,
|
||||
self::STATUS_NEEDS_ATTENTION__WARNING,
|
||||
self::STATUS_NEEDS_ATTENTION__ERROR,
|
||||
);
|
||||
|
||||
/**
|
||||
@ -105,21 +125,28 @@ class Products {
|
||||
*/
|
||||
public static function get_products_classes() {
|
||||
$classes = array(
|
||||
'anti-spam' => Products\Anti_Spam::class,
|
||||
'backup' => Products\Backup::class,
|
||||
'boost' => Products\Boost::class,
|
||||
'crm' => Products\Crm::class,
|
||||
'creator' => Products\Creator::class,
|
||||
'extras' => Products\Extras::class,
|
||||
'jetpack-ai' => Products\Jetpack_Ai::class,
|
||||
'scan' => Products\Scan::class,
|
||||
'search' => Products\Search::class,
|
||||
'social' => Products\Social::class,
|
||||
'security' => Products\Security::class,
|
||||
'protect' => Products\Protect::class,
|
||||
'videopress' => Products\Videopress::class,
|
||||
'stats' => Products\Stats::class,
|
||||
'ai' => Products\Jetpack_Ai::class,
|
||||
'anti-spam' => Products\Anti_Spam::class,
|
||||
'backup' => Products\Backup::class,
|
||||
'boost' => Products\Boost::class,
|
||||
'crm' => Products\Crm::class,
|
||||
'creator' => Products\Creator::class,
|
||||
'extras' => Products\Extras::class,
|
||||
'jetpack-ai' => Products\Jetpack_Ai::class,
|
||||
// TODO: Remove this duplicate class ('ai')? See: https://github.com/Automattic/jetpack/pull/35910#pullrequestreview-2456462227
|
||||
'ai' => Products\Jetpack_Ai::class,
|
||||
'scan' => Products\Scan::class,
|
||||
'search' => Products\Search::class,
|
||||
'social' => Products\Social::class,
|
||||
'security' => Products\Security::class,
|
||||
'protect' => Products\Protect::class,
|
||||
'videopress' => Products\Videopress::class,
|
||||
'stats' => Products\Stats::class,
|
||||
'growth' => Products\Growth::class,
|
||||
'complete' => Products\Complete::class,
|
||||
// Features
|
||||
'newsletter' => Products\Newsletter::class,
|
||||
'site-accelerator' => Products\Site_Accelerator::class,
|
||||
'related-posts' => Products\Related_Posts::class,
|
||||
);
|
||||
|
||||
/**
|
||||
@ -149,17 +176,99 @@ class Products {
|
||||
return $final_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register endpoints related to product classes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_product_endpoints() {
|
||||
$classes = self::get_products_classes();
|
||||
|
||||
foreach ( $classes as $class ) {
|
||||
$class::register_endpoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of product slugs that are displayed on the main My Jetpack page
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $shown_products = array(
|
||||
'anti-spam',
|
||||
'backup',
|
||||
'boost',
|
||||
'crm',
|
||||
'jetpack-ai',
|
||||
'search',
|
||||
'social',
|
||||
'protect',
|
||||
'videopress',
|
||||
'stats',
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the list of product slugs that are Not displayed on the main My Jetpack page
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_not_shown_products() {
|
||||
return array_diff( array_keys( static::get_products_classes() ), self::$shown_products );
|
||||
}
|
||||
|
||||
/**
|
||||
* Product data
|
||||
*
|
||||
* @param array $product_slugs (optional) An array of specified product slugs.
|
||||
* @return array Jetpack products on the site and their availability.
|
||||
*/
|
||||
public static function get_products() {
|
||||
$products = array();
|
||||
foreach ( self::get_products_classes() as $class ) {
|
||||
$product_slug = $class::$slug;
|
||||
$products[ $product_slug ] = $class::get_info();
|
||||
public static function get_products( $product_slugs = array() ) {
|
||||
$all_classes = self::get_products_classes();
|
||||
$products = array();
|
||||
// If an array of $product_slugs are passed, return only the products specified in $product_slugs array
|
||||
if ( $product_slugs ) {
|
||||
foreach ( $product_slugs as $product_slug ) {
|
||||
if ( isset( $all_classes[ $product_slug ] ) ) {
|
||||
$class = $all_classes[ $product_slug ];
|
||||
$products[ $product_slug ] = $class::get_info();
|
||||
}
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
// Otherwise return All products.
|
||||
foreach ( $all_classes as $slug => $class ) {
|
||||
$products[ $slug ] = $class::get_info();
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products data related to the wpcom api
|
||||
*
|
||||
* @param array $product_slugs - (optional) An array of specified product slugs.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_products_api_data( $product_slugs = array() ) {
|
||||
$all_classes = self::get_products_classes();
|
||||
$products = array();
|
||||
// If an array of $product_slugs are passed, return only the products specified in $product_slugs array
|
||||
if ( $product_slugs ) {
|
||||
foreach ( $product_slugs as $product_slug ) {
|
||||
if ( isset( $all_classes[ $product_slug ] ) ) {
|
||||
$class = $all_classes[ $product_slug ];
|
||||
$products[ $product_slug ] = $class::get_wpcom_info();
|
||||
}
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
// Otherwise return All products.
|
||||
foreach ( $all_classes as $slug => $class ) {
|
||||
$products[ $slug ] = $class::get_wpcom_info();
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
@ -341,7 +450,7 @@ class Products {
|
||||
'protect',
|
||||
'crm',
|
||||
'search',
|
||||
'ai',
|
||||
'jetpack-ai',
|
||||
);
|
||||
|
||||
// Add plugin action links for the core Jetpack plugin.
|
||||
|
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Red Bubble Notifications rest api endpoint and helper functions
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Registers REST route for getting red bubble notification data
|
||||
* and includes all helper functions related to red bubble notifications
|
||||
*/
|
||||
class Red_Bubble_Notifications {
|
||||
private const MISSING_CONNECTION_NOTIFICATION_KEY = 'missing-connection';
|
||||
private const MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY = 'my-jetpack-red-bubble-transient';
|
||||
|
||||
/**
|
||||
* Summary of register_rest_routes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'red-bubble-notifications',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => __CLASS__ . '::rest_api_get_red_bubble_alerts',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
'args' => array(
|
||||
'dismissal_cookies' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Array of dismissal cookies to set for the red bubble notifications.',
|
||||
'required' => false,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'sanitize_callback' => function ( $param ) {
|
||||
return array_map( 'sanitize_text_field', $param );
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user capability to access the endpoint.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugins that need installed or activated for each paid plan.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plans_plugins_requirements() {
|
||||
$plugin_requirements = array();
|
||||
foreach ( Products::get_products_classes() as $slug => $product_class ) {
|
||||
// Skip these- we don't show them in My Jetpack.
|
||||
if ( in_array( $slug, Products::get_not_shown_products(), true ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! $product_class::has_paid_plan_for_product() ) {
|
||||
continue;
|
||||
}
|
||||
$purchase = $product_class::get_paid_plan_purchase_for_product();
|
||||
if ( ! $purchase ) {
|
||||
continue;
|
||||
}
|
||||
// Check if required plugin needs installed or activated.
|
||||
if ( ! $product_class::is_plugin_installed() ) {
|
||||
// Plugin needs installed (and activated)
|
||||
$plugin_requirements[ $purchase->product_slug ]['needs_installed'][] = $product_class::$slug;
|
||||
} elseif ( ! $product_class::is_plugin_active() ) {
|
||||
// Plugin is installed, but not activated.
|
||||
$plugin_requirements[ $purchase->product_slug ]['needs_activated_only'][] = $product_class::$slug;
|
||||
}
|
||||
}
|
||||
|
||||
return $plugin_requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for features broken by a disconnected user or site
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function check_for_broken_modules() {
|
||||
$connection = new Connection_Manager();
|
||||
$is_user_connected = $connection->is_user_connected() || $connection->has_connected_owner();
|
||||
$is_site_connected = $connection->is_connected();
|
||||
$broken_modules = array(
|
||||
'needs_site_connection' => array(),
|
||||
'needs_user_connection' => array(),
|
||||
);
|
||||
|
||||
if ( $is_user_connected && $is_site_connected ) {
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
$products = Products::get_products_classes();
|
||||
$historically_active_modules = Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
if ( ! in_array( $product::$slug, $historically_active_modules, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product::$requires_user_connection && ! $is_user_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_user_connection'], true ) ) {
|
||||
$broken_modules['needs_user_connection'][] = $product::$slug;
|
||||
}
|
||||
} elseif ( ! $is_site_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_site_connection'], true ) ) {
|
||||
$broken_modules['needs_site_connection'][] = $product::$slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if the site is missing a site connection
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_missing_connection( array $red_bubble_slugs ) {
|
||||
$broken_modules = self::check_for_broken_modules();
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
// Checking for site connection issues first.
|
||||
if ( ! empty( $broken_modules['needs_site_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! empty( $broken_modules['needs_user_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'user',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! $connection->is_connected() ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => false,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if Backups are failing or having an issue.
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_last_backup_failed( array $red_bubble_slugs ) {
|
||||
// Make sure the Notice wasn't previously dismissed.
|
||||
if ( ! empty( $_COOKIE['backup_failure_dismissed'] ) ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
// Make sure there's a Backup paid plan
|
||||
if ( ! Products\Backup::is_plugin_active() || ! Products\Backup::has_paid_plan_for_product() ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
// Make sure the plan isn't just recently purchased in last 30min.
|
||||
// Give some time to queue & run the first backup.
|
||||
$purchase = Products\Backup::get_paid_plan_purchase_for_product();
|
||||
if ( $purchase ) {
|
||||
$thirty_minutes_after_plan_purchase = strtotime( $purchase->subscribed_date . ' +30 minutes' );
|
||||
if ( strtotime( 'now' ) < $thirty_minutes_after_plan_purchase ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
}
|
||||
|
||||
$backup_failed_status = Products\Backup::does_module_need_attention();
|
||||
if ( $backup_failed_status ) {
|
||||
$red_bubble_slugs['backup_failure'] = $backup_failed_status;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if Protect has scan threats/vulnerabilities.
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_protect_has_threats( array $red_bubble_slugs ) {
|
||||
// Make sure the Notice hasn't been dismissed.
|
||||
if ( ! empty( $_COOKIE['protect_threats_detected_dismissed'] ) ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
// Make sure we're dealing with the Protect product only
|
||||
if ( ! Products\Protect::has_paid_plan_for_product() ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
$protect_threats_status = Products\Protect::does_module_need_attention();
|
||||
|
||||
if ( $protect_threats_status ) {
|
||||
$red_bubble_slugs['protect_has_threats'] = $protect_threats_status;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if any paid plan/products are expiring or expired.
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_paid_plan_expiring( array $red_bubble_slugs ) {
|
||||
$connection = new Connection_Manager();
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
$product_classes = Products::get_products_classes();
|
||||
|
||||
$products_included_in_expiring_plan = array();
|
||||
foreach ( $product_classes as $key => $product ) {
|
||||
// Skip these- we don't show them in My Jetpack.
|
||||
if ( in_array( $key, Products::get_not_shown_products(), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product::has_paid_plan_for_product() ) {
|
||||
$purchase = $product::get_paid_plan_purchase_for_product();
|
||||
if ( $purchase ) {
|
||||
$redbubble_notice_data = array(
|
||||
'product_slug' => $purchase->product_slug,
|
||||
'product_name' => $purchase->product_name,
|
||||
'expiry_date' => $purchase->expiry_date,
|
||||
'expiry_message' => $purchase->expiry_message,
|
||||
'manage_url' => $product::get_manage_paid_plan_purchase_url(),
|
||||
);
|
||||
|
||||
if ( $product::is_paid_plan_expired() && empty( $_COOKIE[ "$purchase->product_slug--plan_expired_dismissed" ] ) ) {
|
||||
$red_bubble_slugs[ "$purchase->product_slug--plan_expired" ] = $redbubble_notice_data;
|
||||
if ( ! $product::is_bundle_product() ) {
|
||||
$products_included_in_expiring_plan[ "$purchase->product_slug--plan_expired" ][] = $product::get_name();
|
||||
}
|
||||
}
|
||||
if ( $product::is_paid_plan_expiring() && empty( $_COOKIE[ "$purchase->product_slug--plan_expiring_soon_dismissed" ] ) ) {
|
||||
$red_bubble_slugs[ "$purchase->product_slug--plan_expiring_soon" ] = $redbubble_notice_data;
|
||||
$red_bubble_slugs[ "$purchase->product_slug--plan_expiring_soon" ]['manage_url'] = $product::get_renew_paid_plan_purchase_url();
|
||||
if ( ! $product::is_bundle_product() ) {
|
||||
$products_included_in_expiring_plan[ "$purchase->product_slug--plan_expiring_soon" ][] = $product::get_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $products_included_in_expiring_plan as $expiring_plan => $products ) {
|
||||
$red_bubble_slugs[ $expiring_plan ]['products_effected'] = $products;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alert slug if a site's paid plan requires a plugin install and/or activation.
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function alert_if_paid_plan_requires_plugin_install_or_activation( array $red_bubble_slugs ) {
|
||||
$connection = new Connection_Manager();
|
||||
// Don't trigger red bubble (and show notice) when the site is not connected or if the
|
||||
// user doesn't have plugin installation/activation permissions.
|
||||
if ( ! $connection->is_connected() || ! current_user_can( 'activate_plugins' ) ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
$plugins_needing_installed_activated = self::get_paid_plans_plugins_requirements();
|
||||
if ( empty( $plugins_needing_installed_activated ) ) {
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
foreach ( $plugins_needing_installed_activated as $plan_slug => $plugins_requirements ) {
|
||||
if ( empty( $_COOKIE[ "$plan_slug--plugins_needing_installed_dismissed" ] ) ) {
|
||||
$red_bubble_slugs[ "$plan_slug--plugins_needing_installed_activated" ] = $plugins_requirements;
|
||||
}
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add relevant red bubble notifications
|
||||
*
|
||||
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_red_bubble_alerts( array $red_bubble_slugs ) {
|
||||
if ( wp_doing_ajax() ) {
|
||||
return array();
|
||||
}
|
||||
$connection = new Connection_Manager();
|
||||
$welcome_banner_dismissed = Jetpack_Options::get_option( 'dismissed_welcome_banner', false );
|
||||
if ( Initializer::is_jetpack_user_new() && ! $welcome_banner_dismissed ) {
|
||||
$red_bubble_slugs['welcome-banner-active'] = array(
|
||||
'is_silent' => $connection->is_connected(), // we don't display the red bubble if the user is connected
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
} else {
|
||||
return array_merge(
|
||||
self::alert_if_missing_connection( $red_bubble_slugs ),
|
||||
self::alert_if_last_backup_failed( $red_bubble_slugs ),
|
||||
self::alert_if_paid_plan_expiring( $red_bubble_slugs ),
|
||||
self::alert_if_protect_has_threats( $red_bubble_slugs ),
|
||||
self::alert_if_paid_plan_requires_plugin_install_or_activation( $red_bubble_slugs )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all possible alerts that we might use a red bubble notification for
|
||||
*
|
||||
* @param bool $bypass_cache - whether to bypass the red bubble cache.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_red_bubble_alerts( bool $bypass_cache = false ) {
|
||||
static $red_bubble_alerts = array();
|
||||
|
||||
// check for stored alerts
|
||||
$stored_alerts = get_transient( self::MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY );
|
||||
|
||||
// Cache bypass for red bubbles should only happen on the My Jetpack page
|
||||
if ( $stored_alerts !== false && ! ( $bypass_cache ) ) {
|
||||
return $stored_alerts;
|
||||
}
|
||||
|
||||
// go find the alerts
|
||||
$red_bubble_alerts = apply_filters( 'my_jetpack_red_bubble_notification_slugs', $red_bubble_alerts );
|
||||
|
||||
// cache the alerts for one hour
|
||||
set_transient( self::MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY, $red_bubble_alerts, 3600 );
|
||||
|
||||
return $red_bubble_alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red bubble alerts, bypassing cache when called via the REST API
|
||||
*
|
||||
* @param WP_REST_Request $request The REST API request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public static function rest_api_get_red_bubble_alerts( $request ) {
|
||||
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( __CLASS__, 'add_red_bubble_alerts' ) );
|
||||
|
||||
$cookies = $request->get_param( 'dismissal_cookies' );
|
||||
|
||||
// Update $_COOKIE superglobal with the provided cookies
|
||||
if ( ! empty( $cookies ) && is_array( $cookies ) ) {
|
||||
foreach ( $cookies as $cookie_string ) {
|
||||
// Parse cookie string in format "name=value"
|
||||
$parts = explode( '=', $cookie_string, 2 );
|
||||
if ( count( $parts ) === 2 ) {
|
||||
$name = trim( $parts[0] );
|
||||
$value = trim( $parts[1] );
|
||||
$_COOKIE[ $name ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$red_bubble_alerts = self::get_red_bubble_alerts( true );
|
||||
return rest_ensure_response( $red_bubble_alerts );
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Product Data REST API endpoints.
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Registers the REST routes for Product Data
|
||||
*/
|
||||
class REST_Product_Data {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Get backup undo event
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/backup/undo-event',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_backup_undo_event',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/backup/count-items',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::count_things_that_can_be_backed_up',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has the correct permissions
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* This will fetch the last rewindable event from the Activity Log and
|
||||
* the last rewind_id prior to that.
|
||||
*
|
||||
* @return array|WP_Error|null
|
||||
*/
|
||||
public static function get_site_backup_undo_event() {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_user(
|
||||
'/sites/' . $blog_id . '/activity/rewindable?force=wpcom',
|
||||
'v2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = json_decode( $response['body'], true );
|
||||
|
||||
if ( ! isset( $body['current'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Preparing the response structure
|
||||
$undo_event = array(
|
||||
'last_rewindable_event' => null,
|
||||
'undo_backup_id' => null,
|
||||
);
|
||||
|
||||
// List of events that will not be considered to be undo.
|
||||
// Basically we should not `undo` a full backup event, but we could
|
||||
// use them to undo any other action like plugin updates.
|
||||
$last_event_exceptions = array(
|
||||
'rewind__backup_only_complete_full',
|
||||
'rewind__backup_only_complete_initial',
|
||||
'rewind__backup_only_complete',
|
||||
'rewind__backup_complete_full',
|
||||
'rewind__backup_complete_initial',
|
||||
'rewind__backup_complete',
|
||||
);
|
||||
|
||||
// Looping through the events to find the last rewindable event and the last backup_id.
|
||||
// The idea is to find the last rewindable event and then the last rewind_id before that.
|
||||
$found_last_event = false;
|
||||
foreach ( $body['current']['orderedItems'] as $event ) {
|
||||
if ( $event['is_rewindable'] ) {
|
||||
if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
|
||||
$undo_event['last_rewindable_event'] = $event;
|
||||
$found_last_event = true;
|
||||
} elseif ( $found_last_event ) {
|
||||
$undo_event['undo_backup_id'] = $event['rewind_id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $undo_event );
|
||||
}
|
||||
|
||||
/**
|
||||
* This will collect a count of all the items that could be backed up
|
||||
* This is used to show what backup could be doing if it is not enabled
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function count_things_that_can_be_backed_up() {
|
||||
$image_mime_type = 'image';
|
||||
$video_mime_type = 'video';
|
||||
$audio_mime_type = 'audio';
|
||||
|
||||
$data = array();
|
||||
|
||||
// Add all post types together to get the total post count
|
||||
$data['total_post_count'] = array_sum( (array) wp_count_posts( 'post' ) );
|
||||
|
||||
// Add all page types together to get the total page count
|
||||
$data['total_page_count'] = array_sum( (array) wp_count_posts( 'page' ) );
|
||||
|
||||
// Add all comments together to get the total comment count
|
||||
$comments = (array) wp_count_comments();
|
||||
$data['total_comment_count'] = $comments ? $comments['total_comments'] : 0;
|
||||
|
||||
// Add all image attachments together to get the total image count
|
||||
$data['total_image_count'] = array_sum( (array) wp_count_attachments( $image_mime_type ) );
|
||||
|
||||
// Add all video attachments together to get the total video count
|
||||
$data['total_video_count'] = array_sum( (array) wp_count_attachments( $video_mime_type ) );
|
||||
|
||||
// Add all audio attachments together to get the total audio count
|
||||
$data['total_audio_count'] = array_sum( (array) wp_count_attachments( $audio_mime_type ) );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
}
|
@ -23,47 +23,42 @@ class REST_Products {
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_products',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
'callback' => __CLASS__ . '::get_products_api_data',
|
||||
'permission_callback' => __CLASS__ . '::view_products_permissions_callback',
|
||||
'args' => array(
|
||||
'products' => array(
|
||||
'description' => __( 'Comma seperated list of product slugs that should be retrieved.', 'jetpack-my-jetpack' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
'validate_callback' => __CLASS__ . '::check_products_string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_products_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
$product_arg = array(
|
||||
'description' => __( 'Product slug', 'jetpack-my-jetpack' ),
|
||||
'type' => 'string',
|
||||
'enum' => Products::get_products_slugs(),
|
||||
$products_arg = array(
|
||||
'description' => __( 'Array of Product slugs', 'jetpack-my-jetpack' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'enum' => Products::get_products_slugs(),
|
||||
'type' => 'string',
|
||||
),
|
||||
'required' => true,
|
||||
'validate_callback' => __CLASS__ . '::check_product_argument',
|
||||
'validate_callback' => __CLASS__ . '::check_products_argument',
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/products/(?P<product>[a-z\-]+)',
|
||||
'site/products/install',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_product',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
'args' => array(
|
||||
'product' => $product_arg,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::activate_product',
|
||||
'callback' => __CLASS__ . '::install_plugins',
|
||||
'permission_callback' => __CLASS__ . '::edit_permissions_callback',
|
||||
'args' => array(
|
||||
'product' => $product_arg,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => __CLASS__ . '::deactivate_product',
|
||||
'permission_callback' => __CLASS__ . '::edit_permissions_callback',
|
||||
'args' => array(
|
||||
'product' => $product_arg,
|
||||
'products' => $products_arg,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -71,18 +66,45 @@ class REST_Products {
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/products/(?P<product>[a-z\-]+)/install-standalone',
|
||||
'site/products/activate',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::install_standalone',
|
||||
'callback' => __CLASS__ . '::activate_products',
|
||||
'permission_callback' => __CLASS__ . '::edit_permissions_callback',
|
||||
'args' => array(
|
||||
'product' => $product_arg,
|
||||
'products' => $products_arg,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/products/deactivate',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => __CLASS__ . '::deactivate_products',
|
||||
'permission_callback' => __CLASS__ . '::edit_permissions_callback',
|
||||
'args' => array(
|
||||
'products' => $products_arg,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/products-ownership',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_products_by_ownership',
|
||||
'permission_callback' => __CLASS__ . '::view_products_permissions_callback',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +134,16 @@ class REST_Products {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Product arguments.
|
||||
* Check if the user is permitted to view the product and product info
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function view_products_permissions_callback() {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Products string (comma separated string).
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
@ -120,7 +151,7 @@ class REST_Products {
|
||||
* @param mixed $value - Value of the 'product' argument.
|
||||
* @return true|WP_Error True if the value is valid, WP_Error otherwise.
|
||||
*/
|
||||
public static function check_product_argument( $value ) {
|
||||
public static function check_products_string( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
@ -129,28 +160,89 @@ class REST_Products {
|
||||
);
|
||||
}
|
||||
|
||||
$products_array = explode( ',', $value );
|
||||
$all_products = Products::get_products_slugs();
|
||||
|
||||
foreach ( $products_array as $product_slug ) {
|
||||
if ( ! in_array( $product_slug, $all_products, true ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %s is the product_slug, it should Not be translated. */
|
||||
__( 'The specified product argument %s is an invalid product.', 'jetpack-my-jetpack' ),
|
||||
$product_slug
|
||||
)
|
||||
),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Products argument.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param mixed $value - Value of the 'product' argument.
|
||||
* @return true|WP_Error True if the value is valid, WP_Error otherwise.
|
||||
*/
|
||||
public static function check_products_argument( $value ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
esc_html__( 'The product argument must be an array.', 'jetpack-my-jetpack' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Site products endpoint.
|
||||
*
|
||||
* @return array of site products list.
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function get_products() {
|
||||
$response = Products::get_products();
|
||||
return rest_ensure_response( $response, 200 );
|
||||
public static function get_products( $request ) {
|
||||
$slugs = $request->get_param( 'products' );
|
||||
$product_slugs = ! empty( $slugs ) ? array_map( 'trim', explode( ',', $slugs ) ) : array();
|
||||
|
||||
$response = Products::get_products( $product_slugs );
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Site single product endpoint.
|
||||
* Site API product data endpoint
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return array of site products list.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function get_product( $request ) {
|
||||
$product_slug = $request->get_param( 'product' );
|
||||
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
|
||||
public static function get_products_api_data( $request ) {
|
||||
$slugs = $request->get_param( 'products' );
|
||||
$product_slugs = ! empty( $slugs ) ? array_map( 'trim', explode( ',', $slugs ) ) : array();
|
||||
|
||||
$response = Products::get_products_api_data( $product_slugs );
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Site products endpoint.
|
||||
*
|
||||
* @return \WP_REST_Response of site products list.
|
||||
*/
|
||||
public static function get_products_by_ownership() {
|
||||
$response = array(
|
||||
'unownedProducts' => Products::get_products_by_ownership( 'unowned' ),
|
||||
'ownedProducts' => Products::get_products_by_ownership( 'owned' ),
|
||||
);
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,81 +261,102 @@ class REST_Products {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for activating a product
|
||||
* Callback for activating products
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return \WP_REST_Response
|
||||
* @return \WP_REST_Response|\WP_Error
|
||||
*/
|
||||
public static function activate_product( $request ) {
|
||||
$product_slug = $request->get_param( 'product' );
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'not_implemented',
|
||||
esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
public static function activate_products( $request ) {
|
||||
$products_array = $request->get_param( 'products' );
|
||||
|
||||
$activate_product_result = call_user_func( array( $product['class'], 'activate' ) );
|
||||
if ( is_wp_error( $activate_product_result ) ) {
|
||||
$activate_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $activate_product_result;
|
||||
}
|
||||
set_transient( 'my_jetpack_product_activated', $product_slug, 10 );
|
||||
foreach ( $products_array as $product_slug ) {
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'product_class_handler_not_found',
|
||||
sprintf(
|
||||
/* translators: %s is the product_slug */
|
||||
__( 'The product slug %s does not have an associated class handler.', 'jetpack-my-jetpack' ),
|
||||
$product_slug
|
||||
),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
|
||||
$activate_product_result = call_user_func( array( $product['class'], 'activate' ) );
|
||||
if ( is_wp_error( $activate_product_result ) ) {
|
||||
$activate_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $activate_product_result;
|
||||
}
|
||||
}
|
||||
set_transient( 'my_jetpack_product_activated', implode( ',', $products_array ), 10 );
|
||||
|
||||
return rest_ensure_response( Products::get_products( $products_array ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for deactivating a product
|
||||
* Callback for deactivating products
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return \WP_REST_Response
|
||||
* @return \WP_REST_Response|\WP_Error
|
||||
*/
|
||||
public static function deactivate_product( $request ) {
|
||||
$product_slug = $request->get_param( 'product' );
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'not_implemented',
|
||||
esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
public static function deactivate_products( $request ) {
|
||||
$products_array = $request->get_param( 'products' );
|
||||
|
||||
foreach ( $products_array as $product_slug ) {
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'product_class_handler_not_found',
|
||||
sprintf(
|
||||
/* translators: %s is the product_slug */
|
||||
__( 'The product slug %s does not have an associated class handler.', 'jetpack-my-jetpack' ),
|
||||
$product_slug
|
||||
),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
$deactivate_product_result = call_user_func( array( $product['class'], 'deactivate' ) );
|
||||
if ( is_wp_error( $deactivate_product_result ) ) {
|
||||
$deactivate_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $deactivate_product_result;
|
||||
}
|
||||
}
|
||||
|
||||
$deactivate_product_result = call_user_func( array( $product['class'], 'deactivate' ) );
|
||||
if ( is_wp_error( $deactivate_product_result ) ) {
|
||||
$deactivate_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $deactivate_product_result;
|
||||
}
|
||||
|
||||
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
|
||||
return rest_ensure_response( Products::get_products( $products_array ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for installing the standalone plugin on a Hybrid Product.
|
||||
* Callback for installing (and activating) multiple product plugins.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return \WP_REST_Response
|
||||
* @return \WP_REST_Response|\WP_Error
|
||||
*/
|
||||
public static function install_standalone( $request ) {
|
||||
$product_slug = $request->get_param( 'product' );
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'not_implemented',
|
||||
__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
public static function install_plugins( $request ) {
|
||||
$products_array = $request->get_param( 'products' );
|
||||
|
||||
foreach ( $products_array as $product_slug ) {
|
||||
$product = Products::get_product( $product_slug );
|
||||
if ( ! isset( $product['class'] ) ) {
|
||||
return new \WP_Error(
|
||||
'product_class_handler_not_found',
|
||||
sprintf(
|
||||
/* translators: %s is the product_slug */
|
||||
__( 'The product slug %s does not have an associated class handler.', 'jetpack-my-jetpack' ),
|
||||
$product_slug
|
||||
),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
$install_product_result = call_user_func( array( $product['class'], 'install_and_activate_standalone' ) );
|
||||
if ( is_wp_error( $install_product_result ) ) {
|
||||
$install_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $install_product_result;
|
||||
}
|
||||
}
|
||||
|
||||
$install_product_result = call_user_func( array( $product['class'], 'install_and_activate_standalone' ) );
|
||||
if ( is_wp_error( $install_product_result ) ) {
|
||||
$install_product_result->add_data( array( 'status' => 400 ) );
|
||||
return $install_product_result;
|
||||
}
|
||||
|
||||
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
|
||||
return rest_ensure_response( Products::get_products( $products_array ) );
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class REST_Purchases {
|
||||
/**
|
||||
* Site purchases endpoint.
|
||||
*
|
||||
* @return array of site purchases.
|
||||
* @return array|WP_Error of site purchases.
|
||||
*/
|
||||
public static function get_site_current_purchases() {
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
@ -72,6 +72,6 @@ class REST_Purchases {
|
||||
return new WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body, 200 );
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ class REST_Recommendations_Evaluation {
|
||||
\Jetpack_Options::update_option( 'dismissed_recommendations', true );
|
||||
|
||||
if ( isset( $show_welcome_banner ) && $show_welcome_banner === 'true' ) {
|
||||
\Jetpack_Options::update_option( 'recommendations_first_run', false );
|
||||
\Jetpack_Options::delete_option( 'dismissed_welcome_banner' );
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ class REST_Zendesk_Chat {
|
||||
public static function get_chat_authentication() {
|
||||
$authentication = get_transient( self::ZENDESK_AUTH_TOKEN );
|
||||
if ( $authentication ) {
|
||||
return rest_ensure_response( $authentication, 200 );
|
||||
return rest_ensure_response( $authentication );
|
||||
}
|
||||
|
||||
$proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
|
||||
@ -97,7 +97,7 @@ class REST_Zendesk_Chat {
|
||||
}
|
||||
|
||||
set_transient( self::ZENDESK_AUTH_TOKEN, $body, self::TRANSIENT_EXPIRY );
|
||||
return rest_ensure_response( $body, 200 );
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,6 +117,6 @@ class REST_Zendesk_Chat {
|
||||
return new WP_Error( 'chat_config_data_fetch_failed', 'Chat config data fetch failed', array( 'status' => $response_code ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body, 200 );
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\Status\Visitor;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
@ -349,6 +350,23 @@ class Wpcom_Products {
|
||||
return $purchases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the site's currently active "plan" (bundle).
|
||||
*
|
||||
* @param bool $reload Whether to refresh data from wpcom or not.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_site_current_plan( $reload = false ) {
|
||||
static $reloaded_already = false;
|
||||
|
||||
if ( $reload && ! $reloaded_already ) {
|
||||
Current_Plan::refresh_from_wpcom();
|
||||
$reloaded_already = true;
|
||||
}
|
||||
|
||||
return Current_Plan::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the request failures to retry the API requests.
|
||||
*
|
||||
|
@ -36,6 +36,20 @@ class Anti_Spam extends Product {
|
||||
*/
|
||||
public static $plugin_slug = 'akismet';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'security';
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'antispam';
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
@ -107,41 +121,32 @@ class Anti_Spam extends Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the site has an Akismet plan by checking for an API key
|
||||
* Note that some Akismet Plans are free - we're just checking for an API key and don't have the perspective of the plan attached to it here
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return bool - whether an API key was found
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$products_with_anti_spam = array(
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_anti_spam',
|
||||
'jetpack_complete',
|
||||
'jetpack_security',
|
||||
'jetpack_personal',
|
||||
'jetpack_premium',
|
||||
'jetpack_business',
|
||||
'jetpack_anti_spam_monthly',
|
||||
'jetpack_anti_spam_bi_yearly',
|
||||
);
|
||||
// Check if the site has an API key for Akismet
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the product has a free plan
|
||||
* In this case we are only checking for an API key. The has_paid_plan_for_product will check to see if the specific site has a paid plan
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_free_plan_for_product() {
|
||||
$akismet_api_key = apply_filters( 'akismet_get_api_key', defined( 'WPCOM_API_KEY' ) ? constant( 'WPCOM_API_KEY' ) : get_option( 'wordpress_api_key' ) );
|
||||
$fallback = ! empty( $akismet_api_key );
|
||||
|
||||
// Check for existing plans
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return $fallback;
|
||||
if ( ! empty( $akismet_api_key ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $products_with_anti_spam as $product ) {
|
||||
if ( strpos( $purchase->product_slug, $product ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,7 +180,7 @@ class Anti_Spam extends Product {
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'security' );
|
||||
return array( 'security', 'complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Boost product
|
||||
* Backup product
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
@ -11,13 +11,13 @@ use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Backup product
|
||||
*/
|
||||
class Backup extends Hybrid_Product {
|
||||
public const BACKUP_STATUS_TRANSIENT_KEY = 'my-jetpack-backup-status';
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
@ -44,6 +44,13 @@ class Backup extends Hybrid_Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-backup';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'security';
|
||||
|
||||
/**
|
||||
* Backup has a standalone plugin
|
||||
*
|
||||
@ -65,6 +72,32 @@ class Backup extends Hybrid_Product {
|
||||
*/
|
||||
public static $requires_plan = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'backups';
|
||||
|
||||
/**
|
||||
* Backup initialization
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_endpoints(): void {
|
||||
parent::register_endpoints();
|
||||
// Get backup undo event
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/backup/undo-event',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_backup_undo_event',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -165,12 +198,82 @@ class Backup extends Hybrid_Product {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has the correct permissions
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* This will fetch the last rewindable event from the Activity Log and
|
||||
* the last rewind_id prior to that.
|
||||
*
|
||||
* @return array|WP_Error|null
|
||||
*/
|
||||
public static function get_site_backup_undo_event() {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_user(
|
||||
'/sites/' . $blog_id . '/activity/rewindable?force=wpcom',
|
||||
'v2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = json_decode( $response['body'], true );
|
||||
|
||||
if ( ! isset( $body['current'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Preparing the response structure
|
||||
$undo_event = array(
|
||||
'last_rewindable_event' => null,
|
||||
'undo_backup_id' => null,
|
||||
);
|
||||
|
||||
// List of events that will not be considered to be undo.
|
||||
// Basically we should not `undo` a full backup event, but we could
|
||||
// use them to undo any other action like plugin updates.
|
||||
$last_event_exceptions = array(
|
||||
'rewind__backup_only_complete_full',
|
||||
'rewind__backup_only_complete_initial',
|
||||
'rewind__backup_only_complete',
|
||||
'rewind__backup_complete_full',
|
||||
'rewind__backup_complete_initial',
|
||||
'rewind__backup_complete',
|
||||
);
|
||||
|
||||
// Looping through the events to find the last rewindable event and the last backup_id.
|
||||
// The idea is to find the last rewindable event and then the last rewind_id before that.
|
||||
$found_last_event = false;
|
||||
foreach ( $body['current']['orderedItems'] as $event ) {
|
||||
if ( $event['is_rewindable'] ) {
|
||||
if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
|
||||
$undo_event['last_rewindable_event'] = $event;
|
||||
$found_last_event = true;
|
||||
} elseif ( $found_last_event ) {
|
||||
$undo_event['undo_backup_id'] = $event['rewind_id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $undo_event );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hits the wpcom api to check rewind status.
|
||||
*
|
||||
* @todo Maybe add caching.
|
||||
*
|
||||
* @return Object|WP_Error
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
private static function get_state_from_wpcom() {
|
||||
static $status = null;
|
||||
@ -179,9 +282,15 @@ class Backup extends Hybrid_Product {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom',
|
||||
'2',
|
||||
array( 'timeout' => 2 ),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
$status = new WP_Error( 'rewind_state_fetch_failed' );
|
||||
@ -194,16 +303,98 @@ class Backup extends Hybrid_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
* Hits the wpcom api to retrieve the last 10 backup records.
|
||||
*
|
||||
* @return boolean
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$rewind_data = static::get_state_from_wpcom();
|
||||
if ( is_wp_error( $rewind_data ) ) {
|
||||
return false;
|
||||
public static function get_latest_backups() {
|
||||
static $backups = null;
|
||||
|
||||
if ( $backups !== null ) {
|
||||
return $backups;
|
||||
}
|
||||
return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state;
|
||||
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/sites/%d/rewind/backups', $site_id ) . '?force=wpcom',
|
||||
'2',
|
||||
array( 'timeout' => 2 ),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
$backups = new WP_Error( 'rewind_backups_fetch_failed' );
|
||||
return $backups;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$backups = json_decode( $body );
|
||||
return $backups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the module/plugin/product needs the users attention.
|
||||
* Typically due to some sort of error where user troubleshooting is needed.
|
||||
*
|
||||
* @return boolean|array
|
||||
*/
|
||||
public static function does_module_need_attention() {
|
||||
$previous_backup_status = get_transient( self::BACKUP_STATUS_TRANSIENT_KEY );
|
||||
|
||||
// If we have a previous backup status, show it.
|
||||
if ( ! empty( $previous_backup_status ) ) {
|
||||
return $previous_backup_status === 'no_errors' ? false : $previous_backup_status;
|
||||
}
|
||||
|
||||
$backup_failed_status = false;
|
||||
// First check the status of Rewind for failure.
|
||||
$rewind_state = self::get_state_from_wpcom();
|
||||
if ( ! is_wp_error( $rewind_state ) ) {
|
||||
if ( $rewind_state->state !== 'active' && $rewind_state->state !== 'provisioning' && $rewind_state->state !== 'awaiting_credentials' ) {
|
||||
$backup_failed_status = array(
|
||||
'type' => 'error',
|
||||
'data' => array(
|
||||
'source' => 'rewind',
|
||||
'status' => isset( $rewind_state->reason ) && ! empty( $rewind_state->reason ) ? $rewind_state->reason : $rewind_state->state,
|
||||
'last_updated' => $rewind_state->last_updated,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Next check for a failed last backup.
|
||||
$latest_backups = self::get_latest_backups();
|
||||
if ( ! is_wp_error( $latest_backups ) ) {
|
||||
// Get the last/latest backup record.
|
||||
$last_backup = null;
|
||||
foreach ( $latest_backups as $backup ) {
|
||||
if ( $backup->is_backup ) {
|
||||
$last_backup = $backup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $last_backup && isset( $last_backup->status ) ) {
|
||||
if ( $last_backup->status !== 'started' && ! preg_match( '/-will-retry$/', $last_backup->status ) && $last_backup->status !== 'finished' ) {
|
||||
$backup_failed_status = array(
|
||||
'type' => 'error',
|
||||
'data' => array(
|
||||
'source' => 'last_backup',
|
||||
'status' => $last_backup->status,
|
||||
'last_updated' => $last_backup->last_updated,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_array( $backup_failed_status ) && $backup_failed_status['type'] === 'error' ) {
|
||||
set_transient( self::BACKUP_STATUS_TRANSIENT_KEY, $backup_failed_status, 5 * MINUTE_IN_SECONDS );
|
||||
} else {
|
||||
set_transient( self::BACKUP_STATUS_TRANSIENT_KEY, 'no_errors', HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $backup_failed_status;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,7 +404,7 @@ class Backup extends Hybrid_Product {
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'security' );
|
||||
return array( 'security', 'complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -239,4 +430,26 @@ class Backup extends Hybrid_Product {
|
||||
return Redirect::get_url( 'my-jetpack-manage-backup' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_backup_daily',
|
||||
'jetpack_backup_daily_monthly',
|
||||
'jetpack_backup_realtime',
|
||||
'jetpack_backup_realtime_monthly',
|
||||
'jetpack_backup_t1_yearly',
|
||||
'jetpack_backup_t1_monthly',
|
||||
'jetpack_backup_t1_bi_yearly',
|
||||
'jetpack_backup_t2_yearly',
|
||||
'jetpack_backup_t2_monthly',
|
||||
'jetpack_backup_t0_yearly',
|
||||
'jetpack_backup_t0_monthly',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,20 @@ class Boost extends Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-boost';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'performance';
|
||||
|
||||
/**
|
||||
* Defines whether or not to show a product interstitial as tiered pricing or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $is_tiered_pricing = true;
|
||||
|
||||
/**
|
||||
* Boost has a standalone plugin
|
||||
*
|
||||
@ -65,6 +79,13 @@ class Boost extends Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'cloud-critical-css';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -134,7 +155,7 @@ class Boost extends Product {
|
||||
public static function get_features_by_tier() {
|
||||
return array(
|
||||
array(
|
||||
'name' => __( 'Optimize CSS Loading', 'jetpack-my-jetpack' ),
|
||||
'name' => __( 'Auto CSS Optimization', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Move important styling information to the start of the page, which helps pages display your content sooner, so your users don’t have to wait for the entire page to load. Commonly referred to as Critical CSS.',
|
||||
@ -143,8 +164,8 @@ class Boost extends Product {
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( 'Must be done manually', 'jetpack-my-jetpack' ),
|
||||
'included' => false,
|
||||
'description' => __( 'Manual', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'title' => __( 'Manual Critical CSS regeneration', 'jetpack-my-jetpack' ),
|
||||
'content' => __(
|
||||
@ -163,7 +184,7 @@ class Boost extends Product {
|
||||
),
|
||||
self::UPGRADED_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( 'Automatically updated', 'jetpack-my-jetpack' ),
|
||||
'description' => __( 'Included', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'title' => __( 'Automatic Critical CSS regeneration', 'jetpack-my-jetpack' ),
|
||||
'content' => __(
|
||||
@ -176,15 +197,51 @@ class Boost extends Product {
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Defer non-essential JavaScript', 'jetpack-my-jetpack' ),
|
||||
'name' => __( 'Automatic image size analysis', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Run non-essential JavaScript after the page has loaded so that styles and images can load more quickly.',
|
||||
'Scan your site for images that aren’t properly sized for the device they’re being viewed on.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
'link' => array(
|
||||
'id' => 'jetpack-boost-defer-js',
|
||||
'title' => 'web.dev',
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Historical performance scores', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Get access to your historical performance scores and see advanced Core Web Vitals data.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Dedicated email support', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'<p>Paid customers get dedicated email support from our world-class Happiness Engineers to help with any issue.</p>
|
||||
<p>All other questions are handled by our team as quickly as we are able to go through the WordPress support forum.</p>',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Page Cache', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Page caching speeds up load times by storing a copy of each web page on the first visit, allowing subsequent visits to be served instantly. This reduces server load and improves user experience by delivering content faster, without waiting for the page to be generated again.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
@ -193,15 +250,37 @@ class Boost extends Product {
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Lazy image loading', 'jetpack-my-jetpack' ),
|
||||
'name' => __( 'Image CDN Quality Settings', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Improve page loading speed by only loading images when they are required.',
|
||||
'Fine-tune image quality settings to your liking.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
'link' => array(
|
||||
'id' => 'jetpack-boost-lazy-load',
|
||||
'title' => 'web.dev',
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Image CDN Auto-Resize Lazy Images', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Optimizes lazy-loaded images by dynamically serving perfectly sized images for each device.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Image CDN', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Deliver images from Jetpack\'s Content Delivery Network. Automatically resizes your images to an appropriate size, converts them to modern efficient formats like WebP, and serves them from a worldwide network of servers.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
@ -223,10 +302,10 @@ class Boost extends Product {
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Image CDN', 'jetpack-my-jetpack' ),
|
||||
'name' => __( 'Defer non-essential JavaScript', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Deliver images from Jetpack\'s Content Delivery Network. Automatically resizes your images to an appropriate size, converts them to modern efficient formats like WebP, and serves them from a worldwide network of servers.',
|
||||
'Run non-essential JavaScript after the page has loaded so that styles and images can load more quickly.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
@ -236,16 +315,15 @@ class Boost extends Product {
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Dedicated email support', 'jetpack-my-jetpack' ),
|
||||
'name' => __( 'Concatenate JS and CSS', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'<p>Paid customers get dedicated email support from our world-class Happiness Engineers to help with any issue.</p>
|
||||
<p>All other questions are handled by our team as quickly as we are able to go through the WordPress support forum.</p>',
|
||||
'Boost your website performance by merging and compressing JavaScript and CSS files, reducing site loading time and number of requests.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
@ -284,27 +362,6 @@ class Boost extends Product {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Boost is available as standalone bundle and as part of the Complete plan.
|
||||
if ( strpos( $purchase->product_slug, 'jetpack_boost' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
@ -334,4 +391,28 @@ class Boost extends Product {
|
||||
|
||||
return $product_activation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_boost_yearly',
|
||||
'jetpack_boost_monthly',
|
||||
'jetpack_boost_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* Complete plan
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Complete plan
|
||||
*/
|
||||
class Complete extends Module_Product {
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'complete';
|
||||
|
||||
/**
|
||||
* The Jetpack module name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $module_name = 'complete';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Complete Bundle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return 'Jetpack Complete';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'The ultimate tool kit for best-in-class websites.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Get the full Jetpack suite with real-time security tools, improved site performance, and tools to grow your business.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized features list
|
||||
* Most of these are not translated as they are product names
|
||||
*
|
||||
* @return array Complete features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
'VaultPress Backup',
|
||||
'Scan',
|
||||
'Akismet Anti-spam',
|
||||
'VideoPress',
|
||||
'Boost',
|
||||
'Social',
|
||||
'Search',
|
||||
'AI Assistant',
|
||||
_x( 'Stats (100K site views, upgradeable)', 'Complete Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'CRM Entrepreneur', 'Complete Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Newsletter and monetization tools', 'Complete Product Feature', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product pricing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
$product_slug = static::get_wpcom_product_slug();
|
||||
return array_merge(
|
||||
array(
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => $product_slug,
|
||||
),
|
||||
Wpcom_Products::get_product_pricing( $product_slug )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_complete';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Jetpack module is active
|
||||
*
|
||||
* This is a bundle and not a product. We should not use this information for anything
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_module_active() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the product by installing and activating its plugin
|
||||
*
|
||||
* @param WP_Error|bool $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public static function do_product_specific_activation( $current_result ) {
|
||||
$product_activation = parent::do_product_specific_activation( $current_result );
|
||||
|
||||
// A bundle is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module.
|
||||
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
|
||||
return $product_activation;
|
||||
}
|
||||
|
||||
// At this point, Jetpack plugin is installed. Let's activate each individual product.
|
||||
$activation = Social::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Stats::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Anti_Spam::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Backup::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Scan::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Boost::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = CRM::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Search::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = VideoPress::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
return $activation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active
|
||||
*
|
||||
* Security is a bundle and not a module. Activation takes place on WPCOM. So lets consider it active if jetpack is active and has the plan.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active() && static::has_required_plan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_required_plan() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_complete',
|
||||
'jetpack_complete_monthly',
|
||||
'jetpack_complete_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether product is a bundle.
|
||||
*
|
||||
* @return boolean True
|
||||
*/
|
||||
public static function is_bundle_product() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the products it contains.
|
||||
*
|
||||
* @return array Product slugs
|
||||
*/
|
||||
public static function get_supported_products() {
|
||||
return array(
|
||||
'anti-spam',
|
||||
'backup',
|
||||
'boost',
|
||||
'crm',
|
||||
'scan',
|
||||
'search',
|
||||
'social',
|
||||
'stats',
|
||||
'videopress',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return '';
|
||||
}
|
||||
}
|
@ -319,24 +319,30 @@ class Creator extends Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
* Get the product-slugs of the paid bundles/plans that this product/module is included in
|
||||
*
|
||||
* @return boolean
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Creator is available as standalone bundle and as part of the Complete plan.
|
||||
if ( strpos( $purchase->product_slug, 'jetpack_creator' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static function get_paid_bundles_that_include_product() {
|
||||
return array(
|
||||
'jetpack_complete',
|
||||
'jetpack_complete_monthly',
|
||||
'jetpack_complete_bi-yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_creator_yearly',
|
||||
'jetpack_creator_monthly',
|
||||
'jetpack_creator_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,13 @@ class Crm extends Product {
|
||||
*/
|
||||
public static $plugin_slug = 'zero-bs-crm';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'management';
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
@ -172,4 +179,27 @@ class Crm extends Product {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid bundles/plans that this product/module is included in.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_bundles_that_include_product() {
|
||||
return array(
|
||||
'jetpack_complete',
|
||||
'jetpack_complete_monthly',
|
||||
'jetpack_complete_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* Growth plan
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Growth plan
|
||||
*/
|
||||
class Growth extends Module_Product {
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'growth';
|
||||
|
||||
/**
|
||||
* The Jetpack module name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $module_name = 'growth';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Growth Bundle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return 'Jetpack Growth';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Grow and track your audience effortlessly.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized feature list
|
||||
*
|
||||
* @return array Growth features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
_x( 'Jetpack Social', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Earn more from your content', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
_x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product pricing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
$product_slug = static::get_wpcom_product_slug();
|
||||
return array_merge(
|
||||
array(
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => $product_slug,
|
||||
),
|
||||
Wpcom_Products::get_product_pricing( $product_slug )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_growth_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Jetpack module is active
|
||||
*
|
||||
* This is a bundle and not a product. We should not use this information for anything
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_module_active() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the product by installing and activating its plugin
|
||||
*
|
||||
* @param WP_Error|bool $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public static function do_product_specific_activation( $current_result ) {
|
||||
$product_activation = parent::do_product_specific_activation( $current_result );
|
||||
|
||||
// A bundle is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module.
|
||||
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
|
||||
return $product_activation;
|
||||
}
|
||||
|
||||
// At this point, Jetpack plugin is installed. Let's activate each individual product.
|
||||
$activation = Social::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
$activation = Stats::activate();
|
||||
if ( is_wp_error( $activation ) ) {
|
||||
return $activation;
|
||||
}
|
||||
|
||||
return $activation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active
|
||||
*
|
||||
* Growth is a bundle and not a module. Activation takes place on WPCOM. So lets consider it active if jetpack is active and has the plan.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active() && static::has_required_plan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchase) of the site already supports the product
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_required_plan() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if (
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_growth' ) ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_complete' )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_growth_yearly',
|
||||
'jetpack_growth_monthly',
|
||||
'jetpack_growth_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product is a bundle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_bundle_product() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all products it contains.
|
||||
*
|
||||
* @return array Product slugs
|
||||
*/
|
||||
public static function get_supported_products() {
|
||||
return array( 'social', 'stats' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return '';
|
||||
}
|
||||
}
|
@ -28,6 +28,13 @@ class Jetpack_Ai extends Product {
|
||||
*/
|
||||
public static $slug = 'jetpack-ai';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'create';
|
||||
|
||||
/**
|
||||
* Whether this product has a free offering
|
||||
*
|
||||
@ -36,20 +43,11 @@ class Jetpack_Ai extends Product {
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Get the Product info for the API
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @throws \Exception If required attribute is not declared in the child class.
|
||||
* @return array
|
||||
* @var string
|
||||
*/
|
||||
public static function get_info() {
|
||||
// Call parent method to get the default info.
|
||||
$info = parent::get_info();
|
||||
|
||||
// Populate the product with the feature data.
|
||||
$info['ai-assistant-feature'] = self::get_ai_assistant_feature();
|
||||
|
||||
return $info;
|
||||
}
|
||||
public static $feature_identifying_paid_plan = 'ai-assistant';
|
||||
|
||||
/**
|
||||
* Get the plugin slug - ovewrite it and return Jetpack's
|
||||
@ -272,6 +270,7 @@ class Jetpack_Ai extends Product {
|
||||
}
|
||||
|
||||
$features = array(
|
||||
__( 'High request capacity *', 'jetpack-my-jetpack' ),
|
||||
__( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
|
||||
__( 'Easily refine content to your liking', 'jetpack-my-jetpack' ),
|
||||
__( 'Make your content easier to read', 'jetpack-my-jetpack' ),
|
||||
@ -444,23 +443,17 @@ class Jetpack_Ai extends Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the site has a paid plan for this product
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return boolean
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if ( str_contains( $purchase->product_slug, 'jetpack_ai' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_ai_yearly',
|
||||
'jetpack_ai_monthly',
|
||||
'jetpack_ai_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -499,7 +492,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_post_checkout_url() {
|
||||
return 'admin.php?page=my-jetpack#/jetpack-ai';
|
||||
return self::get_manage_url();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -508,7 +501,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_post_activation_url() {
|
||||
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
|
||||
return self::get_manage_url();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -517,7 +510,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
|
||||
return admin_url( 'admin.php?page=my-jetpack#/jetpack-ai' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -610,7 +603,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return void
|
||||
*/
|
||||
public static function extend_plugin_action_links() {
|
||||
add_action( 'admin_enqueue_scripts', array( static::class, 'admin_enqueue_scripts' ) );
|
||||
add_action( 'myjetpack_enqueue_scripts', array( static::class, 'admin_enqueue_scripts' ) );
|
||||
add_filter( 'default_content', array( static::class, 'add_ai_block' ), 10, 2 );
|
||||
}
|
||||
|
||||
@ -648,9 +641,11 @@ class Jetpack_Ai extends Product {
|
||||
* @param WP_Post $post The post object.
|
||||
* @return string
|
||||
*/
|
||||
public static function add_ai_block( $content, WP_Post $post ) {
|
||||
public static function add_ai_block( $content, $post ) {
|
||||
if ( isset( $_GET['use_ai_block'] ) && isset( $_GET['_wpnonce'] )
|
||||
&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ai-assistant-content-nonce' )
|
||||
&& ! empty( $post )
|
||||
&& ! is_wp_error( $post )
|
||||
&& current_user_can( 'edit_post', $post->ID )
|
||||
&& '' === $content
|
||||
) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Jetpack;
|
||||
use WP_Error;
|
||||
|
||||
@ -79,12 +80,42 @@ abstract class Module_Product extends Product {
|
||||
return Jetpack::is_module_active( static::$module_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product status.
|
||||
* We don't use parent::get_status() to avoid complexity.
|
||||
*
|
||||
* @return string Product status.
|
||||
*/
|
||||
private static function get_feature_status() {
|
||||
if ( ! static::is_plugin_installed() ) {
|
||||
return Products::STATUS_PLUGIN_ABSENT;
|
||||
}
|
||||
|
||||
if ( ! static::is_plugin_active() ) {
|
||||
return Products::STATUS_INACTIVE;
|
||||
}
|
||||
|
||||
if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
|
||||
return Products::STATUS_USER_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
if ( ! static::is_module_active() ) {
|
||||
return Products::STATUS_MODULE_DISABLED;
|
||||
}
|
||||
|
||||
return Products::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current status of the product
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_status() {
|
||||
if ( static::$is_feature ) {
|
||||
return static::get_feature_status();
|
||||
}
|
||||
|
||||
$status = parent::get_status();
|
||||
if ( Products::STATUS_INACTIVE === $status && ! static::is_module_active() ) {
|
||||
$status = Products::STATUS_MODULE_DISABLED;
|
||||
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Feature: Newsletter
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Newsletter module.
|
||||
*/
|
||||
class Newsletter extends Module_Product {
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'newsletter';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'growth';
|
||||
|
||||
/**
|
||||
* The slug of the plugin associated with this product.
|
||||
* Newsletter is a feature available as part of the Jetpack plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
|
||||
|
||||
/**
|
||||
* The Plugin file associated with stats
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
|
||||
|
||||
/**
|
||||
* The Jetpack module name associated with this product
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $module_name = 'subscriptions';
|
||||
|
||||
/**
|
||||
* Whether this module is a Jetpack feature
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $is_feature = true;
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $requires_user_connection = true;
|
||||
|
||||
/**
|
||||
* Whether this product has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = false;
|
||||
|
||||
/**
|
||||
* Whether this product has a free offering
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Whether the product requires a plan to run
|
||||
* The plan could be paid or free
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $requires_plan = false;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Newsletter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return 'Newsletter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Draw your readers from one post to another', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product long description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Draw your readers from one post to another, increasing overall traffic on your site', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized feature list
|
||||
*
|
||||
* @return array Newsletter features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
return array(
|
||||
'available' => true,
|
||||
'is_free' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the plugin is installed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_plugin_installed() {
|
||||
return static::is_jetpack_plugin_installed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack#/settings?term=newsletter' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the Jetpack plugin
|
||||
*
|
||||
* @return null|WP_Error Null on success, WP_Error on invalid file.
|
||||
*/
|
||||
public static function activate_plugin(): ?WP_Error {
|
||||
$plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG );
|
||||
|
||||
if ( $plugin_filename ) {
|
||||
return activate_plugin( $plugin_filename );
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Plugins_Installer;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
@ -47,6 +48,13 @@ abstract class Product {
|
||||
*/
|
||||
public static $plugin_slug = null;
|
||||
|
||||
/**
|
||||
* The category of the product in the Jetpack ecosystem. The options are performance, growth, security, management, and create
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = null;
|
||||
|
||||
/**
|
||||
* The Jetpack plugin slug
|
||||
*
|
||||
@ -64,6 +72,27 @@ abstract class Product {
|
||||
'jetpack-dev/jetpack.php',
|
||||
);
|
||||
|
||||
/**
|
||||
* The duration of time after the plan expiration date that we stop showing the plan status as "expired".
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EXPIRATION_CUTOFF_TIME = '+2 months';
|
||||
|
||||
/**
|
||||
* Transient key for storing site features
|
||||
*
|
||||
* @var string;
|
||||
*/
|
||||
const MY_JETPACK_SITE_FEATURES_TRANSIENT_KEY = 'my-jetpack-site-features';
|
||||
|
||||
/**
|
||||
* Whether this module is a Jetpack feature
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $is_feature = false;
|
||||
|
||||
/**
|
||||
* Whether this product requires a site connection
|
||||
*
|
||||
@ -100,6 +129,20 @@ abstract class Product {
|
||||
*/
|
||||
public static $requires_plan = false;
|
||||
|
||||
/**
|
||||
* Defines whether or not to show a product interstitial as tiered pricing or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $is_tiered_pricing = false;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = '';
|
||||
|
||||
/**
|
||||
* Get the plugin slug
|
||||
*
|
||||
@ -118,6 +161,24 @@ abstract class Product {
|
||||
return static::$plugin_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called in the class initializer to register the product's endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_endpoints(): void {
|
||||
// This method should be implemented in the child class.
|
||||
}
|
||||
/**
|
||||
* Get data about the AI Assistant feature
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_ai_assistant_feature() {
|
||||
// This method should be optionally set in the child class.
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the installed plugin filename, considering all possible filenames a plugin might have
|
||||
*
|
||||
@ -140,7 +201,7 @@ abstract class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Product info for the API
|
||||
* Get the Static Product Info
|
||||
*
|
||||
* @throws \Exception If required attribute is not declared in the child class.
|
||||
* @return array
|
||||
@ -151,30 +212,29 @@ abstract class Product {
|
||||
}
|
||||
return array(
|
||||
'slug' => static::$slug,
|
||||
'plugin_slug' => static::$plugin_slug,
|
||||
'plugin_slug' => static::get_plugin_slug(),
|
||||
'name' => static::get_name(),
|
||||
'title' => static::get_title(),
|
||||
'category' => static::$category,
|
||||
/* Maintain legacy compatibility with the old product info structure. See: #42271 */
|
||||
'description' => static::get_description(),
|
||||
'long_description' => static::get_long_description(),
|
||||
'tiers' => static::get_tiers(),
|
||||
'features' => static::get_features(),
|
||||
'features_by_tier' => static::get_features_by_tier(),
|
||||
/* End of legacy compatibility fields. */
|
||||
'disclaimers' => static::get_disclaimers(),
|
||||
'status' => static::get_status(),
|
||||
'pricing_for_ui' => static::get_pricing_for_ui(),
|
||||
'is_bundle' => static::is_bundle_product(),
|
||||
'is_plugin_active' => static::is_plugin_active(),
|
||||
'is_upgradable' => static::is_upgradable(),
|
||||
'is_tiered_pricing' => static::$is_tiered_pricing,
|
||||
'is_upgradable_by_bundle' => static::is_upgradable_by_bundle(),
|
||||
'is_feature' => static::$is_feature,
|
||||
'supported_products' => static::get_supported_products(),
|
||||
'wpcom_product_slug' => static::get_wpcom_product_slug(),
|
||||
'requires_user_connection' => static::$requires_user_connection,
|
||||
'has_any_plan_for_product' => static::has_any_plan_for_product(),
|
||||
'has_free_plan_for_product' => static::has_free_plan_for_product(),
|
||||
'has_paid_plan_for_product' => static::has_paid_plan_for_product(),
|
||||
'feature_identifying_paid_plan' => static::$feature_identifying_paid_plan,
|
||||
'has_free_offering' => static::$has_free_offering,
|
||||
'manage_url' => static::get_manage_url(),
|
||||
'purchase_url' => static::get_purchase_url(),
|
||||
'post_activation_url' => static::get_post_activation_url(),
|
||||
'post_activation_urls_by_feature' => static::get_manage_urls_by_feature(),
|
||||
'standalone_plugin_info' => static::get_standalone_info(),
|
||||
@ -184,18 +244,60 @@ abstract class Product {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Product Info that requires http requests to get
|
||||
*
|
||||
* @throws \Exception If required attribute is not declared in the child class.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_wpcom_info() {
|
||||
if ( static::$slug === null ) {
|
||||
throw new \Exception( 'Product classes must declare the $slug attribute.' );
|
||||
}
|
||||
|
||||
$product_data = array(
|
||||
'status' => static::get_status(),
|
||||
'pricing_for_ui' => static::get_pricing_for_ui(),
|
||||
'is_upgradable' => static::is_upgradable(),
|
||||
'description' => static::get_description(),
|
||||
'tiers' => static::get_tiers(),
|
||||
'features' => static::get_features(),
|
||||
'features_by_tier' => static::get_features_by_tier(),
|
||||
'long_description' => static::get_long_description(),
|
||||
'has_any_plan_for_product' => static::has_any_plan_for_product(),
|
||||
'has_free_plan_for_product' => static::has_free_plan_for_product(),
|
||||
'has_paid_plan_for_product' => static::has_paid_plan_for_product(),
|
||||
'purchase_url' => static::get_purchase_url(),
|
||||
'manage_paid_plan_purchase_url' => static::get_manage_paid_plan_purchase_url(),
|
||||
'renew_paid_plan_purchase_url' => static::get_renew_paid_plan_purchase_url(),
|
||||
'does_module_need_attention' => static::does_module_need_attention(),
|
||||
);
|
||||
|
||||
if ( static::$slug === 'jetpack-ai' ) {
|
||||
$product_data['ai-assistant-feature'] = static::get_ai_assistant_feature();
|
||||
}
|
||||
|
||||
return $product_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the site's active features
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
private static function get_site_features_from_wpcom() {
|
||||
public static function get_site_features_from_wpcom() {
|
||||
static $features = null;
|
||||
|
||||
if ( $features !== null ) {
|
||||
return $features;
|
||||
}
|
||||
|
||||
// Check for a cached value before doing lookup
|
||||
$stored_features = get_transient( self::MY_JETPACK_SITE_FEATURES_TRANSIENT_KEY );
|
||||
if ( $stored_features !== false ) {
|
||||
return $stored_features;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/features', $site_id ), '1.1' );
|
||||
|
||||
@ -206,7 +308,13 @@ abstract class Product {
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$feature_return = json_decode( $body );
|
||||
$features = $feature_return->active;
|
||||
|
||||
$features = array(
|
||||
'active' => $feature_return->active,
|
||||
'available' => $feature_return->available,
|
||||
);
|
||||
// set a short transient to help with multiple lookups on the same page load.
|
||||
set_transient( self::MY_JETPACK_SITE_FEATURES_TRANSIENT_KEY, $features, 15 );
|
||||
|
||||
return $features;
|
||||
}
|
||||
@ -228,7 +336,7 @@ abstract class Product {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( $feature, $features, true );
|
||||
return in_array( $feature, $features['active'], true );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -379,12 +487,42 @@ abstract class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the site has a paid plan for the product
|
||||
* This ignores free products, it only checks if there is a purchase that supports the product
|
||||
* Checks whether the site has a paid plan for the product.
|
||||
*
|
||||
* This function relies on the product's `$feature_identifying_paid_plan` and `get_paid_plan_product_slugs()` function.
|
||||
* If the product does not define a `$feature_identifying_paid_plan`, be sure the product includes functions for both
|
||||
* `get_paid_plan_product_slugs()` and `get_paid_bundles_that_include_product()` which return all the product slugs and
|
||||
* bundle slugs that include the product, respectively.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
// First check site features (if there's a feature that identifies the paid plan)
|
||||
if ( static::$feature_identifying_paid_plan ) {
|
||||
if ( static::does_site_have_feature( static::$feature_identifying_paid_plan ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Otherwise check site purchases
|
||||
$plans_with_product = array_merge(
|
||||
static::get_paid_bundles_that_include_product(),
|
||||
static::get_paid_plan_product_slugs()
|
||||
);
|
||||
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $plans_with_product as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -407,6 +545,164 @@ abstract class Product {
|
||||
return static::has_paid_plan_for_product() || static::has_free_plan_for_product();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid bundles/plans that this product/module is included in.
|
||||
*
|
||||
* This function relies on the product's `$feature_identifying_paid_plan`
|
||||
* If the product does not define a `$feature_identifying_paid_plan`, be sure to include this
|
||||
* function in the product's class and have it return all the paid bundle slugs that include
|
||||
* the product.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_bundles_that_include_product() {
|
||||
if ( static::is_bundle_product() ) {
|
||||
return array();
|
||||
}
|
||||
$features = static::get_site_features_from_wpcom();
|
||||
if ( is_wp_error( $features ) ) {
|
||||
return array();
|
||||
}
|
||||
$idendifying_feature = static::$feature_identifying_paid_plan;
|
||||
if ( empty( $features['available'] ) ) {
|
||||
return array();
|
||||
}
|
||||
$paid_bundles = $features['available']->$idendifying_feature ?? array();
|
||||
$current_bundle = Wpcom_Products::get_site_current_plan( true );
|
||||
|
||||
if ( in_array( static::$feature_identifying_paid_plan, $current_bundle['features']['active'], true ) ) {
|
||||
$paid_bundles[] = $current_bundle['product_slug'];
|
||||
}
|
||||
|
||||
return $paid_bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the paid plan's purchase/subsciption info, or null if no paid plan purchases.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public static function get_paid_plan_purchase_for_product() {
|
||||
$paid_plans = array_merge(
|
||||
static::get_paid_plan_product_slugs(),
|
||||
static::get_paid_bundles_that_include_product()
|
||||
);
|
||||
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $paid_plans as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return $purchase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the paid plan's expiry date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_paid_plan_expiration_date() {
|
||||
$purchase = static::get_paid_plan_purchase_for_product();
|
||||
if ( ! $purchase ) {
|
||||
return 'paid-plan-does-not-exist';
|
||||
}
|
||||
|
||||
return $purchase->expiry_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the paid plan's expiry status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_paid_plan_expiration_status() {
|
||||
$purchase = static::get_paid_plan_purchase_for_product();
|
||||
if ( ! $purchase ) {
|
||||
return 'paid-plan-does-not-exist';
|
||||
}
|
||||
|
||||
return $purchase->expiry_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the paid plan is expired or not.
|
||||
*
|
||||
* @param bool $not_expired_after_cutoff - whether to not return the plan as expired if the plan has been expired for some duration of time.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_paid_plan_expired( $not_expired_after_cutoff = false ) {
|
||||
$expiry_status = static::get_paid_plan_expiration_status();
|
||||
$expiry_date = static::get_paid_plan_expiration_date();
|
||||
$expiry_cutoff = strtotime( $expiry_date . ' ' . self::EXPIRATION_CUTOFF_TIME );
|
||||
|
||||
return $not_expired_after_cutoff
|
||||
? $expiry_status === Products::STATUS_EXPIRED && strtotime( 'now' ) < $expiry_cutoff
|
||||
: $expiry_status === Products::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the paid plan is expiring soon or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_paid_plan_expiring() {
|
||||
$expiry_status = static::get_paid_plan_expiration_status();
|
||||
|
||||
return $expiry_status === Products::STATUS_EXPIRING_SOON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url to manage the paid plan's purchased subscription (for plan renewal, canceling, removal, etc).
|
||||
*
|
||||
* @return string|null The url to the purchase management page.
|
||||
*/
|
||||
public static function get_manage_paid_plan_purchase_url() {
|
||||
$purchase = static::get_paid_plan_purchase_for_product();
|
||||
$site_suffix = ( new Status() )->get_site_suffix();
|
||||
|
||||
if ( $purchase && $site_suffix ) {
|
||||
return 'https://wordpress.com/me/purchases/' . $site_suffix . '/' . $purchase->ID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url to renew the paid plan's purchased subscription.
|
||||
*
|
||||
* @return string|null The url to the checkout renewal page.
|
||||
*/
|
||||
public static function get_renew_paid_plan_purchase_url() {
|
||||
$purchase = static::get_paid_plan_purchase_for_product();
|
||||
$site_suffix = ( new Status() )->get_site_suffix();
|
||||
|
||||
if ( $purchase && $site_suffix ) {
|
||||
return 'https://wordpress.com/checkout/' . $purchase->product_slug . '/renew/' . $purchase->ID . '/' . $site_suffix;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product supports trial or not
|
||||
*
|
||||
@ -426,7 +722,7 @@ abstract class Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_upgradable() {
|
||||
return false;
|
||||
return ! static::has_paid_plan_for_product() && ! static::is_bundle_product();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,6 +802,19 @@ abstract class Product {
|
||||
}
|
||||
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
|
||||
$status = Products::STATUS_USER_CONNECTION_ERROR;
|
||||
} elseif ( static::has_paid_plan_for_product() ) {
|
||||
$needs_attention = static::does_module_need_attention();
|
||||
if ( ! empty( $needs_attention ) && is_array( $needs_attention ) ) {
|
||||
$status = Products::STATUS_NEEDS_ATTENTION__WARNING;
|
||||
if ( isset( $needs_attention['type'] ) && 'error' === $needs_attention['type'] ) {
|
||||
$status = Products::STATUS_NEEDS_ATTENTION__ERROR;
|
||||
}
|
||||
}
|
||||
if ( static::is_paid_plan_expired() ) {
|
||||
$status = Products::STATUS_EXPIRED;
|
||||
} elseif ( static::is_paid_plan_expiring() ) {
|
||||
$status = Products::STATUS_EXPIRING_SOON;
|
||||
}
|
||||
} elseif ( static::is_upgradable() ) {
|
||||
$status = Products::STATUS_CAN_UPGRADE;
|
||||
}
|
||||
@ -772,4 +1081,14 @@ abstract class Product {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the module/plugin/product needs the users attention.
|
||||
* Typically due to some sort of error where user troubleshooting is needed.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function does_module_need_attention() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,18 @@
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\My_Jetpack\Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Jetpack_Options;
|
||||
use Automattic\Jetpack\Protect_Status\Status as Protect_Status;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Waf\Waf_Runner;
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Protect product
|
||||
*/
|
||||
class Protect extends Product {
|
||||
class Protect extends Hybrid_Product {
|
||||
|
||||
const FREE_TIER_SLUG = 'free';
|
||||
const UPGRADED_TIER_SLUG = 'upgraded';
|
||||
@ -50,6 +52,20 @@ class Protect extends Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-protect';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'security';
|
||||
|
||||
/**
|
||||
* Defines whether or not to show a product interstitial as tiered pricing or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $is_tiered_pricing = true;
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
@ -71,6 +87,39 @@ class Protect extends Product {
|
||||
*/
|
||||
public static $has_standalone_plugin = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'scan';
|
||||
|
||||
/**
|
||||
* Setup Protect REST API endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_endpoints(): void {
|
||||
parent::register_endpoints();
|
||||
// Get Jetpack Protect data.
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/protect/data',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_protect_data',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has the correct permissions
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -121,33 +170,6 @@ class Protect extends Product {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hits the wpcom api to check scan status.
|
||||
*
|
||||
* @todo Maybe add caching.
|
||||
*
|
||||
* @return Object|WP_Error
|
||||
*/
|
||||
private static function get_state_from_wpcom() {
|
||||
static $status = null;
|
||||
|
||||
if ( $status !== null ) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'scan_state_fetch_failed' );
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$status = json_decode( $body );
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product's available tiers
|
||||
*
|
||||
@ -265,33 +287,55 @@ class Protect extends Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
* Determines whether the module/plugin/product needs the users attention.
|
||||
* Typically due to some sort of error where user troubleshooting is needed.
|
||||
*
|
||||
* @return boolean
|
||||
* @return boolean|array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$plans_with_scan = array(
|
||||
'jetpack_scan',
|
||||
'jetpack_security',
|
||||
'jetpack_complete',
|
||||
'jetpack_premium',
|
||||
'jetpack_business',
|
||||
);
|
||||
public static function does_module_need_attention() {
|
||||
$protect_threat_status = false;
|
||||
$scan_data = Protect_Status::get_status();
|
||||
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
// Check if there are scan threats.
|
||||
$protect_data = $scan_data;
|
||||
if ( is_wp_error( $protect_data ) ) {
|
||||
return $protect_threat_status; // false
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $plans_with_scan as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$critical_threat_count = false;
|
||||
if ( ! empty( $protect_data->threats ) ) {
|
||||
$critical_threat_count = array_reduce(
|
||||
$protect_data->threats,
|
||||
function ( $accum, $threat ) {
|
||||
return $threat->severity >= 5 ? ++$accum : $accum;
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
$protect_threat_status = array(
|
||||
'type' => $critical_threat_count ? 'error' : 'warning',
|
||||
'data' => array(
|
||||
'threat_count' => count( $protect_data->threats ),
|
||||
'critical_threat_count' => $critical_threat_count,
|
||||
'fixable_threat_ids' => $protect_data->fixable_threat_ids,
|
||||
),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
|
||||
return $protect_threat_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_scan',
|
||||
'jetpack_scan_monthly',
|
||||
'jetpack_scan_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,7 +374,13 @@ class Protect extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack-protect' );
|
||||
// check standalone first
|
||||
if ( static::is_standalone_plugin_active() ) {
|
||||
return admin_url( 'admin.php?page=jetpack-protect' );
|
||||
// otherwise, check for the main Jetpack plugin
|
||||
} elseif ( static::is_jetpack_plugin_active() ) {
|
||||
return Redirect::get_url( 'my-jetpack-manage-scan' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,4 +404,40 @@ class Protect extends Product {
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'security', 'complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return site Jetpack Protect data for the REST API.
|
||||
*
|
||||
* @return WP_Rest_Response|WP_Error
|
||||
*/
|
||||
public static function get_site_protect_data() {
|
||||
$scan_data = Protect_Status::get_status();
|
||||
|
||||
$waf_config = array();
|
||||
$waf_supported = false;
|
||||
$is_waf_enabled = false;
|
||||
|
||||
if ( class_exists( 'Automattic\Jetpack\Waf\Waf_Runner' ) ) {
|
||||
// @phan-suppress-next-line PhanUndeclaredClassMethod
|
||||
$waf_config = Waf_Runner::get_config();
|
||||
// @phan-suppress-next-line PhanUndeclaredClassMethod
|
||||
$is_waf_enabled = Waf_Runner::is_enabled();
|
||||
// @phan-suppress-next-line PhanUndeclaredClassMethod
|
||||
$waf_supported = Waf_Runner::is_supported_environment();
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'scanData' => $scan_data,
|
||||
'wafConfig' => array_merge(
|
||||
$waf_config,
|
||||
array(
|
||||
'waf_supported' => $waf_supported,
|
||||
'waf_enabled' => $is_waf_enabled,
|
||||
),
|
||||
array( 'blocked_logins' => (int) get_site_option( 'jetpack_protect_blocked_attempts', 0 ) )
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Feature: Related Posts
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Related Posts module.
|
||||
*/
|
||||
class Related_Posts extends Module_Product {
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'related-posts';
|
||||
|
||||
/**
|
||||
* The slug of the plugin associated with this product.
|
||||
* Related Posts is a feature available as part of the Jetpack plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
|
||||
|
||||
/**
|
||||
* The Plugin file associated with stats
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
|
||||
|
||||
/**
|
||||
* The Jetpack module name associated with this product
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $module_name = 'related-posts';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'growth';
|
||||
|
||||
/**
|
||||
* Whether this module is a Jetpack feature
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $is_feature = true;
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $requires_user_connection = false;
|
||||
|
||||
/**
|
||||
* Whether this product has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = false;
|
||||
|
||||
/**
|
||||
* Whether this product has a free offering
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Whether the product requires a plan to run
|
||||
* The plan could be paid or free
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $requires_plan = false;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Related Posts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return 'Related Posts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Draw your readers from one post to another', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product long description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Draw your readers from one post to another, increasing overall traffic on your site', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized feature list
|
||||
*
|
||||
* @return array Newsletter features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
return array(
|
||||
'available' => true,
|
||||
'is_free' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the plugin is installed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_plugin_installed() {
|
||||
return static::is_jetpack_plugin_installed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack#/traffic?term=related%20posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the Jetpack plugin
|
||||
*
|
||||
* @return null|WP_Error Null on success, WP_Error on invalid file.
|
||||
*/
|
||||
public static function activate_plugin(): ?WP_Error {
|
||||
$plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG );
|
||||
|
||||
if ( $plugin_filename ) {
|
||||
return activate_plugin( $plugin_filename );
|
||||
}
|
||||
}
|
||||
}
|
@ -7,11 +7,9 @@
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
@ -33,6 +31,20 @@ class Scan extends Module_Product {
|
||||
*/
|
||||
public static $module_name = 'scan';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'security';
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'scan';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -107,47 +119,6 @@ class Scan extends Module_Product {
|
||||
return 'jetpack_scan';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hits the wpcom api to check scan status.
|
||||
*
|
||||
* @todo Maybe add caching.
|
||||
*
|
||||
* @return Object|WP_Error
|
||||
*/
|
||||
private static function get_state_from_wpcom() {
|
||||
static $status = null;
|
||||
|
||||
if ( $status !== null ) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
$status = new WP_Error( 'scan_state_fetch_failed' );
|
||||
return $status;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$status = json_decode( $body );
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_required_plan() {
|
||||
$scan_data = static::get_state_from_wpcom();
|
||||
if ( is_wp_error( $scan_data ) ) {
|
||||
return false;
|
||||
}
|
||||
return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active
|
||||
*
|
||||
@ -156,7 +127,7 @@ class Scan extends Module_Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active() && static::has_required_plan();
|
||||
return static::is_jetpack_plugin_active();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,6 +159,20 @@ class Scan extends Module_Product {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_scan',
|
||||
'jetpack_scan_monthly',
|
||||
'jetpack_scan_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
@ -195,7 +180,7 @@ class Scan extends Module_Product {
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'security' );
|
||||
return array( 'security', 'complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,6 +36,8 @@ class Search_Stats {
|
||||
const CACHE_EXPIRY = 1 * MINUTE_IN_SECONDS;
|
||||
const CACHE_GROUP = 'jetpack_search';
|
||||
const POST_TYPE_BREAKDOWN_CACHE_KEY = 'post_type_break_down';
|
||||
const TOTAL_POSTS_COUNT_CACHE_KEY = 'total-post-count';
|
||||
const POST_COUNT_QUERY_LIMIT = 1e5;
|
||||
|
||||
/**
|
||||
* Get stats from the WordPress.com API for the current blog ID.
|
||||
@ -58,6 +60,25 @@ class Search_Stats {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue querying the post type breakdown from WordPress.com API for the current blog ID.
|
||||
*/
|
||||
public function queue_post_count_query_from_wpcom() {
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
if ( ! is_numeric( $blog_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Client::wpcom_json_api_request_as_blog(
|
||||
'/sites/' . (int) $blog_id . '/jetpack-search/queue-post-count',
|
||||
'2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate record counts via a local database query.
|
||||
*/
|
||||
@ -127,7 +148,7 @@ class Search_Stats {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw post type breakdown from the database.
|
||||
* Get raw post type breakdown from the database or a remote request if posts count is high.
|
||||
*/
|
||||
protected static function get_raw_post_type_breakdown() {
|
||||
global $wpdb;
|
||||
@ -137,6 +158,27 @@ class Search_Stats {
|
||||
return $results;
|
||||
}
|
||||
|
||||
$total_posts_count = wp_cache_get( self::TOTAL_POSTS_COUNT_CACHE_KEY, self::CACHE_GROUP );
|
||||
if ( false === $total_posts_count ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery */
|
||||
$total_posts_counts = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
|
||||
wp_cache_set( self::TOTAL_POSTS_COUNT_CACHE_KEY, $total_posts_counts, self::CACHE_GROUP, self::CACHE_EXPIRY );
|
||||
}
|
||||
|
||||
// Get post type breakdown from a remote request if the post count is high
|
||||
if ( $total_posts_count > self::POST_COUNT_QUERY_LIMIT ) {
|
||||
$search_stats = new Search_Stats();
|
||||
$search_stats->queue_post_count_query_from_wpcom();
|
||||
$wpcom_stats = json_decode( wp_remote_retrieve_body( $search_stats->get_stats_from_wpcom() ), true );
|
||||
if ( ! empty( $wpcom_stats['raw_post_type_breakdown'] ) ) {
|
||||
$results = $wpcom_stats['raw_post_type_breakdown'];
|
||||
wp_cache_set( self::POST_TYPE_BREAKDOWN_CACHE_KEY, $results, self::CACHE_GROUP, self::CACHE_EXPIRY );
|
||||
return $results;
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
$query = "SELECT post_type, post_status, COUNT( * ) AS num_posts
|
||||
FROM {$wpdb->posts}
|
||||
WHERE post_password = ''
|
||||
|
@ -13,7 +13,6 @@ use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\Search\Module_Control as Search_Module_Control;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
@ -41,6 +40,13 @@ class Search extends Hybrid_Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-search';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'performance';
|
||||
|
||||
/**
|
||||
* Search has a standalone plugin
|
||||
*
|
||||
@ -80,6 +86,13 @@ class Search extends Hybrid_Product {
|
||||
*/
|
||||
public static $requires_user_connection = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'search';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -265,39 +278,6 @@ class Search extends Hybrid_Product {
|
||||
return $pricings[ $record_count ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hits the wpcom api to check Search status.
|
||||
*
|
||||
* @todo Maybe add caching.
|
||||
*
|
||||
* @return Object|WP_Error
|
||||
*/
|
||||
private static function get_state_from_wpcom() {
|
||||
static $status = null;
|
||||
|
||||
if ( $status !== null ) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
'/sites/' . $blog_id . '/jetpack-search/plan',
|
||||
'2',
|
||||
array( 'timeout' => 5 ),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'search_state_fetch_failed' );
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$status = json_decode( $body );
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product supports trial or not
|
||||
*
|
||||
@ -312,26 +292,16 @@ class Search extends Hybrid_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site purchases contain a paid search plan
|
||||
* Get the product-slugs of the paid plans for this product (not including bundles)
|
||||
*
|
||||
* @return bool
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Search is available as standalone product and as part of the Complete plan.
|
||||
if (
|
||||
( str_contains( $purchase->product_slug, 'jetpack_search' ) && ! str_contains( $purchase->product_slug, 'jetpack_search_free' ) ) ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_search',
|
||||
'jetpack_search_monthly',
|
||||
'jetpack_search_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -391,4 +361,14 @@ class Search extends Hybrid_Product {
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack-search' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class Security extends Module_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Security';
|
||||
return 'Security Bundle';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +69,7 @@ class Security extends Module_Product {
|
||||
/**
|
||||
* Get the internationalized features list
|
||||
*
|
||||
* @return array Boost features list
|
||||
* @return array Security features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
@ -81,17 +81,18 @@ class Security extends Module_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
* Get the product pricing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
$product_slug = static::get_wpcom_product_slug();
|
||||
return array_merge(
|
||||
array(
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => static::get_wpcom_product_slug(),
|
||||
'wpcom_product_slug' => $product_slug,
|
||||
),
|
||||
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
|
||||
Wpcom_Products::get_product_pricing( $product_slug )
|
||||
);
|
||||
}
|
||||
|
||||
@ -160,6 +161,22 @@ class Security extends Module_Product {
|
||||
return static::is_jetpack_plugin_active() && static::has_required_plan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_security_t1_yearly',
|
||||
'jetpack_security_t1_monthly',
|
||||
'jetpack_security_t1_bi_yearly',
|
||||
'jetpack_security_t2_yearly',
|
||||
'jetpack_security_t2_monthly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
@ -195,7 +212,7 @@ class Security extends Module_Product {
|
||||
/**
|
||||
* Return all the products it contains.
|
||||
*
|
||||
* @return Array Product slugs
|
||||
* @return array Product slugs
|
||||
*/
|
||||
public static function get_supported_products() {
|
||||
return array( 'backup', 'scan', 'anti-spam' );
|
||||
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Feature: Site Accelerator
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the Site Accelerator module.
|
||||
*/
|
||||
class Site_Accelerator extends Module_Product {
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'site-accelerator';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'performance';
|
||||
|
||||
/**
|
||||
* The slug of the plugin associated with this product.
|
||||
* Site Accelerator is a feature available as part of the Jetpack plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
|
||||
|
||||
/**
|
||||
* The Plugin file associated with stats
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
|
||||
|
||||
/**
|
||||
* The Jetpack module name associated with this product
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $module_name = 'site-accelerator';
|
||||
|
||||
/**
|
||||
* Whether this module is a Jetpack feature
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $is_feature = true;
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $requires_user_connection = false;
|
||||
|
||||
/**
|
||||
* Whether this product has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = false;
|
||||
|
||||
/**
|
||||
* Whether this product has a free offering
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Whether the product requires a plan to run
|
||||
* The plan could be paid or free
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $requires_plan = false;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'Site Accelerator';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return 'Site Accelerator';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Draw your readers from one post to another', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product long description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Draw your readers from one post to another, increasing overall traffic on your site', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized feature list
|
||||
*
|
||||
* @return array Site Accelerattor features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
return array(
|
||||
'available' => true,
|
||||
'is_free' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Product is active.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return static::is_jetpack_plugin_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the plugin is installed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_plugin_installed() {
|
||||
return static::is_jetpack_plugin_installed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack#/settings?term=site%20accelerator' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the Jetpack plugin
|
||||
*
|
||||
* @return null|WP_Error Null on success, WP_Error on invalid file.
|
||||
*/
|
||||
public static function activate_plugin(): ?WP_Error {
|
||||
$plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG );
|
||||
|
||||
if ( $plugin_filename ) {
|
||||
return activate_plugin( $plugin_filename );
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Products;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
@ -37,6 +38,13 @@ class Social extends Hybrid_Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-social';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'growth';
|
||||
|
||||
/**
|
||||
* Social has a standalone plugin
|
||||
*
|
||||
@ -62,6 +70,13 @@ class Social extends Hybrid_Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'social-enhanced-publishing';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -144,56 +159,77 @@ class Social extends Hybrid_Product {
|
||||
return 'jetpack_social_v1_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'status' of the Social product
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_status() {
|
||||
$status = parent::get_status();
|
||||
if ( Products::STATUS_NEEDS_PLAN === $status ) {
|
||||
// If the status says that the site needs a plan,
|
||||
// My Jetpack shows "Learn more" CTA,
|
||||
// We want to instead show the "Activate" CTA.
|
||||
$status = Products::STATUS_NEEDS_ACTIVATION;
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product (not including bundles)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_social_v1_yearly',
|
||||
'jetpack_social_v1_monthly',
|
||||
'jetpack_social_v1_bi_yearly',
|
||||
'jetpack_social_basic_yearly',
|
||||
'jetpack_social_monthly',
|
||||
'jetpack_social_basic_monthly',
|
||||
'jetpack_social_basic_bi_yearly',
|
||||
'jetpack_social_advanced_yearly',
|
||||
'jetpack_social_advanced_monthly',
|
||||
'jetpack_social_advanced_bi_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$plans_with_social = array(
|
||||
'jetpack_social',
|
||||
'jetpack_complete',
|
||||
'jetpack_business',
|
||||
'jetpack_premium',
|
||||
'jetpack_personal',
|
||||
);
|
||||
if ( parent::has_paid_plan_for_product() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For atomic sites, do a feature check to see if the republicize feature is available
|
||||
// This feature is available by default on all Jetpack sites
|
||||
if ( ( new Host() )->is_woa_site() ) {
|
||||
return static::does_site_have_feature( 'republicize' );
|
||||
if ( ( new Host() )->is_woa_site() && static::does_site_have_feature( 'republicize' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $plans_with_social as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product.
|
||||
*
|
||||
* If the standalone plugin is active,
|
||||
* it will redirect to the standalone plugin settings page.
|
||||
* Otherwise, it will redirect to the Jetpack settings page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
if ( static::is_standalone_plugin_active() ) {
|
||||
return admin_url( 'admin.php?page=jetpack-social' );
|
||||
}
|
||||
return admin_url( 'admin.php?page=jetpack-social' );
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=jetpack#/settings?term=publicize' );
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'growth', 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,19 @@ class Starter extends Module_Product {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product-slugs of the paid plans for this product.
|
||||
* (Do not include bundle plans, unless it's a bundle plan itself).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_starter_yearly',
|
||||
'jetpack_starter_monthly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether product is a bundle.
|
||||
*
|
||||
|
@ -32,6 +32,13 @@ class Stats extends Module_Product {
|
||||
*/
|
||||
public static $module_name = 'stats';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'growth';
|
||||
|
||||
/**
|
||||
* The Plugin slug associated with stats
|
||||
*
|
||||
@ -67,6 +74,13 @@ class Stats extends Module_Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'stats-paid';
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -194,7 +208,10 @@ class Stats extends Module_Product {
|
||||
if ( ! is_wp_error( $purchases_data ) && is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Jetpack complete includes Stats commercial & cannot be upgraded
|
||||
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
if (
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_growth' )
|
||||
) {
|
||||
return false;
|
||||
} elseif (
|
||||
// Stats commercial purchased with highest tier cannot be upgraded.
|
||||
@ -217,24 +234,17 @@ class Stats extends Module_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site has a paid plan that supports this product
|
||||
* Get the product-slugs of the paid plans for this product (not including bundles)
|
||||
*
|
||||
* @return boolean
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Stats is available as standalone product and as part of the Complete plan.
|
||||
if ( strpos( $purchase->product_slug, 'jetpack_stats' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_stats_yearly',
|
||||
'jetpack_stats_monthly',
|
||||
'jetpack_stats_bi_yearly',
|
||||
'jetpack_stats_pwyw_yearly',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,6 +299,10 @@ class Stats extends Module_Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_purchase_url() {
|
||||
$status = static::get_status();
|
||||
if ( $status === Products::STATUS_NEEDS_FIRST_SITE_CONNECTION ) {
|
||||
return null;
|
||||
}
|
||||
// The returning URL could be customized by changing the `redirect_uri` param with relative path.
|
||||
return sprintf(
|
||||
'%s#!/stats/purchase/%d?from=jetpack-my-jetpack%s&redirect_uri=%s',
|
||||
@ -307,4 +321,14 @@ class Stats extends Module_Product {
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=stats' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'growth', 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,16 @@ namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\VideoPress\Stats as VideoPress_Stats;
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class responsible for handling the VideoPress product
|
||||
*/
|
||||
class Videopress extends Hybrid_Product {
|
||||
private const VIDEOPRESS_STATS_KEY = 'my-jetpack-videopress-stats';
|
||||
private const VIDEOPRESS_PERIOD_KEY = 'my-jetpack-videopress-period';
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
@ -36,6 +41,13 @@ class Videopress extends Hybrid_Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-videopress';
|
||||
|
||||
/**
|
||||
* The category of the product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $category = 'performance';
|
||||
|
||||
/**
|
||||
* The filename (id) of the plugin associated with this product.
|
||||
*
|
||||
@ -68,6 +80,39 @@ class Videopress extends Hybrid_Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* The feature slug that identifies the paid plan
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $feature_identifying_paid_plan = 'videopress';
|
||||
|
||||
/**
|
||||
* Setup VideoPress REST API endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_endpoints(): void {
|
||||
parent::register_endpoints();
|
||||
// Get Jetpack VideoPress data.
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/videopress/data',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_videopress_data',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has the correct permissions
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -174,30 +219,97 @@ class Videopress extends Hybrid_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the site has a paid plan for this product
|
||||
* Get the product-slugs of the paid plans for this product (not including bundles)
|
||||
*
|
||||
* @return boolean
|
||||
* @return array
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$plans_with_videopress = array(
|
||||
public static function get_paid_plan_product_slugs() {
|
||||
return array(
|
||||
'jetpack_videopress',
|
||||
'jetpack_complete',
|
||||
'jetpack_business',
|
||||
'jetpack_premium',
|
||||
'jetpack_videopress_monthly',
|
||||
'jetpack_videopress_bi_yearly',
|
||||
);
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
*
|
||||
* @return boolean|array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats for VideoPress
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private static function get_videopress_stats() {
|
||||
$video_count = array_sum( (array) wp_count_attachments( 'video' ) );
|
||||
|
||||
if ( ! class_exists( 'Automattic\Jetpack\VideoPress\Stats' ) ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $plans_with_videopress as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$featured_stats = get_transient( self::VIDEOPRESS_STATS_KEY );
|
||||
|
||||
if ( $featured_stats ) {
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
$stats_period = get_transient( self::VIDEOPRESS_PERIOD_KEY );
|
||||
$videopress_stats = new VideoPress_Stats();
|
||||
|
||||
// If the stats period exists, retrieve that information without checking the view count.
|
||||
// If it does not, check the view count of monthly stats and determine if we want to show yearly or monthly stats.
|
||||
if ( $stats_period ) {
|
||||
if ( $stats_period === 'day' ) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
|
||||
if (
|
||||
! is_wp_error( $featured_stats ) &&
|
||||
$featured_stats &&
|
||||
( $featured_stats['data']['views']['current'] < 500 || $featured_stats['data']['views']['previous'] < 500 )
|
||||
) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
if ( is_wp_error( $featured_stats ) || ! $featured_stats ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
set_transient( self::VIDEOPRESS_PERIOD_KEY, $featured_stats['period'], WEEK_IN_SECONDS );
|
||||
set_transient( self::VIDEOPRESS_STATS_KEY, $featured_stats, DAY_IN_SECONDS );
|
||||
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VideoPress data for the REST API
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function get_site_videopress_data() {
|
||||
$videopress_stats = self::get_videopress_stats();
|
||||
|
||||
return rest_ensure_response( $videopress_stats );
|
||||
}
|
||||
}
|
||||
|