modified file plugins

This commit is contained in:
2023-10-22 22:21:44 +00:00
committed by Gitium
parent c72a65abc1
commit 96c0ee892f
4817 changed files with 752216 additions and 0 deletions

View File

@ -0,0 +1,870 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.9.1] - 2023-03-28
### Changed
- Move GoldenTokenModal component to licensing package. [#29748]
## [2.9.0] - 2023-03-27
### Added
- Adds new route for Golden Token. Also the experience of redeeming one [#29644]
- Clarified when license activation fails that a user connection is required, added a link to complete the connection, and added functionality to redirect back to license activation when the connection is done. [#29443]
## [2.8.1] - 2023-03-20
### Changed
- Product Interstitial: Use browser history to go back, in case they're coming from somewhere outside of the app. [#29138]
- Updated My Jetpack wpwrap color to --jp-white-off [#29568]
- Updated package dependencies. [#29471]
## [2.8.0] - 2023-03-13
### Added
- Add Zendesk chat module to My Jetpack page [#28712]
## [2.7.13] - 2023-03-08
### Changed
- Updated package dependencies. [#29216]
## [2.7.12] - 2023-02-28
### Changed
- Update billing language [#29126]
## [2.7.11] - 2023-02-20
### Fixed
- My Jetpack: Fix button to add bundle in product interstitial component [#28984]
## [2.7.10] - 2023-02-15
### Changed
- Update to React 18. [#28710]
## [2.7.9] - 2023-02-08
### Changed
- Updated package dependencies. [#28682, #28700]
## [2.7.8] - 2023-02-06
### Changed
- Updated package dependencies.
## [2.7.7] - 2023-01-26
### Changed
- Use `flex-start` instead of `start` for better browser compatibility. [#28530]
## [2.7.6] - 2023-01-25
### Changed
- Minor internal updates.
## [2.7.5] - 2023-01-23
### Fixed
- Components: Fix usage of box-sizing across the elements [#28489]
- Fixes the price display for products with intro offers for the first month. [#28424]
## [2.7.4] - 2023-01-16
### Changed
- Updated package dependencies. [#28303]
## [2.7.3] - 2023-01-11
### Changed
- Updated package dependencies.
## [2.7.2] - 2023-01-02
### Added
- My Jetpack: Move VideoPress from Hybrid [#28097]
### Changed
- My Jetpack: Move Search out of hybrid and deprecate Hybrid_Product class [#28113]
## [2.7.1] - 2022-12-27
### Changed
- Fix layout visual issues [#28055]
- My Jetpack: Move Backup out of hybrid product [#28022]
- My Jetpack: Move Social out of hybrid product [#28074]
## [2.7.0] - 2022-12-19
### Added
- Implement detached licenses redux store. [#27609]
### Changed
- Updated package dependencies. [#27916]
### Fixed
- Add translation context to Security product name. [#27920]
## [2.6.1] - 2022-12-12
### Changed
- Updated package dependencies. [#27888]
## [2.6.0] - 2022-12-05
### Changed
- Improve design of the error notice. [#27340]
- Updated package dependencies. [#27340]
## [2.5.2] - 2022-12-02
### Changed
- My Jetpack: Requires connection only if needed [#27615]
- Updated package dependencies. [#27697]
## [2.5.1] - 2022-11-30
## [2.5.0] - 2022-11-28
### Changed
- My Jetpack: rename Backup and Anti-Spam to new product names [#27377]
- Show My Jetpack even if site is disconnected [#26967]
- Updated package dependencies. [#27576]
## [2.4.1] - 2022-11-22
### Changed
- Updated package dependencies. [#27043]
## [2.4.0] - 2022-11-17
### Added
- Added Jetpack Protect to My Jetpack. [#26069]
### Changed
- Updated package dependencies. [#26736]
## [2.3.5] - 2022-11-10
### Changed
- Updated package dependencies. [#27319]
## [2.3.4] - 2022-11-08
### Changed
- Updated package dependencies. [#27289]
## [2.3.3] - 2022-11-01
### Changed
- Updated package dependencies. [#27196]
## [2.3.2] - 2022-10-25
### Changed
- Updated package dependencies. [#26705]
## [2.3.1] - 2022-10-25
### Added
- Add a flag to indicate if the pricing is introductory with product price [#26982]
- My Jetpack: Support trial [#27033]
### Changed
- Search: now support 38 languages [#27025]
### Fixed
- Don't show old price when it's the same as new one [#27015]
- Search: check if free plan and new pricing is active using wpcom API [#27016]
## [2.3.0] - 2022-10-19
### Added
- Added support for free product and added free product for Search [#26808]
### Changed
- Updated package dependencies. [#26883]
## [2.2.3] - 2022-10-17
### Changed
- Updated package dependencies. [#26851]
## [2.2.2] - 2022-10-13
### Changed
- Updated package dependencies. [#26790]
## [2.2.1] - 2022-10-11
### Changed
- Updated package dependencies. [#25973]
## [2.2.0] - 2022-10-05
### Added
- Integrate the new connection error message React component into My Jetpack. [#26485]
- Search: add post type breakdown endpoint [#26463]
- Trigger restore connection flow. [#26489]
### Changed
- Updated package dependencies. [#26457]
## [2.1.1] - 2022-09-27
### Changed
- Updated package dependencies.
## [2.1.0] - 2022-09-20
### Added
- Added the ConnectionErrorNotice React component. [#26259]
### Changed
- Updated package dependencies.
### Fixed
- Fixed the tests for the Module_Product class by creating and using a sample, test-only module product class as the test subject instead of relying on Videopress or other concrete products. [#26227]
## [2.0.5] - 2022-09-08
### Changed
- Change VideoPress into a Hybrid product in My Jetpack [#25954]
- Updated package dependencies.
## [2.0.4] - 2022-08-31
### Added
- Allow plugins to override a product class. [#25891]
### Changed
- Updated package dependencies. [#25856]
## [2.0.3] - 2022-08-29
### Changed
- Updated package dependencies.
## [2.0.2] - 2022-08-25
### Changed
- Activate plugins in normal mode to trigger plugin_activated hooks [#25727]
- Updated package dependencies. [#25814]
### Fixed
- Licensing: do not enable the Licensing UI if My Jetpack cannot be enabled. [#25667]
- Search: increased search plan/pricing API timeouts to 5s [#25775]
## [2.0.1] - 2022-08-23
### Added
- My Jetpack: Add container for JITMs [#22452]
### Changed
- Updated package dependencies. [#25338, #25339, #25377, #25422, #25628, #25762, #25764]
## [2.0.0] - 2022-08-09
### Added
- Make product cards compatible with disclaimers and add disclaimer for backup card [#25265]
### Changed
- Search: changed to only require site level connection [#24477]
## [1.8.3] - 2022-08-03
### Changed
- Updated package dependencies. [#25300, #25315]
## [1.8.2] - 2022-07-27
### Changed
- My Jetpack: changed link used in ProductCard component to a button when the plugin is absent
## [1.8.1] - 2022-07-26
### Changed
- My Jetpack: changed button used in ProductCard component from WordPress to Jetpack default [#25146]
- Updated package dependencies. [#25147]
## [1.8.0] - 2022-07-19
### Added
- My Jetpack: scroll window to top on route change [#25086]
### Changed
- Updated package dependencies. [#24710]
## [1.7.4] - 2022-07-12
### Changed
- Updated package dependencies. [#25048, #25055]
## [1.7.3] - 2022-07-06
### Added
- Display alert when we cant automatically install the plugin [#24884]
### Changed
- Updated package dependencies. [#24923]
## [1.7.2] - 2022-06-28
### Changed
- Disambiguate redirectUrls vars [#24839]
- Search: use centralized search pricing API [#24795]
- Updated package dependencies. [#24826]
### Fixed
- Search Pricing: fix pricing fetch issue before site is connected [#24826]
## [1.7.1] - 2022-06-21
### Fixed
- My Jetpack: Replace wordpress from PNG to SVG at Connection [#24793]
## [1.7.0] - 2022-06-21
### Changed
- My Jetpack: set products grid to 3x3 for large viewport size
- Renaming master to trunk.
- Renaming `master` references to `trunk`
## [1.6.2] - 2022-06-14
### Changed
- Updated package dependencies. [#24529]
## [1.6.1] - 2022-06-08
### Changed
- Reorder JS imports for `import/order` eslint rule. [#24601]
- Updated package dependencies. [#24510]
## [1.6.0] - 2022-05-30
### Added
- Added Social to My Jetpack.
### Changed
- Make My Jetpack use the new Modules class
- Replace deprecated external-link variation by using isExternalLink prop
- Updated package dependencies
## [1.5.0] - 2022-05-24
### Changed
- Default licensing UI in My Jetpack to be enabled [#24396]
- Updated package dependencies. [#24449]
## [1.4.1] - 2022-05-19
### Changed
- Updated package dependencies. [#24395]
## [1.4.0] - 2022-05-18
### Added
- Added a filter to allow stand-alone plugins to add product specific activation routines [#24334]
- My Jetpack: Add Protect class [#24347]
- My Jetpack: compute Search plugin price based on price tier [#24337]
### Changed
- Add tiered pricing copy and update titles for Jetpack Search [#24357]
- Hide Activate a license link if there is no user connection since user connection is required [#24251]
- My Jetpack Hybrid products: Install Jetpack if stand-alone plugin installation fails [#24335]
- Updated Jetpack Scan feature list. [#23863] [#23795] [#24361] [#24372]
## [1.3.0] - 2022-05-10
### Changed
- Adds from arg to connection screen in My Jetpack so that we can begin tracking connections originating from My Jetpack [#24283]
- Updated package dependencies. [#24189]
- Updated references to old licensing activation UI to licensing activation UI in My Jetpack [#24189]
## [1.2.1] - 2022-05-04
### Added
- Add missing JavaScript dependencies. [#24096]
### Changed
- My Jetpack: remove duplicated site suffix stored on rawUrl. Use siteSuffix instead. [#24094]
- Updated package dependencies. [#24095] [#24198]
### Deprecated
- Moved the options class into Connection. [#24095]
## [1.2.0] - 2022-04-26
### Added
- Added activation screen component to My Jetpack licensing page
### Changed
- Updated package dependencies.
- Update package.json metadata.
## [1.1.0] - 2022-04-19
### Added
- Adds a skeleton for the licensing UI along with a feature flag
- Better error handling for when the WPCOM server is unreachable
- Introduced ConnectedProductOffer component
### Changed
- Turn Search into a Hybrid Product
- Updated package dependencies
- use connected plugin list from the connection store and ask for an update after activating a product
## [1.0.2] - 2022-04-12
### Changed
- Updated package dependencies.
### Fixed
- Fixed bug in checkout URLs for sites installed in subdirs.
## [1.0.1] - 2022-04-06
### Removed
- Removed tracking dependency.
## [1.0.0] - 2022-04-05
### Added
- My Jetpack: improve Product and Interstitial components.
### Changed
- Bump My Jetpack package to major version 1.0.0.
- My Jetpack: align price boxes in the interstitial product page.
- Updated package dependencies.
## [0.6.13] - 2022-03-31
### Added
- Added tracking events to Plans section external links
### Changed
- My Jetpack: tweak plans section typography and descriptive text
## [0.6.12] - 2022-03-29
### Added
- Add missing JS peer dependencies.
### Changed
- Microperformance: Use === null instead of is_null
- My Jetpack: Moved in product icon components
- My Jetpack: Tweak plan sections styles/sizes
- My Jetpack: Update ProductDetailCard to use components and theme variables
- My Jetpack: Use components to render headers elements
- Use different URLs for manage and purchase links in plans section
### Fixed
- My Jetpack: Connect Screen logos quality
## [0.6.11] - 2022-03-23
### Added
- My Jetpack: add error styles to the whole Product card component
- My Jetpack: Make whole Product card clickable
### Changed
- Changed opacity of product icons to 40%
- Changed title
- Improved should_initialize method
- My Jetpack: remove dropdown from CTA button in Product cards
- My Jetpack: Use Text and CSS vars on ProductCard
- Updated Boost product icon for clarity
- Updated package dependencies.
- Updated package dependencies.
- Updated styles for each product card status
- Update organization and copy of the Plans section
### Removed
- My Jetpack: Remove client code that allows to deactivate a product
### Fixed
- Fix Plans section top margin for plan list
- My Jetpack: jetpack_my_jetpack_should_initialize filter now respected when set to true.
## [0.6.10] - 2022-03-15
### Changed
- Make Backup go through the purchase flow after activation
- My Jetpack: Use ThemeProvider instead base-styles
- Updated package dependencies
### Removed
- Removed Beta badge from menu item
## [0.6.9] - 2022-03-09
### Changed
- Updated package dependencies.
## [0.6.8] - 2022-03-08
### Added
- Added connected plugins slugs to My Jetpack tracking events
- Add link to jetpack.com in the footer
- My Jetpack: Add jetpack features link on connection screen
- My Jetpack: tidy Product card component
- My Jetpack: update Spinner in checkout button
### Changed
- Components: update attributes used within the Button component to match recent deprecations and changes.
- My Jetpack: Add Connected Product Card stories
- My Jetpack: Add connection screen footer
- My Jetpack: clean/tidy Product data
- My Jetpack: Remove Layout component
- Only consider Backup product active when the plan is purchased
### Fixed
- Fixed Backup flow when Jetpack plugin is active
- My Jetpack: align CTA buttons of My Jetpack overview
- My Jetpack: Fix button height in the Interstitial pages
## [0.6.7] - 2022-03-02
### Added
- Add My Jetpack action link to all Jetpack plugins
- My Jetpack: Handle cosmetic tweaks
- My Jetpack: Pass requiresUserConnection to ConnectionStatusCard
### Changed
- Refactor and simplify Products class
- Updated package dependencies.
### Fixed
- Disable browser cache for My Jetpack
- My Jetpack: fix products card section story
- My Jetpack: fix stories for the Interstitial pages
## [0.6.6] - 2022-02-28
### Fixed
- Re-doing 0.6.5 to fixup bad release.
## [0.6.5] - 2022-02-28
### Changed
- Tweak product card box model.
### Fixed
- Use namespace in My Jetpack REST Products class to prevent fatal
## [0.6.4] - 2022-02-25
### Added
- Activate Jetpack plugin from Extras product card
- Added list of connected plugins to Disconnect dialog in My Jetpack
- Add Extras interstitial page
- My Jetpack: Handle cosmetic tweaks
- My Jetpack: Remove global notices when in my jetpack page
- My Jetpack: set height of Jetpack logo in the footer
- My Jetpack: tweak height of Jetpack Logo
- My Jetpack: update Product logos
### Changed
- Disable My Jetpack on MS
- My Jetpack: compress extras product image
- Updated package dependencies.
- Update My Jetpack dashboard headline
### Removed
- Remove unnecessary line from My Jetpack Initial state
### Fixed
- Fix beta badge for RTL languages
- Handle plugin activating from Hybrid class
- Memoized RecordEvent from usAnalytics hook
- My Jetpack: Fix post activation url redirect
- My Jetpack: Move product list that requires user connection to selector
- Products::get_products_names should not load all product information
- Update automattic logo href in the footer
## [0.6.3] - 2022-02-22
### Changed
- Updated inline documentation
### Fixed
- Use Jetpack namespace to fix fatal error
## [0.6.2] - 2022-02-22
### Added
- Add Beta badge
- Add Extras class
- Apply coupon discount to Product price when it exists
- Filesystem write access to the initial state
- Improve Product detail layout
- Implement close link on layout nav
- Prevent calling activation hook when activating backup
### Changed
- Bump package versions.
- Improve My Jetpack link styles
- Improve redirect process after installing Product
- Fix interstitial CTA buttons layout
- Move from isPressed to CSS vars
- Redirect connect user to connection interstitial
- Point the link of the Manage button of CRM to its dashboard
- Redirect to post_activation_url after product activation from iterstitial screen
- Remove conditional loading depending on constant
- Send My Jetpack version with fired events
- Update the layout of interstitial page when it has an upgradable bundle
### Fixed
- Check if product is active before checking if requires plan
- Fix check for plugin installation for hybrid products
- Fix click on security and add click on My Jetpack interstitial
- Fix clicks on VideoPress and CRM cards
- Fix Product prices
- Make Scan and Search interstitials install the plugin
- Purchases: ensure we retrieve translated version of active purchases
- Return needs purchase status for products module
## [0.6.1] - 2022-02-16
### Added
- Add Anti-Spam Product detail card
- Add CRM interstitial page
- Added wpcom plan slug to product info
- add manage and post activation urls
- Add Scan product: interstitial, route, data, etc...
- Add Security Product Bundle
- Add VideoPress data
- Add VideoPress interstitial cmp. Story.
- Add `#/add-videopress` route
- Change the discount value for all Products to 50
- checks and activation for Security Bundle
- consume prices dynamically
- Do not show Porduct price when plan has required plan
- Finish Backup, Anti-Spam and Scan interstitial pages
- Fire Tracks Event when user clicks on Product Interstitial Back link
- Install proudcts from interstitial pages
- Make click on Fix connection show connection route
- package version to main class
- Pull product discount from wpcom
- Refactoring -> add icons component -> tweak icons for interstitial pages
- Register `#/add-anti-spam` route. Connect with interstitial page
- Restore Security bundle card in the UI
- Set default values for product data in the client (custom hook)
- set product status as error if active and requiring a missing user connection
- Set properly the checkout link for Products
- Set unlink=1 in the checkout URL when the user is not connected
- Tidy Product stories
- Update Backup product data. Tweak Icon.
- Update mock data for Search product. useMyJetpackNavigate() hook
### Changed
- Adapt Scan actiavtion behavior as it is not a module
- Add global notices for activate/deactivate failures
- Add manage redirect
- Apply correct style for CTA buttons on Interstitial
- Avoid usage of 100vh on layout
- Fix setting height of the Product title in the detail component
- Implement is fulfilled handler for product resolver
- Improve global notice layout
- Reduce size of boost and search interstitial images
- Update structure with Layout component
- Only pass a productUrl if the product is not free on interstitial pages
- Only show the deactivate action when it makes sense
- Pass slug prop to event firing on product card button actions instead of name
- Remove unnecessary payload from request to activate or deactivate a product
- Replace renderActionButton function for ActionButton component
- Updated package dependencies.
- Use useMyJetpackNavigate when it's worth it
### Fixed
- Fixed connection check labels and error message
- Fix upgradability for anti-spam and backup products
- Remove duplicted Icon in Product Card story
- Use key when looping through product detail icons
## [0.6.0] - 2022-02-09
### Added
- Add Boost interstitial cmp.
- Add has_required_plan to product info and implement method in Search
- Add Product princign harcoded data
- Add search product data
- Add title and features to products data
- anti spam product class
- Connect Boost card with the interstitial page via /add-boost root
- Fire Tracks event when clicking CTA button on product Interstitial page
- Fire Tracks event when clicking Manage button on product card
- Fire Tracks event when clickn Add link on My Jetpack product card
- Fire Tracks event when showing the Interstitial page
- Implement Free price for Boost product
- Implement Search product interstitial
- Introduce basic Layout component. Add GoBackLink component
- Introduce ProductDetailCard component
- My Jetpack: Add Connection screen
- Pass slug prop to ProductCard'
- Plan verification for Backup and Scan
- Restore getProduct() resolver
- Set the checkout URL for the Product detail component
- useCallback for functions that are bound to onEvents
### Changed
- My Jetpack: Implement handler of connection notices
- My Jetpack: Update notice style and implements redirect for connection route
- Support multiple possible folder for each plugin
- Updated package dependencies
### Removed
- dependency from search package
### Fixed
- Fix My Jetpack's reducer for SET_PRODUCT_STATUS
- Fix the redirect URL value
- Show discounted price in Product Detail card
- typo
## [0.5.0] - 2022-02-02
### Added
- Added plugin installation functionality
- Adds Tracks events for activating and deactivating products from the product cards
- Fixes stories for ProductCard component
- Handle when site is not connected
- Initial approach to handle global notice
- Module Products
- My Jetpack: Add route handling
- My Jetpack: connect all product cards with data provider
- My Jetpack: connect Backup product class with Product class. Add long description and features fields.
- My Jetpack: handle redirect when no connection #22549
- My Jetpack: reorganize stores by project/name
- Remove getProduct() resolver
- Support to Hybrid products
- Tweak dimms of the Product card status
- Update data handling - Implement request status in Product Card
- User connection requirement to product info
- uses the Plugin Installer package to safely checks plugins statuses
### Changed
- Added filter for disabling the initialization of the My Jetpack package
- Build: remove unneeded files from production build.
- Do not initialize My Jetpack id site is not connected
- My Jetpack: Refactor styles to use layout components and theme provider
- My Jetpack: Update gap between product cards section
- Pick API root and nonce values from a new window var myJetpackRest
- Updated package dependencies.
- Update plugin absent status consistently
### Fixed
- added unit test mock for new global variable myJetpackRest
- Fix tests
- Fix unsafe optional chaining.
- my-jetpack: fix tracking event when activating product
- Resolved minor code standard violation after implementing a stricter rule.
## [0.4.0] - 2022-01-25
### Added
- add API endpoints to manipulate products
- Added css module for My Jetpack Plans Section
- Added useAnalytics hook
- Added Visitor class for status regarding the site visitor.
- Add first data approach
- Add Products and REST_Products basic classes
- Adds very basic product cards section component to my jetpack
- My Jetpack: Add Product Card component
- My Jetpack: check user connectivity before to hit wpcom side
- My Jetpack: Implement data handling for enable/disable products
- Removed endpoint plans superseded by purchases
### Changed
- Add Connections Section wrapping the Connection Status Card to My Jetpack
- Build: do not ship raw files in production bundle.
### Removed
- Remove unused usePlans() custom react hook
### Fixed
- Fixed svg attribute strokeWidth for Boost Card
## [0.3.3] - 2022-01-18
### Added
- Added redux store specific to my-jetpack
- Implement plans list properly in the PlansSection
- My Jetpack: Add scripts for JS tests
- My Jetpack: Include wordpress components as dep
- Reduxify purchases data
### Changed
- General: update required node version to v16.13.2
- Properly style the Plans Section according to proposed design
- Updated package dependencies.
## [0.3.2] - 2022-01-13
### Added
- My Jetpack: add story to `<PlanSection />` component
- My Jetpack: first PlanSection implementation
### Fixed
- Rename method enqueue_scritps to enqueue_scripts
## [0.3.1] - 2022-01-11
### Added
- Add devvelopment testing instructions to doc.
### Changed
- Updated package dependencies.
### Removed
- Remove use of deprecated `~` in sass-loader imports.
## [0.3.0] - 2022-01-04
### Changed
- Drop isRegistered and isUserConnected params from ConnectionStatusCard component
- Switch to pcov for code coverage.
- Updated package dependencies.
- Updated package textdomain from `jetpack` to `jetpack-my-jetpack`.
## [0.2.0] - 2021-12-14
### Added
- Added Connection Status Card to the page.
- Janitorial: add watch command to the plugin.
### Changed
- Adapt to new layout components.
- Build: do not ship scss and jsx files in production build.
### Fixed
- Build minimized JS for the production build.
- Fix JavaScript i18n strings.
## [0.1.3] - 2021-12-07
### Changed
- Updated package dependencies.
## [0.1.2] - 2021-11-30
### Added
- Janitorial: create mirror repo for the package.
### Changed
- Remove now-redundant `output.filename` from Webpack config.
## 0.1.1 - 2021-11-23
### Changed
- Updated package dependencies.
## 0.1.0 - 2021-11-17
### Added
- Created package
[2.9.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.8.1...2.9.0
[2.8.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.8.0...2.8.1
[2.8.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.13...2.8.0
[2.7.13]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.12...2.7.13
[2.7.12]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.11...2.7.12
[2.7.11]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.10...2.7.11
[2.7.10]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.9...2.7.10
[2.7.9]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.8...2.7.9
[2.7.8]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.7...2.7.8
[2.7.7]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.6...2.7.7
[2.7.6]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.5...2.7.6
[2.7.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.4...2.7.5
[2.7.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.3...2.7.4
[2.7.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.2...2.7.3
[2.7.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.1...2.7.2
[2.7.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.0...2.7.1
[2.7.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.6.1...2.7.0
[2.6.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.6.0...2.6.1
[2.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.5.2...2.6.0
[2.5.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.5.1...2.5.2
[2.5.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.5.0...2.5.1
[2.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.4.1...2.5.0
[2.4.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.4.0...2.4.1
[2.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.5...2.4.0
[2.3.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.4...2.3.5
[2.3.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.3...2.3.4
[2.3.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.2...2.3.3
[2.3.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.1...2.3.2
[2.3.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.3.0...2.3.1
[2.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.3...2.3.0
[2.2.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.2...2.2.3
[2.2.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.1...2.2.2
[2.2.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.2.0...2.2.1
[2.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.1.1...2.2.0
[2.1.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.5...2.1.0
[2.0.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.4...2.0.5
[2.0.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.3...2.0.4
[2.0.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.8.3...2.0.0
[1.8.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.8.2...1.8.3
[1.8.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.8.1...1.8.2
[1.8.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.8.0...1.8.1
[1.8.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.7.4...1.8.0
[1.7.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.7.3...1.7.4
[1.7.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.7.2...1.7.3
[1.7.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.7.1...1.7.2
[1.7.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.7.0...1.7.1
[1.7.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.6.2...1.7.0
[1.6.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.6.1...1.6.2
[1.6.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.6.0...1.6.1
[1.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.5.0...1.6.0
[1.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.4.1...1.5.0
[1.4.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.4.0...1.4.1
[1.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.3.0...1.4.0
[1.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.2.1...1.3.0
[1.2.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.2.0...1.2.1
[1.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.1.0...1.2.0
[1.1.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.0.2...1.1.0
[1.0.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.13...1.0.0
[0.6.13]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.12...0.6.13
[0.6.12]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.11...0.6.12
[0.6.11]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.10...0.6.11
[0.6.10]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.9...0.6.10
[0.6.9]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.8...0.6.9
[0.6.8]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.7...0.6.8
[0.6.7]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.6...0.6.7
[0.6.6]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.5...0.6.6
[0.6.5]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.4...0.6.5
[0.6.4]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.3...0.6.4
[0.6.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.2...0.6.3
[0.6.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.1...0.6.2
[0.6.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.6.0...0.6.1
[0.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.5.0...0.6.0
[0.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.4.0...0.5.0
[0.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.3.3...0.4.0
[0.3.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.3.2...0.3.3
[0.3.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.3.1...0.3.2
[0.3.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.3.0...0.3.1
[0.3.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.2.0...0.3.0
[0.2.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.1.3...0.2.0
[0.1.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/0.1.1...0.1.2

View File

@ -0,0 +1,357 @@
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,38 @@
# Security Policy
Full details of the Automattic Security Policy can be found on [automattic.com](https://automattic.com/security/).
## Supported Versions
Generally, only the latest version of Jetpack has continued support. If a critical vulnerability is found in the current version of Jetpack, we may opt to backport any patches to previous versions.
## Reporting a Vulnerability
[Jetpack](https://jetpack.com/) is an open-source plugin for WordPress. Our HackerOne program covers the plugin software, as well as a variety of related projects and infrastructure.
**For responsible disclosure of security issues and to be eligible for our bug bounty program, please submit your report via the [HackerOne](https://hackerone.com/automattic) portal.**
Our most critical targets are:
* Jetpack and the Jetpack composer packages (all within this repo)
* Jetpack.com -- the primary marketing site.
* cloud.jetpack.com -- a management site.
* wordpress.com -- the shared management site for both Jetpack and WordPress.com sites.
For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic).
_Please note that the **WordPress software is a separate entity** from Automattic. Please report vulnerabilities for WordPress through [the WordPress Foundation's HackerOne page](https://hackerone.com/wordpress)._
## Guidelines
We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines:
* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines).
* Pen-testing Production:
* Please **setup a local environment** instead whenever possible. Most of our code is open source (see above).
* If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC.
* **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels.
* To be eligible for a bounty, all of these guidelines must be followed.
* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability.
We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties.

View File

@ -0,0 +1,3 @@
<svg width="14" height="17" viewBox="0 0 14 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.3584 2.68018C9.85059 2.08545 10.1992 1.26514 10.1992 0.444824C10.1992 0.332031 10.189 0.219238 10.1685 0.126953C9.36865 0.157715 8.39453 0.660156 7.82031 1.33691C7.35889 1.85986 6.93848 2.68018 6.93848 3.51074C6.93848 3.63379 6.95898 3.75684 6.96924 3.79785C7.02051 3.80811 7.10254 3.81836 7.18457 3.81836C7.9126 3.81836 8.8252 3.33643 9.3584 2.68018ZM9.92236 3.99268C8.7124 3.99268 7.72803 4.73096 7.10254 4.73096C6.43604 4.73096 5.5542 4.03369 4.49805 4.03369C2.49854 4.03369 0.478516 5.69482 0.478516 8.80176C0.478516 10.75 1.22705 12.7905 2.1499 14.1133C2.94971 15.231 3.64697 16.1538 4.6416 16.1538C5.63623 16.1538 6.06689 15.4873 7.29736 15.4873C8.54834 15.4873 8.83545 16.1333 9.92236 16.1333C11.0093 16.1333 11.7373 15.1387 12.4141 14.1543C13.1831 13.0264 13.501 11.9395 13.5112 11.8779C13.4497 11.8574 11.3682 11.0166 11.3682 8.6377C11.3682 6.57666 13.0088 5.66406 13.1011 5.59229C12.0244 4.03369 10.3838 3.99268 9.92236 3.99268Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 42 28.9" style="enable-background:new 0 0 42 28.9;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;}
</style>
<path class="st0" d="M10.2,8.7c-0.1,0-0.3,0-0.4,0C4.4,8.7,0,13.2,0,18.8s4.4,10.1,9.8,10.1l0.1,0h23.7l0,0c4.6,0,8.4-3.9,8.4-8.7
c0-4.3-3-7.9-7-8.5l0-0.1C35,5.2,29.4,0,22.4,0C16.5,0,11.6,3.7,10.2,8.7L10.2,8.7z M31,15l0-3.4l0-0.1c0-3.9-3.5-7.6-8.7-7.6
c-4.3,0-7.5,2.7-8.4,5.8l-0.8,3L10,12.6c-0.1,0-0.2,0-0.2,0c-3.1,0-5.9,2.6-5.9,6.2c0,3.5,2.7,6.2,5.9,6.2l0.1,0h23.7l0,0
c2.4,0,4.5-2,4.5-4.7c0-2.4-1.7-4.3-3.7-4.7L31,15z"/>
</svg>

After

Width:  |  Height:  |  Size: 856 B

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2;}
.st1{clip-path:url(#SVGID_00000131361309518468547900000009062464345318814348_);}
.st2{fill:#F6F7F7;filter:url(#Adobe_OpacityMaskFilter);}
.st3{mask:url(#mask0_2900_10877_00000159448472586931246160000013733448166778715831_);}
.st4{fill:#F0F2EB;}
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:#646970;}
</style>
<circle class="st0" cx="18" cy="18" r="17"/>
<g>
<defs>
<rect id="SVGID_1_" width="36" height="36"/>
</defs>
<clipPath id="SVGID_00000143603845024951162640000013916798552048052664_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000143603845024951162640000013916798552048052664_);">
<defs>
<filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="0" y="0" width="36" height="36">
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
</filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="36" id="mask0_2900_10877_00000159448472586931246160000013733448166778715831_">
<circle class="st2" cx="18" cy="18" r="18"/>
</mask>
<g class="st3">
<circle class="st4" cx="18" cy="18" r="18"/>
<path class="st5" d="M12.7,23c-1.2-1.3-2-3.1-2-5c0-4,3.3-7.3,7.3-7.3s7.3,3.3,7.3,7.3c0,1.9-0.7,3.7-2,5v-0.5
c0-1.7-1.4-3.1-3.1-3.1h-4.5c-1.7,0-3.1,1.4-3.1,3.1V23z M14.3,24.3c1.1,0.6,2.3,1,3.7,1s2.6-0.4,3.7-1v-1.8
c0-0.8-0.6-1.4-1.4-1.4h-4.5c-0.8,0-1.4,0.6-1.4,1.4V24.3z M9,18c0-5,4-9,9-9s9,4,9,9s-4,9-9,9S9,23,9,18z M20.2,15.8
c0,1.2-1,2.2-2.2,2.2s-2.2-1-2.2-2.2s1-2.2,2.2-2.2S20.2,14.5,20.2,15.8z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg class="social-icons social-icons__google social-icons--enabled" width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M19.6 10.227c0-.709-.064-1.39-.182-2.045H10v3.868h5.382a4.6 4.6 0 0 1-1.996 3.018v2.51h3.232c1.891-1.742 2.982-4.305 2.982-7.35z" fill="#4285F4"></path><path d="M10 20c2.7 0 4.964-.895 6.618-2.423l-3.232-2.509c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.595-4.123H1.064v2.59A9.996 9.996 0 0 0 10 20z" fill="#34A853"></path><path d="M4.405 11.9c-.2-.6-.314-1.24-.314-1.9 0-.66.114-1.3.314-1.9V5.51H1.064A9.996 9.996 0 0 0 0 10c0 1.614.386 3.14 1.064 4.49l3.34-2.59z" fill="#FBBC05"></path><path d="M10 3.977c1.468 0 2.786.505 3.823 1.496l2.868-2.868C14.959.99 12.695 0 10 0 6.09 0 2.71 2.24 1.064 5.51l3.34 2.59C5.192 5.736 7.396 3.977 10 3.977z" fill="#EA4335"></path></g></svg>

After

Width:  |  Height:  |  Size: 873 B

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_00000115494289508776443440000006419494924222942340_);}
.st1{stroke:#FFFFFF;stroke-width:2;}
.st2{fill:#FFFFFF;}
</style>
<g>
<defs>
<rect id="SVGID_1_" x="0" y="0" width="32" height="32"/>
</defs>
<clipPath id="SVGID_00000114031482566589492640000009417605807093364883_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000114031482566589492640000009417605807093364883_);">
<path class="st1" d="M16,33c9.4,0,17-7.6,17-17c0-9.4-7.6-17-17-17C6.6-1-1,6.6-1,16C-1,25.4,6.6,33,16,33z"/>
<path class="st2" d="M16.8,13.3v15.5l8-15.5H16.8z"/>
<path class="st2" d="M15.2,18.7V3.2l-8,15.5H15.2z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg width="110" height="18" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" fill="#000"><path d="M79.42 9.335c0-.484.523-.672 1.085-.672.908 0 1.769.282 1.769.282V7.628c-.59-.174-1.193-.241-1.89-.241-1.528 0-2.6.725-2.6 1.908 0 2.299 3.162 1.708 3.162 2.823 0 .551-.495.7-1.218.7-.51 0-1.327-.2-1.916-.406v1.304c.49.195 1.18.378 1.862.378 1.487 0 2.907-.444 2.907-2.043 0-2.218-3.162-1.64-3.162-2.715l.001-.001Zm8.337 1.345c0-1.491.817-2.003 1.783-2.003.643 0 1.32.19 1.774.364V7.696c-.536-.174-1.01-.31-1.802-.31-2.104 0-3.404 1.291-3.404 3.375 0 1.975.938 3.333 3.31 3.333.75 0 1.333-.148 1.976-.364v-1.317c-.723.282-1.279.39-1.775.39-1.044 0-1.862-.484-1.862-2.124v.001ZM42.273 8.596h-.054V7.548h-1.474v6.384h1.581v-3.804c.496-.94 1.099-1.25 2.105-1.25h.16V7.494s-.227-.027-.428-.027c-.858 0-1.448.336-1.89 1.13Zm19.837 0h-.053V7.548h-1.474v6.384h1.58v-3.804c.497-.94 1.1-1.25 2.105-1.25h.16V7.494s-.227-.027-.428-.027c-.857 0-1.447.336-1.889 1.13Zm45.76-1.21c-.682 0-1.46.377-2.171.848l-.187.12c-.335-.712-1.005-.967-1.742-.967-.683 0-1.461.35-2.171.82v-.66h-1.501v6.385h1.594V9.35c.59-.39 1.219-.632 1.676-.632.523 0 .844.282.844 1.183v4.032h1.581v-4.57c.59-.39 1.219-.645 1.676-.645.523 0 .845.282.845 1.183v4.032h1.581V9.524c0-1.237-.738-2.137-2.024-2.137h-.001Zm-12.262 5.431c-1.032 0-1.58-.671-1.58-2.07 0-1.398.548-2.07 1.58-2.07s1.568.673 1.568 2.07c0 1.398-.536 2.07-1.568 2.07Zm0-5.43c-2.184 0-3.216 1.411-3.216 3.36 0 1.95 1.032 3.347 3.216 3.347s3.216-1.399 3.216-3.347-1.045-3.36-3.216-3.36ZM73.664 9.336c0-.484.523-.672 1.086-.672.907 0 1.768.282 1.768.282V7.63c-.59-.174-1.193-.241-1.889-.241-1.527 0-2.6.725-2.6 1.908 0 2.299 3.163 1.708 3.163 2.823 0 .551-.496.7-1.22.7-.508 0-1.325-.2-1.915-.406v1.304c.491.195 1.178.378 1.862.378 1.487 0 2.908-.444 2.908-2.043 0-2.218-3.163-1.64-3.163-2.715v-.001Zm-6.86.7c.094-.887.576-1.452 1.366-1.452.845 0 1.099.673 1.099 1.452h-2.466Zm1.38-2.648c-2.037 0-3.096 1.586-3.096 3.32 0 2.38 1.245 3.387 3.267 3.387.817 0 1.523-.109 2.347-.363v-1.32c-.664.244-1.263.391-1.905.391-1.178 0-1.967-.323-2.008-1.72h4.074c.027-.256.054-.497.054-.94 0-1.345-.71-2.756-2.734-2.756ZM56.3 9.483h-1.254V6.257H56.3c1.019 0 1.527.564 1.527 1.573s-.469 1.653-1.527 1.653Zm-.013-4.556h-2.928v9.004h1.688V10.8h1.24c1.916 0 3.31-1.035 3.31-2.97 0-1.935-1.394-2.903-3.31-2.903ZM49.82 12.09c-.51.47-.992.725-1.5.725-.871 0-1.435-.578-1.435-1.935 0-1.465.697-2.231 1.903-2.231.375 0 .764.094 1.032.202v3.239Zm0-4.557c-.47-.094-.805-.149-1.193-.149-2.184 0-3.377 1.466-3.377 3.603 0 2.137 1.193 3.105 2.6 3.105.697 0 1.54-.323 2.05-.753v.591h1.5V4.926h-1.58v2.608Zm-13.565 5.282c-1.031 0-1.58-.671-1.58-2.07 0-1.398.549-2.07 1.58-2.07 1.032 0 1.569.673 1.569 2.07 0 1.397-.537 2.07-1.569 2.07Zm0-5.43c-2.184 0-3.216 1.41-3.216 3.36s1.032 3.347 3.216 3.347c2.185 0 3.216-1.399 3.216-3.347s-1.044-3.36-3.215-3.36Zm47.372 6.545h1.587v-1.64h-1.587v1.64ZM31.608 4.926 29.82 11.82 28.03 4.926h-1.805l.42 1.477-1.575 5.65-1.712-7.128H21.62l2.441 9.005h1.892l1.462-4.815 1.37 4.815h1.892l2.563-9.005h-1.634.001ZM8.973 0C4.025 0 0 4.037 0 9s4.025 9 8.973 9 8.973-4.038 8.973-9-4.026-9-8.973-9ZM.906 9c0-1.173.25-2.287.698-3.293l3.849 10.575A8.095 8.095 0 0 1 .906 9Zm8.067 8.092a8.052 8.052 0 0 1-2.28-.329l2.42-7.055 2.48 6.814c.017.04.037.076.059.112a8.02 8.02 0 0 1-2.68.458Zm1.111-11.885a16 16 0 0 0 .924-.077c.435-.052.383-.693-.051-.667 0 0-1.307.103-2.15.103-.793 0-2.126-.103-2.126-.103-.435-.026-.485.64-.051.667 0 0 .411.052.846.077L8.733 8.66l-1.766 5.311L4.03 5.206c.486-.026.924-.077.924-.077.434-.052.383-.693-.052-.667 0 0-1.306.103-2.15.103-.151 0-.33-.004-.519-.01A8.049 8.049 0 0 1 14.42 3.033c-.035-.003-.069-.007-.105-.007-.792 0-1.355.693-1.355 1.436 0 .667.384 1.231.792 1.897.307.54.666 1.232.666 2.232 0 .692-.266 1.496-.614 2.615l-.806 2.697-2.915-8.698.002.002Zm5.967-.088a8.077 8.077 0 0 1 .989 3.882 8.09 8.09 0 0 1-4.012 6.994l2.464-7.146c.461-1.155.614-2.078.614-2.9 0-.297-.02-.573-.055-.831v.001Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h109.895v18H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '9fc14b80f5a467303947');

View File

@ -0,0 +1,46 @@
/*
* Exposes number format capability
*
* @copyright Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io) and Contributors (http://phpjs.org/authors).
* @license See CREDITS.md
* @see https://github.com/kvz/phpjs/blob/ffe1356af23a6f2512c84c954dd4e828e92579fa/functions/strings/number_format.js
*/
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/**
* @remix-run/router v1.2.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router DOM v6.6.2
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router v6.6.2
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/

View File

@ -0,0 +1,76 @@
{
"name": "automattic/jetpack-my-jetpack",
"description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-admin-ui": "^0.2.17",
"automattic/jetpack-assets": "^1.17.34",
"automattic/jetpack-connection": "^1.51.4",
"automattic/jetpack-jitm": "^2.3.4",
"automattic/jetpack-licensing": "^1.8.1",
"automattic/jetpack-plugins-installer": "^0.2.3",
"automattic/jetpack-redirect": "^1.7.24",
"automattic/jetpack-constants": "^1.6.21"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.4",
"automattic/jetpack-changelogger": "^3.3.2",
"automattic/wordbless": "@dev",
"automattic/jetpack-videopress": "^0.13.4-alpha"
},
"autoload": {
"classmap": [
"src/",
"src/products"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-php": [
"@composer phpunit"
],
"test-js": [
"pnpm run test"
],
"test-js-watch": [
"Composer\\Config::disableProcessTimeout",
"pnpm run test --watch"
],
"build-development": [
"pnpm run build"
],
"build-production": [
"NODE_ENV=production pnpm run build"
],
"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,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-my-jetpack",
"textdomain": "jetpack-my-jetpack",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "2.9.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"
}
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true
}
}
}

View File

@ -0,0 +1,314 @@
<?php
/**
* WP Admin page with information and configuration shared among all Jetpack stand-alone plugins
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use Automattic\Jetpack\JITMS\JITM as JITM;
use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status as Status;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
/**
* The main Initializer class that registers the admin menu and eneuque the assets.
*/
class Initializer {
/**
* My Jetpack package version
*
* @var string
*/
const PACKAGE_VERSION = '2.9.1';
/**
* Initialize My Jetpack
*
* @return void
*/
public static function init() {
if ( ! self::should_initialize() || did_action( 'my_jetpack_init' ) ) {
return;
}
// Extend jetpack plugins action links.
Products::extend_plugins_action_links();
// Set up the REST authentication hooks.
Connection_Rest_Authentication::init();
if ( self::is_licensing_ui_enabled() ) {
Licensing::instance()->initialize();
}
// 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' ),
'manage_options',
'my-jetpack',
array( __CLASS__, 'admin_page' ),
999
);
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
// Sets up JITMS.
JITM::configure();
/**
* Fires after the My Jetpack package is initialized
*
* @since 0.1.0
*/
do_action( 'my_jetpack_init' );
}
/**
* Acts as a feature flag, returning a boolean for whether we should show the licensing UI.
*
* @since 1.2.0
*
* @return boolean
*/
public static function is_licensing_ui_enabled() {
// Default changed to true in 1.5.0.
$is_enabled = true;
/*
* Bail if My Jetpack is not enabled,
* and thus the licensing UI shouldn't be enabled either.
*/
if ( ! self::should_initialize() ) {
$is_enabled = false;
}
/**
* Acts as a feature flag, returning a boolean for whether we should show the licensing UI.
*
* @param bool $is_enabled Defaults to true.
*
* @since 1.2.0
* @since 1.5.0 Update default value to true.
*/
return apply_filters(
'jetpack_my_jetpack_should_enable_add_license_screen',
$is_enabled
);
}
/**
* Callback for the load my jetpack page hook.
*
* @return void
*/
public static function admin_init() {
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
// Product statuses are constantly changing, so we never want to cache the page.
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
}
/**
* Returns whether we are in condition to track to use
* Analytics functionality like Tracks, MC, or GA.
*/
public static function can_use_analytics() {
$status = new Status();
$connection = new Connection_Manager();
$tracking = new Tracking( 'jetpack', $connection );
return $tracking->should_enable_tracking( new Terms_Of_Service(), $status );
}
/**
* Enqueue admin page assets.
*
* @return void
*/
public static function enqueue_scripts() {
Assets::register_script(
'my_jetpack_main_app',
'../build/index.js',
__FILE__,
array(
'enqueue' => true,
'in_footer' => true,
'textdomain' => 'jetpack-my-jetpack',
)
);
wp_localize_script(
'my_jetpack_main_app',
'myJetpackInitialState',
array(
'products' => array(
'items' => Products::get_products(),
),
'purchases' => array(
'items' => array(),
),
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'myJetpackVersion' => self::PACKAGE_VERSION,
'fileSystemWriteAccess' => self::has_file_system_write_access(),
'loadAddLicenseScreen' => self::is_licensing_ui_enabled(),
'adminUrl' => esc_url( admin_url() ),
)
);
wp_localize_script(
'my_jetpack_main_app',
'myJetpackRest',
array(
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
)
);
// Connection Initial State.
wp_add_inline_script( 'my_jetpack_main_app', Connection_Initial_State::render(), 'before' );
// Required for Analytics.
if ( self::can_use_analytics() ) {
Tracking::register_tracks_functions_scripts( true );
}
}
/**
* Echoes the admin page content.
*
* @return void
*/
public static function admin_page() {
echo '<div id="my-jetpack-container"></div>';
}
/**
* Register the REST API routes.
*
* @return void
*/
public static function register_rest_endpoints() {
new REST_Products();
new REST_Purchases();
register_rest_route(
'my-jetpack/v1',
'site',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_site',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
}
/**
* Check user capability to access the endpoint.
*
* @access public
* @static
*
* @return true|WP_Error
*/
public static function permissions_callback() {
return current_user_can( 'manage_options' );
}
/**
* Return true if we should initialize the My Jetpack admin page.
*/
public static function should_initialize() {
$should = true;
if ( is_multisite() ) {
$should = false;
}
/**
* Allows filtering whether My Jetpack should be initialized.
*
* @since 0.5.0-alpha
*
* @param bool $shoud_initialize Should we initialize My Jetpack?
*/
return apply_filters( 'jetpack_my_jetpack_should_initialize', $should );
}
/**
* Site full-data endpoint.
*
* @return object Site data.
*/
public static function get_site() {
$site_id = \Jetpack_Options::get_option( 'id' );
$wpcom_endpoint = sprintf( '/sites/%d?force=wpcom', $site_id );
$wpcom_api_version = '1.1';
$response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $wpcom_api_version );
$response_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ) );
}
return rest_ensure_response( $body, 200 );
}
/**
* Returns true if the site has file write access to the plugins folder, false otherwise.
*
* @return bool
**/
public static function has_file_system_write_access() {
$cache = get_transient( 'my_jetpack_write_access' );
if ( false !== $cache ) {
return $cache;
}
if ( ! function_exists( 'get_filesystem_method' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
require_once ABSPATH . 'wp-admin/includes/template.php';
$write_access = 'no';
$filesystem_method = get_filesystem_method( array(), WP_PLUGIN_DIR );
if ( 'direct' === $filesystem_method ) {
$write_access = 'yes';
}
if ( ! $write_access ) {
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
ob_end_clean();
if ( $filesystem_credentials_are_stored ) {
$write_access = 'yes';
}
}
set_transient( 'my_jetpack_write_access', $write_access, 30 * MINUTE_IN_SECONDS );
return $write_access;
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* Class for manipulating products
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
/**
* A class for everything related to product handling in My Jetpack
*/
class Products {
/**
* Get the list of Products classes
*
* Here's where all the existing Products are registered
*
* @throws \Exception If the result of a filter has invalid classes.
* @return array List of class names
*/
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,
'extras' => Products\Extras::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,
);
/**
* This filter allows plugin to override the Product class of a given product. The new class must be a child class of the default one declared in My Jetpack
*
* For example, a stand-alone plugin could overwrite its product class to control specific behavior of the product in the My Jetpack page after it is active without having to commit changes to the My Jetpack package:
*
* add_filter( 'my_jetpack_products_classes', function( $classes ) {
* $classes['my_plugin'] = 'My_Plugin'; // a class that extends the original one declared in the My Jetpack package.
* return $classes
* } );
*
* @param array $classes An array where the keys are the product slugs and the values are the class names.
*/
$final_classes = apply_filters( 'my_jetpack_products_classes', $classes );
// Check that the classes are still child of the same original classes.
foreach ( (array) $final_classes as $slug => $final_class ) {
if ( $final_class === $classes[ $slug ] ) {
continue;
}
if ( ! class_exists( $final_class ) || ! is_subclass_of( $final_class, $classes[ $slug ] ) ) {
throw new \Exception( 'You can only overwrite a Product class with a child of the original class.' );
}
}
return $final_classes;
}
/**
* Product data
*
* @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();
}
return $products;
}
/**
* Get one product data by its slug
*
* @param string $product_slug The product slug.
*
* @return ?array
*/
public static function get_product( $product_slug ) {
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ]::get_info();
}
}
/**
* Get one product Class name
*
* @param string $product_slug The product slug.
*
* @return ?string
*/
public static function get_product_class( $product_slug ) {
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ];
}
}
/**
* Return product slugs list.
*
* @return array Product slugs array.
*/
public static function get_products_slugs() {
return array_keys( self::get_products_classes() );
}
/**
* Gets the json schema for the product data
*
* @return array
*/
public static function get_product_data_schema() {
return array(
'title' => 'The requested product data',
'type' => 'object',
'properties' => array(
'product' => array(
'description' => __( 'Product slug', 'jetpack-my-jetpack' ),
'type' => 'string',
'enum' => __CLASS__ . '::get_product_slugs',
'required' => false,
'validate_callback' => __CLASS__ . '::check_product_argument',
),
'action' => array(
'description' => __( 'Production action to execute', 'jetpack-my-jetpack' ),
'type' => 'string',
'enum' => array( 'activate', 'deactivate' ),
'required' => false,
'validate_callback' => __CLASS__ . '::check_product_argument',
),
'slug' => array(
'title' => 'The product slug',
'type' => 'string',
),
'name' => array(
'title' => 'The product name',
'type' => 'string',
),
'description' => array(
'title' => 'The product description',
'type' => 'string',
),
'status' => array(
'title' => 'The product status',
'type' => 'string',
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'error' ),
),
'class' => array(
'title' => 'The product class handler',
'type' => 'string',
),
),
);
}
/**
* Extend actions links for plugins
* tied to the Products.
*/
public static function extend_plugins_action_links() {
$products = array(
'backup',
'boost',
'crm',
'videopress', // we use videopress here to add the plugin action to the Jetpack plugin itself
);
foreach ( $products as $product ) {
$class_name = self::get_product_class( $product );
$class_name::extend_plugin_action_links();
}
}
}

View File

@ -0,0 +1,208 @@
<?php
/**
* Sets up the Products REST API endpoints.
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use WP_Error;
/**
* Registers the REST routes for Products.
*/
class REST_Products {
/**
* Constructor.
*/
public function __construct() {
register_rest_route(
'my-jetpack/v1',
'site/products',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_products',
'permission_callback' => __CLASS__ . '::permissions_callback',
),
'schema' => array( $this, 'get_products_schema' ),
)
);
$product_arg = array(
'description' => __( 'Product slug', 'jetpack-my-jetpack' ),
'type' => 'string',
'enum' => Products::get_products_slugs(),
'required' => true,
'validate_callback' => __CLASS__ . '::check_product_argument',
);
register_rest_route(
'my-jetpack/v1',
'site/products/(?P<product>[a-z\-]+)',
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',
'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,
),
),
)
);
}
/**
* Get the schema for the products endpoint
*
* @return array
*/
public function get_products_schema() {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'products',
'type' => 'object',
'properties' => Products::get_product_data_schema(),
);
}
/**
* Check user capability to access the endpoint.
*
* @access public
* @static
*
* @return true|WP_Error
*/
public static function permissions_callback() {
return current_user_can( 'manage_options' );
}
/**
* Check Product arguments.
*
* @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_product_argument( $value ) {
if ( ! is_string( $value ) ) {
return new WP_Error(
'rest_invalid_param',
esc_html__( 'The product argument must be a string.', 'jetpack-my-jetpack' ),
array( 'status' => 400 )
);
}
return true;
}
/**
* Site products endpoint.
*
* @return array of site products list.
*/
public static function get_products() {
$response = Products::get_products();
return rest_ensure_response( $response, 200 );
}
/**
* Site single product endpoint.
*
* @param \WP_REST_Request $request The request object.
* @return array of site products list.
*/
public static function get_product( $request ) {
$product_slug = $request->get_param( 'product' );
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
/**
* Check permission to edit product
*
* @return bool
*/
public static function edit_permissions_callback() {
if ( ! current_user_can( 'activate_plugins' ) ) {
return false;
}
if ( is_multisite() && ! current_user_can( 'manage_network' ) ) {
return false;
}
return true;
}
/**
* Callback for activating a product
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response
*/
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 )
);
}
$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;
}
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
/**
* Callback for deactivating a product
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response
*/
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 )
);
}
$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 );
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Sets up the Purchases REST API endpoints.
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
/**
* Registers the REST routes for Purchases.
*/
class REST_Purchases {
/**
* Constructor.
*/
public function __construct() {
register_rest_route(
'my-jetpack/v1',
'/site/purchases',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_site_current_purchases',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
}
/**
* Check user capability to access the endpoint.
*
* @access public
* @static
*
* @return true|WP_Error
*/
public static function permissions_callback() {
$connection = new Connection_Manager();
$is_site_connected = $connection->is_connected();
if ( ! $is_site_connected ) {
return new \WP_Error(
'not_connected',
__( 'Your site is not connected to Jetpack.', 'jetpack-my-jetpack' ),
array(
'status' => 400,
)
);
}
return current_user_can( 'manage_options' );
}
/**
* Site purchases endpoint.
*
* @return array of site purchases.
*/
public static function get_site_current_purchases() {
$site_id = \Jetpack_Options::get_option( 'id' );
$wpcom_endpoint = sprintf( '/sites/%1$d/purchases?locale=%2$s', $site_id, get_user_locale() );
$wpcom_api_version = '1.1';
$response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $wpcom_api_version );
$response_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) || 200 !== $response_code ) {
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 );
}
}

View File

@ -0,0 +1,229 @@
<?php
/**
* Fetches and store the list of Jetpack products available in WPCOM
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Status\Visitor;
use WP_Error;
/**
* Stores the list of products available for purchase in WPCOM
*/
class Wpcom_Products {
/**
* The meta name used to store the cache date
*
* @var string
*/
const CACHE_DATE_META_NAME = 'my-jetpack-cache-date';
/**
* The meta name used to store the cache
*
* @var string
*/
const CACHE_META_NAME = 'my-jetpack-cache';
/**
* Fetches the list of products from WPCOM
*
* @return Object|WP_Error
*/
private static function get_products_from_wpcom() {
$blog_id = \Jetpack_Options::get_option( 'id' );
$ip = ( new Visitor() )->get_ip( true );
$headers = array(
'X-Forwarded-For' => $ip,
);
// If has a blog id, use connected endpoint.
if ( $blog_id ) {
$endpoint = sprintf( '/sites/%d/products/?_locale=%s&type=jetpack', $blog_id, get_user_locale() );
$wpcom_request = Client::wpcom_json_api_request_as_blog(
$endpoint,
'1.1',
array(
'method' => 'GET',
'headers' => $headers,
)
);
} else {
$endpoint = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
$wpcom_request = wp_remote_get(
esc_url_raw( $endpoint ),
array(
'headers' => $headers,
)
);
}
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
if ( 200 === $response_code ) {
return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
} else {
return new WP_Error(
'failed_to_fetch_wpcom_products',
esc_html__( 'Unable to fetch the products list from WordPress.com', 'jetpack-my-jetpack' ),
array( 'status' => $response_code )
);
}
}
/**
* Update the cache with new information retrieved from WPCOM
*
* We store one cache for each user, as the information is internationalized based on user preferences
* Also, the currency is based on the user IP address
*
* @param Object $products_list The products list as received from WPCOM.
* @return bool
*/
private static function update_cache( $products_list ) {
update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
return update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $products_list );
}
/**
* Checks if the cache is old, meaning we need to fetch new data from WPCOM
*/
private static function is_cache_old() {
if ( empty( self::get_products_from_cache() ) ) {
return true;
}
$cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
return time() - (int) $cache_date > ( 7 * DAY_IN_SECONDS );
}
/**
* Gets the product list from the user cache
*/
private static function get_products_from_cache() {
return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true );
}
/**
* Gets the product list
*
* Attempts to retrieve the products list from the user cache if cache is not too old.
* If cache is old, it will attempt to fetch information from WPCOM. If it fails, we return what we have in cache, if anything, otherwise we return an error.
*
* @param bool $skip_cache If true it will ignore the cache and attempt to fetch fresh information from WPCOM.
*
* @return Object|WP_Error
*/
public static function get_products( $skip_cache = false ) {
// This is only available for logged in users.
if ( ! get_current_user_id() ) {
return null;
}
if ( ! self::is_cache_old() && ! $skip_cache ) {
return self::get_products_from_cache();
}
$products = self::get_products_from_wpcom();
if ( is_wp_error( $products ) ) {
// Let's see if we have it cached.
$cached = self::get_products_from_cache();
if ( ! empty( $cached ) ) {
return $cached;
} else {
return $products;
}
}
self::update_cache( $products );
return $products;
}
/**
* Get one product
*
* @param string $product_slug The product slug.
*
* @return ?Object The product details if found
*/
public static function get_product( $product_slug ) {
$products = self::get_products();
if ( ! empty( $products->$product_slug ) ) {
return $products->$product_slug;
}
}
/**
* Get only the product currency code and price in an array
*
* @param string $product_slug The product slug.
*
* @return array An array with currency_code and full_price. Empty array if product not found.
*/
public static function get_product_pricing( $product_slug ) {
$product = self::get_product( $product_slug );
if ( empty( $product ) ) {
return array();
}
$cost = $product->cost;
$discount_price = $cost;
$is_introductory_offer = false;
$introductory_offer = null;
// Get/compute the discounted price.
if ( isset( $product->introductory_offer->cost_per_interval ) ) {
$discount_price = $product->introductory_offer->cost_per_interval;
$is_introductory_offer = true;
$introductory_offer = $product->introductory_offer;
}
$pricing = array(
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'is_introductory_offer' => $is_introductory_offer,
'introductory_offer' => $introductory_offer,
);
return self::populate_with_discount( $product, $pricing, $discount_price );
}
/**
* Populate the pricing array with the discount information.
*
* @param {object} $product - The product object.
* @param {object} $pricing - The pricing array.
* @param {float} $price - The price to be discounted.
* @return {object} The pricing array with the discount information.
*/
public static function populate_with_discount( $product, $pricing, $price ) {
// Check whether the product has a coupon.
if ( ! isset( $product->sale_coupon ) ) {
return $pricing;
}
// Check whether it is still valid.
$coupon = $product->sale_coupon;
$coupon_start_date = strtotime( $coupon->start_date );
$coupon_expires = strtotime( $coupon->expires );
if ( $coupon_start_date > time() || $coupon_expires < time() ) {
return $pricing;
}
$coupon_discount = intval( $coupon->discount );
// Populate response with coupon discount.
$pricing['coupon_discount'] = $coupon_discount;
// Apply coupon discount to the price.
$pricing['discount_price'] = $price * ( 100 - $coupon_discount ) / 100;
return $pricing;
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* Anti_Spam product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Anti_Spam product
*/
class Anti_Spam extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'anti-spam';
/**
* The filename (id) of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
* @var string
*/
public static $plugin_filename = 'akismet/akismet.php';
/**
* The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
* @var string
*/
public static $plugin_slug = 'akismet';
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Akismet Anti-spam', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Akismet Anti-spam', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Stop comment and form spam', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Save time and get better responses by automatically blocking spam from your comments and forms.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
_x( 'Comment and form spam protection', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Powered by Akismet', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Block spam without CAPTCHAs', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Advanced stats', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_anti_spam';
}
/**
* Return product bundles list
* that supports the product.
*
* @return boolean|array Products bundle list.
*/
public static function is_upgradable_by_bundle() {
return array( 'security' );
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=akismet-key-config' );
}
}

View File

@ -0,0 +1,214 @@
<?php
/**
* Boost product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\My_Jetpack\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 Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'backup';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-backup/jetpack-backup.php',
'backup/jetpack-backup.php',
'jetpack-backup-dev/jetpack-backup.php',
);
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-backup';
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'VaultPress Backup', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack VaultPress Backup', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Save every change', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Backup features list
*/
public static function get_features() {
return array(
_x( 'Real-time cloud backups', 'Backup Product Feature', 'jetpack-my-jetpack' ),
_x( '10GB of backup storage', 'Backup Product Feature', 'jetpack-my-jetpack' ),
_x( '30-day archive & activity log*', 'Backup Product Feature', 'jetpack-my-jetpack' ),
_x( 'One-click restores', 'Backup Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get disclaimers corresponding to a feature
*
* @return array Backup disclaimers list
*/
public static function get_disclaimers() {
return array(
array(
'text' => _x( '* Subject to your usage and storage limit.', 'Backup Product Disclaimer', 'jetpack-my-jetpack' ),
'link_text' => _x( 'Learn more', 'Backup Product Disclaimer', 'jetpack-my-jetpack' ),
'url' => Redirect::get_url( 'jetpack-faq-backup-disclaimer' ),
),
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_backup_t1_yearly';
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Hits the wpcom api to check rewind 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/rewind', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'rewind_state_fetch_failed' );
}
$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() {
$rewind_data = static::get_state_from_wpcom();
if ( is_wp_error( $rewind_data ) ) {
return false;
}
return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state;
}
/**
* Return product bundles list
* that supports the product.
*
* @return boolean|array Products bundle list.
*/
public static function is_upgradable_by_bundle() {
return array( 'security' );
}
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return ''; // stay in My Jetpack page or continue the purchase flow if needed.
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
if ( static::is_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-backup' );
}
}
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_active() {
return parent::is_active() && static::has_required_plan();
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* Boost product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
/**
* Class responsible for handling the Boost product
*/
class Boost extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'boost';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-boost/jetpack-boost.php',
'boost/jetpack-boost.php',
'jetpack-boost-dev/jetpack-boost.php',
);
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-boost';
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Boost', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Boost', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Instant speed and SEO', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Jetpack Boost gives your site the same performance advantages as the worlds leading websites, no developer required.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
__( 'Check your site performance', 'jetpack-my-jetpack' ),
__( 'Enable improvements in one click', 'jetpack-my-jetpack' ),
__( 'Standalone free plugin for those focused on speed', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array(
'available' => true,
'is_free' => true,
);
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-boost' );
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* Boost product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
/**
* Class responsible for handling the CRM product
*/
class Crm extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'crm';
/**
* The filename (id) of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
* @var string
*/
public static $plugin_filename = 'zero-bs-crm/ZeroBSCRM.php';
/**
* The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
* @var string
*/
public static $plugin_slug = 'zero-bs-crm';
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'CRM', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack CRM', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Connect with your people', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'All of your contacts in one place. Build better relationships with your customers and clients.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array CRM features list
*/
public static function get_features() {
return array(
__( 'Manage unlimited contacts', 'jetpack-my-jetpack' ),
__( 'Manage billing and create invoices', 'jetpack-my-jetpack' ),
__( 'Fully integrated with WordPress & WooCommerce', 'jetpack-my-jetpack' ),
__( 'Infinitely customizable with integrations and extensions', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array(
'available' => true,
'is_free' => true,
);
}
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return admin_url( 'admin.php?page=zerobscrm-plugin' ); // Welcome page.
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=zerobscrm-dash' );
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* Extras product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
/**
* Class responsible for handling the Extras product.
* Extras, so far, could be considered as Jetpack plugin bridge.
*/
class Extras extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'extras';
/**
* The slug of the plugin associated with this product.
* Extras, is in short, Jetpack plugin bridge so far.
*
* @var string
*/
public static $plugin_slug = 'jetpack';
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Extras', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Extras', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Basic tools for a successful site', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( "Secure and speed up your site for free with Jetpack's powerful WordPress tools.", 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
__( 'Measure your impact with beautiful stats', 'jetpack-my-jetpack' ),
__( 'Speed up your site with optimized images', 'jetpack-my-jetpack' ),
__( 'Protect your site against bot attacks', 'jetpack-my-jetpack' ),
__( 'Get notifications if your site goes offline', 'jetpack-my-jetpack' ),
__( 'Enhance your site with dozens of other features', 'jetpack-my-jetpack' ),
);
}
/**
* 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.
* If Jetpack plugin is active, then Extras will be inactive.
*
* @return boolean
*/
public static function is_active() {
return static::is_jetpack_plugin_active();
}
/**
* Checks whether the plugin is installed
* If Jetpack plugin is installed, then Extras will be inactive.
*
* @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' );
}
/**
* Activates the Jetpack plugin
*
* @return null|WP_Error Null on success, WP_Error on invalid file.
*/
public static function activate_plugin() {
return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) );
}
}

View File

@ -0,0 +1,132 @@
<?php
/**
* Base product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\Plugins_Installer;
use WP_Error;
/**
*
* DEPRECATED: This class is deprecated and will be removed in a future version.
*
* All product classes have been moved out of the hybrid class concept
*
* @deprecated 2.7.2
*
* Class responsible for handling the hybrid products
*
* Hybrid products are those that may work both as a stand-alone plugin or with the Jetpack plugin.
*
* In case Jetpack plugin is active, it will not attempt to install its stand-alone plugin.
*
* But if Jetpack plugin is not active, then it will prompt to install and activate its stand-alone plugin.
*/
abstract class Hybrid_Product extends Product {
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_plugin_active() {
return parent::is_plugin_active() || parent::is_jetpack_plugin_active();
}
/**
* Checks whether the plugin is installed
*
* @return boolean
*/
public static function is_plugin_installed() {
return parent::is_plugin_installed() || static::is_jetpack_plugin_installed();
}
/**
* Checks whether the Jetpack module is active only if a module_name is defined
*
* @return bool
*/
public static function is_module_active() {
if ( ! empty( static::$module_name ) ) {
return ( new Modules() )->is_active( static::$module_name );
}
return true;
}
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_active() {
return parent::is_active() && static::is_module_active();
}
/**
* Activates the plugin
*
* @return null|WP_Error Null on success, WP_Error on invalid file.
*/
public static function activate_plugin() {
/*
* Activate self-installed plugin if it's installed.
*/
if ( parent::is_plugin_installed() ) {
return activate_plugin( static::get_installed_plugin_filename() );
}
/*
* Otherwise, activate Jetpack plugin.
*/
if ( static::is_jetpack_plugin_installed() ) {
return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) );
}
return new WP_Error( 'plugin_not_found', __( 'Activation failed. Plugin is not installed', 'jetpack-my-jetpack' ) );
}
/**
* Activates the product. If the Hybrid product has declared a jetpack module name, let's try to activate it if Jetpack plugin is active
*
* @param bool|WP_Error $product_activation 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( $product_activation ) {
if ( is_wp_error( $product_activation ) ) {
// If we failed to install the stand-alone plugin because the package was not found, let's try and install Jetpack plugin instead.
// This might happen, for example, while the stand-alone plugin was not released to the WP.org repository yet.
if ( 'no_package' === $product_activation->get_error_code() ) {
$product_activation = Plugins_Installer::install_plugin( self::JETPACK_PLUGIN_SLUG );
if ( ! is_wp_error( $product_activation ) ) {
$product_activation = static::activate_plugin();
}
}
if ( is_wp_error( $product_activation ) ) {
return $product_activation;
}
}
if ( ! empty( static::$module_name ) ) {
if ( ! static::has_required_plan() ) {
// translators: %s is the product name. e.g. Jetpack Search.
return new WP_Error( 'not_supported', sprintf( __( 'Your plan does not support %s.', 'jetpack-my-jetpack' ), static::get_title() ) );
}
$module_activation = ( new Modules() )->activate( static::$module_name, false, false );
if ( ! $module_activation ) {
return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) );
}
return $module_activation;
}
return true;
}
}

View File

@ -0,0 +1,134 @@
<?php
/**
* Base Module product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Jetpack;
use WP_Error;
/**
* Class responsible for handling the Module products
*
* Module products are those that are a Jetpack module behind the scenes.
*
* They require Jetpack plugin and will then activate/deactivate a module.
*/
abstract class Module_Product extends Product {
/**
* The Jetpack module name associated with this product
*
* @var string|null
*/
public static $module_name = null;
/**
* Get the plugin slug - ovewrite it ans return Jetpack's
*
* @return ?string
*/
public static function get_plugin_slug() {
return self::JETPACK_PLUGIN_SLUG;
}
/**
* Get the plugin filename - ovewrite it ans return Jetpack's
*
* @return ?string
*/
public static function get_plugin_filename() {
return self::JETPACK_PLUGIN_FILENAME;
}
/**
* Ensure that child classes define $module_name attribute
*
* @throws \Exception If required attribute is not declared in the child class.
* @return void
*/
private static function check_for_module_name() {
if ( empty( static::$module_name ) ) {
throw new \Exception( 'Module Product classes must declare the $module_name attribute.' );
}
}
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_active() {
return static::is_jetpack_plugin_active() && static::is_module_active();
}
/**
* Checks whether the Jetpack module is active
*
* @return bool
*/
public static function is_module_active() {
self::check_for_module_name();
if ( ! class_exists( 'Jetpack' ) ) {
return false;
}
return Jetpack::is_module_active( static::$module_name );
}
/**
* Gets the current status of the product
*
* @return string
*/
public static function get_status() {
$status = parent::get_status();
if ( 'active' === $status && ! static::is_module_active() ) {
$status = 'module_disabled';
}
return $status;
}
/**
* Activates the product by installing and activating its plugin
*
* @param bool|WP_Error $plugin_activation Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
*/
public static function do_product_specific_activation( $plugin_activation ) {
self::check_for_module_name();
if ( is_wp_error( $plugin_activation ) ) {
return $plugin_activation;
}
if ( ! class_exists( 'Jetpack' ) ) {
return new WP_Error( 'plugin_activation_failed', __( 'Error activating Jetpack plugin', 'jetpack-my-jetpack' ) );
}
$module_activation = Jetpack::activate_module( static::$module_name, false, false );
if ( ! $module_activation ) {
return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) );
}
return $module_activation;
}
/**
* Deactivate the module
*
* @return boolean
*/
public static function deactivate() {
self::check_for_module_name();
if ( ! class_exists( 'Jetpack' ) ) {
return true;
}
return Jetpack::deactivate_module( static::$module_name );
}
}

View File

@ -0,0 +1,468 @@
<?php
/**
* Base product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use WP_Error;
/**
* Class responsible for handling the products
*/
abstract class Product {
/**
* The product slug
*
* @var string
*/
public static $slug = null;
/**
* The filename (id) of the plugin associated with this product. Can be a string with a single value or a list of possible values
*
* @var string|string[]
*/
protected static $plugin_filename = null;
/**
* The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
* @var string
*/
public static $plugin_slug = null;
/**
* The Jetpack plugin slug
*
* @var string
*/
const JETPACK_PLUGIN_SLUG = 'jetpack';
/**
* The Jetpack plugin filename
*
* @var string
*/
const JETPACK_PLUGIN_FILENAME = array(
'jetpack/jetpack.php',
'jetpack-dev/jetpack.php',
);
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = true;
/**
* Get the plugin slug
*
* @return ?string
*/
public static function get_plugin_slug() {
return static::$plugin_slug;
}
/**
* Get the plugin filename
*
* @return ?string
*/
public static function get_plugin_filename() {
return static::$plugin_filename;
}
/**
* Get the installed plugin filename, considering all possible filenames a plugin might have
*
* @param string $plugin Which plugin to check. jetpack for the jetpack plugin or product for the product specific plugin.
*
* @return ?string
*/
public static function get_installed_plugin_filename( $plugin = 'product' ) {
$all_plugins = Plugins_Installer::get_plugins();
$filename = 'jetpack' === $plugin ? self::JETPACK_PLUGIN_FILENAME : static::get_plugin_filename();
if ( ! is_array( $filename ) ) {
$filename = array( $filename );
}
foreach ( $filename as $name ) {
$installed = array_key_exists( $name, $all_plugins );
if ( $installed ) {
return $name;
}
}
}
/**
* Get the Product info for the API
*
* @throws \Exception If required attribute is not declared in the child class.
* @return array
*/
public static function get_info() {
if ( static::$slug === null ) {
throw new \Exception( 'Product classes must declare the $slug attribute.' );
}
return array(
'slug' => static::$slug,
'plugin_slug' => static::$plugin_slug,
'name' => static::get_name(),
'title' => static::get_title(),
'description' => static::get_description(),
'long_description' => static::get_long_description(),
'features' => static::get_features(),
'disclaimers' => static::get_disclaimers(),
'status' => static::get_status(),
'pricing_for_ui' => static::get_pricing_for_ui(),
'is_bundle' => static::is_bundle_product(),
'is_upgradable_by_bundle' => static::is_upgradable_by_bundle(),
'supported_products' => static::get_supported_products(),
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'requires_user_connection' => static::$requires_user_connection,
'has_required_plan' => static::has_required_plan(),
'manage_url' => static::get_manage_url(),
'post_activation_url' => static::get_post_activation_url(),
'class' => get_called_class(),
);
}
/**
* Get the internationalized product name
*
* @return string
*/
abstract public static function get_name();
/**
* Get the internationalized product title
*
* @return string
*/
abstract public static function get_title();
/**
* Get the internationalized product description
*
* @return string
*/
abstract public static function get_description();
/**
* Get the internationalized product long description
*
* @return string
*/
abstract public static function get_long_description();
/**
* Get the internationalized features list
*
* @return array
*/
abstract public static function get_features();
/**
* Get the product pricing
*
* @return array
*/
abstract public static function get_pricing_for_ui();
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
abstract public static function get_manage_url();
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return static::get_manage_url();
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return null;
}
/**
* Get the disclaimers corresponding to a feature
*
* @return ?array
*/
public static function get_disclaimers() {
return array();
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* Returns true if it supports. Return false if a purchase is still required.
*
* Free products will always return true.
*
* @return boolean
*/
public static function has_required_plan() {
return true;
}
/**
* Checks whether the product supports trial or not
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return false;
}
/**
* Checks whether product is a bundle.
*
* @return boolean True if product is a bundle. Otherwise, False.
*/
public static function is_bundle_product() {
return false;
}
/**
* Check whether the product is upgradable
* by a product bundle.
*
* @return boolean|array Bundles list or False if not upgradable by a bundle.
*/
public static function is_upgradable_by_bundle() {
return false;
}
/**
* In case it's a bundle product,
* return all the products it contains.
* Empty array by default.
*
* @return Array Product slugs
*/
public static function get_supported_products() {
return array();
}
/**
* Undocumented function
*
* @return string
*/
public static function get_status() {
if ( ! static::is_plugin_installed() ) {
$status = 'plugin_absent';
} elseif ( static::is_active() ) {
$status = 'active';
// We only consider missing user connection an error when the Product is active.
if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'error';
} elseif ( ! static::has_required_plan() ) { // We need needs_purchase here as well because some products we consider active without the required plan.
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
}
} elseif ( ! static::has_required_plan() ) {
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
} else {
$status = 'inactive';
}
return $status;
}
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_active() {
return static::is_plugin_active() && static::has_required_plan();
}
/**
* Checks whether the plugin is installed
*
* @return boolean
*/
public static function is_plugin_installed() {
return (bool) static::get_installed_plugin_filename();
}
/**
* Checks whether the plugin is active
*
* @return boolean
*/
public static function is_plugin_active() {
return Plugins_Installer::is_plugin_active( static::get_installed_plugin_filename() );
}
/**
* Checks whether the Jetpack plugin is installed
*
* @return boolean
*/
public static function is_jetpack_plugin_installed() {
return (bool) static::get_installed_plugin_filename( 'jetpack' );
}
/**
* Checks whether the Jetpack plugin is active
*
* @return boolean
*/
public static function is_jetpack_plugin_active() {
return Plugins_Installer::is_plugin_active( static::get_installed_plugin_filename( 'jetpack' ) );
}
/**
* Activates the plugin
*
* @return null|WP_Error Null on success, WP_Error on invalid file.
*/
public static function activate_plugin() {
return activate_plugin( static::get_installed_plugin_filename() );
}
/**
* Perform the top level activation routines, which is installing and activating the required plugin
*
* @return bool|WP_Error
*/
private static function do_activation() {
if ( static::is_active() ) {
return true;
}
if ( ! static::is_plugin_installed() ) {
$installed = Plugins_Installer::install_plugin( static::get_plugin_slug() );
if ( is_wp_error( $installed ) ) {
return $installed;
}
}
if ( ! current_user_can( 'activate_plugins' ) ) {
return new WP_Error( 'not_allowed', __( 'You are not allowed to activate plugins on this site.', 'jetpack-my-jetpack' ) );
}
$result = static::activate_plugin();
if ( is_wp_error( $result ) ) {
return $result;
}
return true;
}
/**
* Activates the product by installing and activating its plugin
*
* @return boolean|WP_Error
*/
final public static function activate() {
$result = self::do_activation();
$result = static::do_product_specific_activation( $result );
$product_slug = static::$slug;
/**
* Fires after My Jetpack activates a product and filters the result
* Use this filter to run additional routines for a product activation on stand-alone plugins
*
* @param bool|WP_Error $result The result of the previous steps of activation.
*/
$result = apply_filters( "my_jetpack_{$product_slug}_activation", $result );
return $result;
}
/**
* Override this method to perform product specific activation routines.
*
* @param bool|WP_Error $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 ) {
return $current_result;
}
/**
* Deactivate the product
*
* @return boolean
*/
public static function deactivate() {
deactivate_plugins( static::get_installed_plugin_filename() );
return true;
}
/**
* Returns filtered Jetpack plugin actions links.
*
* @param array $actions - Jetpack plugin action links.
* @return array Filtered Jetpack plugin actions links.
*/
public static function get_plugin_actions_links( $actions ) {
// My Jetpack action link.
$my_jetpack_home_link = array(
'jetpack-home' => sprintf(
'<a href="%1$s" title="%3$s">%2$s</a>',
admin_url( 'admin.php?page=my-jetpack' ),
__( 'My Jetpack', 'jetpack-my-jetpack' ),
__( 'My Jetpack dashboard', 'jetpack-my-jetpack' )
),
);
// Otherwise, add it to the beginning of the array.
return array_merge( $my_jetpack_home_link, $actions );
}
/**
* Extend the plugin action links.
*/
public static function extend_plugin_action_links() {
$filenames = static::get_plugin_filename();
if ( ! is_array( $filenames ) ) {
$filenames = array( $filenames );
}
foreach ( $filenames as $filename ) {
$hook = 'plugin_action_links_' . $filename;
$callback = array( static::class, 'get_plugin_actions_links' );
if ( ! has_filter( $hook, $callback ) ) {
add_filter( $hook, $callback, 20, 2 );
}
}
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* Protect product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
/**
* Class responsible for handling the Protect product
*/
class Protect extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'protect';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-protect/jetpack-protect.php',
'protect/jetpack-protect.php',
'jetpack-protect-dev/jetpack-protect.php',
);
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-protect';
/**
* Whether this product requires a user connection
*
* @var string
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Protect', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Protect', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Protect your site and scan for security vulnerabilities.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Protect your site and scan for security vulnerabilities listed in our database.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Protect features list
*/
public static function get_features() {
return array(
__( 'Over 20,000 listed vulnerabilities', 'jetpack-my-jetpack' ),
__( 'Daily automatic scans', 'jetpack-my-jetpack' ),
__( 'Check plugin and theme version status', 'jetpack-my-jetpack' ),
__( 'Easy to navigate and use', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array(
'available' => true,
'is_free' => true,
);
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-protect' );
}
}

View File

@ -0,0 +1,217 @@
<?php
/**
* Scan product
*
* @package my-jetpack
*/
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;
/**
* Class responsible for handling the Scan product
*/
class Scan extends Module_Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'scan';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'scan';
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Scan', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Scan', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Scan features list
*/
public static function get_features() {
return array(
_x( 'Automated daily scanning', 'Scan Product Feature', 'jetpack-my-jetpack' ),
_x( 'One-click fixes for most issues', 'Scan Product Feature', 'jetpack-my-jetpack' ),
_x( 'Instant email notifications', 'Scan Product Feature', 'jetpack-my-jetpack' ),
_x( 'Access to latest Firewall rules', 'Scan Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
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 ) ) {
return new WP_Error( 'scan_state_fetch_failed' );
}
$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
*
* Scan is not actually 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();
}
/**
* Activates the product by installing and activating its plugin
*
* @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
*/
public static function do_product_specific_activation( $current_result ) {
$product_activation = parent::do_product_specific_activation( $current_result );
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
// Scan is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module.
$product_activation = true;
}
return $product_activation;
}
/**
* Checks whether the Jetpack module is active
*
* Scan is not a module. Nothing needs to be active. Let's always consider it active.
*
* @return bool
*/
public static function is_module_active() {
return true;
}
/**
* Return product bundles list
* that supports the product.
*
* @return boolean|array Products bundle list.
*/
public static function is_upgradable_by_bundle() {
return array( 'security' );
}
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return ''; // stay in My Jetpack page.
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return Redirect::get_url( 'my-jetpack-manage-scan' );
}
}

View File

@ -0,0 +1,150 @@
<?php
/**
* Get search stats for use in the wp-admin dashboard.
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Jetpack_Options;
/**
* Search stats (e.g. post count, post type breakdown)
*/
class Search_Stats {
const EXCLUDED_POST_TYPES = array(
'elementor_library', // Used by Elementor.
'jp_sitemap', // Used by Jetpack.
'revision',
'vip-legacy-redirect',
'scheduled-action',
'nbcs_video_lookup',
'reply', // bbpress, these get included in the topic
'product_variation', // woocommerce, not really public
'nav_menu_item',
'shop_order', // woocommerce, not really public
'redirect_rule', // Used by the Safe Redirect plugin.
);
const DO_NOT_EXCLUDE_POST_TYPES = array(
'topic', // bbpress
'forum', // bbpress
);
const CACHE_EXPIRY = 1 * MINUTE_IN_SECONDS;
const CACHE_GROUP = 'jetpack_search';
const POST_TYPE_BREAKDOWN_CACHE_KEY = 'post_type_break_down';
/**
* Get stats from the WordPress.com API for the current blog ID.
*/
public function get_stats_from_wpcom() {
$blog_id = Jetpack_Options::get_option( 'id' );
if ( ! is_numeric( $blog_id ) ) {
return null;
}
$response = Client::wpcom_json_api_request_as_blog(
'/sites/' . (int) $blog_id . '/jetpack-search/stats',
'2',
array(),
null,
'wpcom'
);
return $response;
}
/**
* Estimate record counts via a local database query.
*/
public static function estimate_count() {
return array_sum( static::get_post_type_breakdown() );
}
/**
* Calculate breakdown of post types for the site.
*/
public static function get_post_type_breakdown() {
$indexable_post_types = get_post_types(
array(
'public' => true,
'exclude_from_search' => false,
)
);
$indexable_status_array = get_post_stati(
array(
'public' => true,
'exclude_from_search' => false,
)
);
$raw_posts_counts = static::get_raw_post_type_breakdown();
if ( ! $raw_posts_counts || is_wp_error( $raw_posts_counts ) ) {
return array();
}
$posts_counts = static::get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array );
return $posts_counts;
}
/**
* Calculate breakdown of post types with passed in indexable post types and statuses.
* The function is going to be used from WPCOM as well for consistency.
*
* @param array $raw_posts_counts Array of post types with counts as value.
* @param array $indexable_post_types Array of indexable post types.
* @param array $indexable_status_array Array of indexable post statuses.
*/
public static function get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array ) {
$posts_counts = array();
foreach ( $raw_posts_counts as $row ) {
// ignore if status is not public.
if ( ! in_array( $row['post_status'], $indexable_status_array, true ) ) {
continue;
}
// ignore if post type is in excluded post types.
if ( in_array( $row['post_type'], self::EXCLUDED_POST_TYPES, true ) ) {
continue;
}
// ignore if post type is not public and is not explicitly included.
if ( ! in_array( $row['post_type'], $indexable_post_types, true ) &&
! in_array( $row['post_type'], self::DO_NOT_EXCLUDE_POST_TYPES, true )
) {
continue;
}
// add up post type counts of potentially multiple post_status.
if ( ! isset( $posts_counts[ $row['post_type'] ] ) ) {
$posts_counts[ $row['post_type'] ] = 0;
}
$posts_counts[ $row['post_type'] ] += intval( $row['num_posts'] );
}
arsort( $posts_counts, SORT_NUMERIC );
return $posts_counts;
}
/**
* Get raw post type breakdown from the database.
*/
protected static function get_raw_post_type_breakdown() {
global $wpdb;
$results = wp_cache_get( self::POST_TYPE_BREAKDOWN_CACHE_KEY, self::CACHE_GROUP );
if ( false !== $results ) {
return $results;
}
$query = "SELECT post_type, post_status, COUNT( * ) AS num_posts
FROM {$wpdb->posts}
WHERE post_password = ''
GROUP BY post_type, post_status";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_results( $query, ARRAY_A );
wp_cache_set( self::POST_TYPE_BREAKDOWN_CACHE_KEY, $results, self::CACHE_GROUP, self::CACHE_EXPIRY );
return $results;
}
}

View File

@ -0,0 +1,324 @@
<?php
/**
* Search product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Search\Module_Control as Search_Module_Control;
use Jetpack_Options;
use WP_Error;
/**
* Class responsible for handling the Search product
*/
class Search extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'search';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'search';
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-search';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-search/jetpack-search.php',
'search/jetpack-search.php',
'jetpack-search-dev/jetpack-search.php',
);
/**
* Search only requires site connection
*
* @var boolean
*/
public static $requires_user_connection = false;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Search', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Search', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Help them find what they need', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Help your site visitors find answers instantly so they keep reading and buying. Great for sites with a lot of content.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
__( 'Instant search and indexing', 'jetpack-my-jetpack' ),
__( 'Powerful filtering', 'jetpack-my-jetpack' ),
__( 'Supports 38 languages', 'jetpack-my-jetpack' ),
__( 'Spelling correction', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
// Basic pricing info.
$pricing = array_merge(
array(
'available' => true,
'trial_available' => static::has_trial_support(),
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'wpcom_free_product_slug' => static::get_wpcom_free_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
$record_count = intval( Search_Stats::estimate_count() );
$search_pricing = static::get_pricing_from_wpcom( $record_count );
if ( is_wp_error( $search_pricing ) ) {
return $pricing;
}
$pricing['estimated_record_count'] = $record_count;
return array_merge( $pricing, $search_pricing );
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_search';
}
/**
* Get the WPCOM free product slug
*
* @return ?string
*/
public static function get_wpcom_free_product_slug() {
return 'jetpack_search_free';
}
/**
* Returns true if the new_pricing_202208 is set to not empty in URL for testing purpose, or it's active.
*/
public static function is_new_pricing_202208() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( isset( $_GET['new_pricing_202208'] ) && $_GET['new_pricing_202208'] ) {
return true;
}
$record_count = intval( Search_Stats::estimate_count() );
$search_pricing = static::get_pricing_from_wpcom( $record_count );
if ( is_wp_error( $search_pricing ) ) {
return false;
}
return '202208' === $search_pricing['pricing_version'];
}
/**
* Override status to `needs_purchase_or_free` when status is `needs_purchase`.
*/
public static function get_status() {
$status = parent::get_status();
return $status;
}
/**
* Use centralized Search pricing API.
*
* The function is also used by the search package, as a result it could be called before site connection - i.e. blog token might not be available.
*
* @param int $record_count Record count to estimate pricing.
*
* @return array|WP_Error
*/
public static function get_pricing_from_wpcom( $record_count ) {
static $pricings = array();
if ( isset( $pricings[ $record_count ] ) ) {
return $pricings[ $record_count ];
}
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
// For simple sites fetch the response directly.
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
'2',
array( 'timeout' => 5 ),
null,
'wpcom'
);
} else {
// For non-simple sites we have to use the wp_remote_get, as connection might not be available.
$response = wp_remote_get(
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
array( 'timeout' => 5 )
);
}
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'search_pricing_fetch_failed' );
}
$body = wp_remote_retrieve_body( $response );
$pricings[ $record_count ] = json_decode( $body, true );
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
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return static::is_new_pricing_202208();
}
/**
* Checks whether the current plan of the site already supports the product
*
* Returns true if it supports. Return false if a purchase is still required.
*
* Free products will always return true.
*
* @return boolean
*/
public static function has_required_plan() {
$search_state = static::get_state_from_wpcom();
return ! empty( $search_state->supports_search ) || ! empty( $search_state->supports_instant_search );
}
/**
* Activates the product. Try to enable instant search after the Search module was enabled.
*
* @param bool|WP_Error $product_activation 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( $product_activation ) {
$product_activation = parent::do_product_specific_activation( $product_activation );
if ( is_wp_error( $product_activation ) ) {
return $product_activation;
}
if ( class_exists( 'Automattic\Jetpack\Search\Module_Control' ) ) {
( new Search_Module_Control() )->enable_instant_search();
}
// we don't want to change the success of the activation if we fail to activate instant search. That's not mandatory.
return $product_activation;
}
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return ''; // stay in My Jetpack page or continue the purchase flow if needed.
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-search' );
}
}

View File

@ -0,0 +1,246 @@
<?php
/**
* Security product
*
* @package my-jetpack
*/
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 Jetpack_Options;
use WP_Error;
/**
* Class responsible for handling the Security product
*/
class Security extends Module_Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'security';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'security';
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return _x( 'Security', 'Jetpack product name', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return _x( 'Security', 'Jetpack product name', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Comprehensive site security, including VaultPress Backup, Scan, and Akismet Anti-spam.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Comprehensive site security, including VaultPress Backup, Scan, and Akismet Anti-spam.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
_x( 'Real-time cloud backups with 10GB storage', 'Security Product Feature', 'jetpack-my-jetpack' ),
_x( 'Automated real-time malware scan', 'Security Product Feature', 'jetpack-my-jetpack' ),
_x( 'One-click fixes for most threats', 'Security Product Feature', 'jetpack-my-jetpack' ),
_x( 'Comment & form spam protection', 'Security Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_security_t1_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 bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
*/
public static function do_product_specific_activation( $current_result ) {
$product_activation = parent::do_product_specific_activation( $current_result );
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
// 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.
$product_activation = true;
}
// At this point, Jetpack plugin is installed. Let's activate each individual product.
$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;
}
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();
}
/**
* 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/purchases', $site_id ),
'1.1',
array(
'method' => 'GET',
)
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'purchases_state_fetch_failed' );
}
$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() {
$purchases_data = static::get_state_from_wpcom();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if (
0 === strpos( $purchase->product_slug, 'jetpack_security' ) ||
0 === strpos( $purchase->product_slug, 'jetpack_complete' )
) {
return true;
}
}
}
return false;
}
/**
* 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( 'backup', 'scan', 'anti-spam' );
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return '';
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* Search product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Social product
*/
class Social extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'social';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'publicize';
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-social';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-social/jetpack-social.php',
'social/jetpack-social.php',
'jetpack-social-dev/jetpack-social.php',
);
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Social', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Social', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Reach your audience on social media', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Promote your content on social media by automatically publishing when you publish on your site.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Social features list
*/
public static function get_features() {
return array(
__( 'Post to social networks', 'jetpack-my-jetpack' ),
__( 'Schedule publishing', 'jetpack-my-jetpack' ),
__( 'Supports the major social networks', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product pricing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_social';
}
/**
* Get the URL where the user manages the product
*
* @return string
*/
public static function get_manage_url() {
if ( static::is_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-social' );
}
}
}

View File

@ -0,0 +1,151 @@
<?php
/**
* VideoPress product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the VideoPress product
*/
class Videopress extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'videopress';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'videopress';
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-videopress';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-videopress/jetpack-videopress.php',
'videopress/jetpack-videopress.php',
'jetpack-videopress-dev/jetpack-videopress.php',
);
/**
* Search only requires site connection
*
* @var boolean
*/
public static $requires_user_connection = true;
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'VideoPress', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack VideoPress', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'High quality, ad-free video', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'High-quality, ad-free video built specifically for WordPress.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Boost features list
*/
public static function get_features() {
return array(
_x( '1TB of storage', 'VideoPress Product Feature', 'jetpack-my-jetpack' ),
_x( 'Built into WordPress editor', 'VideoPress Product Feature', 'jetpack-my-jetpack' ),
_x( 'Ad-free and customizable player', 'VideoPress Product Feature', 'jetpack-my-jetpack' ),
_x( 'Unlimited users', 'VideoPress Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_videopress';
}
/**
* Get the URL the user is taken after activating the product
*
* @return ?string
*/
public static function get_post_activation_url() {
return ''; // stay in My Jetpack page.
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
if ( method_exists( 'Automattic\Jetpack\VideoPress\Initializer', 'should_initialize_admin_ui' ) && \Automattic\Jetpack\VideoPress\Initializer::should_initialize_admin_ui() ) {
return \Automattic\Jetpack\VideoPress\Admin_UI::get_admin_page_url();
}
}
}